View Javadoc
1       package org.apache.fulcrum.json.gson;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.lang.reflect.Type;
23  import java.text.DateFormat;
24  import java.text.SimpleDateFormat;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Date;
28  import java.util.EnumSet;
29  import java.util.Enumeration;
30  import java.util.HashSet;
31  import java.util.Hashtable;
32  import java.util.Set;
33  import java.util.concurrent.Callable;
34  
35  import org.apache.avalon.framework.activity.Initializable;
36  import org.apache.avalon.framework.configuration.Configurable;
37  import org.apache.avalon.framework.configuration.Configuration;
38  import org.apache.avalon.framework.configuration.ConfigurationException;
39  import org.apache.avalon.framework.logger.AbstractLogEnabled;
40  import org.apache.fulcrum.json.JsonService;
41  
42  import com.google.gson.ExclusionStrategy;
43  import com.google.gson.FieldAttributes;
44  import com.google.gson.Gson;
45  import com.google.gson.GsonBuilder;
46  import com.google.gson.reflect.TypeToken;
47  import com.jayway.jsonpath.Option;
48  import com.jayway.jsonpath.spi.json.GsonJsonProvider;
49  import com.jayway.jsonpath.spi.json.JsonProvider;
50  import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
51  import com.jayway.jsonpath.spi.mapper.MappingProvider;
52  
53  /**
54   * 
55   * By default multiple serialization of the same object in a single thread is
56   * not support (e.g adapter + default for the same bean / object).
57   * 
58   * 
59   * @author gk
60   * @version $Id$
61   * 
62   */
63  public class GSONBuilderService extends AbstractLogEnabled implements
64          JsonService, Initializable, Configurable {
65  
66      private static final String GLOBAL_ADAPTERS = "globalAdapters";
67  
68      private static final String DATE_FORMAT = "dateFormat";
69      
70      private static final String USEJSONPATH = "useJsonPath";
71  
72      private String dateFormat;
73  
74      private Hashtable<String, String> adapters = null;
75  
76      private boolean useJsonPath = false;
77      
78      GsonBuilder gson;
79  
80      @Override
81      public String ser(Object src) throws Exception {
82          getLogger().debug("ser" + src);
83          return gson.create().toJson(src);
84      }
85  
86      @Override
87      public <T> String ser(Object src, Class<T> type) throws Exception {
88          getLogger().debug("ser::" + src + " with type" + type);
89  
90          Type collectionType = new TypeToken<T>() {
91          }.getType();
92          return gson.create().toJson(src, collectionType);
93      }
94  
95      @Override
96      public <T> T deSer(String json, Class<T> type) throws Exception {
97          // TODO Auto-generated method stub
98          getLogger().debug("deser:" + json);
99          return gson.create().fromJson(json, type);
100     }
101     
102     @Override
103     public <T> Collection<T> deSerCollection(String json, Object collectionType,
104             Class<T> elementType) throws Exception {
105         getLogger().debug("deser:" + json);
106         getLogger().debug("collectionType:" + collectionType);
107         return  gson.create().fromJson(json, (Type)collectionType);
108     }
109 
110     @Override
111     public String serializeOnlyFilter(Object src, String... filterAttr)
112             throws Exception {
113         return  gson
114                 .addSerializationExclusionStrategy(
115                         include(null,filterAttr)).create().toJson(src);
116     }
117 
118     @Override
119     public String serializeOnlyFilter(Object src, Boolean notused,
120             String... filterAttr) throws Exception {
121         return  gson
122                 .addSerializationExclusionStrategy(
123                         include(null,filterAttr)).create().toJson(src);
124     }
125 
126     @Override
127     public <T> String serializeOnlyFilter(Object src, Class<T> filterClass,
128             String... filterAttr) throws Exception {
129         return  gson
130         .addSerializationExclusionStrategy(
131                 include(filterClass, filterAttr)).create().toJson(src);
132     }
133     
134     @Override
135     public <T> String serializeOnlyFilter(Object arg0, Class<T> arg1,
136             Boolean arg2, String... arg3) throws Exception {
137         throw new Exception("Not yet implemented!");
138     }
139 
140     /**
141      * registering an adapter 
142      * 
143      * @see GsonBuilder#registerTypeAdapter(Type, Object)
144      */
145     @Override
146     public JsonService addAdapter(String name, Class target, Object adapter)
147             throws Exception {
148         gson.registerTypeAdapter(target, adapter);
149         return this;
150     }
151 
152     /**
153      * registering an adapter. Unregistering could be only done by reinitialize {@link GsonBuilder} 
154      * using @link {@link GSONBuilderService#initialize()}, although a new Adapter with the same target overwrites the previously defined.
155      * 
156      * @see GsonBuilder#registerTypeAdapter(Type, Object)
157      */
158     @Override
159     public JsonService addAdapter(String name, Class target, Class adapter)
160             throws Exception {
161         gson.registerTypeAdapter(target, adapter.getConstructor().newInstance());
162         return null;
163     }
164 
165     @Override
166     public <T> String serializeAllExceptFilter(Object src,
167             Class<T> filterClass, String... filterAttr) throws Exception {
168         return gson
169                 .addSerializationExclusionStrategy(
170                         exclude(filterClass, filterAttr)).create().toJson(src);
171     }
172     
173     @Override
174     public <T> String serializeAllExceptFilter(Object src, Class<T> filterClass,
175             Boolean clearCache, String... filterAttr) throws Exception {
176         throw new Exception("Not yet implemented!");
177     }
178     
179     @Override
180     public String serializeAllExceptFilter(Object src, String... filterAttr)
181             throws Exception {
182         return gson
183                 .addSerializationExclusionStrategy(
184                         exclude(null, filterAttr)).create().toJson(src);
185     }
186 
187     @Override
188     public String serializeAllExceptFilter(Object src, Boolean notused,
189             String... filterAttr) throws Exception {
190         return gson
191                 .addSerializationExclusionStrategy(
192                         exclude(null, filterAttr)).create().toJson(src);
193     }
194     
195     @Override
196     public String ser(Object src, Boolean refreshCache) throws Exception {
197         throw new Exception("Not implemented!");
198     }
199 
200     @Override
201     public <T> String ser(Object src, Class<T> type, Boolean refreshCache)
202             throws Exception {
203         throw new Exception("Not implemented!");
204     }
205 
206     public JsonService registerTypeAdapter(Object serdeser, Type type) {
207         gson.registerTypeAdapter(type, serdeser);
208         return this;
209     }
210     
211     /**
212      * Alternative method to calling {@link #registerTypeAdapter(Object, Type)}
213      * Note: Always use either this direct format call or the other adapter register call,
214      * otherwise inconsistencies may occur!
215      * 
216      * @param dfStr date format string
217      */
218     public void setDateFormat(final String dfStr) {
219         gson.setDateFormat(dfStr);
220     }
221 
222     /* (non-Javadoc)
223      * @see org.apache.fulcrum.json.JsonService#setDateFormat(java.text.DateFormat)
224      */
225     @Override
226     public void setDateFormat(final DateFormat df) {
227         DateTypeAdapterr.html#DateTypeAdapter">DateTypeAdapter dateTypeAdapter = new DateTypeAdapter();
228         dateTypeAdapter.setCustomDateFormat(df);
229         gson.registerTypeAdapter(Date.class,dateTypeAdapter);
230     }
231 
232     public void getJsonService() throws InstantiationException {
233         // gson.registerTypeAdapter(Date.class, ser).
234         // addSerializationExclusionStrategy( exclude(ObjectKey.class) ).
235         // addSerializationExclusionStrategy( exclude(ComboKey.class) );
236         // return gson.create().toJson( src );
237     }
238 
239     /* (non-Javadoc)
240      * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
241      */
242     @Override
243     public void configure(Configuration conf) throws ConfigurationException {
244 
245         getLogger().debug("conf.getName()" + conf.getName());
246         final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT,
247                 false);
248         if (configuredDateFormat != null) {
249             this.dateFormat = configuredDateFormat.getValue();// DEFAULTDATEFORMAT);
250         }
251         final Configuration configuredAdapters = conf.getChild(GLOBAL_ADAPTERS,
252                 true);
253         if (configuredAdapters != null) {
254             Configuration[] nameVal = configuredAdapters.getChildren();
255             for (int i = 0; i < nameVal.length; i++) {
256                 String key = nameVal[i].getName();
257                 getLogger().debug("configured key: " + key);
258                 if (key.equals("adapter")) {
259                     String forClass = nameVal[i].getAttribute("forClass");
260                     this.adapters = new Hashtable<String, String>();
261                     this.adapters.put(forClass, nameVal[i].getValue());
262                 }
263             }
264         }
265         // TODO provide configurable Type Adapters
266         final Configuration configuredjsonPath = conf.getChild(
267                 USEJSONPATH, false);
268         if (configuredjsonPath != null) {
269             this.useJsonPath  = configuredjsonPath.getValueAsBoolean();
270         }
271     }
272 
273     /* (non-Javadoc)
274      * @see org.apache.avalon.framework.activity.Initializable#initialize()
275      */
276     @Override
277     public void initialize() throws Exception {
278         gson = new GsonBuilder();
279         getLogger().debug("initialized: gson:" + gson);
280         if (dateFormat != null) {
281             getLogger().info("setting date format to: " + dateFormat);
282             setDateFormat(new SimpleDateFormat(dateFormat));
283             //setDateFormat(dateFormat);
284         }
285 
286         if (adapters != null) {
287             Enumeration<String> enumKey = adapters.keys();
288             while (enumKey.hasMoreElements()) {
289                 String forClass = enumKey.nextElement();
290                 String avClass = adapters.get(forClass);
291                 if (avClass != null) {
292                     try {
293                         getLogger().debug(
294                                 "initializing: adapters " + avClass
295                                         + " forClass:" + forClass);
296                         Class adapterForClass = Class.forName(forClass);
297                         Class adapterClass = Class.forName(avClass);
298                         addAdapter("Test Adapter", adapterForClass,
299                                 adapterClass);
300 
301                     } catch (Exception e) {
302                         throw new InstantiationException(
303                                 "JsonMapperService: Error instantiating one of the adapters: "
304                                         + avClass + " for " + forClass);
305                     }
306                 }
307             }
308         }
309         
310         if (useJsonPath) {
311             // set it before runtime
312             com.jayway.jsonpath.Configuration.setDefaults(new com.jayway.jsonpath.Configuration.Defaults() {
313                 
314                 private Callable<Gson> gsonFuture = new Callable<Gson>() {
315                     @Override
316                     public Gson call() {
317                         return GSONBuilderService.this.gson.create();
318                     }
319                 };
320 
321                 private final JsonProvider jsonProvider = new GsonJsonProvider(GSONBuilderService.this.gson.create());
322                 private final MappingProvider mappingProvider = new GsonMappingProvider(gsonFuture);
323 
324                 @Override
325                 public JsonProvider jsonProvider() {
326                     return jsonProvider;
327                 }
328 
329                 @Override
330                 public MappingProvider mappingProvider() {
331                     return mappingProvider;
332                 }
333 
334                 @Override
335                 public Set<Option> options() {
336                     return EnumSet.noneOf(Option.class);
337                 }
338             });
339         }
340     }
341 
342     /**
343      * Simple Exclusion strategy to filter class or fields used by this service
344      * for serialization (not yet deserialization).
345      * 
346      * @param clazz
347      *            The class to be filtered out.
348      * @param filterAttrs
349      *            The fieldnames to be filtered as string
350      * @return the strategy applied by GSON
351      */
352     private ExclusionStrategy exclude(Class clazz, String... filterAttrs) {
353         return new ExclusionStrategy() {
354 
355             public Class<?> excludedThisClass;
356             public HashSet<String> excludedAttributes;
357 
358             private ExclusionStrategy init(Class<?> excludedThisClass,
359                     String... filterAttrs) {
360                 this.excludedThisClass = excludedThisClass;
361                 if (filterAttrs != null) {
362                     this.excludedAttributes = new HashSet<String>(
363                             filterAttrs.length);
364                     Collections.addAll(this.excludedAttributes, filterAttrs);
365                 } else
366                     this.excludedAttributes = new HashSet<String>();
367 
368                 return this;
369             }
370 
371             @Override
372             public boolean shouldSkipClass(Class<?> clazz) {
373                 return (excludedThisClass != null) ? excludedThisClass
374                         .equals(clazz) : false;
375             }
376 
377             @Override
378             public boolean shouldSkipField(FieldAttributes paramFieldAttributes) {
379                 // return paramFieldAttributes.getDeclaringClass() ==
380                 // excludedThisClass &&
381                 // excludesAttributes.contains(paramFieldAttributes.getName());
382                 return !excludedAttributes.isEmpty() ? this.excludedAttributes
383                         .contains(paramFieldAttributes.getName()) : false;
384             }
385         }.init(clazz, filterAttrs);
386     }
387     
388     /**
389      * @param clazz the class to exclude
390      * @param filterAttrs bean elements not to be serialized
391      * @return
392      */
393     private ExclusionStrategy include(Class clazz, String... filterAttrs) {
394         return new ExclusionStrategy() {
395 
396             private Class<?> includeThisClass;
397             private HashSet<String> includedAttributes;
398 
399             private ExclusionStrategy init(Class<?> includeThisClass,
400                     String... filterAttrs) {
401                 this.includeThisClass = includeThisClass;
402                 if (filterAttrs != null) {
403                     this.includedAttributes = new HashSet<String>(
404                             filterAttrs.length);
405                     getLogger().debug(" ... adding includedAttributes:" + filterAttrs.length);
406                     Collections.addAll(this.includedAttributes, filterAttrs);
407                     for (String includedAttribute : includedAttributes) {
408                         getLogger().debug("includedAttribute:" +includedAttribute);
409                     }
410                 } else
411                     this.includedAttributes = new HashSet<String>();
412 
413                 return this;
414             }
415 
416             /**
417              * skip is current class is not equal provided class
418              */
419             @Override
420             public boolean shouldSkipClass(Class<?> clazz) {
421                 getLogger().debug(includeThisClass+ ": comparing include class:" + clazz);
422                 return includeThisClass != null ? !includeThisClass
423                         .equals(clazz) : false;
424             }
425 
426             /**
427              * skip if current field attribute is not included are skip else
428              */
429             @Override
430             public boolean shouldSkipField(FieldAttributes paramFieldAttributes) { 
431                 return !includedAttributes.isEmpty() ? !this.includedAttributes
432                         .contains(paramFieldAttributes.getName()) : true;        
433 
434             }
435         }.init(clazz, filterAttrs);
436     }
437 
438 }