Jackson deserialiser affected by the Deadbolt "restrict" annotation - jackson

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

I have a temporary solution. I override class loader for current thread to class loader from play.Environment
public class MyController extends Controller {
#Inject
private Environment environment;
#Restrict({#Group({"Admin"})})
public CompletionStage<Result> action(long id) {
Thread.currentThread().setContextClassLoader(environment.classLoader());
String json = getJsonFromStorage();
Map<String, MyClass> result = new HashMap<>();
try {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE, "class");
JsonFactory factory = mapper.getFactory();
JsonParser parser = factory.createParser(new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))));
JavaType type = mapper.getTypeFactory().constructType(result.getClass());
t = mapper.readValue(parser, type);
} catch (IOException e) {
e.printStackTrace();
}
return ok("ok")
}
}

Related

Spring WebSession Redis Exception

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

Jackson Serialization Problems

I am having some trouble serializing/deserializing my classes below.
My Data class holds a list of other classes.
When I call the serialize/deserialize methods in the Data class, I get the following error:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.amazon.rancor.storage.types.ChildData: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
The error comes from the deserialize method. But I also believe the serialization is not working properly. This is what the serialized Data object looks like:
{childData:[{zipCode:{present:true},countryCode:"US"}]
The Optional field is not being serialized properly even though I have set the objectMapper.registerModule(new Jdk8Module()); field
I can't seem to figure out what I am doing wrong. Maybe I need to change something in ChildData and ChildDataV2 class. But I am not sure what.
Any pointers would be appreciated!
public class Data {
private List<ChildData> childData;
private List<ChildDataV2> childDataV2;
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new Jdk8Module());
}
public Data() { }
#JsonCreator
public Data(#JsonProperty("childData") final List<ChildData> childData,
#JsonProperty("childDataV2") final List<ChildDataV2> childDataV2) {
this.childData = childData;
this.childDataV2 = childDataV2;
}
public List<ChildData> getChildData() {
return childData;
}
public void setChildData(final List<ChildData> childData) {
this.childData = childData;
}
public List<ChildDataV2> getChildDataV2() {
return childDataV2;
}
public void setChildDataV2(final List<ChildDataV2> childDataV2) {
this.childDataV2 = childDataV2;
}
public String serialize() {
try {
return objectMapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize. Data: " + this, e);
}
}
public Data deSerialize(final String data) {
try {
return objectMapper.readValue(data, Data.class);
} catch (IOException e) {
throw new RuntimeException("Failed to deserialize. Data" + data, e);
}
}
}
public class ChildData {
private final String countryCode;
private final Optional<String> zipCode;
public ChildData(final String countryCode, final Optional<String> zipCode) {
this.countryCode = countryCode;
this.zipCode = zipCode;
}
public Optional<String> getZipCode() {
return zipCode;
}
public String getCountryCode() {
return countryCode;
}
}
public class ChildDataV2 extends ChildData {
private final Object struct;
public ChildDataV2(final String cc, final Optional<String> postalCode,
final Object struct) {
super(cc, postalcode);
this.struct = struct;
}
}
The exception is quite clear right? You need to add a default constructor for ChildData or annotate the existing constructor like this:
#JsonCreator
public ChildData(#JsonProperty("countryCode") String countryCode, #JsonProperty("zipCode") Optional<String> zipCode) {
this.countryCode = countryCode;
this.zipCode = zipCode;
}

jackson deserialization anysetter NPE

#JsonInclude(JsonInclude.Include.NON_NULL)
public class Payer{
private String name;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
When I use objectmepper.readValue(json_string, Payer.class) with following json string:
{
"name": "fakeName",
"state": "verifird"
}
I get NPE. Since I have #JsonAnySetter, the state string should be put into additionalProperties, I'd like to know why do I get NPE here?
Include getter and setter for name in Payer class:
#JsonInclude(JsonInclude.Include.NON_NULL)
class Payer{
private String name;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Parse the Json:
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
String json_string = "{\n" +
" \"name\": \"fakeName\",\n" +
" \"state\": \"verifird\"\n" +
"}";
Payer readValue = mapper.readValue(json_string , Payer.class);
System.out.println(readValue.getName());
System.out.println(readValue.getAdditionalProperties());
}
Output:
fakeName
{state=verifird}

Set Gemfire entry-ttl in Java Beans

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

Populating RecyclerView with parsed JSON. onPostExecute error

I have created a HTTPManager class to make the GET request to download a simple JSON file from http://dev1.digitalnativeshq.com/userJSONtest.json
In ASyncTask, the GET request is made and a string is returned. That string is parsed by the JSONParser class. JSONParser returns a array of User objects. In onPostExecute I want to set the adapter data to the information that is downloaded and parsed in doInBackGround. I am getting an error when I try
adapter = new ContactAdapter(MainActivity.this, contactList);
MainActivity.java
public class MainActivity extends Activity {
private ContactAdapter adapter;
private static String testURL = "http://dev1.digitalnativeshq.com/userJSONtest.json";
private RecyclerView rView;
private List<ContactInfo> contactList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView rView = (RecyclerView)findViewById(R.id.list);
rView.setHasFixedSize(true);
LinearLayoutManager layout = new LinearLayoutManager(this);
layout.setOrientation(LinearLayoutManager.VERTICAL);
rView.setLayoutManager(layout);
fillRecyclerview task = new fillRecyclerview();
task.execute();
}
private class fillRecyclerview extends AsyncTask<Void, Void, List<ContactInfo>>{
#Override
protected List<ContactInfo> doInBackground(Void... params) {
String json = HTTPManager.getData(testURL);
contactList = JSONParser.parseFeed(json);
return contactList;
}
#Override
protected void onPostExecute(List<ContactInfo> result){
adapter = new ContactAdapter(MainActivity.this, contactList);
rView.setAdapter(adapter);
}
}
JSONParser.java
public class JSONParser {
public static List<ContactInfo> parseFeed(String content){
try {
JSONArray ar = new JSONArray(content);
List<ContactInfo> userList = new ArrayList<>();
//iterate through JSON object and set fields to strings
for (int i = 0; i < ar.length(); i++) {
JSONObject obj = ar.getJSONObject(i);
ContactInfo user = new ContactInfo();
user.setName(obj.getString("name"));
user.setEmail(obj.getString("email"));
userList.add(user);
}
return userList;
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
ContactAdapter.java
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> {
private List<ContactInfo> contactList;
public ContactAdapter(List<ContactInfo> contactList) {
this.contactList = contactList;
}
#Override
public int getItemCount() {
return contactList.size();
}
public static class ContactViewHolder extends RecyclerView.ViewHolder {
protected TextView vName;
protected TextView vEmail;
public ContactViewHolder(View v) {
super(v);
vName = (TextView) v.findViewById(R.id.name);
vEmail = (TextView) v.findViewById(R.id.email);
}
}
#Override
public void onBindViewHolder(ContactViewHolder contactViewHolder, int i) {
ContactInfo ci = contactList.get(i);
contactViewHolder.vName.setText(ci.name);
contactViewHolder.vEmail.setText(ci.email);
}
//select XML layout for each card
#Override
public ContactViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.cardview, viewGroup, false);
return new ContactViewHolder(itemView);
}
}
HTTPManager.java
public class HTTPManager {
public static String getData(String uri){
BufferedReader reader = null;
try{
URL url = new URL(uri);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
StringBuilder sb = new StringBuilder();
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line;
while((line = reader.readLine()) != null){
sb.append(line + "\n");
}
return sb.toString();
} catch (Exception e){
e.printStackTrace();
return null;
} finally {
if (reader == null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
In your MainActivity.java you get the List<ContactInfo> called result. But when using the adapter you are using contactList like in the doInBackground.
#Override
protected void onPostExecute(List<ContactInfo> result){
adapter = new ContactAdapter(MainActivity.this, contactList);
rView.setAdapter(adapter);
}
don't know if that will fix it but did you try this?
#Override
protected void onPostExecute(List<ContactInfo> result){
adapter = new ContactAdapter(MainActivity.this, result);
rView.setAdapter(adapter);
}