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

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

Related

BinaryInvalidTypeException in Ignite Remote Filter

The following code is based on a combination of Ingite's CacheQueryExample and CacheContinuousQueryExample.
The code starts a fat Ignite client. Three organizations are created in the cache and we are listening to the updates to the cache. The remote filter is set to trigger the continuous query if the organization name is "Google". Peer class loading is enabled by the default examples xml config file (example-ignite.xml), so the expectation is that the remote node is aware of the Organization class.
However the following exceptions are shown in the Ignite server's console (one for each cache entry) and all three records are returned to the client in the continuous query's event handler instead of just the "Google" record. If the filter is changed to check on the key instead of the value, the correct behavior is observed and a single record is returned to the local listener.
[08:28:43,302][SEVERE][sys-stripe-1-#2][query] CacheEntryEventFilter failed: class o.a.i.binary.BinaryInvalidTypeException: o.a.i.examples.model.Organization
[08:28:51,819][SEVERE][sys-stripe-2-#3][query] CacheEntryEventFilter failed: class o.a.i.binary.BinaryInvalidTypeException: o.a.i.examples.model.Organization
[08:28:52,692][SEVERE][sys-stripe-3-#4][query] CacheEntryEventFilter failed: class o.a.i.binary.BinaryInvalidTypeException: o.a.i.examples.model.Organization
To run the code
Start an ignite server using examples/config/example-ignite.xml as the configuration file.
Replace the content of ignite's CacheContinuousQueryExample.java with the following code. You may have to change the path to the configuration file to an absolute path.
package org.apache.ignite.examples.datagrid;
import javax.cache.Cache;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryEventFilter;
import javax.cache.event.CacheEntryUpdatedListener;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.affinity.AffinityKey;
import org.apache.ignite.cache.query.ContinuousQuery;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.cache.query.ScanQuery;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.examples.ExampleNodeStartup;
import org.apache.ignite.examples.model.Organization;
import org.apache.ignite.examples.model.Person;
import org.apache.ignite.lang.IgniteBiPredicate;
import java.util.Collection;
/**
* This examples demonstrates continuous query API.
* <p>
* Remote nodes should always be started with special configuration file which
* enables P2P class loading: {#code 'ignite.{sh|bat} examples/config/example-ignite.xml'}.
* <p>
* Alternatively you can run {#link ExampleNodeStartup} in another JVM which will
* start node with {#code examples/config/example-ignite.xml} configuration.
*/
public class CacheContinuousQueryExample {
/** Organizations cache name. */
private static final String ORG_CACHE = CacheQueryExample.class.getSimpleName() + "Organizations";
/**
* Executes example.
*
* #param args Command line arguments, none required.
* #throws Exception If example execution failed.
*/
public static void main(String[] args) throws Exception {
Ignition.setClientMode(true);
try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
System.out.println();
System.out.println(">>> Cache continuous query example started.");
CacheConfiguration<Long, Organization> orgCacheCfg = new CacheConfiguration<>(ORG_CACHE);
orgCacheCfg.setCacheMode(CacheMode.PARTITIONED); // Default.
orgCacheCfg.setIndexedTypes(Long.class, Organization.class);
// Auto-close cache at the end of the example.
try {
ignite.getOrCreateCache(orgCacheCfg);
// Create new continuous query.
ContinuousQuery<Long, Organization> qry = new ContinuousQuery<>();
// Callback that is called locally when update notifications are received.
qry.setLocalListener(new CacheEntryUpdatedListener<Long, Organization>() {
#Override public void onUpdated(Iterable<CacheEntryEvent<? extends Long, ? extends Organization>> evts) {
for (CacheEntryEvent<? extends Long, ? extends Organization> e : evts)
System.out.println("Updated entry [key=" + e.getKey() + ", val=" + e.getValue() + ']');
}
});
// This filter will be evaluated remotely on all nodes.
// Entry that pass this filter will be sent to the caller.
qry.setRemoteFilterFactory(new Factory<CacheEntryEventFilter<Long, Organization>>() {
#Override public CacheEntryEventFilter<Long, Organization> create() {
return new CacheEntryEventFilter<Long, Organization>() {
#Override public boolean evaluate(CacheEntryEvent<? extends Long, ? extends Organization> e) {
//return e.getKey() == 3;
return e.getValue().name().equals("Google");
}
};
}
});
ignite.getOrCreateCache(ORG_CACHE).query(qry);
// Populate caches.
initialize();
Thread.sleep(2000);
}
finally {
// Distributed cache could be removed from cluster only by #destroyCache() call.
ignite.destroyCache(ORG_CACHE);
}
}
}
/**
* Populate cache with test data.
*/
private static void initialize() {
IgniteCache<Long, Organization> orgCache = Ignition.ignite().cache(ORG_CACHE);
// Clear cache before running the example.
orgCache.clear();
// Organizations.
Organization org1 = new Organization("ApacheIgnite");
Organization org2 = new Organization("Apple");
Organization org3 = new Organization("Google");
orgCache.put(org1.id(), org1);
orgCache.put(org2.id(), org2);
orgCache.put(org3.id(), org3);
}
}
Here is an interim workaround that involves using and deserializing binary objects. Hopefully, someone can post a proper solution.
Here is the main() function modified to work with BinaryObjects instead of the Organization object:
public static void main(String[] args) throws Exception {
Ignition.setClientMode(true);
try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
System.out.println();
System.out.println(">>> Cache continuous query example started.");
CacheConfiguration<Long, Organization> orgCacheCfg = new CacheConfiguration<>(ORG_CACHE);
orgCacheCfg.setCacheMode(CacheMode.PARTITIONED); // Default.
orgCacheCfg.setIndexedTypes(Long.class, Organization.class);
// Auto-close cache at the end of the example.
try {
ignite.getOrCreateCache(orgCacheCfg);
// Create new continuous query.
ContinuousQuery<Long, BinaryObject> qry = new ContinuousQuery<>();
// Callback that is called locally when update notifications are received.
qry.setLocalListener(new CacheEntryUpdatedListener<Long, BinaryObject>() {
#Override public void onUpdated(Iterable<CacheEntryEvent<? extends Long, ? extends BinaryObject>> evts) {
for (CacheEntryEvent<? extends Long, ? extends BinaryObject> e : evts) {
Organization org = e.getValue().deserialize();
System.out.println("Updated entry [key=" + e.getKey() + ", val=" + org + ']');
}
}
});
// This filter will be evaluated remotely on all nodes.
// Entry that pass this filter will be sent to the caller.
qry.setRemoteFilterFactory(new Factory<CacheEntryEventFilter<Long, BinaryObject>>() {
#Override public CacheEntryEventFilter<Long, BinaryObject> create() {
return new CacheEntryEventFilter<Long, BinaryObject>() {
#Override public boolean evaluate(CacheEntryEvent<? extends Long, ? extends BinaryObject> e) {
//return e.getKey() == 3;
//return e.getValue().name().equals("Google");
return e.getValue().field("name").equals("Google");
}
};
}
});
ignite.getOrCreateCache(ORG_CACHE).withKeepBinary().query(qry);
// Populate caches.
initialize();
Thread.sleep(2000);
}
finally {
// Distributed cache could be removed from cluster only by #destroyCache() call.
ignite.destroyCache(ORG_CACHE);
}
}
}
Peer class loading is enabled ... so the expectation is that the remote node is aware of the Organization class.
This is the problem. You can't peer class load "model" objects, i.e., objects used to create the table.
Two solutions:
Deploy the model class(es) to the server ahead of time. The rest of the code -- the filters -- can be peer class loaded
As #rgb1380 demonstrates, you can use BinaryObjects, which is the underlying data format
Another small point, to use "autoclose" you need to structure your code like this:
// Auto-close cache at the end of the example.
try (var cache = ignite.getOrCreateCache(orgCacheCfg)) {
// do stuff
}

How do I configure spring-kafka to ignore messages in the wrong format?

We have an issue with one of our Kafka topics which is consumed by the DefaultKafkaConsumerFactory & ConcurrentMessageListenerContainer combination described here with a JsonDeserializer used by the Factory. Unfortunately someone got a little enthusiastic and published some invalid messages onto the topic. It appears that spring-kafka silently fails to process past the first of these messages. Is it possible to have spring-kafka log an error and continue? Looking at the error messages which are logged it seems that perhaps the Apache kafka-clients library should deal with the case that when iterating a batch of messages one or more of them may fail to parse?
The below code is an example test case illustrating this issue:
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.junit.ClassRule;
import org.junit.Test;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.listener.KafkaMessageListenerContainer;
import org.springframework.kafka.listener.MessageListener;
import org.springframework.kafka.listener.config.ContainerProperties;
import org.springframework.kafka.support.SendResult;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.kafka.support.serializer.JsonSerializer;
import org.springframework.kafka.test.rule.KafkaEmbedded;
import org.springframework.kafka.test.utils.ContainerTestUtils;
import org.springframework.util.concurrent.ListenableFuture;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.springframework.kafka.test.hamcrest.KafkaMatchers.hasKey;
import static org.springframework.kafka.test.hamcrest.KafkaMatchers.hasValue;
/**
* #author jfreedman
*/
public class TestSpringKafka {
private static final String TOPIC1 = "spring.kafka.1.t";
#ClassRule
public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, 1, TOPIC1);
#Test
public void submitMessageThenGarbageThenAnotherMessage() throws Exception {
final BlockingQueue<ConsumerRecord<String, JsonObject>> records = createListener(TOPIC1);
final KafkaTemplate<String, JsonObject> objectTemplate = createPublisher("json", new JsonSerializer<JsonObject>());
sendAndVerifyMessage(records, objectTemplate, "foo", new JsonObject("foo"), 0L);
// push some garbage text to Kafka which cannot be marshalled, this should not interrupt processing
final KafkaTemplate<String, String> garbageTemplate = createPublisher("garbage", new StringSerializer());
final SendResult<String, String> garbageResult = garbageTemplate.send(TOPIC1, "bar","bar").get(5, TimeUnit.SECONDS);
assertEquals(1L, garbageResult.getRecordMetadata().offset());
sendAndVerifyMessage(records, objectTemplate, "baz", new JsonObject("baz"), 2L);
}
private <T> KafkaTemplate<String, T> createPublisher(final String label, final Serializer<T> serializer) {
final Map<String, Object> producerProps = new HashMap<>();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
producerProps.put(ProducerConfig.CLIENT_ID_CONFIG, "TestPublisher-" + label);
producerProps.put(ProducerConfig.ACKS_CONFIG, "all");
producerProps.put(ProducerConfig.RETRIES_CONFIG, 2);
producerProps.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
producerProps.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000);
producerProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 5000);
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, serializer.getClass());
final DefaultKafkaProducerFactory<String, T> pf = new DefaultKafkaProducerFactory<>(producerProps);
pf.setValueSerializer(serializer);
return new KafkaTemplate<>(pf);
}
private BlockingQueue<ConsumerRecord<String, JsonObject>> createListener(final String topic) throws Exception {
final Map<String, Object> consumerProps = new HashMap<>();
consumerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "TestConsumer");
consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
consumerProps.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100");
consumerProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 15000);
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
final DefaultKafkaConsumerFactory<String, JsonObject> cf = new DefaultKafkaConsumerFactory<>(consumerProps);
cf.setValueDeserializer(new JsonDeserializer<>(JsonObject.class));
final KafkaMessageListenerContainer<String, JsonObject> container = new KafkaMessageListenerContainer<>(cf, new ContainerProperties(topic));
final BlockingQueue<ConsumerRecord<String, JsonObject>> records = new LinkedBlockingQueue<>();
container.setupMessageListener((MessageListener<String, JsonObject>) records::add);
container.setBeanName("TestListener");
container.start();
ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic());
return records;
}
private void sendAndVerifyMessage(final BlockingQueue<ConsumerRecord<String, JsonObject>> records,
final KafkaTemplate<String, JsonObject> template,
final String key, final JsonObject value,
final long expectedOffset) throws InterruptedException, ExecutionException, TimeoutException {
final ListenableFuture<SendResult<String, JsonObject>> future = template.send(TOPIC1, key, value);
final ConsumerRecord<String, JsonObject> record = records.poll(5, TimeUnit.SECONDS);
assertThat(record, hasKey(key));
assertThat(record, hasValue(value));
assertEquals(expectedOffset, future.get(5, TimeUnit.SECONDS).getRecordMetadata().offset());
}
public static final class JsonObject {
private String value;
public JsonObject() {}
JsonObject(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
#Override
public boolean equals(final Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
final JsonObject that = (JsonObject) o;
return Objects.equals(value, that.value);
}
#Override
public int hashCode() {
return Objects.hash(value);
}
#Override
public String toString() {
return "JsonObject{" +
"value='" + value + '\'' +
'}';
}
}
}
I have a solution but I don't know if it's the best one, I extended JsonDeserializer as follows which results in a null value being consumed by spring-kafka and requires the necessary downstream changes to handle that case.
class SafeJsonDeserializer[A >: Null](targetType: Class[A], objectMapper: ObjectMapper) extends JsonDeserializer[A](targetType, objectMapper) with Logging {
override def deserialize(topic: String, data: Array[Byte]): A = try {
super.deserialize(topic, data)
} catch {
case e: Exception =>
logger.error("Failed to deserialize data [%s] from topic [%s]".format(new String(data), topic), e)
null
}
}
Starting from the spring-kafka-2.x.x, we now have the comfort of declaring beans in the config file for the interface KafkaListenerErrorHandler with a implementation something as
#Bean
public ConsumerAwareListenerErrorHandler listen3ErrorHandler() {
return (m, e, c) -> {
this.listen3Exception = e;
MessageHeaders headers = m.getHeaders();
c.seek(new org.apache.kafka.common.TopicPartition(
headers.get(KafkaHeaders.RECEIVED_TOPIC, String.class),
headers.get(KafkaHeaders.RECEIVED_PARTITION_ID, Integer.class)),
headers.get(KafkaHeaders.OFFSET, Long.class));
return null;
};
}
more resources can be found at https://docs.spring.io/spring-kafka/reference/htmlsingle/#annotation-error-handling There is also another link with the similar issue: Spring Kafka error handling - v1.1.x and How to handle SerializationException after deserialization
Use ErrorHandlingDeserializer2. This is a delegating key/value deserializer that catches exceptions, returning them in the headers as serialized java objects.
Under consumer configuration, add/update the below lines:
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
classOf[ErrorHandlingDeserializer2[JsonDeserializer]].getName)
configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, classOf[ErrorHandlingDeserializer2[StringDeserializer]].getName)
configProps.put(ErrorHandlingDeserializer2.KEY_DESERIALIZER_CLASS, classOf[StringDeserializer].getName)
configProps.put(ErrorHandlingDeserializer2.VALUE_DESERIALIZER_CLASS, classOf[JsonDeserializer].getName)

Spring restController: how to error when unknown #RequestParam is in url

I'm using spring 4.2 to create some restfull webservices.
But we realized that when a user mistypes one of the not-mandatory #RequestParam, we do not get an error that the param he passed is unknown.
like we have #RequestParam(required=false, value="valueA") String value A and in the call he uses '?valuueA=AA' -> we want an error.
But I do not seem to find a way to do this, the value is just ignored and the user is unaware of this.
One possible solution would be to create an implementation of HandlerInterceptor which will verify that all request parameters passed to the handler method are declared in its #RequestParam annotated parameters.
However you should consider the disadvantages of such solution. There might be situations where you want to allow certain parameters to be passed in and not be declared as request params. For instance if you have request like GET /foo?page=1&offset=0 and have handler with following signature:
#RequestMapping
public List<Foo> listFoos(PagingParams page);
and PagingParams is a class containing page and offset properties, it will normally be mapped from the request parameters. Implementation of a solution you want would interfere with this Spring MVC'c functionality.
That being said, here is a sample implementation I had in mind:
public class UndeclaredParamsHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
checkParams(request, getDeclaredRequestParams(handlerMethod));
}
return true;
}
private void checkParams(HttpServletRequest request, Set<String> allowedParams) {
request.getParameterMap().entrySet().forEach(entry -> {
String param = entry.getKey();
if (!allowedParams.contains(param)) {
throw new UndeclaredRequestParamException(param, allowedParams);
}
});
}
private Set<String> getDeclaredRequestParams(HandlerMethod handlerMethod) {
Set<String> declaredRequestParams = new HashSet<>();
MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
for (MethodParameter methodParameter : methodParameters) {
if (methodParameter.hasParameterAnnotation(RequestParam.class)) {
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
if (StringUtils.hasText(requestParam.value())) {
declaredRequestParams.add(requestParam.value());
} else {
methodParameter.initParameterNameDiscovery(parameterNameDiscoverer);
declaredRequestParams.add(methodParameter.getParameterName());
}
}
}
return declaredRequestParams;
}
}
Basically this will do what I described above. You can then add exception handler for the exception it throws and translate it to HTTP 400 response. I've put more of an complete sample on Github, which includes a way to selectively enable this behavior for individual handler methods via annotation.
I translated Bohuslav Burghardt's solution for Spring WebFlux applications.
I dropped the #DisallowUndeclaredRequestParams annotation class from GitHub because I didn't need it -- it just applies the filter to all HandlerMethods. But someone else could update this answer and put it back.
package com.example.springundeclaredparamerror;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
/**
* Handler interceptor used for ensuring that no request params other than those explicitly
* declared via {#link RequestParam} parameters of the handler method are passed in.
*/
// Implementation translated into WebFlux WebFilter from:
// https://github.com/bohuslav-burghardt/spring-sandbox/tree/master/handler-interceptors/src/main/java/handler_interceptors
#Component
public class DisallowUndeclaredParamsFilter implements WebFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(DisallowUndeclaredParamsFilter.class);
#Autowired
#Qualifier("requestMappingHandlerMapping")
RequestMappingHandlerMapping mapping;
#Autowired
ObjectMapper mapper;
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
Object o = mapping.getHandler(serverWebExchange).toFuture().getNow(null);
Optional<String> undeclaredParam = Optional.empty();
if (o != null && o instanceof HandlerMethod) {
var handlerMethod = (HandlerMethod) o;
undeclaredParam = checkParams(serverWebExchange.getRequest(),
getDeclaredRequestParams(handlerMethod));
}
return undeclaredParam.map((param) -> RespondWithError(serverWebExchange, param))
.orElseGet(() -> webFilterChain.filter(serverWebExchange));
}
/** Responds to the request with an error message for the given undeclared parameter. */
private Mono<Void> RespondWithError(ServerWebExchange serverWebExchange, String undeclaredParam) {
final HttpStatus status = HttpStatus.BAD_REQUEST;
serverWebExchange.getResponse().setStatusCode(status);
serverWebExchange.getResponse().getHeaders().add(
"Content-Type", "application/json");
UndeclaredParamErrorResponse response = new UndeclaredParamErrorResponse();
response.message = "Parameter not expected: " + undeclaredParam;
response.statusCode = status.value();
String error = null;
try {
error = mapper.writeValueAsString(response);
} catch (JsonProcessingException e) {
error = "Parameter not expected; error generating JSON response";
LOGGER.warn("Error generating JSON response for undeclared argument", e);
}
return serverWebExchange.getResponse().writeAndFlushWith(
Mono.just(Mono.just(serverWebExchange.getResponse().bufferFactory().wrap(
error.getBytes(StandardCharsets.UTF_8)))));
}
/** Structure for generating error JSON. */
static class UndeclaredParamErrorResponse {
public String message;
public int statusCode;
}
/**
* Check that all of the request params of the specified request are contained within the specified set of allowed
* parameters.
*
* #param request Request whose params to check.
* #param allowedParams Set of allowed request parameters.
* #return Name of a param in the request that is not allowed, or empty if all params in the request are allowed.
*/
private Optional<String> checkParams(ServerHttpRequest request, Set<String> allowedParams) {
return request.getQueryParams().keySet().stream().filter(param ->
!allowedParams.contains(param)
).findFirst();
}
/**
* Extract all request parameters declared via {#link RequestParam} for the specified handler method.
*
* #param handlerMethod Handler method to extract declared params for.
* #return Set of declared request parameters.
*/
private Set<String> getDeclaredRequestParams(HandlerMethod handlerMethod) {
Set<String> declaredRequestParams = new HashSet<>();
MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
for (MethodParameter methodParameter : methodParameters) {
if (methodParameter.hasParameterAnnotation(RequestParam.class)) {
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
if (StringUtils.hasText(requestParam.value())) {
declaredRequestParams.add(requestParam.value());
} else {
methodParameter.initParameterNameDiscovery(parameterNameDiscoverer);
declaredRequestParams.add(methodParameter.getParameterName());
}
}
}
return declaredRequestParams;
}
}
Here's the unit test I wrote for it. I recommend checking it into your codebase as well.
package com.example.springundeclaredparamerror;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
#RunWith(SpringRunner.class)
#WebFluxTest(controllers = {DisallowUndeclaredParamFilterTest.TestController.class})
public class DisallowUndeclaredParamFilterTest {
private static final String TEST_ENDPOINT = "/disallowUndeclaredParamFilterTest";
#Rule
public final WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort());
#Autowired
private WebTestClient webClient;
#Configuration
#Import({TestController.class, DisallowUndeclaredParamsFilter.class})
static class TestConfig {
}
#RestController
static class TestController {
#GetMapping(TEST_ENDPOINT)
public Mono<String> retrieveEntity(#RequestParam(name = "a", required = false) final String a) {
return Mono.just("ok");
}
}
#Test
public void testAllowsNoArgs() {
webClient.get().uri(TEST_ENDPOINT).exchange().expectBody(String.class).isEqualTo("ok");
}
#Test
public void testAllowsDeclaredArg() {
webClient.get().uri(TEST_ENDPOINT + "?a=1").exchange().expectBody(String.class).isEqualTo("ok");
}
#Test
public void testDisallowsUndeclaredArg() {
webClient.get().uri(TEST_ENDPOINT + "?b=1").exchange().expectStatus().is4xxClientError();
}
}

Caused by: java.lang.ClassCastException Wrapper cannot be cast to

I am trying to run Remote EJB running on a glassfish 3.1 container with a Javafax 2.2 client and I throw an exeption when I "lookup" the remote EJB.
The purpose of my Application is to get/put with Javafx Client objects which are save/retrieve as XML files on the server side.
On the server side the EJB is packaged into an EAR.
A controller "scrubber_S_Controller" is the Stateless session EJB
package scrubber_S_Controller;
import java.io.Serializable;
import javax.ejb.Stateless;
import javax.xml.bind.JAXBException;
import scrubber_S_Model.SimpleObject;
/**
* Session Bean implementation class Session
*/
#Stateless
public class Session implements SessionRemote, Serializable {
/**
*
*/
private static final long serialVersionUID = -5718452084852474986L;
/**
* Default constructor.
*/
public Session() {
// TODO Auto-generated constructor stub
}
#Override
public SimpleObject getSimpleObject() throws JAXBException {
SimpleObject simpleobjet = new SimpleObject();
return simpleobjet.retrieveSimpleObject();
}
#Override
public void setSimpleObject(SimpleObject simpleobject) throws JAXBException {
simpleobject.saveSimpleObject(simpleobject);
}
}
The remote interface used is
package scrubber_S_Controller;
import javax.ejb.Remote;
import javax.xml.bind.JAXBException;
import scrubber_S_Model.SimpleObject;
#Remote
public interface SessionRemote {
public SimpleObject getSimpleObject() throws JAXBException;
public void setSimpleObject(SimpleObject simpleobject) throws JAXBException;
}
SimpleObject are managed in a scrubber_S_Model package:
package scrubber_S_Model;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "SimpleObject")
public class SimpleObject implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 306212289216139111L;
/**
* Used to define a simpleObject Value Object
*/
#XmlElement(name = "scrubberValveValue")
private int scrubberValveValue;
#XmlElement(name = "bypassValveValue")
private int bypassValveValue;
#XmlElement(name = "exhaustState")
private boolean exhaustState;
#XmlElement(name = "layoutColor")
private String layoutColor;
#XmlElement(name = "textValue")
private String textValue;
#XmlElement(name = "textColor")
private String textColor;
#XmlElement(name = "pressureThreshold")
private int pressureThreshold;
public SimpleObject(int bypassvalvevalue, int scrubbervalvevalue,
boolean exhauststate, String layoutcolor, String textvalue,
String textcolor, int pressurethreshold) {
this.bypassValveValue = bypassvalvevalue;
this.scrubberValveValue = scrubbervalvevalue;
this.exhaustState = exhauststate;
this.layoutColor = layoutcolor;
this.textValue = textvalue;
this.textColor = textcolor;
this.pressureThreshold = pressurethreshold;
}
/**
* Empty constructor, just to enable JAXB.
*/
public SimpleObject() {
}
/**
* Gets the value of the scrubberValveValue property.
*
*/
public int getScrubberValveValue() {
return this.scrubberValveValue;
}
/**
* Sets the value of the scrubberValveValue property.
*
*/
public void setScrubberValveValue(int value) {
this.scrubberValveValue = value;
}
/**
* Gets the value of the bypassValveValue property.
*
*/
public int getBypassValveValue() {
return this.bypassValveValue;
}
/**
* Sets the value of the bypassValveValue property.
*
*/
public void setBypassValveValue(int value) {
this.bypassValveValue = value;
}
/**
* Gets the value of the exhaustState property.
*
*/
public boolean isExhaustState() {
return this.exhaustState;
}
/**
* Sets the value of the exhaustState property.
*
*/
public void setExhaustState(boolean value) {
this.exhaustState = value;
}
/**
* Gets the value of the layoutColor property.
*
* #return possible object is {#link String }
*
*/
public String getLayoutColor() {
return this.layoutColor;
}
/**
* Sets the value of the layoutColor property.
*
* #param value
* allowed object is {#link String }
*
*/
public void setLayoutColor(String value) {
this.layoutColor = value;
}
/**
* Gets the value of the textValue property.
*
* #return possible object is {#link String }
*
*/
public String getTextValue() {
return this.textValue;
}
/**
* Sets the value of the textValue property.
*
* #param value
* allowed object is {#link String }
*
*/
public void setTextValue(String value) {
this.textValue = value;
}
/**
* Gets the value of the textColor property.
*
* #return possible object is {#link String }
*
*/
public String getTextColor() {
return this.textColor;
}
/**
* Sets the value of the textColor property.
*
* #param value
* allowed object is {#link String }
*
*/
public void setTextColor(String value) {
this.textColor = value;
}
/**
* Gets the value of the pressureThreshold property.
*
*/
public int getPressureThreshold() {
return this.pressureThreshold;
}
/**
* Sets the value of the pressureThreshold property.
*
*/
public void setPressureThreshold(int value) {
this.pressureThreshold = value;
}
public void saveSimpleObject(SimpleObject simpleobjet) throws JAXBException {
FileOutputStream fileout = null;
JAXBContext jc = JAXBContext.newInstance(SimpleObject.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
try {
fileout = new FileOutputStream("simpleobjectfile.xml");
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
marshaller.marshal(simpleobjet, fileout);
try {
fileout.flush();
fileout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public SimpleObject retrieveSimpleObject() throws JAXBException {
FileInputStream fileinput = null;
JAXBContext jc = JAXBContext.newInstance(SimpleObject.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
try {
fileinput = new FileInputStream("simpleobjectfile.xml");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
SimpleObject simpleobjet = (SimpleObject)unmarshaller.unmarshal(fileinput);
try {
fileinput.close();
} catch (IOException e) {
e.printStackTrace();
}
return simpleobjet;
}
}
Junit test of the marshalling/unmashalling are working fine.
Deployment of the EJB give the following JNDI naming:
INFO: EJB5181:Portable JNDI names for EJB Session: [java:global/Scrubber_S_EAR/Scrubber_S/Session, java:global/Scrubber_S_EAR/Scrubber_S/Session!scrubber_S_Controller.SessionRemote]
INFO: CORE10010: Loading application Scrubber_S_EAR done in 4 406 ms
On the client side the Javafx application as follow:
package ScrubberView;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.xml.bind.JAXBException;
import scrubber_CView_Model.SimpleObject;
import session.SessionRemote;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class scrubberView extends Application {
#Override
public void start(Stage primaryStage) throws JAXBException {
try {
Properties propriétés = new Properties();
propriétés.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
Context ctx = new InitialContext(propriétés);
SessionRemote mySession = (SessionRemote)ctx.lookup("java:global/Scrubber_S_EAR/Scrubber_S/Session");
//Create an object to exchange
SimpleObject simpleObject = new SimpleObject(1, 2, true, "layoutcolor", "text", "textcolor", 10 );
mySession.setSimpleObject(simpleObject);
#SuppressWarnings("unused")
SimpleObject simpleObject2 = new SimpleObject();
simpleObject2 = mySession.getSimpleObject();
} catch (NamingException e) {
// TODO Auto-generated catch block
System.out.println(e.toString() );
e.printStackTrace();
}
//compose the scrubberview scene and show it
primaryStage.setTitle("scrubberView");
BorderPane borderpane = new BorderPane();
Scene scene = new Scene(borderpane, 350, 80, Color.GREY);
primaryStage.setScene(scene);
scene.getStylesheets().add("./CleanRoomControl.css");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The following jar are in the buid enc of the Application
C:\glassfish3\glassfish\modules\auto-depends.jar
C:\glassfish3\glassfish\modules\common-util.jar
C:\glassfish3\glassfish\modules\config-api.jar
C:\glassfish3\glassfish\modules\config-types.jar
C:\glassfish3\glassfish\modules\config.jar
C:\glassfish3\glassfish\modules\deployment-common.jar
C:\glassfish3\glassfish\modules\dol.jar
C:\glassfish3\glassfish\modules\ejb-container.jar
C:\glassfish3\glassfish\modules\ejb.security.jar
C:\glassfish3\glassfish\modules\glassfish-api.jar
C:\glassfish3\glassfish\modules\glassfish-corba-asm.jar
C:\glassfish3\glassfish\modules\glassfish-corba-codegen.jar
C:\glassfish3\glassfish\modules\glassfish-corba-csiv2-idl.jar
C:\glassfish3\glassfish\modules\glassfish-corba-newtimer.jar
C:\glassfish3\glassfish\modules\glassfish-corba-omgapi.jar
C:\glassfish3\glassfish\modules\glassfish-corba-orb.jar
C:\glassfish3\glassfish\modules\glassfish-corba-orbgeneric.jar
C:\glassfish3\glassfish\modules\glassfish-naming.jar
C:\glassfish3\glassfish\modules\gmbal.jar
C:\glassfish3\glassfish\modules\hk2-core.jar
C:\glassfish3\glassfish\modules\internal-api.jar
C:\glassfish3\glassfish\modules\javax.ejb.jar
C:\glassfish3\glassfish\modules\kernel.jar
C:\glassfish3\glassfish\modules\management-api.jar
C:\glassfish3\glassfish\modules\orb-connector.jar
C:\glassfish3\glassfish\modules\orb-iiop.jar
C:\glassfish3\glassfish\modules\security.jar
C:\glassfish3\glassfish\modules\transaction-internal-api.jar
when running the
SessionRemote mySession = (SessionRemote)ctx.lookup("java:global/Scrubber_S_EAR/Scrubber_S/Session");
line, it raises the following exeption:
Exception in Application start method
Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:403)
at com.sun.javafx.application.LauncherImpl.access$000(LauncherImpl.java:47)
at com.sun.javafx.application.LauncherImpl$1.run(LauncherImpl.java:115)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.ClassCastException: scrubber_S_Controller._SessionRemote_Wrapper cannot be cast to session.SessionRemote
at ScrubberView.scrubberView.start(scrubberView.java:27)
at com.sun.javafx.application.LauncherImpl$5.run(LauncherImpl.java:319)
at com.sun.javafx.application.PlatformImpl$5.run(PlatformImpl.java:215)
at com.sun.javafx.application.PlatformImpl$4$1.run(PlatformImpl.java:179)
at com.sun.javafx.application.PlatformImpl$4$1.run(PlatformImpl.java:176)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl$4.run(PlatformImpl.java:176)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(WinApplication.java:29)
at com.sun.glass.ui.win.WinApplication$3$1.run(WinApplication.java:73)
... 1 more
If I try with the other naming:
SessionRemote mySession = (SessionRemote)ctx.lookup("java:global/Scrubber_S_EAR/Scrubber_S/Session!scrubber_S_Controller.SessionRemote");
It raises the same exeption.
It will be great if somebody can help me to fix this issue.
Many thanks in advance for your help.
I hope that my english is not too bad for the understanding.
I fix this issue with the following:
Remove the jar files from the env and only put gf-client.jar in the classpath as explain in
How do I access a Remote EJB component from a stand-alone java client?
Rename "scrubber_S_Controler" to "session" (as the corresponding package in the client side)
Remove "retrieveSimpleObject()" and "saveSimpleObject(SimpleObject simpleobjet)" from "SimpleObject" class and add it to a new "SimpleObjectPersist class"
use the "SimpleObjectPersist" to save and retreive a SimpleObject.
Ough! After this, its running well.

Can I use RE-Captcha with Wicket?

Can I use recaptcha with apache wicket 1.5.3? Is there some good example?
In terms of Google reCAPTCHA v2, you can just follow its instruction, which is straightforward.
First of all, go to Google reCAPTCHA, and register your application there. Then you can work on the client and server sides respectively as below:
On the client side (see ref)
First, paste the snippet below <script...></script> before the closing tag on your HTML template, for example:
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
Then paste the snippet below <div...></div> at the end of the where you want the reCAPTCHA widget to appear, for example:
<div class="g-recaptcha" data-sitekey="{your public site key given by Google reCAPTCHA}"></div>
</form>
That's all on the client side.
On the server side (see ref)
When a user submits the form, you need to get the user response token from the g-recaptcha-response POST parameter. Then use the token, together with the secret key given by Google reCAPTCHA, and optional with the user's IP address, and then POST a request to the Google reCAPTCHA API. You'll then get the response from Google reCAPTHA, indicating whether the form verification succeeds or fails.
Below is the sample code on the server side.
User summits a Wicket form (Wicket 6 in this example):
protected void onSubmit() {
HttpServletRequest httpServletRequest = (HttpServletRequest)getRequest().getContainerRequest();
boolean isValidRecaptcha = ReCaptchaV2.getInstance().verify(httpServletRequest);
if(!isValidRecaptcha){
verificationFailedFeedbackPanel.setVisible(true);
return;
}
// reCAPTCHA verification succeeded, carry on handling form submission
...
}
ReCaptchaV2.java (Just Java, web framework independent)
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class ReCaptchaV2 {
private final static Logger logger = Logger.getLogger(ReCaptchaV2.class);
private final static String VERIFICATION_URL = "https://www.google.com/recaptcha/api/siteverify";
private final static String SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
private static ReCaptchaV2 instance = new ReCaptchaV2();
private ReCaptchaV2() {}
public static ReCaptchaV2 getInstance() {
return instance;
}
private boolean verify(String recaptchaUserResponse, String remoteip) {
boolean ret = false;
if (recaptchaUserResponse == null) {
return ret;
}
RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("secret", SECRET);
map.add("response", recaptchaUserResponse);
if (remoteip != null) {
map.add("remoteip", remoteip);
}
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<String> res = null;
try {
res = rt.exchange(VERIFICATION_URL, HttpMethod.POST, httpEntity, String.class);
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
}
if (res == null || res.getBody() == null) {
return ret;
}
Response response = null;
try {
response = new ObjectMapper().readValue(res.getBody(), Response.class);
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
}
if (response != null && response.isSuccess()) {
ret = true;
}
logger.info("Verification result: " + ret);
return ret;
}
public boolean verify(HttpServletRequest httpServletRequest) {
boolean ret = false;
if (httpServletRequest == null) {
return ret;
}
String recaptchaUserResponse = httpServletRequest.getParameter("g-recaptcha-response");
String remoteAddr = httpServletRequest.getRemoteAddr();
return verify(recaptchaUserResponse, remoteAddr);
}
}
Response.java (Java POJO)
public class Response {
private String challenge_ts;
private String hostname;
private boolean success;
public Response() {}
public String getChallenge_ts() {
return challenge_ts;
}
public void setChallenge_ts(String challenge_ts) {
this.challenge_ts = challenge_ts;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
#Override
public String toString() {
return "ClassPojo [challenge_ts = " + challenge_ts + ", hostname = " + hostname + ", success = " + success + "]";
}
}
Have you read this?
I have added the guide here in case page disappears.
Usage
We will create a panel called RecaptchaPanel. In order to use this component to your application all you'll have to do is this:
add(new RecaptchaPanel("recaptcha"));
and of course, add the component in your markup:
<div wicket:id="recaptcha"></div>
Implementation
Implementation is simple. All you have to do, is to follow several steps:
Add recaptcha dependency to your project
<dependency>
<groupid>net.tanesha.recaptcha4j</groupid>
<artifactid>recaptcha4j</artifactid>
<version>0.0.7</version>
</dependency>
This library hides the implementation details and expose an API for dealing with recaptcha service.
Create associated markup (RecaptchaPanel.html)
<wicket:panel><div wicket:id="captcha"></div></wicket:panel>
Create RecaptchaPanel.java
import net.tanesha.recaptcha.ReCaptcha;
import net.tanesha.recaptcha.ReCaptchaFactory;
import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
/**
* Displays recaptcha widget. It is configured using a pair of public/private keys which can be registered at the
* following location:
*
* https://www.google.com/recaptcha/admin/create
* <br>
* More details about recaptcha API: http://code.google.com/apis/recaptcha/intro.html
*
* #author Alex Objelean
*/
#SuppressWarnings("serial")
public class RecaptchaPanel extends Panel {
private static final Logger LOG = LoggerFactory.getLogger(RecaptchaPanel.class);
#SpringBean
private ServiceProvider serviceProvider;
public RecaptchaPanel(final String id) {
super(id);
final ReCaptcha recaptcha = ReCaptchaFactory.newReCaptcha(serviceProvider.getSettings().getRecaptchaPublicKey(),
serviceProvider.getSettings().getRecaptchaPrivateKey(), false);
add(new FormComponent<void>("captcha") {
#Override
protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
replaceComponentTagBody(markupStream, openTag, recaptcha.createRecaptchaHtml(null, null));
}
#Override
public void validate() {
final WebRequest request = (WebRequest)RequestCycle.get().getRequest();
final String remoteAddr = request.getHttpServletRequest().getRemoteAddr();
final ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
reCaptcha.setPrivateKey(serviceProvider.getSettings().getRecaptchaPrivateKey());
final String challenge = request.getParameter("recaptcha_challenge_field");
final String uresponse = request.getParameter("recaptcha_response_field");
final ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr, challenge, uresponse);
if (!reCaptchaResponse.isValid()) {
LOG.debug("wrong captcha");
error("Invalid captcha!");
}
}
});
}
}
</void>
Things to notice:
ServiceProvider - is a spring bean containing reCaptcha configurations (public key and private key). These keys are different depending on the domain where your application is deployed (by default works for any key when using localhost domain). You can generate keys here: https://www.google.com/recaptcha/admin/create
The RecaptchaPanel contains a FormComponent, which allows implementing validate method, containing the validation logic.
Because reCaptcha use hardcoded values for hidden fields, this component cannot have multiple independent instances on the same page.
Maybe the xaloon wicket components can be a solution for you. They have a Recaptcha plugin.