Coverage Report - org.apache.fulcrum.json.jackson.JacksonMapperService
 
Classes in this File Line Coverage Branch Coverage Complexity
JacksonMapperService
69%
125/181
43%
44/102
2,75
JacksonMapperService$MixinModule
100%
6/6
N/A
2,75
 
 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  
  * 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.text.DateFormat;
 23  
 import java.text.SimpleDateFormat;
 24  
 import java.util.Collection;
 25  
 import java.util.Collections;
 26  
 import java.util.Enumeration;
 27  
 import java.util.HashMap;
 28  
 import java.util.Hashtable;
 29  
 import java.util.Map;
 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.fulcrum.json.JsonService;
 37  
 import org.codehaus.jackson.Version;
 38  
 import org.codehaus.jackson.map.AnnotationIntrospector;
 39  
 import org.codehaus.jackson.map.JsonDeserializer;
 40  
 import org.codehaus.jackson.map.JsonSerializer;
 41  
 import org.codehaus.jackson.map.Module;
 42  
 import org.codehaus.jackson.map.ObjectMapper;
 43  
 import org.codehaus.jackson.map.ObjectMapper.DefaultTyping;
 44  
 import org.codehaus.jackson.map.ObjectReader;
 45  
 import org.codehaus.jackson.map.SerializationConfig.Feature;
 46  
 import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
 47  
 import org.codehaus.jackson.map.module.SimpleModule;
 48  
 import org.codehaus.jackson.map.ser.FilterProvider;
 49  
 import org.codehaus.jackson.map.ser.StdSerializerProvider;
 50  
 import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
 51  
 import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
 52  
 
 53  
 /**
 54  
  * Jackson 1 Impl of @link {@link JsonService}.
 55  
  * 
 56  
  * By default multiple serialization of the same object in a single thread is
 57  
  * not support (e.g filter + mixin or default + filter for the same bean /
 58  
  * object).
 59  
  * 
 60  
  * Note: Filters could not easily unregistered. Try setting @link
 61  
  * {@link #cacheFilters} to <code>false</code>.
 62  
  * 
 63  
  * 
 64  
  * @author gk
 65  
  * @version $Id: JacksonMapperService.java 1800593 2017-07-03 06:36:53Z gk $
 66  
  * 
 67  
  */
 68  22
 public class JacksonMapperService extends AbstractLogEnabled implements
 69  
         JsonService, Initializable, Configurable {
 70  
 
 71  
     private static final String DEFAULT_TYPING = "defaultTyping";
 72  
     private static final String CACHE_FILTERS = "cacheFilters";
 73  
     private static final String DATE_FORMAT = "dateFormat";
 74  
     ObjectMapper mapper;
 75  
     AnnotationIntrospector primary; // support default
 76  
     AnnotationIntrospector secondary;
 77  
 
 78  20
     public String ANNOTATIONINSPECTOR = "annotationInspectors";
 79  
 
 80  20
     private Hashtable<String, String> annotationInspectors = null;
 81  20
     private Hashtable<String, Boolean> features = null;
 82  
 
 83  
     private Map<String, FilterProvider> filters;
 84  
     private String dateFormat;
 85  
 
 86  20
     final String DEFAULTDATEFORMAT = "MM/dd/yyyy";
 87  
 
 88  20
     final boolean defaultType = false;
 89  20
     public boolean cacheFilters = true; // more efficient if not using multiple
 90  
                                         // serialization in one thread
 91  20
     String[] defaultTypeDefs = null;
 92  
 
 93  
     @Override
 94  
     public synchronized String ser(Object src) throws Exception {
 95  14
         return ser(src, false);
 96  
     }
 97  
 
 98  
     @Override
 99  
     public <T> String ser(Object src, Class<T> type) throws Exception {
 100  0
         return ser(src, type, false);
 101  
     }
 102  
 
 103  
     public synchronized <T> String ser(Object src, FilterProvider filters)
 104  
             throws Exception {
 105  7
         if (filters == null) {
 106  0
             getLogger().debug("ser class::" + src.getClass() + " without filter "); 
 107  0
             return ser(src);
 108  
         } 
 109  7
         getLogger().debug("ser::" + src + " with filters " + filters);
 110  7
         String serResult = mapper.writer(filters).writeValueAsString(src);
 111  7
         return serResult;
 112  
     }
 113  
     
 114  
     @Override
 115  
     public String ser(Object src, Boolean cleanCache) throws Exception {
 116  14
         if (src.getClass() != null && filters.containsKey(src.getClass().getName())) {
 117  0
             getLogger().warn(
 118  
                     "Found registered filter - using instead of default view filter for class:"
 119  
                             + src.getClass().getName());
 120  
             // throw new
 121  
             // Exception("Found registered filter - could not use custom view and custom filter for class:"+
 122  
             // src.getClass().getName());
 123  
         }
 124  14
         if (!cacheFilters || cleanCache) {
 125  0
             cleanSerializerCache();
 126  
         }
 127  14
         return mapper.writer().writeValueAsString(src);
 128  
     }
 129  
 
 130  
     @Override
 131  
     public <T> String ser(Object src, Class<T> type, Boolean cleanCache)
 132  
             throws Exception {
 133  0
         getLogger().debug("ser::" + src + " with type" + type);
 134  0
         if (src.getClass() != null && filters.containsKey(src.getClass().getName())) {
 135  0
             getLogger()
 136  
                     .warn("Found registered filter - could not use custom view and custom filter for class:"
 137  
                             + src.getClass().getName());
 138  
             // throw new
 139  
             // Exception("Found registered filter - could not use custom view and custom filter for class:"+
 140  
             // src.getClass().getName());
 141  
         }
 142  0
         if (!cacheFilters || cleanCache) {
 143  0
             cleanSerializerCache();
 144  
         }
 145  0
         return mapper.writerWithView(type).writeValueAsString(src);
 146  
     }
 147  
 
 148  
     @Override
 149  
     public <T> T deSer(String src, Class<T> type) throws Exception {
 150  3
         ObjectReader reader = mapper.reader(type);
 151  3
         return (T) reader.readValue(src);
 152  
     }
 153  
     
 154  
     @Override
 155  
     public <T> Collection<T> deSerCollection(String json,
 156  
             Object collectionType, Class<T> elementType) throws Exception {
 157  1
         return mapper.readValue(json, mapper.getTypeFactory()
 158  
                 .constructCollectionType(((Collection<T>)collectionType).getClass(), elementType));
 159  
     }
 160  
 
 161  
     public <T> T deSer(String json, Class<? extends Collection> collectionType,
 162  
             Class<T> type) throws Exception {
 163  2
         return  (T) mapper.readValue(json, mapper.getTypeFactory()
 164  
                 .constructCollectionType(collectionType, type));
 165  
     }
 166  
     
 167  
     @Override
 168  
     public <T> String serializeAllExceptFilter(Object src, String... filterAttr)
 169  
             throws Exception {
 170  0
         return serializeAllExceptFilter(src, src.getClass(), true, filterAttr);
 171  
     }
 172  
 
 173  
     @Override
 174  
     public <T> String serializeAllExceptFilter(Object src, Boolean refresh,
 175  
             String... filterAttr) throws Exception {
 176  0
         return serializeAllExceptFilter(src, src.getClass(), refresh, filterAttr);
 177  
     }
 178  
 
 179  
     @Override
 180  
     public synchronized <T> String serializeAllExceptFilter(Object src,
 181  
             Class<T> filterClass, String... filterAttr) throws Exception {
 182  0
         return serializeAllExceptFilter(src, filterClass, true, filterAttr);
 183  
     }
 184  
     
 185  
     @Override
 186  
     public <T> String serializeAllExceptFilter(Object src,
 187  
             Class<T> filterClass, Boolean refreshFilter, String... filterAttr)
 188  
             throws Exception {
 189  0
         setCustomIntrospectorWithExternalFilterId(filterClass);
 190  0
         FilterProvider filter = null;
 191  0
         if ( filterClass != null) {
 192  0
             if (filterAttr != null && filterAttr.length > 0 && 
 193  
                     (refreshFilter || !this.filters.containsKey(filterClass.getName()))) {
 194  0
                 filter = new SimpleFilterProvider().addFilter(
 195  
                         filterClass.getName(),
 196  
                         SimpleBeanPropertyFilter.serializeAllExcept(filterAttr));
 197  0
                 this.filters.put(filterClass.getName(), filter);
 198  
             } else {
 199  0
                 filter = this.filters.get(filterClass.getName());
 200  
             }
 201  
         }
 202  0
         String serialized = ser(src, filter);
 203  0
         if (!cacheFilters || refreshFilter) {
 204  0
             removeFilterClass(filterClass);
 205  0
             cleanSerializerCache();
 206  
         }
 207  0
         return serialized;
 208  
     }
 209  
     
 210  
 
 211  
     @Override
 212  
     public <T> String serializeOnlyFilter(Object src, String... filterAttr)
 213  
             throws Exception {
 214  0
         return serializeOnlyFilter(src, src.getClass(), true, filterAttr);
 215  
     }
 216  
 
 217  
     @Override
 218  
     public <T> String serializeOnlyFilter(Object src, Boolean refresh,
 219  
             String... filterAttr) throws Exception {
 220  0
         return serializeOnlyFilter(src, src.getClass(), refresh, filterAttr);
 221  
     }
 222  
 
 223  
     @Override
 224  
     public synchronized <T> String serializeOnlyFilter(Object src,
 225  
             Class<T> filterClass, String... filterAttr) throws Exception {
 226  7
         return serializeOnlyFilter(src, filterClass, true, filterAttr);
 227  
     }
 228  
     
 229  
     @Override
 230  
     public <T> String serializeOnlyFilter(Object src, Class<T> filterClass,
 231  
             Boolean refreshFilter, String... filterAttr) throws Exception {
 232  7
         setCustomIntrospectorWithExternalFilterId(filterClass);
 233  7
         FilterProvider filter = null;
 234  7
         if (filterClass == null && src != null && src.getClass() != null) {
 235  0
             filterClass =(Class<T>) src.getClass();
 236  
         }
 237  7
         if ( filterClass != null) {
 238  7
             if (!this.filters.containsKey(filterClass.getName())) {
 239  7
                 getLogger().debug("filterClass::" + filterClass.getName() + " with filterAttr: " + filterAttr);
 240  7
                 if (filterAttr != null) {
 241  6
                 filter = new SimpleFilterProvider().addFilter(
 242  
                         filterClass.getName(),
 243  
                         SimpleBeanPropertyFilter.filterOutAllExcept(filterAttr));
 244  6
                 this.filters.put(filterClass.getName(), filter);
 245  
                 } else {
 246  1
                     filter =  new SimpleFilterProvider();
 247  1
                     this.filters.put(filterClass.getName(),filter);   
 248  
                 }
 249  
             } else {
 250  0
                 filter = this.filters.get(filterClass.getName());
 251  
             }
 252  
         }
 253  7
         String serialized = ser(src, filter);
 254  7
         getLogger().debug("serialized " + serialized);
 255  7
         if (!cacheFilters || refreshFilter) {
 256  7
             removeFilterClass(filterClass);
 257  7
             cleanSerializerCache();
 258  
         }
 259  7
         return serialized;
 260  
     }
 261  
 
 262  
     private <T> void removeFilterClass(Class<T> filterClass) {
 263  7
         if (this.filters.containsKey(filterClass.getName())) {
 264  7
             removeCustomIntrospectorWithExternalFilterId(filterClass);
 265  7
             this.filters.remove(filterClass.getName());
 266  7
             mapper.getSerializerProvider().flushCachedSerializers();
 267  7
             getLogger().debug(
 268  
                     "removed from  SimpleFilterProvider filters "
 269  
                             + filterClass.getName());
 270  
         }
 271  7
     }
 272  
     
 273  
     private void cleanSerializerCache() {
 274  7
         if (mapper.getSerializerProvider() instanceof StdSerializerProvider) {
 275  7
             int cachedSerProvs = ((StdSerializerProvider) mapper
 276  
                     .getSerializerProvider()).cachedSerializersCount();
 277  7
             if (cachedSerProvs > 0) {
 278  0
                 getLogger()
 279  
                         .debug("flushing cachedSerializersCount:"
 280  
                                 + cachedSerProvs);
 281  0
                 ((StdSerializerProvider) mapper.getSerializerProvider())
 282  
                         .flushCachedSerializers();
 283  
             }
 284  
         }
 285  7
     }
 286  
 
 287  
     private <T> void setCustomIntrospectorWithExternalFilterId(
 288  
             Class<T> externalFilterId) {
 289  7
         if (primary instanceof CustomIntrospector && externalFilterId != null) {
 290  7
                 ((CustomIntrospector) primary)
 291  
                         .setExternalFilterClasses(externalFilterId);
 292  7
                 getLogger().debug(
 293  
                         "added class from filters "
 294  
                                 + externalFilterId.getName());
 295  
         }
 296  7
     }
 297  
 
 298  
     private <T> void removeCustomIntrospectorWithExternalFilterId(
 299  
             Class<T> externalFilterId) {
 300  7
         if (primary instanceof CustomIntrospector && externalFilterId != null) {
 301  7
                 ((CustomIntrospector) primary)
 302  
                         .removeExternalFilterClass(externalFilterId);
 303  7
                 getLogger().debug(
 304  
                         "removed from introspector filter id  "
 305  
                                 + externalFilterId.getName());
 306  
         }
 307  7
     }
 308  
 
 309  
     public JacksonMapperService registerModule(Module module) {
 310  0
         mapper.withModule(module);
 311  0
         return this;
 312  
     }
 313  
 
 314  
     public <T> void addSimpleModule(SimpleModule module, Class<T> type,
 315  
             JsonSerializer<T> ser) {
 316  0
         module.addSerializer(type, ser);
 317  0
     }
 318  
 
 319  
     public <T> void addSimpleModule(SimpleModule module, Class<T> type,
 320  
             JsonDeserializer<T> deSer) {
 321  0
         module.addDeserializer(type, deSer);
 322  0
     }
 323  
 
 324  
     @Override
 325  
     public void setDateFormat(final DateFormat df) {
 326  1
         mapper.setDateFormat(df);
 327  1
     }
 328  
 
 329  
     @Override
 330  
     public JsonService addAdapter(String name, Class target, Object mixin)
 331  
             throws Exception {
 332  0
         return addAdapter(name, target, mixin.getClass());
 333  
     }
 334  
 
 335  
     @Override
 336  
     public JsonService addAdapter(String name, Class target, Class mixin)
 337  
             throws Exception {
 338  5
         Module mx = new MixinModule(name, target, mixin);
 339  5
         getLogger().debug("registering module " + mx + "  for: " + mixin);
 340  5
         mapper.withModule(mx);
 341  5
         return this;
 342  
     }
 343  
 
 344  
     /**
 345  
      * Avalon component lifecycle method
 346  
      */
 347  
     @Override
 348  
     public void configure(Configuration conf) throws ConfigurationException {
 349  20
         getLogger().debug("conf.getName()" + conf.getName());
 350  20
         this.annotationInspectors = new Hashtable<String, String>();
 351  
 
 352  20
         final Configuration configuredAnnotationInspectors = conf.getChild(
 353  
                 ANNOTATIONINSPECTOR, false);
 354  20
         if (configuredAnnotationInspectors != null) {
 355  20
             Configuration[] nameVal = configuredAnnotationInspectors
 356  
                     .getChildren();
 357  80
             for (int i = 0; i < nameVal.length; i++) {
 358  60
                 String key = nameVal[i].getName();
 359  60
                 getLogger().debug("configured key: " + key);
 360  60
                 if (key.equals("features")) {
 361  20
                     this.features = new Hashtable<String, Boolean>();
 362  20
                     Configuration[] features = nameVal[i].getChildren();
 363  40
                     for (int j = 0; j < features.length; j++) {
 364  20
                         boolean featureValue = features[j]
 365  
                                 .getAttributeAsBoolean("value", false);
 366  20
                         String feature = features[j].getValue();
 367  20
                         getLogger().debug(
 368  
                                 "configuredAnnotationInspectors " + feature
 369  
                                         + ":" + featureValue);
 370  20
                         this.features.put(feature, featureValue);
 371  
                     }
 372  20
                 } else {
 373  40
                     String val = nameVal[i].getValue();
 374  40
                     getLogger()
 375  
                             .debug("configuredAnnotationInspectors " + key
 376  
                                     + ":" + val);
 377  40
                     this.annotationInspectors.put(key, val);
 378  
                 }
 379  
             }
 380  
         }
 381  20
         final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT,
 382  
                 true);
 383  20
         this.dateFormat = configuredDateFormat.getValue(DEFAULTDATEFORMAT);
 384  
 
 385  20
         final Configuration configuredKeepFilter = conf.getChild(CACHE_FILTERS,
 386  
                 false);
 387  20
         if (configuredKeepFilter != null) {
 388  0
             this.cacheFilters = configuredKeepFilter.getValueAsBoolean();
 389  
         }
 390  20
         final Configuration configuredDefaultType = conf.getChild(
 391  
                 DEFAULT_TYPING, false);
 392  20
         if (configuredDefaultType != null) {
 393  0
             defaultTypeDefs = new String[] {
 394  
                     configuredDefaultType.getAttribute("type"),
 395  
                     configuredDefaultType.getAttribute("key") };
 396  
         }
 397  20
     }
 398  
 
 399  
     @Override
 400  
     public void initialize() throws Exception {
 401  20
         mapper = new ObjectMapper();
 402  
 
 403  20
         Enumeration<String> enumKey = annotationInspectors.keys();
 404  60
         while (enumKey.hasMoreElements()) {
 405  40
             String key = enumKey.nextElement();
 406  40
             String avClass = annotationInspectors.get(key);
 407  40
             if (key.equals("primary") && avClass != null) {
 408  
                 try {
 409  20
                     primary = (AnnotationIntrospector) Class.forName(avClass).getConstructor()
 410  
                             .newInstance();
 411  0
                 } catch (Exception e) {
 412  0
                     throw new Exception(
 413  
                             "JsonMapperService: Error instantiating " + avClass
 414  
                                     + " for " + key);
 415  20
                 }
 416  20
             } else if (key.equals("secondary") && avClass != null) {
 417  
                 try {
 418  20
                     secondary = (AnnotationIntrospector) Class.forName(avClass).getConstructor()
 419  
                             .newInstance();
 420  0
                 } catch (Exception e) {
 421  0
                     throw new Exception(
 422  
                             "JsonMapperService: Error instantiating " + avClass
 423  
                                     + " for " + key);
 424  20
                 }
 425  
             }
 426  40
         }
 427  20
         if (primary == null) {
 428  0
             primary = new JacksonAnnotationIntrospector(); // support default
 429  0
             getLogger().info(
 430  
                     "using default introspector:"
 431  
                             + primary.getClass().getName());
 432  0
             mapper.setAnnotationIntrospector(primary);
 433  
         } else {
 434  20
             AnnotationIntrospector pair = new AnnotationIntrospector.Pair(
 435  
                     primary, secondary);
 436  20
             mapper.setAnnotationIntrospector(pair);
 437  
         }
 438  
 
 439  
         // mapper.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE,
 440  
         // "type");
 441  20
         if (features != null) {
 442  20
             Enumeration<String> enumFeatureKey = features.keys();
 443  40
             while (enumFeatureKey.hasMoreElements()) {
 444  20
                 String featureKey = enumFeatureKey.nextElement();
 445  20
                 Boolean featureValue = features.get(featureKey);
 446  
                 Feature feature;
 447  20
                 if (featureKey != null && featureValue != null) {
 448  
                     try {
 449  20
                         String[] featureParts = featureKey.split("\\.");
 450  20
                         getLogger().info(
 451  
                                 "initializing mapper feature: "
 452  
                                         + featureParts[featureParts.length - 1]
 453  
                                         + " with " + featureValue);
 454  20
                         feature = Feature
 455  
                                 .valueOf(featureParts[featureParts.length - 1]);
 456  20
                         mapper.configure(feature, featureValue);
 457  
 
 458  20
                         assert mapper.getSerializationConfig().isEnabled(
 459  
                                 feature) == featureValue;
 460  0
                     } catch (Exception e) {
 461  0
                         throw new Exception(
 462  
                                 "JsonMapperService: Error instantiating feature "
 463  
                                         + featureKey + " with  " + featureValue,
 464  
                                 e);
 465  20
                     }
 466  
                 }
 467  20
             }
 468  
         }
 469  
 
 470  20
         if (defaultTypeDefs != null && defaultTypeDefs.length == 2) {
 471  0
             DefaultTyping defaultTyping = DefaultTyping
 472  
                     .valueOf(defaultTypeDefs[0]);
 473  0
             mapper.enableDefaultTypingAsProperty(defaultTyping,
 474  
                     defaultTypeDefs[1]);
 475  0
             getLogger().info(
 476  
                     "default typing is " + defaultTypeDefs[0] + " with key:"
 477  
                             + defaultTypeDefs[1]);
 478  
         }
 479  
 
 480  20
         getLogger().info("setting date format to:" + dateFormat);
 481  20
         getLogger().info("keepFilters is:" + cacheFilters);
 482  
 
 483  20
         mapper.setDateFormat(new SimpleDateFormat(dateFormat));
 484  
 
 485  20
         filters = Collections
 486  
                 .synchronizedMap(new HashMap<String, FilterProvider>());
 487  20
         getLogger().info("initialized: mapper:" + mapper);
 488  20
     }
 489  
 
 490  
     public ObjectMapper getMapper() {
 491  0
         return mapper;
 492  
     }
 493  
 
 494  
     public void setMapper(ObjectMapper mapper) {
 495  0
         this.mapper = mapper;
 496  0
     }
 497  
 
 498  20
     public static class MixinModule extends SimpleModule {
 499  
         public final Class<?> clazz;
 500  
         public final Class<?> mixin;
 501  
 
 502  
         public <T, U> MixinModule(String name, Class<T> clazz, Class<U> mixin) {
 503  5
             super(name, new Version(1, 0, 0, null));
 504  5
             this.clazz = clazz;
 505  5
             this.mixin = mixin;
 506  
 
 507  5
         }
 508  
 
 509  
         @Override
 510  
         public void setupModule(SetupContext context) {
 511  5
             context.setMixInAnnotations(this.clazz, this.mixin);
 512  5
         }
 513  
     }
 514  
 
 515  
 }