/*
* Copyright 2013 cruxframework.org.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cruxframework.crux.core.server.rest.util;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectMapper.DefaultTypeResolverBuilder;
import org.codehaus.jackson.map.ObjectMapper.DefaultTyping;
import org.codehaus.jackson.map.ObjectReader;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.map.introspect.BasicBeanDescription;
import org.codehaus.jackson.map.jsontype.TypeResolverBuilder;
import org.codehaus.jackson.map.ser.BeanPropertyWriter;
import org.codehaus.jackson.map.ser.BeanSerializerFactory;
import org.codehaus.jackson.type.JavaType;
import org.cruxframework.crux.core.shared.json.annotations.JsonIgnore;
import org.cruxframework.crux.core.shared.json.annotations.JsonSubTypes;
import org.cruxframework.crux.core.utils.ClassUtils;
import org.cruxframework.crux.core.utils.ClassUtils.PropertyInfo;
import org.cruxframework.crux.scanner.archiveiterator.ZIPProtocolIterator;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public class JsonUtil
{
private static ObjectMapper defaultMapper;
private static ObjectMapper subTypeAwareMapper;
private static final Lock LOCK = new ReentrantLock();
private static final Log LOG = LogFactory.getLog(ZIPProtocolIterator.class);
/**
* @param type the class type.
* @return the Jackson ObjectWriter.
*/
public static ObjectReader createReader(Type type)
{
ObjectMapper mapper = getObjectMapper(type);
setGlobalConfigurations(mapper);
JavaType paramJavaType = mapper.getTypeFactory().constructType(type);
ObjectReader reader = mapper.reader(paramJavaType);
return reader;
}
/**
* @param type the class type.
* @return the Jackson ObjectWriter.
*/
public static ObjectWriter createWriter(Type type)
{
ObjectMapper mapper = getObjectMapper(type);
setGlobalConfigurations(mapper);
JavaType paramJavaType = mapper.getTypeFactory().constructType(type);
ObjectWriter writer = mapper.writerWithType(paramJavaType);
return writer;
}
private static void setGlobalConfigurations(ObjectMapper mapper)
{
mapper.configure(Feature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private static ObjectMapper getObjectMapper(Type type)
{
if (defaultMapper == null)
{
LOCK.lock();
try
{
if (defaultMapper == null)
{
defaultMapper = new ObjectMapper();
defaultMapper.setSerializerFactory(new CruxSerializerFactory());
subTypeAwareMapper = new ObjectMapper();
TypeResolverBuilder<?> builder = new SubTypeResolverBuilder();
builder = builder.init(JsonTypeInfo.Id.CLASS, null);
builder = builder.inclusion(JsonTypeInfo.As.PROPERTY);
builder = builder.typeProperty(JsonSubTypes.SUB_TYPE_SELECTOR);
subTypeAwareMapper.setDefaultTyping(builder);
subTypeAwareMapper.setSerializerFactory(new CruxSerializerFactory());
}
}
finally
{
LOCK.unlock();
}
}
Class<?> clazz = ClassUtils.getRawType(type);
return getObjectMapper(type, clazz);
}
private static ObjectMapper getObjectMapper(Type type, Class<?> clazz)
{
if (clazz != null && hasJsonSubTypes(type, clazz, new HashSet<Class<?>>()))
{
return subTypeAwareMapper;
}
return defaultMapper;
}
public static boolean hasJsonSubTypes(Class<?> clazz)
{
JsonSubTypes jsonSubTypes = clazz.getAnnotation(JsonSubTypes.class);
if (jsonSubTypes != null && jsonSubTypes.value() != null)
{
return (jsonSubTypes.value().length > 0);
}
return false;
}
public static boolean hasJsonSubTypes(Type type, Class<?> clazz, Set<Class<?>> searched)
{
while (ClassUtils.isCollection(clazz))
{
type = ClassUtils.getCollectionBaseType(clazz, type);
if (type == null)
{
//If we have something like a plain rawtype (like 'java.util.ArrayList<>')
return false;
}
clazz = ClassUtils.getRawType(type);
}
if (!searched.contains(clazz))
{
searched.add(clazz);
if(hasJsonSubTypes(clazz))
{
return true;
}
PropertyInfo[] propertiesInfo = ClassUtils.extractBeanPropertiesInfo(type);
if (propertiesInfo != null)
{
for (PropertyInfo propertyInfo : propertiesInfo)
{
if (hasJsonSubTypes(propertyInfo.getType(), ClassUtils.getRawType(propertyInfo.getType()), searched))
{
return true;
}
}
}
}
return false;
}
public static Class<?> getJsonSubTypesSuperClass(Type type, Class<?> clazz)
{
if(clazz == null || type == null)
{
return null;
}
if(hasJsonSubTypes(clazz))
{
JsonSubTypes jsonSubTypes = clazz.getAnnotation(JsonSubTypes.class);
for(JsonSubTypes.Type jsonSubType : jsonSubTypes.value())
{
if(jsonSubType != null
&&
jsonSubType.value() != null
&&
jsonSubType.value().equals(type))
{
return clazz;
}
}
}
return getJsonSubTypesSuperClass(type, clazz.getSuperclass());
}
private static class SubTypeResolverBuilder extends DefaultTypeResolverBuilder
{
public SubTypeResolverBuilder()
{
super(DefaultTyping.NON_FINAL);
}
@Override
public boolean useForType(JavaType t)
{
return hasJsonSubTypes(t.getRawClass());
}
}
private static class CruxSerializerFactory extends BeanSerializerFactory
{
protected CruxSerializerFactory()
{
super(new BeanSerializerFactory.ConfigImpl());
}
@Override
protected List<BeanPropertyWriter> filterBeanProperties(SerializationConfig serializationConfig,
BasicBeanDescription beanDescription,
List<BeanPropertyWriter> props)
{
//filter out standard properties (e.g. those marked with @JsonIgnore)
props = super.filterBeanProperties(serializationConfig, beanDescription, props);
Class<?> beanClass = beanDescription.getBeanClass();
//filter out standard properties (e.g. those marked with @org.cruxframework.crux.core.shared.json.annotations.JsonIgnore)
for (Iterator<BeanPropertyWriter> iter = props.iterator(); iter.hasNext();)
{
BeanPropertyWriter beanPropertyWriter = iter.next();
String getterMethodName = ClassUtils.getGetterMethod(beanPropertyWriter.getName(), beanClass);
try
{
Method getterMethod = beanClass.getMethod(getterMethodName);
if (getterMethod != null && getterMethod.isAnnotationPresent(JsonIgnore.class))
{
iter.remove();
}
}
catch (Exception e)
{
LOG.warn("Property ignored in serialization: " + getterMethodName, e);
}
}
return props;
}
}
}