View Javadoc
1   package org.apache.fulcrum.json.jackson;
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   * specific language governing permissions and limitations
18   * under the License.
19   */
20  
21  import java.io.IOException;
22  import java.text.DateFormat;
23  import java.text.SimpleDateFormat;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  
31  import org.apache.avalon.framework.activity.Initializable;
32  import org.apache.avalon.framework.configuration.Configurable;
33  import org.apache.avalon.framework.configuration.Configuration;
34  import org.apache.avalon.framework.configuration.ConfigurationException;
35  import org.apache.avalon.framework.logger.AbstractLogEnabled;
36  import org.apache.avalon.framework.logger.LogEnabled;
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.fulcrum.json.JsonService;
39  import org.apache.fulcrum.json.jackson.filters.CustomModuleWrapper;
40  import org.apache.fulcrum.json.jackson.jsonpath.DefaultJsonPathWrapper;
41  
42  import com.fasterxml.jackson.core.JsonGenerator;
43  import com.fasterxml.jackson.core.JsonParser;
44  import com.fasterxml.jackson.core.JsonParser.Feature;
45  import com.fasterxml.jackson.core.JsonProcessingException;
46  import com.fasterxml.jackson.core.SerializableString;
47  import com.fasterxml.jackson.core.io.CharacterEscapes;
48  import com.fasterxml.jackson.core.type.TypeReference;
49  import com.fasterxml.jackson.databind.AnnotationIntrospector;
50  import com.fasterxml.jackson.databind.DeserializationFeature;
51  import com.fasterxml.jackson.databind.JsonDeserializer;
52  import com.fasterxml.jackson.databind.JsonSerializer;
53  import com.fasterxml.jackson.databind.MapperFeature;
54  import com.fasterxml.jackson.databind.Module;
55  import com.fasterxml.jackson.databind.ObjectMapper;
56  import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
57  import com.fasterxml.jackson.databind.ObjectReader;
58  import com.fasterxml.jackson.databind.SerializationFeature;
59  import com.fasterxml.jackson.databind.SerializerProvider;
60  import com.fasterxml.jackson.databind.cfg.ConfigFeature;
61  import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
62  import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
63  import com.fasterxml.jackson.databind.module.SimpleModule;
64  import com.fasterxml.jackson.databind.ser.FilterProvider;
65  import com.fasterxml.jackson.databind.ser.PropertyFilter;
66  import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
67  import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
68  
69  /**
70   * 
71   * 
72   * By default multiple serialization of the same object in a single thread is
73   * not supported (e.g filter + mixin or default + filter for the same bean /
74   * object).
75   * 
76   * By default a filter is defined by its {@link Class#getName()}.
77   * 
78   * Note: If using {@link SimpleNameIntrospector}, filter caches are set by class
79   * id. Caching is enabled by default, if not (a) by setting
80   * {@link #cacheFilters} to <code>false</code>. By setting (b) the Boolean
81   * parameter clean
82   * {@link #serializeAllExceptFilter(Object, Class, Boolean, String...)} or
83   * {@link #serializeOnlyFilter(Object, Class, Boolean, String...)} you could
84   * clean the filter. If caching is disabled each filter will be unregistered and
85   * the cache cleaned.
86   * 
87   * @author <a href="mailto:gk@apache.org">Georg Kallidis</a>
88   * @version $Id$
89   * 
90   */
91  public class Jackson2MapperService extends AbstractLogEnabled implements JsonService, Initializable, Configurable {
92  
93      private static final String DEFAULT_TYPING = "defaultTyping";
94      private static final String CACHE_FILTERS = "cacheFilters";
95      private static final String DATE_FORMAT = "dateFormat";
96      private static final String ESCAPE_CHARS = "escapeCharsGlobal";
97      private static final String ESCAPE_CHAR_CLASS = "escapeCharsClass";
98      private static final String USE_JSON_PATH = "useJsonPath";
99      ObjectMapper mapper;
100     AnnotationIntrospector primary; // support default
101     AnnotationIntrospector secondary;
102 
103     private static final String ANNOTATIONINSPECTOR = "annotationInspectors";
104 
105     private Map<String, String> annotationInspectors = null;
106     private Map<String, Boolean> features = null;
107     private Map<String, String> featureTypes = null;
108 
109     private String dateFormat;
110 
111     /**
112      * Default dateformat is <code>MM/dd/yyyy</code>, could be overwritten in
113      * {@link #setDateFormat(DateFormat)}.
114      */
115     public static final String DEFAULTDATEFORMAT = "MM/dd/yyyy";
116 
117     
118     private boolean cacheFilters = true; // true -> this is by default true in jackson, if not using
119                                             // multiple serialization in one thread
120     String[] defaultTypeDefs = null;
121     private CacheService cacheService;
122     private boolean escapeCharsGlobal = false; // to be backward compatible, but should be true, then escaping to avoid
123                                                 // XSS payload by default
124     private boolean useJsonPath = false;
125     private String escapeCharsClass = null;
126 
127     @Override
128     public String ser(Object src) throws Exception {
129         return ser(src, false);
130     }
131 
132     @Override
133     public <T> String ser(Object src, Class<T> type) throws Exception {
134         return ser(src, type, false);
135     }
136 
137     public String ser(Object src, FilterProvider filter) throws Exception {
138         return ser(src, filter, false);
139     }
140 
141     /**
142      * 
143      * @param src the object to be serailized as JSON
144      * @param filter may be null, then sserialize without otherwise set into cache service and as filter provider.
145      * @param cleanCache cleans the jackson cache
146      * @return the serialzed JSON string
147      * @throws Exception exception
148      */
149     public String ser(Object src, FilterProvider filter, Boolean cleanCache) throws Exception {
150         String serResult = null;
151         if (src == null) {
152             getLogger().info("no serializable object.");
153             return serResult;
154         }
155         if (filter == null) {
156             getLogger().debug("ser class::" + src.getClass() + " without filter.");
157             return ser(src);
158         } else {
159             getLogger().debug("add filter for cache filter Class " + src.getClass().getName());
160             setCustomIntrospectorWithExternalFilterId(src.getClass(), null); // filter class
161             if (isCacheFilters()) {
162                 cacheService.getFilters().put(src.getClass().getName(), filter);
163             }
164         }
165         getLogger().debug("ser class::" + src.getClass() + " with filter " + filter);
166         mapper.setFilterProvider(filter);
167         String res = mapper.writer(filter).writeValueAsString(src);
168         if (cleanCache) {
169             cacheService.cleanSerializerCache(mapper);
170         }
171         return res;
172     }
173 
174     @Override
175     public <T> T deSer(String json, Class<T> type) throws Exception {
176         ObjectReader reader = null;
177         if (type != null)
178             reader = mapper.readerFor(type);
179         else
180             reader = mapper.reader();
181 
182         return reader.readValue(json);
183     }
184 
185     /**
186      * basically wrapper for {@link ObjectMapper#convertValue(Object, Class)}.
187      * 
188      * @param src Object
189      * @param type target Object
190      * @return
191      */
192     public <T> T deSer(Object src, Class<T> type) {
193         return mapper.convertValue(src, type);
194     }
195 
196     /**
197      * Add a named module or a {@link Module}.
198      * 
199      * @param name   Name of the module, optional. Could be null, if module is a
200      *               {@link Module}.
201      * 
202      * @param target Target class, optional. Could be null, if module is a
203      *               {@link Module}.
204      * 
205      * @param module Either an Jackson Module @link {@link Module} or an custom
206      *               wrapper @link CustomModuleWrapper.
207      */
208     @Override
209     public JsonService addAdapter(String name, Class target, Object module)
210             throws Exception {
211         if (module instanceof CustomModuleWrapper) {
212             CustomModuleWrapper./../org/apache/fulcrum/json/jackson/filters/CustomModuleWrapper.html#CustomModuleWrapper">CustomModuleWrapper cmw = (CustomModuleWrapper) module;
213             Module cm = new CustomModule(name, target, cmw.getSer(),
214                     cmw.getDeSer());
215             getLogger().debug("registering custom module " + cm + "  for: " + target);
216             mapper.registerModule(cm);
217         } else if (module instanceof Module) {
218             getLogger().debug(
219                     "registering module " + module );
220             mapper.registerModule((Module) module);
221         } else {
222             throw new ClassCastException("expecting module type " + Module.class);
223         }
224         return this;
225     }
226     
227     public Class<?> showMixinForClass(Class target) {
228         Class<?> mixin =  mapper.findMixInClassFor( target );
229         getLogger().debug("find mixin for target " + target + " -> mixin: " + mixin);
230         return mixin;
231     }
232     
233     public <T> List<T> deSerList(String json, Class<? extends List> targetList, Class<T> elementType) throws Exception {
234         return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(targetList, elementType));
235     }
236 
237     public <T, U> Map<T, U> deSerMap(String json, Class<? extends Map> mapClass, Class<T> keyClass, Class<U> valueClass)
238             throws Exception {
239         return mapper.readValue(json, mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass));
240     }
241 
242     public <T> Collection<T> deSerCollectionWithTypeReference(String json, TypeReference<T> collectionType)
243             throws Exception {
244         return (Collection<T>) mapper.readValue(json, collectionType);
245     }
246   
247     public <T> Collection<T> deSerCollectionWithType(String json, Class<? extends Collection> collectionClass,
248             Class<T> type) throws Exception {
249         return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(collectionClass, type));
250     }
251     
252     @Override
253     public <T> Collection<T> deSerCollection(String json, Object collectionType, Class<T> elementType)
254             throws Exception {
255         if (collectionType instanceof TypeReference) {
256             return deSerCollectionWithTypeReference(json, (TypeReference<T>) collectionType);
257         } else {
258             return mapper.readValue(json, mapper.getTypeFactory()
259                     .constructCollectionType(((Collection<T>) collectionType).getClass(), elementType));
260         }
261     }
262     
263     /**
264      * 
265      * @param src the collection to be serialized 
266      * @param collectionType the {@link TypeReference}.
267      * @param cleanCache cleans jackson serializer cache 
268      * @return the serialized JSON string
269      * @throws JsonProcessingException JSON processing error
270      */
271     public <T> String serCollectionWithTypeReference(Collection<T> src, TypeReference collectionType,
272                                                      Boolean cleanCache) throws JsonProcessingException {
273          String res = mapper.writerFor(collectionType).writeValueAsString(src);
274          if (cleanCache) {
275              cacheService.cleanSerializerCache(mapper);
276          }
277          return res;
278      }
279     
280      /**
281      * @param name   name of the module
282      * @param target target class
283      * @param mixin  provide mixin as class. Deregistering module could be only done
284      *               by setting this parameter to null.
285      * 
286      * @see #addAdapter(String, Class, Object)
287      */
288     @Override
289     public JsonService addAdapter(String name, Class target, Class mixin) throws Exception {
290         getLogger().debug(
291                 "registering unversioned simple mixin module named " + name + " of type " + mixin + "  for: " + target);
292         mapper.addMixIn(target, mixin);
293         return this;
294     }
295 
296     /**
297      * set a single mixin. convenience method, calls
298      * {@link ObjectMapper#registerModule(Module)}
299      * 
300      * @param src    he object to be serialized
301      * @param name   the name for the mixin
302      * @param target the target class for the mixin
303      * @param mixin  the mixin class
304      * @return serialized result
305      * @throws JsonProcessingException if not properly processed
306      */
307     @SuppressWarnings("rawtypes")
308     public String withMixinModule(Object src, String name, Class target, Class mixin) throws JsonProcessingException {
309         Module mx = new MixinModule(name, target, mixin);
310         getLogger().debug("registering module " + mx + ", mixin: " + mixin);
311         return mapper.registerModule(mx).writer().writeValueAsString(src);
312     }
313 
314     /**
315      * This is a convenience method with read, but the old mixins will be cleared
316      * {@link ObjectMapper#setMixIns(Map)}
317      * 
318      * @param src    the object to be serialized
319      * @param target the target class for the mixin
320      * @param mixin  the mixin class
321      * @return serialized result
322      * @throws JsonProcessingException if fail
323      */
324     @SuppressWarnings("rawtypes")
325     public String withSetMixins(Object src, Class target, Class mixin) throws JsonProcessingException {
326         return setMixins(target, mixin).writer().writeValueAsString(src);
327     }
328 
329     /**
330      * @param target The target class
331      * @param mixin  the mixin class
332      * @return an objectmapper
333      */
334     @SuppressWarnings("rawtypes")
335     public ObjectMapper setMixins(Class target, Class mixin) {
336         Map<Class<?>, Class<?>> sourceMixins = null;
337         if (target != null) {
338             sourceMixins = new HashMap<>(1);
339             sourceMixins.put(target, mixin);
340         }
341         getLogger().debug("complete reset mixins for target " + target + ", mixin: " + mixin);
342         return mapper.setMixIns(sourceMixins);
343     }
344 
345     @Override
346     public String serializeAllExceptFilter(Object src, String... filterAttr) throws Exception {
347         return serializeAllExceptFilter(src, src.getClass(), true, filterAttr);
348     }
349 
350     @Override
351     public synchronized String serializeAllExceptFilter(Object src, Boolean cache, String... filterAttr)
352             throws Exception {
353         return serializeAllExceptFilter(src, src.getClass(), cache, filterAttr);
354     }
355 
356     public synchronized <T> String serializeAllExceptFilter(Object src, Class<T>[] filterClasses, String... filterAttr)
357             throws Exception {
358         return serializeAllExceptFilter(src, filterClasses, true, filterAttr);
359     }
360 
361     @Override
362     public synchronized <T> String serializeAllExceptFilter(Object src, Class<T> filterClass, String... filterAttr)
363             throws Exception {
364         return serializeAllExceptFilter(src, filterClass, true, filterAttr);
365     }
366 
367     @Override
368     public <T> String serializeAllExceptFilter(Object src, Class<T> filterClass, Boolean cleanFilter,
369             String... filterAttr) throws Exception {
370         return serializeAllExceptFilter(src, new Class[] { filterClass }, cleanFilter, filterAttr);
371     }
372 
373     /**
374      * 
375      * @param src           the object to be serailized, may be a list
376      * @param filterClasses the same object class or a detail class, which should be
377      *                      filtered
378      * @param               <T> class type
379      * @param clean         cleaning the cache after serialization
380      * @param filterAttr    attributes to be filtered for filtered class
381      * @return the serailized string
382      * @throws Exception generic exception
383      */
384     public synchronized <T> String serializeAllExceptFilter(Object src, Class<T>[] filterClasses, Boolean clean,
385             String... filterAttr) throws Exception {
386         PropertyFilter pf = null;
387         if (filterAttr != null)
388             pf = SimpleBeanPropertyFilter.serializeAllExcept(filterAttr);
389         else if (filterClasses == null) { // no filter
390             return ser(src, clean);
391             // should be better:
392             // return filter(src, new Class<?>[] { src.getClass() }, filterClasses, pf,
393             // clean);
394         }
395         return filter(src, new Class<?>[] { filterClasses[0] }, filterClasses, pf, clean);
396     }
397 
398     @Override
399     public String serializeOnlyFilter(Object src, String... filterAttrs) throws Exception {
400         return serializeOnlyFilter(src, src.getClass(), true, filterAttrs);
401     }
402 
403     @Override
404     public synchronized String serializeOnlyFilter(Object src, Boolean cache, String... filterAttr) throws Exception {
405         return serializeOnlyFilter(src, src.getClass(), cache, filterAttr);
406     }
407 
408     @Override
409     public synchronized <T> String serializeOnlyFilter(Object src, Class<T> filterClass, String... filterAttr)
410             throws Exception {
411         return serializeOnlyFilter(src, filterClass, true, filterAttr);
412     }
413 
414     @Override
415     public synchronized <T> String serializeOnlyFilter(Object src, Class<T> filterClass, Boolean refresh,
416             String... filterAttr) throws Exception {
417         return serializeOnlyFilter(src, new Class[] { filterClass }, refresh, filterAttr);
418     }
419 
420     public synchronized <T> String serializeOnlyFilter(Object src, Class<T>[] filterClasses, Boolean refresh,
421             String... filterAttr) throws Exception {
422         PropertyFilter pf = null;
423         if (filterAttr != null && filterAttr.length > 0 && !"".equals(filterAttr[0])) {
424             pf = SimpleBeanPropertyFilter.filterOutAllExcept(filterAttr);
425             getLogger().debug("setting filteroutAllexcept filter for size of filterAttr: " + filterAttr.length);
426         } else {
427             getLogger().warn("no filter attributes set!");
428             pf = SimpleBeanPropertyFilter.filterOutAllExcept("dummy");
429         }
430         if (filterClasses == null)
431             throw new AssertionError("You have to provide some class to apply the filtering!");
432         return filter(src, filterClasses, null, pf, refresh);
433     }
434 
435     @Override
436     public String ser(Object src, Boolean cleanCache) throws Exception {
437         if (isCacheFilters() && cacheService.getFilters().containsKey(src.getClass().getName())) {
438             getLogger().warn("Found registered filter - using instead of default view filter for class:"
439                     + src.getClass().getName());
440             SimpleFilterProvider filter = (SimpleFilterProvider) cacheService.getFilters()
441                     .get(src.getClass().getName());
442             return ser(src, filter, cleanCache);// mapper.writerWithView(src.getClass()).writeValueAsString(src);
443         }
444         String res = mapper.writerWithView(Object.class).writeValueAsString(src);
445         if (cleanCache != null && cleanCache) {
446             cacheService.cleanSerializerCache(mapper);
447         }
448         return res;
449     }
450 
451     @Override
452     public <T> String ser(Object src, Class<T> type, Boolean cleanCache) throws Exception {
453         getLogger().info("serializing object:" + src + " for type " + type);
454         if (isCacheFilters() && src != null && cacheService.getFilters().containsKey(src.getClass().getName())) {
455             getLogger().warn("Found registered filter - could not use custom view and custom filter for class:"
456                     + src.getClass().getName());
457             // throw new
458             // Exception("Found registered filter - could not use custom view and custom
459             // filter for class:"+
460             // src.getClass().getName());
461             SimpleFilterProvider filter = (SimpleFilterProvider) cacheService.getFilters()
462                     .get(src.getClass().getName());
463             return ser(src, filter);
464         }
465 
466         String res = (type != null) ? mapper.writerWithView(type).writeValueAsString(src)
467                 : mapper.writeValueAsString(src);
468         if (cleanCache) {
469             cacheService.cleanSerializerCache(mapper);
470         }
471         return res;
472     }
473 
474     /**
475      * 
476      * @param src            The source Object to be filtered.
477      * @param filterClass    This Class array contains at least one element. If no
478      *                       class is provided it is the class type of the source
479      *                       object. The filterClass is to become the key of the
480      *                       filter object cache.
481      * @param excludeClasses The classes to be excluded, optionally used only for
482      *                       methods like
483      *                       {@link #serializeAllExceptFilter(Object, Class[], String...)}.
484      * @param pf             Expecting a property filter from e.g @link
485      *                       {@link SimpleBeanPropertyFilter}.
486      * @param clean          if <code>true</code> does not reuse the filter object
487      *                       (no cashing).
488      * @return The serialized Object as String
489      * @throws Exception
490      */
491     private <T> String filter(Object src, Class<?>[] filterClasses, Class<T>[] excludeClasses, PropertyFilter pf,
492             Boolean clean) throws Exception {
493         FilterProvider filter = null;
494         if (filterClasses.length > 0) {
495             filter = retrieveFilter(pf, filterClasses[0], excludeClasses);
496         }
497         getLogger().info("filtering with filter " + filter);
498         String serialized = ser(src, filter, clean);
499         if (!isCacheFilters() || clean) {
500             if (filterClasses.length > 0) {
501                 boolean exclude = (excludeClasses != null) ? true : false;
502                 cacheService.removeFilter(filterClasses[0], exclude);
503             }
504         }
505         return serialized;
506     }
507 
508     private <T> SimpleFilterProvider retrieveFilter(PropertyFilter pf, Class<?> filterClass,
509             Class<T>[] excludeClasses) {
510         SimpleFilterProvider filter = null;
511         if (pf != null) {
512             filter = new SimpleFilterProvider();
513             filter.setDefaultFilter(pf);
514         }
515         if (isCacheFilters()) {
516             if (!cacheService.getFilters().containsKey(filterClass.getName())) {
517                 getLogger().debug("add filter for cache filter Class " + filterClass.getName());
518                 setCustomIntrospectorWithExternalFilterId(filterClass, excludeClasses); // filter class
519                 if (pf != null) {
520                     cacheService.getFilters().put(filterClass.getName(), filter);
521                 }
522             } else {
523                 filter = (SimpleFilterProvider) cacheService.getFilters().get(filterClass.getName());
524                 // setCustomIntrospectorWithExternalFilterId(filterClass); // filter
525                 // class
526             }
527         }
528         getLogger().debug("set filter:" + filter);
529         return filter;
530     }
531 
532     /**
533      * @param filterClass
534      *                          <li>Adding filterClass into
535      *                          {@link SimpleNameIntrospector#setFilteredClass(Class)}
536      *                          enables the filtering process.
537      * @param externalFilterIds
538      *                          <li>Adding externalFilterIs to
539      *                          {@link SimpleNameIntrospector#setExternalFilterExcludeClasses(Class...)}
540      *                          excludes these classes.
541      */
542     private <T> void setCustomIntrospectorWithExternalFilterId(Class<?> filterClass,
543             Class<T>[] externalFilterClassIds) {
544         if (primary instanceof SimpleNameIntrospector) {
545             // first one is required that we get to the PropertyFilter
546             ((SimpleNameIntrospector) primary).setFilteredClasses(filterClass);
547             if (externalFilterClassIds != null) {
548                 ((SimpleNameIntrospector) primary).setIsExludeType(true);
549                 for (Class<T> filterClazz : externalFilterClassIds) {
550                     getLogger().debug("added class for filters " + filterClazz);
551                 }
552                 ((SimpleNameIntrospector) primary).setExternalFilterExcludeClasses(externalFilterClassIds);
553             }
554         }
555     }
556 
557     public Jackson2MapperService registerModule(Module module) {
558         mapper.registerModule(module);
559         return this;
560     }
561 
562     public <T> void addSimpleModule(SimpleModule module, Class<T> type, JsonSerializer<T> ser) {
563         module.addSerializer(type, ser);
564     }
565 
566     public <T> void addSimpleModule(SimpleModule module, Class<T> type, JsonDeserializer<T> deSer) {
567         module.addDeserializer(type, deSer);
568     }
569 
570     /**
571      * Default Dateformat: {@link #DEFAULTDATEFORMAT}
572      */
573     @Override
574     public void setDateFormat(final DateFormat df) {
575         mapper.setDateFormat(df);
576     }
577 
578     /**
579      * Avalon component lifecycle method
580      */
581     @Override
582     public void configure(Configuration conf) throws ConfigurationException {
583         getLogger().debug("conf.getName()" + conf.getName());
584         this.annotationInspectors = new HashMap<>();
585 
586         final Configuration configuredAnnotationInspectors = conf.getChild(ANNOTATIONINSPECTOR, false);
587 
588         if (configuredAnnotationInspectors != null) {
589             Configuration[] nameVal = configuredAnnotationInspectors.getChildren();
590             Arrays.stream( nameVal).forEach(c->
591             {
592                 String key = c.getName();
593                 getLogger().debug("configured key: " + key);
594                 if (key.equals("features")) {
595                     this.features = new HashMap<>();
596                     this.featureTypes = new HashMap<>();
597                     Arrays.stream( c.getChildren() ).forEach( lf -> {
598                         boolean featureValue = lf.getAttributeAsBoolean("value", false);
599                         String featureType = null;
600                         String feature = null;
601                         try {
602                             featureType = lf.getAttribute("type");
603                             feature = lf.getValue();
604                             getLogger().debug("configuredAnnotationInspectors " + feature + ":" + featureValue);
605                             this.features.put(feature, featureValue);
606                             this.featureTypes.put(feature, featureType);
607                         } catch (ConfigurationException e) {
608                             throw new RuntimeException(e);
609                         }
610                     });
611                 } else {
612                     String val;
613                     try {
614                         val = c.getValue();
615                         getLogger().debug("configuredAnnotationInspectors " + key + ":" + val);
616                         this.annotationInspectors.put(key, val);
617                     } catch (ConfigurationException e) {
618                         throw new RuntimeException(e);
619                     }
620 
621                 }
622             });
623         }
624         final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT, true);
625         this.dateFormat = configuredDateFormat.getValue(DEFAULTDATEFORMAT);
626 
627         final Configuration configuredKeepFilter = conf.getChild(CACHE_FILTERS, false);
628         if (configuredKeepFilter != null) {
629             setCacheFilters( configuredKeepFilter.getValueAsBoolean());
630         }
631         final Configuration configuredEscapeChars = conf.getChild(ESCAPE_CHARS, false);
632         if (configuredEscapeChars != null) {
633             this.escapeCharsGlobal = configuredEscapeChars.getValueAsBoolean();
634         }
635         final Configuration configuredEscapeCharClass = conf.getChild(ESCAPE_CHAR_CLASS, false);
636         if (configuredEscapeCharClass != null) {
637             this.escapeCharsClass = configuredEscapeCharClass.getValue();
638         }
639 
640         final Configuration configuredDefaultType = conf.getChild(DEFAULT_TYPING, false);
641         if (configuredDefaultType != null) {
642             defaultTypeDefs = new String[] { configuredDefaultType.getAttribute("type"),
643                     configuredDefaultType.getAttribute("key") };
644         }
645         final Configuration configuredjsonPath = conf.getChild(USE_JSON_PATH, false);
646         if (configuredjsonPath != null) {
647             this.useJsonPath = configuredjsonPath.getValueAsBoolean();
648         }
649     }
650 
651     @Override
652     public void initialize() throws Exception {
653         mapper = new ObjectMapper(null, null, null);// add configurable JsonFactory,.. later?
654 
655         initAnnotationInspectors();
656 
657         initFeatures();
658 
659         initDefaultTyping();
660 
661         getLogger().info("setting date format to:" + dateFormat);
662         getLogger().info("cacheFilters is:" + isCacheFilters());
663         if (!isCacheFilters()) {
664             mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, true);
665         }
666 
667         mapper.setDateFormat(new SimpleDateFormat(dateFormat));
668 
669         if (escapeCharsGlobal) {
670             mapper.getFactory().setCharacterEscapes(characterEscapes);
671         }
672         if (escapeCharsClass != null) {
673             try {
674                 characterEscapes = (CharacterEscapes) Class.forName(escapeCharsClass).getConstructor().newInstance();
675             } catch (Exception e) {
676                 throw new InstantiationException(
677                         "JsonMapperService: Error instantiating " + escapeCharsClass + " for " + ESCAPE_CHAR_CLASS);
678             }
679         }
680 
681         getLogger().debug("initialized mapper:" + mapper);
682 
683         mapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
684             @Override
685             public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
686                 jgen.writeString("");
687 
688             }
689         });
690         cacheService = new CacheService(primary);
691         if (cacheService instanceof LogEnabled) {
692             cacheService.enableLogging(getLogger().getChildLogger(cacheService.getClass().getSimpleName()));
693             getLogger().info("setting cacheService logger: " + cacheService.getClass().getSimpleName());
694         }
695 
696         if (useJsonPath) {
697             // set it before runtime
698             DefaultJsonPathWrapper djpw = null;
699             try {
700                 djpw = new DefaultJsonPathWrapper(this.mapper);
701                 getLogger().debug("******** initialized new jsonPath defaults: " + djpw.getJsonPathDefault());
702             } catch (Exception e) {
703                 throw new AssertionError(
704                         "JsonMapperService: Error instantiating " + djpw + " using useJsonPath=" + useJsonPath);
705             }
706 
707         }
708     }
709 
710     private void initDefaultTyping() {
711         if (defaultTypeDefs != null && defaultTypeDefs.length == 2) {
712             DefaultTyping defaultTyping = DefaultTyping.valueOf(defaultTypeDefs[0]);
713             mapper.enableDefaultTypingAsProperty(defaultTyping, defaultTypeDefs[1]);
714             getLogger().info("default typing is " + defaultTypeDefs[0] + " with key:" + defaultTypeDefs[1]);
715         }
716     }
717 
718     private void initFeatures() throws Exception {
719         if (features != null && !features.isEmpty()) {
720             features.entrySet().stream().forEach( entry -> {
721                 String featureKey = entry.getKey();
722                 Boolean featureValue = entry.getValue();    
723                 String featureType = featureTypes.get(featureKey);
724                 Class<?> configFeature = null;
725                 try {
726                     getLogger().debug("initializing featureType:  " + featureType);
727                     configFeature = loadClass(featureType); 
728                 } catch (ClassNotFoundException e) {
729                     throw new AssertionError("JsonMapperService: Error instantiating " + featureType + " for " + featureKey, e);
730                 }
731                 ConfigFeature feature = null;
732                 if (!StringUtils.isEmpty(featureKey) && featureValue != null) {
733                    try 
734                    {
735                         if (configFeature.equals(SerializationFeature.class)) {
736                             feature = SerializationFeature.valueOf(featureKey);
737                             mapper.configure((SerializationFeature) feature, featureValue);
738                             assert mapper.getSerializationConfig()
739                                     .isEnabled((SerializationFeature) feature) == featureValue;
740                             getLogger().info("initialized serconfig mapper feature: " + feature + " with "
741                                     + mapper.getSerializationConfig().isEnabled((SerializationFeature) feature));
742                         } else if (configFeature.equals(DeserializationFeature.class)) {
743                             feature = DeserializationFeature.valueOf(featureKey);
744                             mapper.configure((DeserializationFeature) feature, featureValue);
745                             assert mapper.getDeserializationConfig()
746                                     .isEnabled((DeserializationFeature) feature) == featureValue;
747                             getLogger().info("initialized deserconfig mapper feature: " + feature + " with "
748                                     + mapper.getDeserializationConfig().isEnabled((DeserializationFeature) feature));
749                         } else if (configFeature.equals(MapperFeature.class)) {
750                             feature = MapperFeature.valueOf(featureKey);
751                             mapper.configure((MapperFeature) feature, featureValue);
752                             assert mapper.getDeserializationConfig().isEnabled((MapperFeature) feature) == featureValue;
753                             assert mapper.getSerializationConfig().isEnabled((MapperFeature) feature) == featureValue;
754                             getLogger().info("initialized serconfig mapper feature: " + feature + " with "
755                                     + mapper.getDeserializationConfig().isEnabled((MapperFeature) feature));
756                             getLogger().info("initialized deserconfig mapper feature: " + feature + " with "
757                                     + mapper.getSerializationConfig().isEnabled((MapperFeature) feature));
758                         } else if (configFeature.equals(JsonParser.class)) {
759                             Feature parserFeature = JsonParser.Feature.valueOf(featureKey);
760                             getLogger().info("initializing parser feature: " + parserFeature + " with " + featureValue);
761                             mapper.configure(parserFeature, featureValue);
762                         } else if (configFeature.equals(JsonGenerator.class)) {
763                             com.fasterxml.jackson.core.JsonGenerator.Feature genFeature = JsonGenerator.Feature
764                                     .valueOf(featureKey);
765                             getLogger().info("initializing parser feature: " + genFeature + " with " + featureValue);
766                             mapper.configure(genFeature, featureValue);
767                         }
768                    } catch (Exception e) {
769                        throw new RuntimeException("JsonMapperService: Error instantiating feature " + featureKey + " with  "
770                                + featureValue , e);
771                    }
772                     
773                 }
774             });   
775         }
776     }
777 
778     private void initAnnotationInspectors() throws Exception {
779         for (Entry<String, String> entry : annotationInspectors.entrySet()) {
780             String key = entry.getKey();
781             String avClass = entry.getValue();
782             if (key.equals("primary") && !StringUtils.isEmpty(avClass)) {
783                 try {
784                     primary = (AnnotationIntrospector) Class.forName(avClass).getConstructor().newInstance();
785                 } catch (Exception e) {
786                     throw new InstantiationException("JsonMapperService: Error instantiating " + avClass + " for " + key);
787                 }
788             } else if (key.equals("secondary") && avClass != null) {
789                 try {
790                     secondary = (AnnotationIntrospector) Class.forName(avClass).getConstructor().newInstance();
791                 } catch (Exception e) {
792                     throw new InstantiationException("JsonMapperService: Error instantiating " + avClass + " for " + key);
793                 }
794             }
795         }
796         if (primary == null) {
797             primary = new JacksonAnnotationIntrospector(); // support default
798             getLogger().info("using default introspector:" + primary.getClass().getName());
799             mapper.setAnnotationIntrospector(primary);
800         } else if (primary != null && secondary != null) {
801             AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary, secondary);
802             mapper.setAnnotationIntrospector(pair);
803         } else {
804             mapper.setAnnotationIntrospector(primary);
805         }
806 
807         if (primary instanceof LogEnabled) {
808             ((LogEnabled) primary).enableLogging(getLogger().getChildLogger(primary.getClass().getSimpleName()));
809             getLogger().info("setting primary introspector logger: " + primary.getClass().getSimpleName());
810         }
811         if (secondary instanceof LogEnabled) {
812             ((LogEnabled) secondary).enableLogging(getLogger().getChildLogger(secondary.getClass().getSimpleName()));
813             getLogger().info("setting secondary introspector logger: " + secondary.getClass().getSimpleName());
814         }
815     }
816     
817     /**
818      * Loads the named class using the default class loader.
819      *
820      * @param className the name of the class to load.
821      * @return {@inheritDoc} the loaded class.
822      * @throws ClassNotFoundException if the class was not found.
823      */
824     @SuppressWarnings("unchecked")
825     protected <T> Class<T> loadClass(String className) throws ClassNotFoundException {
826         ClassLoader loader = this.getClass().getClassLoader();
827         try {
828             Class<T> clazz;
829 
830             if (loader != null) {
831                 clazz = (Class<T>) loader.loadClass(className);
832             } else {
833                 clazz = (Class<T>) Class.forName(className);
834             }
835 
836             return clazz;
837         } catch (ClassNotFoundException x) {
838             /* Give up. */
839             throw x;
840         }
841     }
842 
843     public ObjectMapper getMapper() {
844         return mapper;
845     }
846 
847     public void setMapper(ObjectMapper mapper) {
848         this.mapper = mapper;
849     }
850 
851     public boolean isCacheFilters() {
852         return cacheFilters;
853     }
854 
855     public void setCacheFilters(boolean cacheFilters) {
856         this.cacheFilters = cacheFilters;
857         if (!cacheFilters)
858             mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, true);
859     }
860 
861     static CharacterEscapes characterEscapes = new CharacterEscapes() {
862         private static final long serialVersionUID = 1L;
863         private final int[] asciiEscapes;
864         { // instance init
865             int[] esc = standardAsciiEscapesForJSON();
866             // this avoids to get evaluated immediately
867             esc['<'] = CharacterEscapes.ESCAPE_STANDARD;
868             esc['>'] = CharacterEscapes.ESCAPE_STANDARD;
869             esc['&'] = CharacterEscapes.ESCAPE_STANDARD;
870             esc['\''] = CharacterEscapes.ESCAPE_STANDARD;
871             // esc['/'] = '/'; //CharacterEscapes.ESCAPE_CUSTOM;
872             asciiEscapes = esc;
873         }
874 
875         @Override
876         public int[] getEscapeCodesForAscii() {
877             return asciiEscapes;
878         }
879 
880         @Override
881         public SerializableString getEscapeSequence(final int ch) {
882 //            if ( ch == '/') { 
883 //                return new SerializedString("\\\\/");
884 //            } else {
885             return null;
886 //            }
887         }
888     };
889 }