Spring WebSession Redis Exception - jackson

I want to store my WebSession in Redis. There is no problem at put operation, but it throws exception when retrieving stored record. Here is my example stack trace
Caused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array
to contain As.WRAPPER_ARRAY type information for class
java.lang.Object at [Source:
(byte[])"{"attributes":["org.springframework.session.web.server.session.SpringSessionWebSessionStore$SpringSessionMap",{}],"id":"2a5c3d9b-3557-4bd6-bca8-9e221c3a5b41","lastAccessTime":{"nano":800305900,"epochSecond":1605268779},"creationTime":{"nano":800305900,"epochSecond":1605268779},"expired":false,"maxIdleTime":{"seconds":5400,"nano":0,"negative":false,"zero":false,"units":["java.util.ImmutableCollections$List12",[["java.time.temporal.ChronoUnit","SECONDS"],["java.time.temporal.ChronoUnit","NANOS"]]]"[truncated
18 bytes]; line: 1, column: 1]
How could I solve this problem? I don't understand why it is happening? Thanks.
Here is my session service.
#Component
#RequiredArgsConstructor
#Slf4j
public class SessionMappingStorage {
private static final String USR_TO_SESSION___KEY = "USR_SESSION_MAP";
private final ReactiveHashOperations<String, String, Object> hashOps;
public Mono<Boolean> addSession(String username, WebSession session) {
return hashOps.put(USR_TO_SESSION___KEY, username, session);
}
public Mono<WebSession> getSessionByUserId(String username) {
return hashOps.get(USR_TO_SESSION___KEY, username).cast(WebSession.class);
}
}
Here is my redis configuration.
#Bean
public ReactiveRedisTemplate<String, String> reactiveRedisTemplate() {
RedisSerializer keySerializer, hashKeySerializer, hashValueSerializer;
keySerializer = hashKeySerializer = new StringRedisSerializer();
hashValueSerializer = new GenericJackson2JsonRedisSerializer(objectMapper());
RedisSerializationContext.RedisSerializationContextBuilder<String, String> builder =
RedisSerializationContext.newSerializationContext(keySerializer);
RedisSerializationContext<String, String> context =
builder.hashKey(hashKeySerializer).hashValue(hashValueSerializer).build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory(), context);
}
#Bean
public ReactiveHashOperations<String, String, Object> hashOps() {
return reactiveRedisTemplate().opsForHash();
}
private ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
return mapper;
}

Related

Jackson deserialiser affected by the Deadbolt "restrict" annotation

I receiving an exception Could not resolve type id 'path.to.MyClass' as a subtype of [simple type, class java.lang.Object]: no such class found on play 2.7 server with DeadBolt (2.6.3 and 2.7.0) when I try deserialise JSON to Map<String, MyClass> inside of route action with a #Restrict annotation. All works fine if Remove this annotation.
MyClass.java
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "class")
public class MyClass implements Serializable {
public String name;
public Integer age;
public MyClass(){}
public MyClass(String name, Integer age){
this.name = name;
this.age = age;
}
}
serialise Map<String, MyClass>
Map<String, MyClass> value = new HashMap<>();
value.put("first", new MyClass("Bob",10));
value.put("second", new MyClass("Rob",20));
ObjectMapper mapper = Json.newDefaultMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
String json = null;
try {
json = mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
output
{
"first":{
"class":"path.to.MyClass",
"name":"Bob",
"age":10
},
"second":{
"class":"path.to.MyClass",
"name":"Rob",
"age":20
}
}
JSON format looks so because of backward compatibility with old server which use old FlexJson.
deserialise
#Restrict({#Group({"Admin"})})
public CompletionStage<Result> action(long id) {
String json = getJsonFromStorage();
Map<String, MyClass> result = new HashMap<>();
try {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
JsonFactory factory = mapper.getFactory();
JsonParser parser = factory.createParser(new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))));
JavaType type = mapper.getTypeFactory().constructType(result.getClass());
t = mapper.readValue(parser, type);
} catch (IOException e) {
e.printStackTrace();
}
return ok("ok")
}
I have a temporary solution. I override class loader for current thread to class loader from play.Environment
public class MyController extends Controller {
#Inject
private Environment environment;
#Restrict({#Group({"Admin"})})
public CompletionStage<Result> action(long id) {
Thread.currentThread().setContextClassLoader(environment.classLoader());
String json = getJsonFromStorage();
Map<String, MyClass> result = new HashMap<>();
try {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
JsonFactory factory = mapper.getFactory();
JsonParser parser = factory.createParser(new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))));
JavaType type = mapper.getTypeFactory().constructType(result.getClass());
t = mapper.readValue(parser, type);
} catch (IOException e) {
e.printStackTrace();
}
return ok("ok")
}
}

Spring data redis returns null on deserializing List type

I am using spring data redis and one of my entity has a list as below.
#RedisHash("person")
#Data
#Builder
public class Person implements Serializable {
#Id
private String name;
private List<Address> addressList;
}
public class Address implements Serializable {
private String postCode;
private String country;
}
The serialisation works fine and the address is stored as
HGETALL person:123456
"name"
"blabla"
"address[0].postCode"
"1111XX"
"address[1].country"
"IN"
but while getting the person back the List is always null ? could someone point out what I am doing wrong here.
My Redis configuration looks as below.
#Configuration
#EnableRedisRepositories
public class RedisConfiguration {
#Bean
public JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
#Bean
public RedisTemplate<String, String> redisTemplate() {
final RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
mapper.findAndRegisterModules();
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
I have some suspicion but can you share a bit more code snippet on how you use redisTemplate to save the data into redis?
But most likely its because you set up both Key and Values initialization of RedisTemplate
RedisTemplate<K, V> as <String, String>
Which is why even with Jackson2JsonSerializer it can't serialize and deserialize the List<Address> class properly.
A possible solution would be:
#Bean
public RedisTemplate<String, Person> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
RedisTemplate<String, Person> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
And then you store the whole Person object into Redis
When you need it then you get the whole object out and implement your getter and setter.
Hope this helps

RabbitMQ not serialize message, error convert

I've seen some related questions here, but none worked for me, the rabbit will not serialize my message coming from another application.
Caused by: org.springframework.amqp.AmqpException: No method found for class [B
Below my configuration class to receive the messages.
#Configuration
public class RabbitConfiguration implements RabbitListenerConfigurer{
public final static String EXCHANGE_NAME = "wallet-accounts";
public final static String QUEUE_PAYMENT = "wallet-accounts.payment";
public final static String QUEUE_RECHARGE = "wallet-accounts.recharge";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_PAYMENT, QUEUE_RECHARGE);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
private List<Declarable> queues(String ... names){
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
result.add(makeQueue(names[i]));
result.add(makeBinding(names[i]));
}
return result;
}
private static Binding makeBinding(String queueName){
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, queueName, null);
}
private static Queue makeQueue(String name){
return new Queue(name);
}
#Bean
public MappingJackson2MessageConverter jackson2Converter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
return converter;
}
#Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
return factory;
}
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}
}
Using this other configuration, the error is almost the same:
Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [br.com.beblue.wallet.payment.application.accounts.PaymentEntryCommand]
Configuration:
#Configuration
public class RabbitConfiguration {
public final static String EXCHANGE_NAME = "wallet-accounts";
public final static String QUEUE_PAYMENT = "wallet-accounts.payment";
public final static String QUEUE_RECHARGE = "wallet-accounts.recharge";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_PAYMENT, QUEUE_RECHARGE);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public TopicExchange exchange() {
return new TopicExchange(EXCHANGE_NAME);
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
private List<Declarable> queues(String ... names){
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
result.add(makeQueue(names[i]));
result.add(makeBinding(names[i]));
}
return result;
}
private static Binding makeBinding(String queueName){
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, queueName, null);
}
private static Queue makeQueue(String name){
return new Queue(name);
}
}
Can anyone tell me what's wrong with these settings, or what's missing?
No method found for class [B
Means there is a default SimpleMessageConverter which can't convert your incoming application/json. It is just not aware of that content-type and just falls back to the byte[] to return.
Class not found [br.com.beblue.wallet.payment.application.accounts.PaymentEntryCommand]
Means that Jackson2JsonMessageConverter can't convert your application/json because the incoming __TypeId__ header, representing class of the content, cannot be found in the local classpath.
Well, definitely your configuration for the DefaultMessageHandlerMethodFactory does not make sense for the AMQP conversion. You should consider to use SimpleRabbitListenerContainerFactory bean definition and its setMessageConverter. And yes, consider to inject the proper org.springframework.amqp.support.converter.MessageConverter implementation.
https://docs.spring.io/spring-amqp/docs/1.7.3.RELEASE/reference/html/_reference.html#async-annotation-conversion
From the Spring Boot perspective there is SimpleRabbitListenerContainerFactoryConfigurer to configure on the matter:
https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-using-amqp-receiving

How Test PUT RestController in Spring Boot

How can I test one PUT request with Spring Boot??
I have this method:
#RequestMapping(method = RequestMethod.PUT, value = "/")
public NaturezaTitulo save(#RequestBody NaturezaTitulo naturezaTitulo){
return naturezaTituloService.save(naturezaTitulo);
}
and this test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class NaturezaTituloControllerTest {
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private MockMvc mockMvc;
private HttpMessageConverter mappingJackson2HttpMessageConverter;
private List<NaturezaTitulo> naturezaTituloList = new ArrayList<>();
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();
Assert.assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void naturezaTituloNotFound() throws Exception {
mockMvc.perform(get("/naturezatitulo/55ce2dd6222e629f4b8d6fe0"))
.andExpect(status().is4xxClientError());
}
#Test
public void naturezaTituloSave() throws Exception {
NaturezaTitulo naturezaTitulo = new NaturezaTitulo();
naturezaTitulo.setNatureza("Testando");
mockMvc.perform(put("/naturezatitulo/").content(this.json(naturezaTitulo))
.contentType(contentType))
.andExpect(jsonPath("$.id", notNullValue()));
}
protected String json(Object o) throws IOException {
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
this.mappingJackson2HttpMessageConverter.write(
o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
but I got this error:
java.lang.IllegalArgumentException: json can not be null or empty at
com.jayway.jsonpath.internal.Utils.notEmpty(Utils.java:259)
how can I pass one object from body in Put test?
tks

Set Gemfire entry-ttl in Java Beans

I would like to create a Gemfire region in a Spring Boot application. Following this sample, it works well wihout adding database support. If I add database, it will shows error like " Error creating bean with name 'dataSource'". However, default gemfire cache bean works well with datasource integration.
#EnableAutoConfiguration
// Sprint Boot Auto Configuration
#ComponentScan(basePackages = "napo.demo")
#EnableCaching
#SuppressWarnings("unused")
public class Application extends SpringBootServletInitializer {
private static final Class<Application> applicationClass = Application.class;
private static final Logger log = LoggerFactory.getLogger(applicationClass);
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
/* **The commented code works well with database.**
#Bean
CacheFactoryBean cacheFactoryBean() {
return new CacheFactoryBean();
}
#Bean
ReplicatedRegionFactoryBean<Integer, Integer> replicatedRegionFactoryBean(final Cache cache) {
ReplicatedRegionFactoryBean<Integer, Integer> region= new ReplicatedRegionFactoryBean<Integer, Integer>() {{
setCache(cache);
setName("demo");
}};
return region;
} */
// This configuration will cause issue as beow
//
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$NonEmbeddedConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.NullPointerException
#Bean
GemfireCacheManager cacheManager(final Cache gemfireCache) {
return new GemfireCacheManager() {
{
setCache(gemfireCache);
}
};
}
// NOTE ideally, "placeholder" properties used by Spring's PropertyPlaceholderConfigurer would be externalized
// in order to avoid re-compilation on property value changes (so... this is just an example)!
#Bean
public Properties placeholderProperties() {
Properties placeholders = new Properties();
placeholders.setProperty("app.gemfire.region.eviction.action", "LOCAL_DESTROY");
placeholders.setProperty("app.gemfire.region.eviction.policy-type", "MEMORY_SIZE");
placeholders.setProperty("app.gemfire.region.eviction.threshold", "4096");
placeholders.setProperty("app.gemfire.region.expiration.entry.tti.action", "INVALIDATE");
placeholders.setProperty("app.gemfire.region.expiration.entry.tti.timeout", "300");
placeholders.setProperty("app.gemfire.region.expiration.entry.ttl.action", "DESTROY");
placeholders.setProperty("app.gemfire.region.expiration.entry.ttl.timeout", "60");
placeholders.setProperty("app.gemfire.region.partition.local-max-memory", "16384");
placeholders.setProperty("app.gemfire.region.partition.redundant-copies", "1");
placeholders.setProperty("app.gemfire.region.partition.total-max-memory", "32768");
return placeholders;
}
#Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer(
#Qualifier("placeholderProperties") Properties placeholders) {
PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer();
propertyPlaceholderConfigurer.setProperties(placeholders);
return propertyPlaceholderConfigurer;
}
#Bean
public Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", "SpringGemFireJavaConfigTest");
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("log-level", "config");
return gemfireProperties;
}
#Bean
#Autowired
public CacheFactoryBean gemfireCache(#Qualifier("gemfireProperties") Properties gemfireProperties) throws Exception {
CacheFactoryBean cacheFactory = new CacheFactoryBean();
cacheFactory.setProperties(gemfireProperties);
return cacheFactory;
}
#Bean(name = "ExamplePartition")
#Autowired
public ReplicatedRegionFactoryBean<Object, Object> examplePartitionRegion(Cache gemfireCache,
#Qualifier("partitionRegionAttributes") RegionAttributes<Object, Object> regionAttributes) throws Exception {
ReplicatedRegionFactoryBean<Object, Object> examplePartitionRegion =
new ReplicatedRegionFactoryBean<Object, Object>();
examplePartitionRegion.setAttributes(regionAttributes);
examplePartitionRegion.setCache(gemfireCache);
examplePartitionRegion.setName("demo");
return examplePartitionRegion;
}
#Bean
#Autowired
public RegionAttributesFactoryBean partitionRegionAttributes(
EvictionAttributes evictionAttributes,
#Qualifier("entryTtiExpirationAttributes") ExpirationAttributes entryTti,
#Qualifier("entryTtlExpirationAttributes") ExpirationAttributes entryTtl) {
RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();
regionAttributes.setEvictionAttributes(evictionAttributes);
regionAttributes.setEntryIdleTimeout(entryTti);
regionAttributes.setEntryTimeToLive(entryTtl);
return regionAttributes;
}
#Bean
public EvictionAttributesFactoryBean defaultEvictionAttributes(
#Value("${app.gemfire.region.eviction.action}") String action,
#Value("${app.gemfire.region.eviction.policy-type}") String policyType,
#Value("${app.gemfire.region.eviction.threshold}") int threshold) {
EvictionAttributesFactoryBean evictionAttributes = new EvictionAttributesFactoryBean();
evictionAttributes.setAction(EvictionActionType.valueOfIgnoreCase(action).getEvictionAction());
evictionAttributes.setThreshold(threshold);
evictionAttributes.setType(EvictionPolicyType.valueOfIgnoreCase(policyType));
return evictionAttributes;
}
#Bean
public ExpirationAttributesFactoryBean entryTtiExpirationAttributes(
#Value("${app.gemfire.region.expiration.entry.tti.action}") String action,
#Value("${app.gemfire.region.expiration.entry.tti.timeout}") int timeout) {
ExpirationAttributesFactoryBean expirationAttributes = new ExpirationAttributesFactoryBean();
expirationAttributes.setAction(ExpirationActionType.valueOfIgnoreCase(action).getExpirationAction());
expirationAttributes.setTimeout(timeout);
return expirationAttributes;
}
#Bean
public ExpirationAttributesFactoryBean entryTtlExpirationAttributes(
#Value("${app.gemfire.region.expiration.entry.ttl.action}") String action,
#Value("${app.gemfire.region.expiration.entry.ttl.timeout}") int timeout) {
ExpirationAttributesFactoryBean expirationAttributes = new ExpirationAttributesFactoryBean();
expirationAttributes.setAction(ExpirationActionType.valueOfIgnoreCase(action).getExpirationAction());
expirationAttributes.setTimeout(timeout);
return expirationAttributes;
}
#Bean
public PartitionAttributesFactoryBean defaultPartitionAttributes(
#Value("${app.gemfire.region.partition.local-max-memory}") int localMaxMemory,
#Value("${app.gemfire.region.partition.redundant-copies}") int redundantCopies,
#Value("${app.gemfire.region.partition.total-max-memory}") int totalMaxMemory) {
PartitionAttributesFactoryBean partitionAttributes = new PartitionAttributesFactoryBean();
partitionAttributes.setLocalMaxMemory(localMaxMemory);
partitionAttributes.setRedundantCopies(redundantCopies);
partitionAttributes.setTotalMaxMemory(totalMaxMemory);
return partitionAttributes;
}
#Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder application) {
return application.sources(applicationClass);
}}
demoService java code:
#Service
public class demoService {
#Autowired
private demoMapper demoMapper;
#Cacheable("demo")
public Fund getDemo(String code) {
Demo demo= demoMapper.getDemo(Code);
return demo;
}
Here is an example of setting entry-ttl among other attributes: https://github.com/spring-projects/spring-gemfire-examples/blob/master/basic/java-config/src/main/java/org/springframework/data/gemfire/example/SpringJavaBasedContainerGemFireConfiguration.java