GSONBuilderService.java

  1.     package org.apache.fulcrum.json.gson;

  2. /*
  3.  * Licensed to the Apache Software Foundation (ASF) under one
  4.  * or more contributor license agreements.  See the NOTICE file
  5.  * distributed with this work for additional information
  6.  * regarding copyright ownership.  The ASF licenses this file
  7.  * to you under the Apache License, Version 2.0 (the
  8.  * "License"); you may not use this file except in compliance
  9.  * with the License.  You may obtain a copy of the License at
  10.  *
  11.  *   http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing,
  14.  * software distributed under the License is distributed on an
  15.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16.  * KIND, either express or implied.  See the License for the
  17.  * specific language governing permissions and limitations
  18.  * under the License.
  19.  */

  20. import java.lang.reflect.Type;
  21. import java.text.DateFormat;
  22. import java.text.SimpleDateFormat;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.Date;
  26. import java.util.EnumSet;
  27. import java.util.Enumeration;
  28. import java.util.HashSet;
  29. import java.util.Hashtable;
  30. import java.util.Set;
  31. import java.util.concurrent.Callable;

  32. import org.apache.avalon.framework.activity.Initializable;
  33. import org.apache.avalon.framework.configuration.Configurable;
  34. import org.apache.avalon.framework.configuration.Configuration;
  35. import org.apache.avalon.framework.configuration.ConfigurationException;
  36. import org.apache.avalon.framework.logger.AbstractLogEnabled;
  37. import org.apache.fulcrum.json.JsonService;

  38. import com.google.gson.ExclusionStrategy;
  39. import com.google.gson.FieldAttributes;
  40. import com.google.gson.Gson;
  41. import com.google.gson.GsonBuilder;
  42. import com.google.gson.reflect.TypeToken;
  43. import com.jayway.jsonpath.Option;
  44. import com.jayway.jsonpath.spi.json.GsonJsonProvider;
  45. import com.jayway.jsonpath.spi.json.JsonProvider;
  46. import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
  47. import com.jayway.jsonpath.spi.mapper.MappingProvider;

  48. /**
  49.  *
  50.  * By default multiple serialization of the same object in a single thread is
  51.  * not support (e.g adapter + default for the same bean / object).
  52.  *
  53.  *
  54.  * @author gk
  55.  * @version $Id$
  56.  *
  57.  */
  58. public class GSONBuilderService extends AbstractLogEnabled implements
  59.         JsonService, Initializable, Configurable {

  60.     private static final String GLOBAL_ADAPTERS = "globalAdapters";

  61.     private static final String DATE_FORMAT = "dateFormat";
  62.    
  63.     private static final String USEJSONPATH = "useJsonPath";

  64.     private String dateFormat;

  65.     private Hashtable<String, String> adapters = null;

  66.     private boolean useJsonPath = false;
  67.    
  68.     GsonBuilder gson;

  69.     @Override
  70.     public String ser(Object src) throws Exception {
  71.         getLogger().debug("ser" + src);
  72.         return gson.create().toJson(src);
  73.     }

  74.     @Override
  75.     public <T> String ser(Object src, Class<T> type) throws Exception {
  76.         getLogger().debug("ser::" + src + " with type" + type);

  77.         Type collectionType = new TypeToken<T>() {
  78.         }.getType();
  79.         return gson.create().toJson(src, collectionType);
  80.     }

  81.     @Override
  82.     public <T> T deSer(String json, Class<T> type) throws Exception {
  83.         // TODO Auto-generated method stub
  84.         getLogger().debug("deser:" + json);
  85.         return gson.create().fromJson(json, type);
  86.     }
  87.    
  88.     @Override
  89.     public <T> Collection<T> deSerCollection(String json, Object collectionType,
  90.             Class<T> elementType) throws Exception {
  91.         getLogger().debug("deser:" + json);
  92.         getLogger().debug("collectionType:" + collectionType);
  93.         return  gson.create().fromJson(json, (Type)collectionType);
  94.     }

  95.     @Override
  96.     public String serializeOnlyFilter(Object src, String... filterAttr)
  97.             throws Exception {
  98.         return  gson
  99.                 .addSerializationExclusionStrategy(
  100.                         include(null,filterAttr)).create().toJson(src);
  101.     }

  102.     @Override
  103.     public String serializeOnlyFilter(Object src, Boolean notused,
  104.             String... filterAttr) throws Exception {
  105.         return  gson
  106.                 .addSerializationExclusionStrategy(
  107.                         include(null,filterAttr)).create().toJson(src);
  108.     }

  109.     @Override
  110.     public <T> String serializeOnlyFilter(Object src, Class<T> filterClass,
  111.             String... filterAttr) throws Exception {
  112.         return  gson
  113.         .addSerializationExclusionStrategy(
  114.                 include(filterClass, filterAttr)).create().toJson(src);
  115.     }
  116.    
  117.     @Override
  118.     public <T> String serializeOnlyFilter(Object arg0, Class<T> arg1,
  119.             Boolean arg2, String... arg3) throws Exception {
  120.         throw new Exception("Not yet implemented!");
  121.     }

  122.     /**
  123.      * registering an adapter
  124.      *
  125.      * @see GsonBuilder#registerTypeAdapter(Type, Object)
  126.      */
  127.     @Override
  128.     public JsonService addAdapter(String name, Class target, Object adapter)
  129.             throws Exception {
  130.         gson.registerTypeAdapter(target, adapter);
  131.         return this;
  132.     }

  133.     /**
  134.      * registering an adapter. Unregistering could be only done by reinitialize {@link GsonBuilder}
  135.      * using @link {@link GSONBuilderService#initialize()}, although a new Adapter with the same target overwrites the previously defined.
  136.      *
  137.      * @see GsonBuilder#registerTypeAdapter(Type, Object)
  138.      */
  139.     @Override
  140.     public JsonService addAdapter(String name, Class target, Class adapter)
  141.             throws Exception {
  142.         gson.registerTypeAdapter(target, adapter.getConstructor().newInstance());
  143.         return null;
  144.     }

  145.     @Override
  146.     public <T> String serializeAllExceptFilter(Object src,
  147.             Class<T> filterClass, String... filterAttr) throws Exception {
  148.         return gson
  149.                 .addSerializationExclusionStrategy(
  150.                         exclude(filterClass, filterAttr)).create().toJson(src);
  151.     }
  152.    
  153.     @Override
  154.     public <T> String serializeAllExceptFilter(Object src, Class<T> filterClass,
  155.             Boolean clearCache, String... filterAttr) throws Exception {
  156.         throw new Exception("Not yet implemented!");
  157.     }
  158.    
  159.     @Override
  160.     public String serializeAllExceptFilter(Object src, String... filterAttr)
  161.             throws Exception {
  162.         return gson
  163.                 .addSerializationExclusionStrategy(
  164.                         exclude(null, filterAttr)).create().toJson(src);
  165.     }

  166.     @Override
  167.     public String serializeAllExceptFilter(Object src, Boolean notused,
  168.             String... filterAttr) throws Exception {
  169.         return gson
  170.                 .addSerializationExclusionStrategy(
  171.                         exclude(null, filterAttr)).create().toJson(src);
  172.     }
  173.    
  174.     @Override
  175.     public String ser(Object src, Boolean refreshCache) throws Exception {
  176.         throw new Exception("Not implemented!");
  177.     }

  178.     @Override
  179.     public <T> String ser(Object src, Class<T> type, Boolean refreshCache)
  180.             throws Exception {
  181.         throw new Exception("Not implemented!");
  182.     }

  183.     public JsonService registerTypeAdapter(Object serdeser, Type type) {
  184.         gson.registerTypeAdapter(type, serdeser);
  185.         return this;
  186.     }
  187.    
  188.     /**
  189.      * Alternative method to calling {@link #registerTypeAdapter(Object, Type)}
  190.      * Note: Always use either this direct format call or the other adapter register call,
  191.      * otherwise inconsistencies may occur!
  192.      *
  193.      * @param dfStr date format string
  194.      */
  195.     public void setDateFormat(final String dfStr) {
  196.         gson.setDateFormat(dfStr);
  197.     }

  198.     /* (non-Javadoc)
  199.      * @see org.apache.fulcrum.json.JsonService#setDateFormat(java.text.DateFormat)
  200.      */
  201.     @Override
  202.     public void setDateFormat(final DateFormat df) {
  203.         DateTypeAdapter dateTypeAdapter = new DateTypeAdapter();
  204.         dateTypeAdapter.setCustomDateFormat(df);
  205.         gson.registerTypeAdapter(Date.class,dateTypeAdapter);
  206.     }

  207.     public void getJsonService() throws InstantiationException {
  208.         // gson.registerTypeAdapter(Date.class, ser).
  209.         // addSerializationExclusionStrategy( exclude(ObjectKey.class) ).
  210.         // addSerializationExclusionStrategy( exclude(ComboKey.class) );
  211.         // return gson.create().toJson( src );
  212.     }

  213.     /* (non-Javadoc)
  214.      * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
  215.      */
  216.     @Override
  217.     public void configure(Configuration conf) throws ConfigurationException {

  218.         getLogger().debug("conf.getName()" + conf.getName());
  219.         final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT,
  220.                 false);
  221.         if (configuredDateFormat != null) {
  222.             this.dateFormat = configuredDateFormat.getValue();// DEFAULTDATEFORMAT);
  223.         }
  224.         final Configuration configuredAdapters = conf.getChild(GLOBAL_ADAPTERS,
  225.                 true);
  226.         if (configuredAdapters != null) {
  227.             Configuration[] nameVal = configuredAdapters.getChildren();
  228.             for (int i = 0; i < nameVal.length; i++) {
  229.                 String key = nameVal[i].getName();
  230.                 getLogger().debug("configured key: " + key);
  231.                 if (key.equals("adapter")) {
  232.                     String forClass = nameVal[i].getAttribute("forClass");
  233.                     this.adapters = new Hashtable<String, String>();
  234.                     this.adapters.put(forClass, nameVal[i].getValue());
  235.                 }
  236.             }
  237.         }
  238.         // TODO provide configurable Type Adapters
  239.         final Configuration configuredjsonPath = conf.getChild(
  240.                 USEJSONPATH, false);
  241.         if (configuredjsonPath != null) {
  242.             this.useJsonPath  = configuredjsonPath.getValueAsBoolean();
  243.         }
  244.     }

  245.     /* (non-Javadoc)
  246.      * @see org.apache.avalon.framework.activity.Initializable#initialize()
  247.      */
  248.     @Override
  249.     public void initialize() throws Exception {
  250.         gson = new GsonBuilder();
  251.         getLogger().debug("initialized: gson:" + gson);
  252.         if (dateFormat != null) {
  253.             getLogger().info("setting date format to: " + dateFormat);
  254.             setDateFormat(new SimpleDateFormat(dateFormat));
  255.             //setDateFormat(dateFormat);
  256.         }

  257.         if (adapters != null) {
  258.             Enumeration<String> enumKey = adapters.keys();
  259.             while (enumKey.hasMoreElements()) {
  260.                 String forClass = enumKey.nextElement();
  261.                 String avClass = adapters.get(forClass);
  262.                 if (avClass != null) {
  263.                     try {
  264.                         getLogger().debug(
  265.                                 "initializing: adapters " + avClass
  266.                                         + " forClass:" + forClass);
  267.                         Class adapterForClass = Class.forName(forClass);
  268.                         Class adapterClass = Class.forName(avClass);
  269.                         addAdapter("Test Adapter", adapterForClass,
  270.                                 adapterClass);

  271.                     } catch (Exception e) {
  272.                         throw new InstantiationException(
  273.                                 "JsonMapperService: Error instantiating one of the adapters: "
  274.                                         + avClass + " for " + forClass);
  275.                     }
  276.                 }
  277.             }
  278.         }
  279.        
  280.         if (useJsonPath) {
  281.             // set it before runtime
  282.             com.jayway.jsonpath.Configuration.setDefaults(new com.jayway.jsonpath.Configuration.Defaults() {
  283.                
  284.                 private Callable<Gson> gsonFuture = new Callable<Gson>() {
  285.                     @Override
  286.                     public Gson call() {
  287.                         return GSONBuilderService.this.gson.create();
  288.                     }
  289.                 };

  290.                 private final JsonProvider jsonProvider = new GsonJsonProvider(GSONBuilderService.this.gson.create());
  291.                 private final MappingProvider mappingProvider = new GsonMappingProvider(gsonFuture);

  292.                 @Override
  293.                 public JsonProvider jsonProvider() {
  294.                     return jsonProvider;
  295.                 }

  296.                 @Override
  297.                 public MappingProvider mappingProvider() {
  298.                     return mappingProvider;
  299.                 }

  300.                 @Override
  301.                 public Set<Option> options() {
  302.                     return EnumSet.noneOf(Option.class);
  303.                 }
  304.             });
  305.         }
  306.     }

  307.     /**
  308.      * Simple Exclusion strategy to filter class or fields used by this service
  309.      * for serialization (not yet deserialization).
  310.      *
  311.      * @param clazz
  312.      *            The class to be filtered out.
  313.      * @param filterAttrs
  314.      *            The fieldnames to be filtered as string
  315.      * @return the strategy applied by GSON
  316.      */
  317.     private ExclusionStrategy exclude(Class clazz, String... filterAttrs) {
  318.         return new ExclusionStrategy() {

  319.             public Class<?> excludedThisClass;
  320.             public HashSet<String> excludedAttributes;

  321.             private ExclusionStrategy init(Class<?> excludedThisClass,
  322.                     String... filterAttrs) {
  323.                 this.excludedThisClass = excludedThisClass;
  324.                 if (filterAttrs != null) {
  325.                     this.excludedAttributes = new HashSet<String>(
  326.                             filterAttrs.length);
  327.                     Collections.addAll(this.excludedAttributes, filterAttrs);
  328.                 } else
  329.                     this.excludedAttributes = new HashSet<String>();

  330.                 return this;
  331.             }

  332.             @Override
  333.             public boolean shouldSkipClass(Class<?> clazz) {
  334.                 return (excludedThisClass != null) ? excludedThisClass
  335.                         .equals(clazz) : false;
  336.             }

  337.             @Override
  338.             public boolean shouldSkipField(FieldAttributes paramFieldAttributes) {
  339.                 // return paramFieldAttributes.getDeclaringClass() ==
  340.                 // excludedThisClass &&
  341.                 // excludesAttributes.contains(paramFieldAttributes.getName());
  342.                 return !excludedAttributes.isEmpty() ? this.excludedAttributes
  343.                         .contains(paramFieldAttributes.getName()) : false;
  344.             }
  345.         }.init(clazz, filterAttrs);
  346.     }
  347.    
  348.     /**
  349.      * @param clazz the class to exclude
  350.      * @param filterAttrs bean elements not to be serialized
  351.      * @return
  352.      */
  353.     private ExclusionStrategy include(Class clazz, String... filterAttrs) {
  354.         return new ExclusionStrategy() {

  355.             private Class<?> includeThisClass;
  356.             private HashSet<String> includedAttributes;

  357.             private ExclusionStrategy init(Class<?> includeThisClass,
  358.                     String... filterAttrs) {
  359.                 this.includeThisClass = includeThisClass;
  360.                 if (filterAttrs != null) {
  361.                     this.includedAttributes = new HashSet<String>(
  362.                             filterAttrs.length);
  363.                     getLogger().debug(" ... adding includedAttributes:" + filterAttrs.length);
  364.                     Collections.addAll(this.includedAttributes, filterAttrs);
  365.                     for (String includedAttribute : includedAttributes) {
  366.                         getLogger().debug("includedAttribute:" +includedAttribute);
  367.                     }
  368.                 } else
  369.                     this.includedAttributes = new HashSet<String>();

  370.                 return this;
  371.             }

  372.             /**
  373.              * skip is current class is not equal provided class
  374.              */
  375.             @Override
  376.             public boolean shouldSkipClass(Class<?> clazz) {
  377.                 getLogger().debug(includeThisClass+ ": comparing include class:" + clazz);
  378.                 return includeThisClass != null ? !includeThisClass
  379.                         .equals(clazz) : false;
  380.             }

  381.             /**
  382.              * skip if current field attribute is not included are skip else
  383.              */
  384.             @Override
  385.             public boolean shouldSkipField(FieldAttributes paramFieldAttributes) {
  386.                 return !includedAttributes.isEmpty() ? !this.includedAttributes
  387.                         .contains(paramFieldAttributes.getName()) : true;        

  388.             }
  389.         }.init(clazz, filterAttrs);
  390.     }

  391. }