blob: 31dc710e2c2059a14001396626912aaddb3a8171 [file] [log] [blame]
package org.codehaus.jackson.map.contextual;
import java.io.IOException;
import java.lang.annotation.*;
import java.util.*;
import org.codehaus.jackson.*;
import org.codehaus.jackson.annotate.JacksonAnnotation;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.module.SimpleModule;
/**
* Test cases to verify that it is possible to define serializers
* that can use contextual information (like field/method
* annotations) for configuration.
*
* @since 1.7
*/
public class TestContextualSerialization extends BaseMapTest
{
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
/* NOTE: important; MUST be considered a 'Jackson' annotation to be seen
* (or recognized otherwise via AnnotationIntrospect.isHandled())
*/
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface Prefix {
public String value();
}
static class ContextualBean
{
protected final String _value;
public ContextualBean(String s) { _value = s; }
@Prefix("see:")
public String getValue() { return _value; }
}
// For [JACKSON-569]
static class AnnotatedContextualBean
{
@Prefix("prefix->")
@JsonSerialize(using=AnnotatedContextualSerializer.class)
protected final String value;
public AnnotatedContextualBean(String s) { value = s; }
}
@Prefix("wrappedBean:")
static class ContextualBeanWrapper
{
@Prefix("wrapped:")
public ContextualBean wrapped;
public ContextualBeanWrapper(String s) {
wrapped = new ContextualBean(s);
}
}
static class ContextualArrayBean
{
@Prefix("array->")
public final String[] beans;
public ContextualArrayBean(String... strings) {
beans = strings;
}
}
static class ContextualListBean
{
@Prefix("list->")
public final List<String> beans = new ArrayList<String>();
public ContextualListBean(String... strings) {
for (String string : strings) {
beans.add(string);
}
}
}
static class ContextualMapBean
{
@Prefix("map->")
public final Map<String, String> beans = new HashMap<String, String>();
}
/**
* Another bean that has class annotations that should be visible for
* contextualizer, too
*/
@Prefix("Voila->")
static class BeanWithClassConfig
{
public String value;
public BeanWithClassConfig(String v) { value = v; }
}
/**
* Annotation-based contextual serializer that simply prepends piece of text.
*/
static class AnnotatedContextualSerializer
extends JsonSerializer<String>
implements ContextualSerializer<String>
{
protected final String _prefix;
public AnnotatedContextualSerializer() { this(""); }
public AnnotatedContextualSerializer(String p) {
_prefix = p;
}
@Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
jgen.writeString(_prefix + value);
}
@Override
public JsonSerializer<String> createContextual(SerializationConfig config, BeanProperty property)
throws JsonMappingException
{
String prefix = "UNKNOWN";
Prefix ann = property.getAnnotation(Prefix.class);
if (ann == null) {
ann = property.getContextAnnotation(Prefix.class);
}
if (ann != null) {
prefix = ann.value();
}
return new AnnotatedContextualSerializer(prefix);
}
}
static class ContextualAndResolvable
extends JsonSerializer<String>
implements ContextualSerializer<String>, ResolvableSerializer
{
protected boolean isContextual;
protected boolean isResolved;
public ContextualAndResolvable() { this(false); }
public ContextualAndResolvable(boolean contextual)
{
isContextual = contextual;
isResolved = false;
}
@Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
jgen.writeString("contextual="+isContextual+",resolved="+isResolved);
}
@Override
public JsonSerializer<String> createContextual(SerializationConfig config, BeanProperty property)
throws JsonMappingException
{
return new ContextualAndResolvable(true);
}
@Override
public void resolve(SerializerProvider provider) {
isResolved = true;
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
/**
* Test to verify that contextual serializer can make use of property
* (method, field) annotations.
*/
public void testMethodAnnotations() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
assertEquals("{\"value\":\"see:foobar\"}", mapper.writeValueAsString(new ContextualBean("foobar")));
}
/**
* Test to verify that contextual serializer can also use annotations
* for enclosing class.
*/
public void testClassAnnotations() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
assertEquals("{\"value\":\"Voila->xyz\"}", mapper.writeValueAsString(new BeanWithClassConfig("xyz")));
}
public void testWrappedBean() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
assertEquals("{\"wrapped\":{\"value\":\"see:xyz\"}}", mapper.writeValueAsString(new ContextualBeanWrapper("xyz")));
}
/**
* Serializer should get passed property context even if contained in an array.
*/
public void testMethodAnnotationInArray() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
ContextualArrayBean beans = new ContextualArrayBean("123");
assertEquals("{\"beans\":[\"array->123\"]}", mapper.writeValueAsString(beans));
}
/**
* Serializer should get passed property context even if contained in a Collection.
*/
public void testMethodAnnotationInList() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
ContextualListBean beans = new ContextualListBean("abc");
assertEquals("{\"beans\":[\"list->abc\"]}", mapper.writeValueAsString(beans));
}
/**
* Serializer should get passed property context even if contained in a Collection.
*/
public void testMethodAnnotationInMap() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new AnnotatedContextualSerializer());
mapper.registerModule(module);
ContextualMapBean map = new ContextualMapBean();
map.beans.put("first", "In Map");
assertEquals("{\"beans\":{\"first\":\"map->In Map\"}}", mapper.writeValueAsString(map));
}
public void testContextualViaAnnotation() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
AnnotatedContextualBean bean = new AnnotatedContextualBean("abc");
assertEquals("{\"value\":\"prefix->abc\"}", mapper.writeValueAsString(bean));
}
// [JACKSON-647]: is resolve() called for contextual instances?
public void testResolveOnContextual() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new ContextualAndResolvable());
mapper.registerModule(module);
assertEquals(quote("contextual=true,resolved=true"), mapper.writeValueAsString("abc"));
}
}