Jackson UnrecognizedProperyException when parsing XML - jackson

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.

Related

Springfox API docu with xml response- Workaround for #XmlAttribute

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?

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();
}
}

Trying to run JUnit test but getting an ERROR: Implementing class

I'm trying to run a simple test with following class (DataStructureTest) code:
public class DataStructureTest extends DatabaseTestCase {
private static final Log log = (Log) LogFactory.getLog(DataStructureTest.class);
#Test
public void testProductData(){
//Creating a product data
log.debug("Creating new product data");
ProductData productData = new ProductData("Öljy");
//Setting basic information of product
log.debug("Setting basic information like name, price, description "
+ "and amount.");
productData.setProductPrice(4.13);
productData.setProductDescription("Öljy rekoille.");
productData.setProductAmount(20);
//Saving entity
EntityManager em = getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(productData);
Integer primarykey = productData.getId();
tx.commit();
//Reading entity from database
log.debug("Reading entity data from database.");
tx = em.getTransaction();
tx.begin();
productData = em.find(ProductData.class, primarykey);
tx.commit();
//Asserting that the product data was correct in database
Assert.assertEquals("Product name was correct", "Öljy",
productData.getProductName());
Assert.assertEquals("Product price was correct", 4.13,
productData.getProductPrice());
Assert.assertEquals("Product description was correct", "Öljy rekoille",
productData.getProductDescription());
Assert.assertEquals("Product amount was incorrect", 21,
productData.getProductAmount());
}
}
DatabaseTestCase class code is following:
public class DatabaseTestCase {
/**
* This method establishes Entity Managerin before every test
* and writes information into log.
*/
private static final Log log = LogFactory.getLog(DatabaseTestCase.class);
private EntityManager entityManager;
private EntityManagerFactory entityManagerFactory;
#Before
public void establishEntityManager(){
log.debug("Establishing database connection!");
entityManagerFactory = Persistence.createEntityManagerFactory("warehouseTestPersistence", null);
entityManager = entityManagerFactory.createEntityManager();
}
#After
public void closeEntityManager(){
if(entityManager != null){
entityManager.close();
} else {
log.warn("Entity was empty (null) in tests.");
}
}
public EntityManager getEntityManager(){
return entityManager;
}
/**
* #param entityManager the entityManager to set
*/
public void setEntityManager(final EntityManager entityManager) {
this.entityManager = entityManager;
}
/**
* #return the entityManagerFactory
*/
public EntityManagerFactory getEntityManagerFactory() {
return entityManagerFactory;
}
/**
* #param entityManagerFactory the entityManagerFactory to set
*/
public void setEntityManagerFactory(final EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
/**
* Save entity to database. Used for assisting in tests.
* #return primary key for specific entity.
*/
protected Integer saveEntity(Entityclass entity){
EntityManager em = getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(entity);
tx.commit();
return entity.getId();
}
/**
* Load entity from save location.
* #param <T>
* Generic class.
* #param entityclass
* Entity class of entity that is going to be loaded.
* #param primarykey
* Entity class primary key.
* #return correspondant entity.
*/
protected <T extends Entityclass> T loadEntity(final Class<T> entitysclass, final Integer primarykey){
EntityManager em = getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
T entity = em.find(entitysclass, primarykey);
tx.commit();
return entity;
}
}
I use NetBeans 7.0.1. All imports for both class are done properly. I have also added all required dependencies. But whenever I run a test I get following result:
-- error field --
No test passed, 1 test caused an error.(0,075 s)
com.mysite.warehouseapp.DataStructureTest FAILED
testProductData caused an ERROR: Implementing class
Implementing class
java.lang.IncompatibleClassChangeError
at....
at com.mysite.warehouseapp.test.DatabaseTestCase.establishEntityManager(DatabaseTestCase.java:36)
-- test result field --
Establishing database connection!
Entity was empty (null) in tests.
if you see the test result image at top I don't know how it got there but it supposed to be in this location.
So briefly said whenever I try to run the test there's no entity. Can anybody tell me the reason? I have tried to find solution for this problem for four days but still with no luck. Any help is appreciated.
Thanks
I found the solution my ownself, which is quiet rewarding :). Anyway the solution for this problem was that the persistence.xml was in wrong folder. And for newbies, I would advice you to put persistence.xml under following folder in your project src/main/resources/META-INF/persistence.xml.
If you don't have resources/META-INF folder created under src/test, then create one and place your persistence.xml file there. I hope this helps somebody :)
Another thing which I found out was that you might have different jars that are "against" each other and thus you need to take out those jars then it will work.

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

Maven plugin can't load class

I'm trying to make a maven plugin that needs to use reflection. I want a project to run the plugin, and give it the full name of a class in the project, and the plugin will load it by reflection to get info from it.
There's something strange with the classloader though, because it can't find the class when I use
Class.forName("package.MyClass");
Looking at the "Guide to Maven Classloading", I can't quite figure out if my plugin's classloader, when being run in a different project, has access to that project's classes.
I'm sure there's a better way, but here's how I got it to work:
Add the following to the javadoc at the top of your mojo:
#requiresDependencyResolution runtime
Add a MavenProject parameter:
/**
* #parameter expression="${project}"
* #required
* #readonly
*/
private MavenProject project;
Then you can get the dependencies at runtime, and make your own classloader:
List runtimeClasspathElements = project.getRuntimeClasspathElements();
URL[] runtimeUrls = new URL[runtimeClasspathElements.size()];
for (int i = 0; i < runtimeClasspathElements.size(); i++) {
String element = (String) runtimeClasspathElements.get(i);
runtimeUrls[i] = new File(element).toURI().toURL();
}
URLClassLoader newLoader = new URLClassLoader(runtimeUrls,
Thread.currentThread().getContextClassLoader());
Then you can load your class using this new classloader:
Class bundle = newLoader.loadClass("package.MyClass");
You should consider using this to add the runtime class path elements to the current class realm. (You can use the PluginDescriptor to retrieve the class realm.
List<String> runtimeClasspathElements = project.getRuntimeClasspathElements();
ClassRealm realm = descriptor.getClassRealm();
for (String element : runtimeClasspathElements)
{
File elementFile = new File(element);
realm.addURL(elementFile.toURI().toURL());
}
This worked perfectly for me!
As Dave asked, here is the way to get the PluginDescriptor:
/**
* The plugin descriptor
*
* #parameter default-value="${descriptor}"
*/
private PluginDescriptor descriptor;
I ran across this exact issue, today. The above suggestions didn't work for me, thought I would submit my solution to the list. I used the HibernateExporter mojo source which can be viewed at: http://grepcode.com/file/repo1.maven.org/maven2/org.codehaus.mojo/hibernate3-maven-plugin/2.2/org/codehaus/mojo/hibernate3/HibernateExporterMojo.java?av=f
/**
* #parameter expression="${project}"
* #required
* #readonly
*/
private MavenProject project;
private ClassLoader getClassLoader() throws MojoExecutionException
{
try
{
List<String> classpathElements = project.getCompileClasspathElements();
classpathElements.add(project.getBuild().getOutputDirectory() );
classpathElements.add(project.getBuild().getTestOutputDirectory() );
URL urls[] = new URL[classpathElements.size()];
for ( int i = 0; i < classpathElements.size(); ++i )
{
urls[i] = new File( (String) classpathElements.get( i ) ).toURI().toURL();
}
return new URLClassLoader(urls, getClass().getClassLoader() );
}
catch (Exception e)//gotta catch em all
{
throw new MojoExecutionException("Couldn't create a classloader.", e);
}
}
public void execute() throws MojoExecutionException
{
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClassLoader());
//... your code here ...
}
Also make sure you are using the right MavenProject class. add this to your pom
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0.3</version>
</dependency>
This worked for me and maven3 to get dependencies into the plugin classpath.
The trick is to use #Component to inject the PluginDescriptor. Otherwise it will not be set up correctly.
#Component
private MavenProject project;
#Component
private PluginDescriptor descriptor;
private void addDependenciesToClasspath(String artifactId) {
for (Artifact artifact : project.getDependencyArtifacts()) {
if (artifact.getArtifactId().equals(artifactId)) {
try {
final URL url = artifact.getFile().toURI().toURL();
final ClassRealm realm = descriptor.getClassRealm();
realm.addURL(url);
}
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
}
It's ok, we need to make our own classloaders
Custom Classloaders