blob: 6e32a84341f137a7ecf0d44c2c8c761340bbb79f [file] [log] [blame]
package org.apache.fulcrum.json.gson;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.fulcrum.json.JsonService;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import com.jayway.jsonpath.spi.mapper.MappingProvider;
/**
*
* By default multiple serialization of the same object in a single thread is
* not support (e.g adapter + default for the same bean / object).
*
*
* @author gk
* @version $Id$
*
*/
public class GSONBuilderService extends AbstractLogEnabled implements
JsonService, Initializable, Configurable {
private static final String GLOBAL_ADAPTERS = "globalAdapters";
private static final String DATE_FORMAT = "dateFormat";
private static final String USEJSONPATH = "useJsonPath";
private String dateFormat;
private Hashtable<String, String> adapters = null;
private boolean useJsonPath = false;
GsonBuilder gson;
@Override
public String ser(Object src) throws Exception {
getLogger().debug("ser" + src);
return gson.create().toJson(src);
}
@Override
public <T> String ser(Object src, Class<T> type) throws Exception {
getLogger().debug("ser::" + src + " with type" + type);
Type collectionType = new TypeToken<T>() {
}.getType();
return gson.create().toJson(src, collectionType);
}
@Override
public <T> T deSer(String json, Class<T> type) throws Exception {
// TODO Auto-generated method stub
getLogger().debug("deser:" + json);
return gson.create().fromJson(json, type);
}
@Override
public <T> Collection<T> deSerCollection(String json, Object collectionType,
Class<T> elementType) throws Exception {
getLogger().debug("deser:" + json);
getLogger().debug("collectionType:" + collectionType);
return gson.create().fromJson(json, (Type)collectionType);
}
@Override
public String serializeOnlyFilter(Object src, String... filterAttr)
throws Exception {
return gson
.addSerializationExclusionStrategy(
include(null,filterAttr)).create().toJson(src);
}
@Override
public String serializeOnlyFilter(Object src, Boolean notused,
String... filterAttr) throws Exception {
return gson
.addSerializationExclusionStrategy(
include(null,filterAttr)).create().toJson(src);
}
@Override
public <T> String serializeOnlyFilter(Object src, Class<T> filterClass,
String... filterAttr) throws Exception {
return gson
.addSerializationExclusionStrategy(
include(filterClass, filterAttr)).create().toJson(src);
}
@Override
public <T> String serializeOnlyFilter(Object arg0, Class<T> arg1,
Boolean arg2, String... arg3) throws Exception {
throw new Exception("Not yet implemented!");
}
/**
* registering an adapter
*
* @see GsonBuilder#registerTypeAdapter(Type, Object)
*/
@Override
public JsonService addAdapter(String name, Class target, Object adapter)
throws Exception {
gson.registerTypeAdapter(target, adapter);
return this;
}
/**
* registering an adapter. Unregistering could be only done by reinitialize {@link GsonBuilder}
* using @link {@link GSONBuilderService#initialize()}, although a new Adapter with the same target overwrites the previously defined.
*
* @see GsonBuilder#registerTypeAdapter(Type, Object)
*/
@Override
public JsonService addAdapter(String name, Class target, Class adapter)
throws Exception {
gson.registerTypeAdapter(target, adapter.getConstructor().newInstance());
return null;
}
@Override
public <T> String serializeAllExceptFilter(Object src,
Class<T> filterClass, String... filterAttr) throws Exception {
return gson
.addSerializationExclusionStrategy(
exclude(filterClass, filterAttr)).create().toJson(src);
}
@Override
public <T> String serializeAllExceptFilter(Object src, Class<T> filterClass,
Boolean clearCache, String... filterAttr) throws Exception {
throw new Exception("Not yet implemented!");
}
@Override
public String serializeAllExceptFilter(Object src, String... filterAttr)
throws Exception {
return gson
.addSerializationExclusionStrategy(
exclude(null, filterAttr)).create().toJson(src);
}
@Override
public String serializeAllExceptFilter(Object src, Boolean notused,
String... filterAttr) throws Exception {
return gson
.addSerializationExclusionStrategy(
exclude(null, filterAttr)).create().toJson(src);
}
@Override
public String ser(Object src, Boolean refreshCache) throws Exception {
throw new Exception("Not implemented!");
}
@Override
public <T> String ser(Object src, Class<T> type, Boolean refreshCache)
throws Exception {
throw new Exception("Not implemented!");
}
public JsonService registerTypeAdapter(Object serdeser, Type type) {
gson.registerTypeAdapter(type, serdeser);
return this;
}
/**
* Alternative method to calling {@link #registerTypeAdapter(Object, Type)}
* Note: Always use either this direct format call or the other adapter register call,
* otherwise inconsistencies may occur!
*
* @param dfStr date format string
*/
public void setDateFormat(final String dfStr) {
gson.setDateFormat(dfStr);
}
/* (non-Javadoc)
* @see org.apache.fulcrum.json.JsonService#setDateFormat(java.text.DateFormat)
*/
@Override
public void setDateFormat(final DateFormat df) {
DateTypeAdapter dateTypeAdapter = new DateTypeAdapter();
dateTypeAdapter.setCustomDateFormat(df);
gson.registerTypeAdapter(Date.class,dateTypeAdapter);
}
public void getJsonService() throws InstantiationException {
// gson.registerTypeAdapter(Date.class, ser).
// addSerializationExclusionStrategy( exclude(ObjectKey.class) ).
// addSerializationExclusionStrategy( exclude(ComboKey.class) );
// return gson.create().toJson( src );
}
/* (non-Javadoc)
* @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
*/
@Override
public void configure(Configuration conf) throws ConfigurationException {
getLogger().debug("conf.getName()" + conf.getName());
final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT,
false);
if (configuredDateFormat != null) {
this.dateFormat = configuredDateFormat.getValue();// DEFAULTDATEFORMAT);
}
final Configuration configuredAdapters = conf.getChild(GLOBAL_ADAPTERS,
true);
if (configuredAdapters != null) {
Configuration[] nameVal = configuredAdapters.getChildren();
for (int i = 0; i < nameVal.length; i++) {
String key = nameVal[i].getName();
getLogger().debug("configured key: " + key);
if (key.equals("adapter")) {
String forClass = nameVal[i].getAttribute("forClass");
this.adapters = new Hashtable<String, String>();
this.adapters.put(forClass, nameVal[i].getValue());
}
}
}
// TODO provide configurable Type Adapters
final Configuration configuredjsonPath = conf.getChild(
USEJSONPATH, false);
if (configuredjsonPath != null) {
this.useJsonPath = configuredjsonPath.getValueAsBoolean();
}
}
/* (non-Javadoc)
* @see org.apache.avalon.framework.activity.Initializable#initialize()
*/
@Override
public void initialize() throws Exception {
gson = new GsonBuilder();
getLogger().debug("initialized: gson:" + gson);
if (dateFormat != null) {
getLogger().info("setting date format to: " + dateFormat);
setDateFormat(new SimpleDateFormat(dateFormat));
//setDateFormat(dateFormat);
}
if (adapters != null) {
Enumeration<String> enumKey = adapters.keys();
while (enumKey.hasMoreElements()) {
String forClass = enumKey.nextElement();
String avClass = adapters.get(forClass);
if (avClass != null) {
try {
getLogger().debug(
"initializing: adapters " + avClass
+ " forClass:" + forClass);
Class adapterForClass = Class.forName(forClass);
Class adapterClass = Class.forName(avClass);
addAdapter("Test Adapter", adapterForClass,
adapterClass);
} catch (Exception e) {
throw new InstantiationException(
"JsonMapperService: Error instantiating one of the adapters: "
+ avClass + " for " + forClass);
}
}
}
}
if (useJsonPath) {
// set it before runtime
com.jayway.jsonpath.Configuration.setDefaults(new com.jayway.jsonpath.Configuration.Defaults() {
private Callable<Gson> gsonFuture = new Callable<Gson>() {
@Override
public Gson call() {
return GSONBuilderService.this.gson.create();
}
};
private final JsonProvider jsonProvider = new GsonJsonProvider(GSONBuilderService.this.gson.create());
private final MappingProvider mappingProvider = new GsonMappingProvider(gsonFuture);
@Override
public JsonProvider jsonProvider() {
return jsonProvider;
}
@Override
public MappingProvider mappingProvider() {
return mappingProvider;
}
@Override
public Set<Option> options() {
return EnumSet.noneOf(Option.class);
}
});
}
}
/**
* Simple Exclusion strategy to filter class or fields used by this service
* for serialization (not yet deserialization).
*
* @param clazz
* The class to be filtered out.
* @param filterAttrs
* The fieldnames to be filtered as string
* @return the strategy applied by GSON
*/
private ExclusionStrategy exclude(Class clazz, String... filterAttrs) {
return new ExclusionStrategy() {
public Class<?> excludedThisClass;
public HashSet<String> excludedAttributes;
private ExclusionStrategy init(Class<?> excludedThisClass,
String... filterAttrs) {
this.excludedThisClass = excludedThisClass;
if (filterAttrs != null) {
this.excludedAttributes = new HashSet<String>(
filterAttrs.length);
Collections.addAll(this.excludedAttributes, filterAttrs);
} else
this.excludedAttributes = new HashSet<String>();
return this;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return (excludedThisClass != null) ? excludedThisClass
.equals(clazz) : false;
}
@Override
public boolean shouldSkipField(FieldAttributes paramFieldAttributes) {
// return paramFieldAttributes.getDeclaringClass() ==
// excludedThisClass &&
// excludesAttributes.contains(paramFieldAttributes.getName());
return !excludedAttributes.isEmpty() ? this.excludedAttributes
.contains(paramFieldAttributes.getName()) : false;
}
}.init(clazz, filterAttrs);
}
/**
* @param clazz the class to exclude
* @param filterAttrs bean elements not to be serialized
* @return
*/
private ExclusionStrategy include(Class clazz, String... filterAttrs) {
return new ExclusionStrategy() {
private Class<?> includeThisClass;
private HashSet<String> includedAttributes;
private ExclusionStrategy init(Class<?> includeThisClass,
String... filterAttrs) {
this.includeThisClass = includeThisClass;
if (filterAttrs != null) {
this.includedAttributes = new HashSet<String>(
filterAttrs.length);
getLogger().debug(" ... adding includedAttributes:" + filterAttrs.length);
Collections.addAll(this.includedAttributes, filterAttrs);
for (String includedAttribute : includedAttributes) {
getLogger().debug("includedAttribute:" +includedAttribute);
}
} else
this.includedAttributes = new HashSet<String>();
return this;
}
/**
* skip is current class is not equal provided class
*/
@Override
public boolean shouldSkipClass(Class<?> clazz) {
getLogger().debug(includeThisClass+ ": comparing include class:" + clazz);
return includeThisClass != null ? !includeThisClass
.equals(clazz) : false;
}
/**
* skip if current field attribute is not included are skip else
*/
@Override
public boolean shouldSkipField(FieldAttributes paramFieldAttributes) {
return !includedAttributes.isEmpty() ? !this.includedAttributes
.contains(paramFieldAttributes.getName()) : true;
}
}.init(clazz, filterAttrs);
}
}