Springfox API docu with xml response- Workaround for #XmlAttribute - jackson

I have some issues setting up Springfox for usage in Swagger UI.
I got it working in general, but the XML examples which are created are partly wrong because some xml annoations are not considered by springfox.
I have these models:
#XmlRootElement(name = "ApplicatorUnits")
#XmlAccessorType(XmlAccessType.FIELD)
public class EHDTOApplicatorUnits
{
#XmlElement(name = "UnitGroup")
private List<EHDTOUnitGroup> ehdtoUnitGroups;
public List<EHDTOUnitGroup> getEhdtoUnitGroups()
{
return ehdtoUnitGroups;
}
public void setEhdtoUnitGroups(List<EHDTOUnitGroup> ehdtoUnitGroups)
{
this.ehdtoUnitGroups = ehdtoUnitGroups;
}
}
#XmlRootElement(name = "UnitGroup")
#XmlAccessorType(XmlAccessType.FIELD)
public class EHDTOUnitGroup
{
#XmlAttribute(name = "name")
private String name;
#XmlElement(name = "unit")
private List<EHDTOUnit> ehdtoUnits;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public List<EHDTOUnit> getEhdtoUnits()
{
return ehdtoUnits;
}
public void setEhdtoUnits(List<EHDTOUnit> ehdtoUnits)
{
this.ehdtoUnits = ehdtoUnits;
}
}
You see some usages of #XmlAttribute, #XmlRootElement and #XmlElement.
After adding jackson jaxb annotation introspectors (I tried different ways) the XML was partly correct, as the #XmlAttribute annotations have been considered after this step:
JaxbAnnotationModule jaxbAnnotationModule = new JaxbAnnotationModule();
jaxbAnnotationModule.setPriority(Priority.PRIMARY);
mapper.registerModule(jaxbAnnotationModule);
/*AnnotationIntrospector primary = new JacksonAnnotationIntrospector();
AnnotationIntrospector secondary = new XmlJaxbAnnotationIntrospector(mapper.getTypeFactory()); //new JaxbAnnotationIntrospector(mapper.getTypeFactory());
AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary,secondary);
mapper.setAnnotationIntrospector(pair);*/
However, the other 2 are still ignored, resulting in this example xml:
<?xml version="1.0"?>
<EHDTOApplicatorUnits>
<UnitGroup>
<name>string</name>
<unit>
<id>1</id>
<name>string</name>
<unitSystem>string</unitSystem>
</unit>
</UnitGroup>
</EHDTOApplicatorUnits>
While the real output of the API is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ApplicatorUnits>
<UnitGroup name="coefTempErrorSpan">
<unit>
<id>1</id>
<name>% span /10K</name>
<unitSystem>SI</unitSystem>
</unit>
<unit>
<id>2</id>
<name>% span /50F</name>
<unitSystem>SI</unitSystem>
</unit>
</UnitGroup>
...
</ApplicatorUnits>
You see the different naming of the root element, and also the "name" of the UnitGroup which is not an attribute in the example xml but an own child-node.
I found I can use #ApiModel(value = "ApplicatorUnits") as workaround for #XmlRootElement but what about #XmlAttribute, is there also a workaround? #ApiModelProperty does not seem to work for this case. Does somebody have an idea how to workaround this issue?

Related

Jackson UnrecognizedProperyException when parsing XML

I am trying to parse an XML where I generate the DTOs using maven-jaxb2-plugin from xsd file. But I get this exception and don't know why, everything seems alright.
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Publish_Date" (class com.compnay.package.SdnList$PublshInformation), not marked as ignorable (2 known properties: "publishDate", "recordCount"])
at [Source: (PushbackInputStream); line: 4, column: 44] (through reference chain: com.compnay.package.SdnList["publshInformation"]->com.compnay.package.domain.SdnList$PublshInformation["Publish_Date"])
Jaxb execution for the relevant xsd
<execution>
<id>tds</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemas>
<schema>
<url>xsd url</url>
</schema>
</schemas>
<generatePackage>com.company.domain</generatePackage>
<generateDirectory>${project.basedir}/domain/src/main/java</generateDirectory>
<episode>false</episode>
</configuration>
</execution>
Part of the XML file where I get the error.
<publshInformation>
<Publish_Date>08/06/2021</Publish_Date>
<Record_Count>9030</Record_Count>
</publshInformation>
Rest template Configuration
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
final XmlMapper xmlMapper = new XmlMapper(module);
xmlMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
// xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Works when this is on
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(xmlMapper);
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_XML));
return new RestTemplateBuilder()
.setReadTimeout(Duration.ofMillis(readTimeout))
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.messageConverters(converter)
.build();
Part of a Generated DTO
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"publshInformation",
"sdnEntry"
})
#XmlRootElement(name = "sdnList")
public class SdnList {
#XmlElement(required = true)
protected SdnList.PublshInformation publshInformation;
#XmlElement(required = true)
protected List<SdnList.SdnEntry> sdnEntry;
........
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType>
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="Publish_Date" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* <element name="Record_Count" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"publishDate",
"recordCount"
})
public static class PublshInformation {
#XmlElement(name = "Publish_Date")
protected String publishDate;
#XmlElement(name = "Record_Count")
protected Integer recordCount;
........
}
}
I can make it work with using xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) but I don't want to lose other data. Can anyone help me figure it out why I get unrecognizedPropertyException? I will appreciate any pointers.
I guess you are doing something before the derealization which is making your input stream empty due to which you are getting this error. I used the provided XML and seems to work fine for me:
XML:
<publshInformation>
<Publish_Date>08/06/2021</Publish_Date>
<Record_Count>9030</Record_Count>
</publshInformation>
PublshInformation.class:
#XmlRootElement(name = "publshInformation")
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class PublshInformation {
#XmlElement(name = "Publish_Date")
private String Publish_Date;
#XmlElement(name = "Record_Count")
private Integer recordCount;
}
PublishMain.class:
public class PublishMain {
public static void main(String[] args) throws JAXBException, XMLStreamException, IOException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("publish.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
/* final Unmarshaller unmarshaller = JAXBContext.newInstance(PublshInformation.class).createUnmarshaller();
final PublshInformation publshInformation = unmarshaller.unmarshal(xmlStreamReader, PublshInformation.class).getValue();
System.out.println(publshInformation.toString());
Marshaller marshaller = JAXBContext.newInstance(PublshInformation.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(publshInformation, System.out);*/
System.out.println(inputStream);
final XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
PublshInformation jacksonPublish = xmlMapper.readValue(xmlStreamReader, PublshInformation.class);
System.out.println(jacksonPublish);
xmlMapper.writerWithDefaultPrettyPrinter().writeValue(System.out, jacksonPublish);
}
}
This would produce the result:
java.io.BufferedInputStream#73a28541
PublshInformation(Publish_Date=null, recordCount=null)
<PublshInformation>
<recordCount/>
<publish_Date/>
</PublshInformation>
The above code works even by using the pure JAXB. If you uncomment then it will do it using the JAXB. I used the latest jackson-dataformat-xml 2.12.4
Make your fields private.
Use the latest version of the Jackson
Ensure your input is not being used before which may become empty.
This should work I believe.

Feign Client and Spring-data-rest (HAL): Howto navigate to linked (`_links`) resorces?

finally after extensive stack-overflowing ;-) and debugging I made it work:
My Feign-client can make requests on Spring-Data-Rest's API and I get a Resource<Something> with filled links back.
My code so far...
The FeignClient:
#FeignClient(name = "serviceclient-hateoas",
url = "${service.url}",
decode404 = true,
path = "${service.basepath:/api/v1}",
configuration = MyFeignHateoasClientConfig.class)
public interface MyFeignHateoasClient {
#RequestMapping(method = RequestMethod.GET, path = "/bookings/search/findByBookingUuid?bookingUuid={uuid}")
Resource<Booking> getBookingByUuid(#PathVariable("uuid") String uuid);
}
The client-config:
#Configuration
public class MyFeignHateoasClientConfig{
#Value("${service.user.name:bla}")
private String serviceUser;
#Value("${service.user.password:blub}")
private String servicePassword;
#Bean
public BasicAuthRequestInterceptor basicAuth() {
return new BasicAuthRequestInterceptor(serviceUser, servicePassword);
}
#Bean
public Decoder decoder() {
return new JacksonDecoder(getObjectMapper());
}
#Bean
public Encoder encoder() {
return new JacksonEncoder(getObjectMapper());
}
public ObjectMapper getObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new Jackson2HalModule());
}
#Bean
public Logger logger() {
return new Slf4jLogger(MyFeignHateoasClient.class);
}
#Bean
public Logger.Level logLevel() {
return Logger.Level.FULL;
}
}
And in the application using the client via an jar-dependency:
#SpringBootApplication
#EnableAutoConfiguration
#EnableFeignClients(basePackageClasses=MyFeignHateoasClient.class)
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
#ComponentScan(excludeFilters = #Filter(type = ... ), basePackageClasses= {....class}, basePackages="...")
public class Application {
...
Now this is working:
#Autowired
private MyFeignHateoasClient serviceClient;
...
void test() {
Resource<Booking> booking = serviceClient.getBookingByUuid(id);
Link link = booking.getLink("relation-name");
}
Now my question:
How do I go on from here, i.e. navigate to the resource in the Link?
The Link is containing an URL on the resource I want to request.
Do I really have to parse the ID out of the URL and add a method to the FeignClient like getRelationById(id)
Is there at least a way to pass the complete resource-url to a method of a FeignClient?
I have found no examples which demonstrate how to proceed from here (despite the POST/modify). Any hints appreciated!
Thx
My current solution:
I added an additional request in the Feign client, taking the whole resource path:
...
public interface MyFeignHateoasClient {
...
#RequestMapping(method = RequestMethod.GET, path = "{resource}")
Resource<MyLinkedEntity> getMyEntityByResource(#PathVariable("resource") String resource);
}
Then I implemented some kind of "HAL-Tool":
...
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.springframework.hateoas.Link;
import feign.Target;
import lombok.SneakyThrows;
public class HalTool {
private Object feignClient;
public static HalTool forClient( Object feignClient ) {
return new HalTool(feignClient);
}
private HalTool( Object feignClient ) {
this.feignClient = feignClient;
}
#SneakyThrows
private String getUrl() {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(feignClient);
Field target = invocationHandler.getClass().getDeclaredField("target");
target.setAccessible(true);
Target<?> value = (Target<?>) target.get(invocationHandler);
return value.url();
}
public String toPath( Link link ) {
String href = link.getHref();
String url = getUrl();
int idx = href.indexOf(url);
if (idx >= 0 ) {
idx += url.length();
}
return href.substring(idx);
}
}
And then I could do request a linked resource like this:
Link link = booking.getLink("relation-name");
Resource<MyLinkedEntity> entity = serviceClient.getMyEntityByResource(
HalTool.forClient(serviceClient).toPath(link));

Unsupported Operation Exception mybatis

So in this code what I want is a random SQL query from the outside that will be loaded into a property file. As of now i've got the property file with a query in it to test this. So i would like some data out, with a headline and all the data beneath. Basically just the data to start with though, as this test should do. But I get the error message that i've linked below. I can't for the life of me figure out where my problem is. Help please! :)
I've got the following code;
DataHandler class.
public class DataHandler{
DataService dataService = new DataService();
public String getPropertyValue() throws IOException {
Properties prop = new Properties();
String propFileName = "randomSqlQuery.properties";
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);
prop.load(inputStream);
if (inputStream == null) {
throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
}
String result = prop.getProperty("sqlQuery");
return result;
}
public Data getKeysAndValues() throws IOException {
String query = getPropertyValue();
List<List<Object>> randomSqlQuery = dataService.getRandomSqlQuery(query);
List<List<Object>> recordList = new ArrayList<>();
List<String> headline = new ArrayList();
if (randomSqlQuery != null && randomSqlQuery.size() > 0) {
{
List<Object> record = randomSqlQuery.get(0);
getHeadlines(record, headline);
}
for (int i = 1; i < randomSqlQuery.size(); i++) {
List<Object> singleRecord = randomSqlQuery.get(i);
recordList.add(singleRecord);
System.out.println(recordList);
}
}
return new DataImpl(headline, recordList);
}
private void getHeadlines(List<Object> record, List<String> headline) {
for (Object headlineName : record) {
headline.add((String) headlineName);
System.out.println(headlineName);
}
}
}
DataMapper class
public interface DataMapper {
public List<List<Object>> getRandomSqlQuery(#Param("query") String query);
}
DataMapper XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="nd.mappers.DataMapper">
<select id="getRandomSqlQuery" resultType="java.util.List">
${query}
</select>
</mapper>
DataImpl class, which has an interface connected to it
public class DataImpl implements Serializable, Data {
private final List<String> headers;
private final List<List<Object>> records;
public DataImpl(List<String> headers, List<List<Object>> records) {
this.headers = Collections.unmodifiableList(headers);
this.records = Collections.unmodifiableList(records);
}
#Override
public List<String> getHeaders() {
return this.headers;
}
#Override
public List<List<Object>> getRecords() {
return this.records;
}
}
And a DataService class
public class DataService implements DataMapper {
#Override
public List<List<Object>> getRandomSqlQuery(String query) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try {
DataMapper dataMapper = sqlSession.getMapper(DataMapper.class);
return dataMapper.getRandomSqlQuery(query);
} finally {
sqlSession.close();
}
}
}
Finally the test
//dataHandler instantiated in top
#Test
public void getKeysAndValues() throws IOException {
dataHandler.getKeysAndValues();
}
And here is my error!
### Error querying database. Cause: java.lang.UnsupportedOperationException
### The error may exist in nd/mappers/DataMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT * FROM PERSON ### Cause: java.lang.UnsupportedOperationException
org.apache.ibatis.exceptions.PersistenceException
### Error querying database. Cause: java.lang.UnsupportedOperationException
### The error may exist in nd/mappers/DataMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT * FROM PERSON
### Cause: java.lang.UnsupportedOperationException
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:26)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:111)
No idea what to do. The SQL is coming from the property file. Sorry for massive text.
I just resolved the same error message. The problem lies here:
<select id="getRandomSqlQuery" resultType="Person">
${query}
</select>
The result type should not be a collection but a type that the
collection contains. In your case it should be some Person
POJO. This type then must be defined in the (say mybatis-config.xml) configuration
file:
<typeAliases>
<typeAlias alias="Person" type="com.example.bean.Book"/>
</typeAliases>
Also, you would want to check for null here:
try {
DataMapper dataMapper = qlSession.getMapper(DataMapper.class);
return dataMapper.getRandomSqlQuery(query);
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}

Why is data getting stored with weird keys in Redis when using Jedis with Spring Data?

I am using Spring Data Redis with Jedis. I am trying to store a hash with key vc:${list_id}. I was able to successfully insert to redis. However, when I inspect the keys using the redis-cli, I don't see the key vc:501381. Instead I see \xac\xed\x00\x05t\x00\tvc:501381.
Why is this happening and how do I change this?
Ok, googled around for a while and found help at http://java.dzone.com/articles/spring-data-redis.
It happened because of Java serialization.
The key serializer for redisTemplate needs to be configured to StringRedisSerializer i.e. like this:
<bean
id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.server}"
p:port="${redis.port}"
p:use-pool="true"/>
<bean
id="stringRedisSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
/>
Now the key in redis is vc:501381.
Or like #niconic says, we can also set the default serializer itself to the string serializer as follows:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:defaultSerializer-ref="stringRedisSerializer"
/>
which means all our keys and values are strings. Notice however that this may not be preferable, since you may want your values to be not just strings.
If your value is a domain object, then you can use Jackson serializer and configure a serializer as mentioned here i.e. like this:
<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
<constructor-arg type="java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>
and configure your template as:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:valueSerialier-ref="userJsonRedisSerializer"
/>
It's a very old question, but my answer might be helpful for someone who got the same issue while working with Redis using Spring Boot. I was stuck on the same issue while storing hash type data in redis. I have written the required config file changes for the RedisTemplate.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.redis")
public class AppCofiguration {
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
jedisConFactory.setHostName("127.0.0.1");
jedisConFactory.setPort(6379);
return jedisConFactory;
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
// the following is not required
template.setHashValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
return template;
}
}
If the data type is String then template.setHashValueSerializer(new StringRedisSerializer()); and template.setHashKeySerializer(new StringRedisSerializer()); are not required.
I know this question has been a while, but I did some research on this topic again recently, so I would like to share how this "semi-hashed" key is generated by going thru part of the spring source code here.
First of all, Spring leverages AOP to resolve annotations like #Cacheable, #CacheEvict or #CachePut etc. The advice class is CacheInterceptor from Spring-context dependency, which is a subclass of CacheAspectSupport (also from Spring-context). For the ease of this explanation, I would use #Cacheable as an example to go thru part of the source code here.
When the method annotated as #Cacheable is invoked, AOP would route it to this method protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) from CacheAspectSupport class, in which it would try to resolve this #Cacheable annotation. In turn, it leads to the invocation of this method public Cache getCache(String name) in the implementing CacheManager. For this explanation, the implementing CacheManage would be RedisCacheManager (from Spring-data-redis dependency).
If the cache was not hit, it will go ahead to create the cache. Below is the key methods from RedisCacheManager:
protected Cache getMissingCache(String name) {
return this.dynamic ? createCache(name) : null;
}
#SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
Essentially, it will instantiate an RedisCache object. To do this, it requires 4 parameters, namely, cacheName, prefix (this is the key parameter with regards to answering this question), redisOperation (aka, the configured redisTemplate), expiration (default to 0) and cacheNullValues (default to false). The constructor below shows more details about RedisCache.
/**
* Constructs a new {#link RedisCache} instance.
*
* #param name cache name
* #param prefix must not be {#literal null} or empty.
* #param redisOperations
* #param expiration
* #param allowNullValues
* #since 1.8
*/
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
long expiration, boolean allowNullValues) {
super(allowNullValues);
Assert.hasText(name, "CacheName must not be null or empty!");
RedisSerializer<?> serializer = redisOperations.getValueSerializer() != null ? redisOperations.getValueSerializer()
: (RedisSerializer<?>) new JdkSerializationRedisSerializer();
this.cacheMetadata = new RedisCacheMetadata(name, prefix);
this.cacheMetadata.setDefaultExpiration(expiration);
this.redisOperations = redisOperations;
this.cacheValueAccessor = new CacheValueAccessor(serializer);
if (allowNullValues) {
if (redisOperations.getValueSerializer() instanceof StringRedisSerializer
|| redisOperations.getValueSerializer() instanceof GenericToStringSerializer
|| redisOperations.getValueSerializer() instanceof JacksonJsonRedisSerializer
|| redisOperations.getValueSerializer() instanceof Jackson2JsonRedisSerializer) {
throw new IllegalArgumentException(String.format(
"Redis does not allow keys with null value ¯\\_(ツ)_/¯. "
+ "The chosen %s does not support generic type handling and therefore cannot be used with allowNullValues enabled. "
+ "Please use a different RedisSerializer or disable null value support.",
ClassUtils.getShortName(redisOperations.getValueSerializer().getClass())));
}
}
}
So what the use of prefix in this RedisCache? --> As shown in the constructor about, it is used in this statement this.cacheMetadata = new RedisCacheMetadata(name, prefix);, and the constructor of RedisCacheMetadata below shows more details:
/**
* #param cacheName must not be {#literal null} or empty.
* #param keyPrefix can be {#literal null}.
*/
public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {
Assert.hasText(cacheName, "CacheName must not be null or empty!");
this.cacheName = cacheName;
this.keyPrefix = keyPrefix;
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// name of the set holding the keys
this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
}
At this point, we know that some prefix parameter has been set to RedisCacheMetadata, but how exactly is this prefix used to form the key in Redis (e.g.,\xac\xed\x00\x05t\x00\tvc:501381 as you mentioned)?
Basically, the CacheInterceptor will subsequently move forward to invoke a method private RedisCacheKey getRedisCacheKey(Object key) from the above-mentioned RedisCache object, which returns an instance of RedisCacheKey by utilizing the prefix from RedisCacheMetadata and keySerializer from RedisOperation.
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
By reaching this point, the "pre" advice of CacheInterceptor is completed, and it would go ahead to execute the actual method annotated by #Cacheable. And after completing the execution of the actual method, it will do the "post" advice of CacheInterceptor, which essentially put the result to RedisCache. Below is the method of putting the result to redis cache:
public void put(final Object key, final Object value) {
put(new RedisCacheElement(getRedisCacheKey(key), toStoreValue(value))
.expireAfter(cacheMetadata.getDefaultExpiration()));
}
/**
* Add the element by adding {#link RedisCacheElement#get()} at {#link RedisCacheElement#getKeyBytes()}. If the cache
* previously contained a mapping for this {#link RedisCacheElement#getKeyBytes()}, the old value is replaced by
* {#link RedisCacheElement#get()}.
*
* #param element must not be {#literal null}.
* #since 1.5
*/
public void put(RedisCacheElement element) {
Assert.notNull(element, "Element must not be null!");
redisOperations
.execute(new RedisCachePutCallback(new BinaryRedisCacheElement(element, cacheValueAccessor), cacheMetadata));
}
Within the RedisCachePutCallback object, its callback method doInRedis() actually invoke a method to form the actual key in redis, and the method name is getKeyBytes() from RedisCacheKey instance. Below shows the details of this method:
/**
* Get the {#link Byte} representation of the given key element using prefix if available.
*/
public byte[] getKeyBytes() {
byte[] rawKey = serializeKeyElement();
if (!hasPrefix()) {
return rawKey;
}
byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length);
System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length);
return prefixedKey;
}
As we can see in the getKeyBytes method, it utilizes both the raw key (vc:501381 in your case) and prefix key (\xac\xed\x00\x05t\x00\t in your case).
Use StringRedisTemplate to replace RedisTemplate.
By default, RedisTemplate uses Java serialization, StringRedisTemplate uses StringRedisSerializer.
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
You have to serialize teh objects that you are sending it to redis. Below is the complete running example of it. It uses interface DomainObject as Serializable
Below are the steps
1) make your maven pom.xml with following jars
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
2) make your configuration xml as follows
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<bean id="jeidsConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="localhost" p:port="6379" p:password="" />
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jeidsConnectionFactory" />
<bean id="imageRepository" class="com.self.common.api.poc.ImageRepository">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
3) Make your classes as follows
package com.self.common.api.poc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class RedisMainApp {
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mvc-dispatcher-servlet.xml");
ImageRepository imageRepository = (ImageRepository) applicationContext.getBean("imageRepository");
BufferedImage img = ImageIO.read(new File("files/img/TestImage.png"));
BufferedImage newImg;
String imagestr;
imagestr = encodeToString(img, "png");
Image image1 = new Image("1", imagestr);
img = ImageIO.read(new File("files/img/TestImage2.png"));
imagestr = encodeToString(img, "png");
Image image2 = new Image("2", imagestr);
imageRepository.put(image1);
System.out.println(" Step 1 output : " + imageRepository.getObjects());
imageRepository.put(image2);
System.out.println(" Step 2 output : " + imageRepository.getObjects());
imageRepository.delete(image1);
System.out.println(" Step 3 output : " + imageRepository.getObjects());
}
/**
* Decode string to image
* #param imageString The string to decode
* #return decoded image
*/
public static BufferedImage decodeToImage(String imageString) {
BufferedImage image = null;
byte[] imageByte;
try {
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(imageString);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
/**
* Encode image to string
* #param image The image to encode
* #param type jpeg, bmp, ...
* #return encoded string
*/
public static String encodeToString(BufferedImage image, String type) {
String imageString = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ImageIO.write(image, type, bos);
byte[] imageBytes = bos.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
imageString = encoder.encode(imageBytes);
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
return imageString;
}
}
package com.self.common.api.poc;
public class Image implements DomainObject {
public static final String OBJECT_KEY = "IMAGE";
public Image() {
}
public Image(String imageId, String imageAsStringBase64){
this.imageId = imageId;
this.imageAsStringBase64 = imageAsStringBase64;
}
private String imageId;
private String imageAsStringBase64;
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
public String getImageName() {
return imageAsStringBase64;
}
public void setImageName(String imageAsStringBase64) {
this.imageAsStringBase64 = imageAsStringBase64;
}
#Override
public String toString() {
return "User [id=" + imageAsStringBase64 + ", imageAsBase64String=" + imageAsStringBase64 + "]";
}
#Override
public String getKey() {
return getImageId();
}
#Override
public String getObjectKey() {
return OBJECT_KEY;
}
}
package com.self.common.api.poc;
import java.io.Serializable;
public interface DomainObject extends Serializable {
String getKey();
String getObjectKey();
}
package com.self.common.api.poc;
import java.util.List;
import com.self.common.api.poc.DomainObject;
public interface Repository<V extends DomainObject> {
void put(V obj);
V get(V key);
void delete(V key);
List<V> getObjects();
}
package com.self.common.api.poc;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import com.self.common.api.poc.DomainObject;
public class ImageRepository implements Repository<Image>{
#Autowired
private RedisTemplate<String,Image> redisTemplate;
public RedisTemplate<String,Image> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String,Image> redisTemplate) {
this.redisTemplate = redisTemplate;
}
#Override
public void put(Image image) {
redisTemplate.opsForHash()
.put(image.getObjectKey(), image.getKey(), image);
}
#Override
public void delete(Image key) {
redisTemplate.opsForHash().delete(key.getObjectKey(), key.getKey());
}
#Override
public Image get(Image key) {
return (Image) redisTemplate.opsForHash().get(key.getObjectKey(),
key.getKey());
}
#Override
public List<Image> getObjects() {
List<Image> users = new ArrayList<Image>();
for (Object user : redisTemplate.opsForHash().values(Image.OBJECT_KEY) ){
users.add((Image) user);
}
return users;
}
}
For more reference on sprinf jedis you can see http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html
Sample Code is taken from http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html

Object serialization duplicating nodes

Can anyone tell me why my output is duplicating the 'FirstNode'?
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
class Program
{
static void Main(string[] args)
{
Root root = new Root();
FirstNode firstNode = new FirstNode();
firstNode.Data = "DATA";
root.FirstNode.Add(firstNode);
XmlSerializer s = new XmlSerializer(typeof(Root));
StringWriter sw = new StringWriter();
s.Serialize(sw, root);
string serializedXml = sw.ToString();
Console.WriteLine(serializedXml);
Console.ReadKey();
}
}
public class Root
{
List<FirstNode> firstNode = new List<FirstNode>();
public List<FirstNode> FirstNode
{
get { return firstNode; }
set { firstNode = value; }
}
}
public class FirstNode
{
string data;
public string Data
{
get { return data; }
set { data = value; }
}
}
OUTPUT
<Root>
<FirstNode>
<FirstNode>
<Data>DATA</Data>
</FirstNode>
</FirstNode>
</Root>
Expected Output
<Root>
<FirstNode>
<Data>DATA</Data>
</FirstNode>
</Root>
Well if you look you have a Property Named FirstNode where you are storing your list, and inside the List you are storing FirstNode object...
If you want to see it just change the name of the property FirstNode in the class Root to Nodes and you will see a differnet output
<Root>
<Nodes>
<FirstNode>
<Data>DATA</Data>
</FirstNode>
</Nodes>
</Root>
The Root tag appears because is the Object Type, as well as the FirsNode , then you have tags like Data and Nodes( in my case) because are the serialized properties of these classes
Think it is this line:
root.FirstNode.Add(firstNode);
You are adding a first node to a first node and therefore getting two layers of first node.
You want:
[XmlElement("FirstNode")]
public List<FirstNode> FirstNode
{
get { return firstNode; }
set { firstNode = value; }
}
This will only add <FirstNode> for the content items - not the list itself. You might also want to look at [XmlArray] / [XmlArrayItem] if you want finer control.