Use #JacksonInject with #JsonCreator on a top level map - java

With Jackson json library, it is possible to deserialize object through the use of the #JsonCreator, and be given the "top level" map representing the input json, as follows:
class MyClass {
final int field;
#JsonCreator
public MyClass(Map<String, Object> map) {
this.field = (int) map.get("theInt");
}
}
or even on a static factory method:
class MyClass {
final int field;
public MyClass(int theInt) {
this.field = theInt;
}
#JsonCreator
static MyClass create(Map<String, Object> map) {
return new MyClass((int) map.get("theInt"));
}
}
The previous examples can process the following kind of json input:
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
This is particularly useful in my case because I would like to deserialize a json which structure I don't know. Being given access to what I call the "top level map" makes things simple.
I would like to deserialize my objects this way as it also allows to create immutable objects, instead of using #JsonAnySetter which doesn't permit this, and #JsonProperty which I can't use as I don't know the properties name in advance as I mentioned earlier.
Then to go further, I would like to inject some configuration in my factory method, and Jackson allows this through the use of #JacksonInject and a call to withInjectableValues(InjectableValues) on the ObjectMapper.
This is eventually the kind of code I would like to use:
class MyClass {
final MyField[] myFields;
public MyClass(MyField... myFields) {
this.myFields = myFields;
}
#JsonCreator
static MyClass create(#JacksonInject("conf") Conf conf, Map<String, Object> map) {
MyFields[] myFields;
// initialize myFields (or any other object) based on the content of map
// and also make use of the inject conf
return new MyClass(myFields);
}
}
Unfortunately, Jackson throws the following kind of exceptions:
when trying the trick on the constructor
JsonMappingException: Argument #1 of constructor [constructor for MyClass, annotations: {JsonCreator=#JsonCreator()}] has no property name annotation; must have name when multiple-paramater constructor annotated as Creator
when trying the trick on the factory method
JsonMappingException: Argument #1 of factory method [method create, annotations: {JsonCreator=#JsonCreator()}] has no property name annotation; must have when multiple-paramater static method annotated as Creator
Does anyone know how I could solve the problem?
To sum up the requirements, I need:
access to the top level map (don't know the json property names in advance)
to create an immutable object (so can't use #JsonAnySetter)
to inject some conf to the #JsonCreator decorated constructor or factory method
I cannot change the json input format, which looks like this:
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
[EDIT]
This is a known issue: http://jira.codehaus.org/browse/JACKSON-711 (not fixed yet)

Right, you would like to both use "delegating" creator (single argument, into which JSON input is first bound) -- different from "property-based" creator where set of named parameters are passed -- and injectable value(s). This should ideally work, but I think it might not work currently.
I think there is a Jira entered for this, so you can check it (http://jira.codehaus.org/browse/JACKSON) out.
Just to make sure: are you using version 1.9.2? There have been some fixes in this are since 1.9.0; which at least would give better error message.

Related

Invoke method using spring SPEL with property

Is it possible to use a property value to invoke a method while assigning a value?
For instance, I know I can do this:
#Value("${name}")
private String name; // will have the value of the `name` property
I wonder if it's possible to do something like this:
#Value("#{myMethod(${name})}")
private String myModifiedVariable; // will have the result of invoking myMethod
After my research and a bit of testing, I found there is a way shown in this article Spring EL method invocation, But mybean should be a string bean
#Value("#{mybean.myMethod('${name}')}")
private String myModifiedVariable;
And if you want to call a method in the existing class then use the spring bean name of the same class
#Configuration // or any sterotype annoations
public class TestConfig {
#Value("#{testConfig.myMethod('${name}')}")
private String myModifiedVariable;
public String getValue(String val){
return val+"testValue";
}
}
When using interface projection (in Spring Data Repository) it is possible to call a static method like this:
public interface MyProjection {
// Here we are creating a 2 element list with 'Spring Data' and value taken from "MY_COLUMN_NAME" column
#Value("#{T(java.util.Arrays).asList('Spring Data', target.MY_COLUMN_NAME)}")
List<String> getSampleList();
}
You can also obtain a value of an enum (constant) with above notation. Sample for Spring Security check:
#PreAuthorize("hasRole(T(com.acme.UserRoles).ADMINISTRATOR.name())")
Similar notation should work in other places. Simply remember to use full path to class

How to use a nested java.reflect.Type in a spring properties file

Recently came upon a case where I want up-front but configurable types (long story). Typically I do this
Type t = new TypeToken<ArrayList<Integer>>() {}.getType();
I would like to place that right hand side into my spring properties file and either issue a call similar to this:
Type t = env.getProperty("type-property");
Or use #ConfigurationProperties.
I typically work with #ConfigurationProperties, but the repo I'm in does not have it available. I could make the case to have dependencies pulled in if the first way simply isn't possible, but the path of least resistance is preferable.
I have tried some variations of the following property definition with angle brackets, parentheses, and square brackets --- none worked out.
type-property=java.util.ArrayList<java.lang.Integer>
I've had a hard time getting anything useful with the kinds of search terms you're forced to use with this problem. I found this related question, but didn't have luck translating it to non-xml configuration (I cannot do xml style in this project either). link: Create a Guava TypeToken in Spring xml config?
EDIT:
Using the answer provided by #LppEdd, I used the following calls for de/serialization:
Type sampleType = new TypeToken<HashSet<ArrayList<Integer>>>() {}.getType();
// serialize via java.lang.reflect.Type.getTypeName()
String serializedType = sampleType.getTypeName();
// above string value is "java.util.HashSet<java.util.ArrayList<java.lang.Integer>>"
// deserialize via org.eclipse.jetty.util.TypeUtil.fromName(String name)
Type sampleTypeAgain = TypeUtil.fromName(serializedType);
To answer your "translating to Java" point:
#Bean
#Qualifier("IntegerArrayList")
public Type integerArrayList() {
return new TypeToken<ArrayList<Integer>>() {}.getType();
}
Usage:
#Autowired
#Qualifier("IntegerArrayList")
private Type type;
Alternatively...
I'll get you started here.
The "better" approach would be to use #ConfigurationProperties with a custom PropertySource implementation. All the Type sub-classes should be serializable, so you can store serialized data and deserialize on the fly.
The fields' names, obviously, would correspond to the Type(s) mapping keys.
class TypePropertySource extends PropertySource<TypeSource> {
private final TypeSource source;
TypePropertySource(final TypeSource source) {
super("a-name", source);
this.source = source;
}
#Override
public Object getProperty(final String name) {
try {
return (Type) source.getAndDeserialize(name);
} catch (final Exception e) {
// Recover or do nothing.
}
return null;
}
}
Edit: I really don't know if types other than String are supported for property values in Spring.
Have you considered using Spring SPEL? It almost accomplishes solution for you except due to bad support for generics you need to create separate class for each of your subclass of TypeToken.
For example with concrete class of :
import java.util.ArrayList;
import com.google.common.reflect.TypeToken;
public class MyType extends TypeToken< ArrayList<Integer> >{
}
You can have property like this:
type-property: new com.comcast.viser.ipPreference.web.MyType().getType()
And inject it like this:
#Value("#{${type-property}}")
private java.lang.reflect.Type type;

get annotation on method from a CDI managed bean

I want to retrieve an annotation (a custom written one) from a method. Usually I can ask the classloader by accessing
class.getMethod("methodName").getAnnotation("annotationName")
But if the bean is managed by a CDI container (I am using OpenWebBeans) the class is enhanced at runtime. Then I have to use the superclass to ask for annotations. Currently I try to detect if the class is managed by looking for "$$" in the classname. But that seems to be a very dirty solution to me.
Is there any good way to retrieve anntations from a CDI managed bean?
In detail my code is something like that:
I created an annotation "Coolnessfactor" to mark a method to be very cool :-)
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Coolnessfactor {
CoolnessValue value();
}
Via the enumeration CoolnessValue I want to specify how cool the method implementation is.
public enum CoolnessValue {
POOR, VERY_COOL, UNBELIEVABLE;
}
Then I mark different methods in my business classe with this annotation, fe:
#Override
#Coolnessfactor(CoolnessValue.POOR)
public void getSingleObjectWithDetails(final Integer techId) {
return this.dao.findCompleteDataByPrimaryKey(techId);
}
Now I want to analyse the value of the annotation which marks the different method. I have to do it in a CDI-Decorator, therefore I cannot do it with an interceptor binding.
At the moment my approach is to use the reflection API to retrieve the annotation value:
public static <A extends Annotation> Map<String, A> getAnnotatedMethodsOfClass(final Class<?> clazz,
final Class<A> annotationClazz) {
final Map<String, A> annotationMap = new HashMap<String, A>();
Method[] declaredMethods;
if (clazz.getName().contains("$$")) {
declaredMethods = clazz.getSuperclass().getDeclaredMethods();
} else {
declaredMethods = clazz.getDeclaredMethods();
}
for (final Method m : declaredMethods) {
if (m.isAnnotationPresent(annotationClazz)) {
annotationMap.put(m.getName(), m.getAnnotation(annotationClazz));
}
}
return annotationMap;
}
But this looks very awful to me. Especcially the detection of a class which is enhanced by the CDI implementation is very bad.
Maybe try it with BeanManager - you will want to use it to get hold of a Bean<?> instance of your bean. The approaches differ here, based on what kind of bean it is. Shuffle through the API and find your way.
Once you get Bean<?> you should be able to use getBeanClass() and with that you gain access to methods and their annotations.

Jackson: final field written to after it is set by #JsonCreator?

It seems Jackson uses reflection to write additional attributes directly into fields even if a #JsonCreator constructor was used and the field is marked as final.
Example:
public class Test {
static class X {
final String s;
#JsonCreator
public X(#JsonProperty("a") String a) {
s = "s";
}
public String getS() {
return s;
}
}
#org.junit.Test
public void ds() throws Exception {
ObjectMapper om = new ObjectMapper();
X x = om.readValue("{`a`:``, `s`: `t`}".replace('`', '"'), X.class);
assertEquals("s", x.s);
}
}
The assert will fail with org.junit.ComparisonFailure: expected:<[s]> but was:<[t]>.
Is this behavior documented anywhere? Is there anyway to disable this globally?
Also, I think this is a very dangerous design: if there are some value that should be read-only to the client, this effectively allows the client to set them even if the class is well designed according to normal immutable class guidelines.
First: yes, Jackson allows deserialization of all visible and not just those for which #JsonCreator property exists. So it is possible to set a smaller set of properties via constructor, and others via setters or fields. This may be needed for some cases like cyclic types.
As to how to prevent use of s for deserialization here. An obvious way is to add #JsonIgnore for field, although if so you will also need #JsonProperty for getS() to avoid both being removed.
But there are other settings as well, in MapperFeature.
ALLOW_FINAL_FIELDS_AS_MUTATORS: if you disable this, final fields are never used directly for deserialization
INFER_PROPERTY_MUTATORS: if you disable this, then neither fields nor setters are "pulled in" in cases where they are not otherwise visible (for fields, public is needed to be visible; for setters, even private is fine)
So may want to disable one or both settings.

Java annotation to set field to a static instance?

I've been playing with annotations, and I'm wondering how to go about doing this. What I'd like to do is to be able to have a field declared in a class and annotated such that the field will be initialized with a static instance of the class.
Given an annotation like this:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME) //or would this be RetentionPolicy.CLASS?
public #interface SetThisField {
}
Something like this:
public class Foo {
#SetThisField
private Bar bar;
}
I've played around with using a parser and setting this at runtime, which works but isn't as elegant as I'd like.
I can't find any really good examples of RetentionPolicy.CLASS but the documentation seems to indicate that I could somehow make the declaration of "bar" get compiled into this:
private Bar bar = Bar.getInstance();
It wouldn't look that way in the source code of course, but it would in the byte code and it would behave like that at runtime.
So am I off base here? Is this possible? Or is the parser the way to go with it?
UPDATE: This is the guts of the parser I'm using
public static void parse(Object instance) throws Exception {
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
//"Property" annotated fields get set to an application.properties value
//using the value of the annotation as the key into the properties
if (field.isAnnotationPresent(Property.class)) {
Property property = field.getAnnotation(Property.class);
String value = property.value();
if (!"".equals(value)) {
setFieldValue(instance, field, properties.getProperty(value));
}
}
//"Resource" annotated fields get static instances of the class allocated
//based upon the type of the field.
if (field.isAnnotationPresent(Resource.class)) {
String name = field.getType().getName();
setFieldValue(instance, field, MyApplication.getResources().get(name));
}
}
}
private static void setFieldValue(Object instance, Field field, Object value) throws IllegalAccessException {
boolean accessibleState = field.isAccessible();
field.setAccessible(true);
field.set(instance, value);
field.setAccessible(accessibleState);
}
I would suggest doing the replacement at run time. This is much simpler to implement and test. Changing the byte code at build time is relatively error prone and tricky to get right. For example you would need to understand how byte code is structured and in this case how to add the code to all the constructors in the right place in the code.
If you make the retention RUNTIME, you can have a library which examines the annotation and sets the value after the object is created.

Categories

Resources