blob: 878e0352755c9038888b5c83c1a307d33a8fc107 [file] [log] [blame]
package org.apache.fulcrum.json.jackson;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.LogEnabled;
import org.apache.commons.lang3.StringUtils;
import org.apache.fulcrum.json.JsonService;
import org.apache.fulcrum.json.jackson.filters.CustomModuleWrapper;
import org.apache.fulcrum.json.jackson.jsonpath.DefaultJsonPathWrapper;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.ConfigFeature;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
/**
*
*
* By default multiple serialization of the same object in a single thread is
* not supported (e.g filter + mixin or default + filter for the same bean /
* object).
*
* By default a filter is defined by its {@link Class#getName()}.
*
* Note: If using {@link SimpleNameIntrospector}, filter caches are set by class
* id. Caching is enabled by default, if not (a) by setting
* {@link #cacheFilters} to <code>false</code>. By setting (b) the Boolean
* parameter clean
* {@link #serializeAllExceptFilter(Object, Class, Boolean, String...)} or
* {@link #serializeOnlyFilter(Object, Class, Boolean, String...)} you could
* clean the filter. If caching is disabled each filter will be unregistered and
* the cache cleaned.
*
* @author <a href="mailto:gk@apache.org">Georg Kallidis</a>
* @version $Id$
*
*/
public class Jackson2MapperService extends AbstractLogEnabled implements JsonService, Initializable, Configurable {
private static final String DEFAULT_TYPING = "defaultTyping";
private static final String CACHE_FILTERS = "cacheFilters";
private static final String DATE_FORMAT = "dateFormat";
private static final String ESCAPE_CHARS = "escapeCharsGlobal";
private static final String ESCAPE_CHAR_CLASS = "escapeCharsClass";
private static final String USE_JSON_PATH = "useJsonPath";
ObjectMapper mapper;
AnnotationIntrospector primary; // support default
AnnotationIntrospector secondary;
private static final String ANNOTATIONINSPECTOR = "annotationInspectors";
private Map<String, String> annotationInspectors = null;
private Map<String, Boolean> features = null;
private Map<String, String> featureTypes = null;
private String dateFormat;
/**
* Default dateformat is <code>MM/dd/yyyy</code>, could be overwritten in
* {@link #setDateFormat(DateFormat)}.
*/
public static final String DEFAULTDATEFORMAT = "MM/dd/yyyy";
private boolean cacheFilters = true; // true -> this is by default true in jackson, if not using
// multiple serialization in one thread
String[] defaultTypeDefs = null;
private CacheService cacheService;
private boolean escapeCharsGlobal = false; // to be backward compatible, but should be true, then escaping to avoid
// XSS payload by default
private boolean useJsonPath = false;
private String escapeCharsClass = null;
@Override
public String ser(Object src) throws Exception {
return ser(src, false);
}
@Override
public <T> String ser(Object src, Class<T> type) throws Exception {
return ser(src, type, false);
}
public String ser(Object src, FilterProvider filter) throws Exception {
return ser(src, filter, false);
}
/**
*
* @param src the object to be serailized as JSON
* @param filter may be null, then sserialize without otherwise set into cache service and as filter provider.
* @param cleanCache cleans the jackson cache
* @return the serialzed JSON string
* @throws Exception exception
*/
public String ser(Object src, FilterProvider filter, Boolean cleanCache) throws Exception {
String serResult = null;
if (src == null) {
getLogger().info("no serializable object.");
return serResult;
}
if (filter == null) {
getLogger().debug("ser class::" + src.getClass() + " without filter.");
return ser(src);
} else {
getLogger().debug("add filter for cache filter Class " + src.getClass().getName());
setCustomIntrospectorWithExternalFilterId(src.getClass(), null); // filter class
if (isCacheFilters()) {
cacheService.getFilters().put(src.getClass().getName(), filter);
}
}
getLogger().debug("ser class::" + src.getClass() + " with filter " + filter);
mapper.setFilterProvider(filter);
String res = mapper.writer(filter).writeValueAsString(src);
if (cleanCache) {
cacheService.cleanSerializerCache(mapper);
}
return res;
}
@Override
public <T> T deSer(String json, Class<T> type) throws Exception {
ObjectReader reader = null;
if (type != null)
reader = mapper.readerFor(type);
else
reader = mapper.reader();
return reader.readValue(json);
}
/**
* basically wrapper for {@link ObjectMapper#convertValue(Object, Class)}.
*
* @param src Object
* @param type target Object
* @return
*/
public <T> T deSer(Object src, Class<T> type) {
return mapper.convertValue(src, type);
}
/**
* Add a named module or a {@link Module}.
*
* @param name Name of the module, optional. Could be null, if module is a
* {@link Module}.
*
* @param target Target class, optional. Could be null, if module is a
* {@link Module}.
*
* @param module Either an Jackson Module @link {@link Module} or an custom
* wrapper @link CustomModuleWrapper.
*/
@Override
public JsonService addAdapter(String name, Class target, Object module)
throws Exception {
if (module instanceof CustomModuleWrapper) {
CustomModuleWrapper cmw = (CustomModuleWrapper) module;
Module cm = new CustomModule(name, target, cmw.getSer(),
cmw.getDeSer());
getLogger().debug("registering custom module " + cm + " for: " + target);
mapper.registerModule(cm);
} else if (module instanceof Module) {
getLogger().debug(
"registering module " + module );
mapper.registerModule((Module) module);
} else {
throw new ClassCastException("expecting module type " + Module.class);
}
return this;
}
public Class<?> showMixinForClass(Class target) {
Class<?> mixin = mapper.findMixInClassFor( target );
getLogger().debug("find mixin for target " + target + " -> mixin: " + mixin);
return mixin;
}
public <T> List<T> deSerList(String json, Class<? extends List> targetList, Class<T> elementType) throws Exception {
return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(targetList, elementType));
}
public <T, U> Map<T, U> deSerMap(String json, Class<? extends Map> mapClass, Class<T> keyClass, Class<U> valueClass)
throws Exception {
return mapper.readValue(json, mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass));
}
public <T> Collection<T> deSerCollectionWithTypeReference(String json, TypeReference<T> collectionType)
throws Exception {
return (Collection<T>) mapper.readValue(json, collectionType);
}
public <T> Collection<T> deSerCollectionWithType(String json, Class<? extends Collection> collectionClass,
Class<T> type) throws Exception {
return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(collectionClass, type));
}
@Override
public <T> Collection<T> deSerCollection(String json, Object collectionType, Class<T> elementType)
throws Exception {
if (collectionType instanceof TypeReference) {
return deSerCollectionWithTypeReference(json, (TypeReference<T>) collectionType);
} else {
return mapper.readValue(json, mapper.getTypeFactory()
.constructCollectionType(((Collection<T>) collectionType).getClass(), elementType));
}
}
/**
*
* @param src the collection to be serialized
* @param collectionType the {@link TypeReference}.
* @param cleanCache cleans jackson serializer cache
* @return the serialized JSON string
* @throws JsonProcessingException JSON processing error
*/
public <T> String serCollectionWithTypeReference(Collection<T> src, TypeReference collectionType,
Boolean cleanCache) throws JsonProcessingException {
String res = mapper.writerFor(collectionType).writeValueAsString(src);
if (cleanCache) {
cacheService.cleanSerializerCache(mapper);
}
return res;
}
/**
* @param name name of the module
* @param target target class
* @param mixin provide mixin as class. Deregistering module could be only done
* by setting this parameter to null.
*
* @see #addAdapter(String, Class, Object)
*/
@Override
public JsonService addAdapter(String name, Class target, Class mixin) throws Exception {
getLogger().debug(
"registering unversioned simple mixin module named " + name + " of type " + mixin + " for: " + target);
mapper.addMixIn(target, mixin);
return this;
}
/**
* set a single mixin. convenience method, calls
* {@link ObjectMapper#registerModule(Module)}
*
* @param src he object to be serialized
* @param name the name for the mixin
* @param target the target class for the mixin
* @param mixin the mixin class
* @return serialized result
* @throws JsonProcessingException if not properly processed
*/
@SuppressWarnings("rawtypes")
public String withMixinModule(Object src, String name, Class target, Class mixin) throws JsonProcessingException {
Module mx = new MixinModule(name, target, mixin);
getLogger().debug("registering module " + mx + ", mixin: " + mixin);
return mapper.registerModule(mx).writer().writeValueAsString(src);
}
/**
* This is a convenience method with read, but the old mixins will be cleared
* {@link ObjectMapper#setMixIns(Map)}
*
* @param src the object to be serialized
* @param target the target class for the mixin
* @param mixin the mixin class
* @return serialized result
* @throws JsonProcessingException if fail
*/
@SuppressWarnings("rawtypes")
public String withSetMixins(Object src, Class target, Class mixin) throws JsonProcessingException {
return setMixins(target, mixin).writer().writeValueAsString(src);
}
/**
* @param target The target class
* @param mixin the mixin class
* @return an objectmapper
*/
@SuppressWarnings("rawtypes")
public ObjectMapper setMixins(Class target, Class mixin) {
Map<Class<?>, Class<?>> sourceMixins = null;
if (target != null) {
sourceMixins = new HashMap<>(1);
sourceMixins.put(target, mixin);
}
getLogger().debug("complete reset mixins for target " + target + ", mixin: " + mixin);
return mapper.setMixIns(sourceMixins);
}
@Override
public String serializeAllExceptFilter(Object src, String... filterAttr) throws Exception {
return serializeAllExceptFilter(src, src.getClass(), true, filterAttr);
}
@Override
public synchronized String serializeAllExceptFilter(Object src, Boolean cache, String... filterAttr)
throws Exception {
return serializeAllExceptFilter(src, src.getClass(), cache, filterAttr);
}
public synchronized <T> String serializeAllExceptFilter(Object src, Class<T>[] filterClasses, String... filterAttr)
throws Exception {
return serializeAllExceptFilter(src, filterClasses, true, filterAttr);
}
@Override
public synchronized <T> String serializeAllExceptFilter(Object src, Class<T> filterClass, String... filterAttr)
throws Exception {
return serializeAllExceptFilter(src, filterClass, true, filterAttr);
}
@Override
public <T> String serializeAllExceptFilter(Object src, Class<T> filterClass, Boolean cleanFilter,
String... filterAttr) throws Exception {
return serializeAllExceptFilter(src, new Class[] { filterClass }, cleanFilter, filterAttr);
}
/**
*
* @param src the object to be serailized, may be a list
* @param filterClasses the same object class or a detail class, which should be
* filtered
* @param <T> class type
* @param clean cleaning the cache after serialization
* @param filterAttr attributes to be filtered for filtered class
* @return the serailized string
* @throws Exception generic exception
*/
public synchronized <T> String serializeAllExceptFilter(Object src, Class<T>[] filterClasses, Boolean clean,
String... filterAttr) throws Exception {
PropertyFilter pf = null;
if (filterAttr != null)
pf = SimpleBeanPropertyFilter.serializeAllExcept(filterAttr);
else if (filterClasses == null) { // no filter
return ser(src, clean);
// should be better:
// return filter(src, new Class<?>[] { src.getClass() }, filterClasses, pf,
// clean);
}
return filter(src, new Class<?>[] { filterClasses[0] }, filterClasses, pf, clean);
}
@Override
public String serializeOnlyFilter(Object src, String... filterAttrs) throws Exception {
return serializeOnlyFilter(src, src.getClass(), true, filterAttrs);
}
@Override
public synchronized String serializeOnlyFilter(Object src, Boolean cache, String... filterAttr) throws Exception {
return serializeOnlyFilter(src, src.getClass(), cache, filterAttr);
}
@Override
public synchronized <T> String serializeOnlyFilter(Object src, Class<T> filterClass, String... filterAttr)
throws Exception {
return serializeOnlyFilter(src, filterClass, true, filterAttr);
}
@Override
public synchronized <T> String serializeOnlyFilter(Object src, Class<T> filterClass, Boolean refresh,
String... filterAttr) throws Exception {
return serializeOnlyFilter(src, new Class[] { filterClass }, refresh, filterAttr);
}
public synchronized <T> String serializeOnlyFilter(Object src, Class<T>[] filterClasses, Boolean refresh,
String... filterAttr) throws Exception {
PropertyFilter pf = null;
if (filterAttr != null && filterAttr.length > 0 && !"".equals(filterAttr[0])) {
pf = SimpleBeanPropertyFilter.filterOutAllExcept(filterAttr);
getLogger().debug("setting filteroutAllexcept filter for size of filterAttr: " + filterAttr.length);
} else {
getLogger().warn("no filter attributes set!");
pf = SimpleBeanPropertyFilter.filterOutAllExcept("dummy");
}
if (filterClasses == null)
throw new AssertionError("You have to provide some class to apply the filtering!");
return filter(src, filterClasses, null, pf, refresh);
}
@Override
public String ser(Object src, Boolean cleanCache) throws Exception {
if (isCacheFilters() && cacheService.getFilters().containsKey(src.getClass().getName())) {
getLogger().warn("Found registered filter - using instead of default view filter for class:"
+ src.getClass().getName());
SimpleFilterProvider filter = (SimpleFilterProvider) cacheService.getFilters()
.get(src.getClass().getName());
return ser(src, filter, cleanCache);// mapper.writerWithView(src.getClass()).writeValueAsString(src);
}
String res = mapper.writerWithView(Object.class).writeValueAsString(src);
if (cleanCache != null && cleanCache) {
cacheService.cleanSerializerCache(mapper);
}
return res;
}
@Override
public <T> String ser(Object src, Class<T> type, Boolean cleanCache) throws Exception {
getLogger().info("serializing object:" + src + " for type " + type);
if (isCacheFilters() && src != null && cacheService.getFilters().containsKey(src.getClass().getName())) {
getLogger().warn("Found registered filter - could not use custom view and custom filter for class:"
+ src.getClass().getName());
// throw new
// Exception("Found registered filter - could not use custom view and custom
// filter for class:"+
// src.getClass().getName());
SimpleFilterProvider filter = (SimpleFilterProvider) cacheService.getFilters()
.get(src.getClass().getName());
return ser(src, filter);
}
String res = (type != null) ? mapper.writerWithView(type).writeValueAsString(src)
: mapper.writeValueAsString(src);
if (cleanCache) {
cacheService.cleanSerializerCache(mapper);
}
return res;
}
/**
*
* @param src The source Object to be filtered.
* @param filterClass This Class array contains at least one element. If no
* class is provided it is the class type of the source
* object. The filterClass is to become the key of the
* filter object cache.
* @param excludeClasses The classes to be excluded, optionally used only for
* methods like
* {@link #serializeAllExceptFilter(Object, Class[], String...)}.
* @param pf Expecting a property filter from e.g @link
* {@link SimpleBeanPropertyFilter}.
* @param clean if <code>true</code> does not reuse the filter object
* (no cashing).
* @return The serialized Object as String
* @throws Exception
*/
private <T> String filter(Object src, Class<?>[] filterClasses, Class<T>[] excludeClasses, PropertyFilter pf,
Boolean clean) throws Exception {
FilterProvider filter = null;
if (filterClasses.length > 0) {
filter = retrieveFilter(pf, filterClasses[0], excludeClasses);
}
getLogger().info("filtering with filter " + filter);
String serialized = ser(src, filter, clean);
if (!isCacheFilters() || clean) {
if (filterClasses.length > 0) {
boolean exclude = (excludeClasses != null) ? true : false;
cacheService.removeFilter(filterClasses[0], exclude);
}
}
return serialized;
}
private <T> SimpleFilterProvider retrieveFilter(PropertyFilter pf, Class<?> filterClass,
Class<T>[] excludeClasses) {
SimpleFilterProvider filter = null;
if (pf != null) {
filter = new SimpleFilterProvider();
filter.setDefaultFilter(pf);
}
if (isCacheFilters()) {
if (!cacheService.getFilters().containsKey(filterClass.getName())) {
getLogger().debug("add filter for cache filter Class " + filterClass.getName());
setCustomIntrospectorWithExternalFilterId(filterClass, excludeClasses); // filter class
if (pf != null) {
cacheService.getFilters().put(filterClass.getName(), filter);
}
} else {
filter = (SimpleFilterProvider) cacheService.getFilters().get(filterClass.getName());
// setCustomIntrospectorWithExternalFilterId(filterClass); // filter
// class
}
}
getLogger().debug("set filter:" + filter);
return filter;
}
/**
* @param filterClass
* <li>Adding filterClass into
* {@link SimpleNameIntrospector#setFilteredClass(Class)}
* enables the filtering process.
* @param externalFilterIds
* <li>Adding externalFilterIs to
* {@link SimpleNameIntrospector#setExternalFilterExcludeClasses(Class...)}
* excludes these classes.
*/
private <T> void setCustomIntrospectorWithExternalFilterId(Class<?> filterClass,
Class<T>[] externalFilterClassIds) {
if (primary instanceof SimpleNameIntrospector) {
// first one is required that we get to the PropertyFilter
((SimpleNameIntrospector) primary).setFilteredClasses(filterClass);
if (externalFilterClassIds != null) {
((SimpleNameIntrospector) primary).setIsExludeType(true);
for (Class<T> filterClazz : externalFilterClassIds) {
getLogger().debug("added class for filters " + filterClazz);
}
((SimpleNameIntrospector) primary).setExternalFilterExcludeClasses(externalFilterClassIds);
}
}
}
public Jackson2MapperService registerModule(Module module) {
mapper.registerModule(module);
return this;
}
public <T> void addSimpleModule(SimpleModule module, Class<T> type, JsonSerializer<T> ser) {
module.addSerializer(type, ser);
}
public <T> void addSimpleModule(SimpleModule module, Class<T> type, JsonDeserializer<T> deSer) {
module.addDeserializer(type, deSer);
}
/**
* Default Dateformat: {@link #DEFAULTDATEFORMAT}
*/
@Override
public void setDateFormat(final DateFormat df) {
mapper.setDateFormat(df);
}
/**
* Avalon component lifecycle method
*/
@Override
public void configure(Configuration conf) throws ConfigurationException {
getLogger().debug("conf.getName()" + conf.getName());
this.annotationInspectors = new HashMap<>();
final Configuration configuredAnnotationInspectors = conf.getChild(ANNOTATIONINSPECTOR, false);
if (configuredAnnotationInspectors != null) {
Configuration[] nameVal = configuredAnnotationInspectors.getChildren();
Arrays.stream( nameVal).forEach(c->
{
String key = c.getName();
getLogger().debug("configured key: " + key);
if (key.equals("features")) {
this.features = new HashMap<>();
this.featureTypes = new HashMap<>();
Arrays.stream( c.getChildren() ).forEach( lf -> {
boolean featureValue = lf.getAttributeAsBoolean("value", false);
String featureType = null;
String feature = null;
try {
featureType = lf.getAttribute("type");
feature = lf.getValue();
getLogger().debug("configuredAnnotationInspectors " + feature + ":" + featureValue);
this.features.put(feature, featureValue);
this.featureTypes.put(feature, featureType);
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
});
} else {
String val;
try {
val = c.getValue();
getLogger().debug("configuredAnnotationInspectors " + key + ":" + val);
this.annotationInspectors.put(key, val);
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}
}
});
}
final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT, true);
this.dateFormat = configuredDateFormat.getValue(DEFAULTDATEFORMAT);
final Configuration configuredKeepFilter = conf.getChild(CACHE_FILTERS, false);
if (configuredKeepFilter != null) {
setCacheFilters( configuredKeepFilter.getValueAsBoolean());
}
final Configuration configuredEscapeChars = conf.getChild(ESCAPE_CHARS, false);
if (configuredEscapeChars != null) {
this.escapeCharsGlobal = configuredEscapeChars.getValueAsBoolean();
}
final Configuration configuredEscapeCharClass = conf.getChild(ESCAPE_CHAR_CLASS, false);
if (configuredEscapeCharClass != null) {
this.escapeCharsClass = configuredEscapeCharClass.getValue();
}
final Configuration configuredDefaultType = conf.getChild(DEFAULT_TYPING, false);
if (configuredDefaultType != null) {
defaultTypeDefs = new String[] { configuredDefaultType.getAttribute("type"),
configuredDefaultType.getAttribute("key") };
}
final Configuration configuredjsonPath = conf.getChild(USE_JSON_PATH, false);
if (configuredjsonPath != null) {
this.useJsonPath = configuredjsonPath.getValueAsBoolean();
}
}
@Override
public void initialize() throws Exception {
mapper = new ObjectMapper(null, null, null);// add configurable JsonFactory,.. later?
initAnnotationInspectors();
initFeatures();
initDefaultTyping();
getLogger().info("setting date format to:" + dateFormat);
getLogger().info("cacheFilters is:" + isCacheFilters());
if (!isCacheFilters()) {
mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, true);
}
mapper.setDateFormat(new SimpleDateFormat(dateFormat));
if (escapeCharsGlobal) {
mapper.getFactory().setCharacterEscapes(characterEscapes);
}
if (escapeCharsClass != null) {
try {
characterEscapes = (CharacterEscapes) Class.forName(escapeCharsClass).getConstructor().newInstance();
} catch (Exception e) {
throw new InstantiationException(
"JsonMapperService: Error instantiating " + escapeCharsClass + " for " + ESCAPE_CHAR_CLASS);
}
}
getLogger().debug("initialized mapper:" + mapper);
mapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString("");
}
});
cacheService = new CacheService(primary);
if (cacheService instanceof LogEnabled) {
cacheService.enableLogging(getLogger().getChildLogger(cacheService.getClass().getSimpleName()));
getLogger().info("setting cacheService logger: " + cacheService.getClass().getSimpleName());
}
if (useJsonPath) {
// set it before runtime
DefaultJsonPathWrapper djpw = null;
try {
djpw = new DefaultJsonPathWrapper(this.mapper);
getLogger().debug("******** initialized new jsonPath defaults: " + djpw.getJsonPathDefault());
} catch (Exception e) {
throw new AssertionError(
"JsonMapperService: Error instantiating " + djpw + " using useJsonPath=" + useJsonPath);
}
}
}
private void initDefaultTyping() {
if (defaultTypeDefs != null && defaultTypeDefs.length == 2) {
DefaultTyping defaultTyping = DefaultTyping.valueOf(defaultTypeDefs[0]);
mapper.enableDefaultTypingAsProperty(defaultTyping, defaultTypeDefs[1]);
getLogger().info("default typing is " + defaultTypeDefs[0] + " with key:" + defaultTypeDefs[1]);
}
}
private void initFeatures() throws Exception {
if (features != null && !features.isEmpty()) {
features.entrySet().stream().forEach( entry -> {
String featureKey = entry.getKey();
Boolean featureValue = entry.getValue();
String featureType = featureTypes.get(featureKey);
Class<?> configFeature = null;
try {
getLogger().debug("initializing featureType: " + featureType);
configFeature = loadClass(featureType);
} catch (ClassNotFoundException e) {
throw new AssertionError("JsonMapperService: Error instantiating " + featureType + " for " + featureKey, e);
}
ConfigFeature feature = null;
if (!StringUtils.isEmpty(featureKey) && featureValue != null) {
try
{
if (configFeature.equals(SerializationFeature.class)) {
feature = SerializationFeature.valueOf(featureKey);
mapper.configure((SerializationFeature) feature, featureValue);
assert mapper.getSerializationConfig()
.isEnabled((SerializationFeature) feature) == featureValue;
getLogger().info("initialized serconfig mapper feature: " + feature + " with "
+ mapper.getSerializationConfig().isEnabled((SerializationFeature) feature));
} else if (configFeature.equals(DeserializationFeature.class)) {
feature = DeserializationFeature.valueOf(featureKey);
mapper.configure((DeserializationFeature) feature, featureValue);
assert mapper.getDeserializationConfig()
.isEnabled((DeserializationFeature) feature) == featureValue;
getLogger().info("initialized deserconfig mapper feature: " + feature + " with "
+ mapper.getDeserializationConfig().isEnabled((DeserializationFeature) feature));
} else if (configFeature.equals(MapperFeature.class)) {
feature = MapperFeature.valueOf(featureKey);
mapper.configure((MapperFeature) feature, featureValue);
assert mapper.getDeserializationConfig().isEnabled((MapperFeature) feature) == featureValue;
assert mapper.getSerializationConfig().isEnabled((MapperFeature) feature) == featureValue;
getLogger().info("initialized serconfig mapper feature: " + feature + " with "
+ mapper.getDeserializationConfig().isEnabled((MapperFeature) feature));
getLogger().info("initialized deserconfig mapper feature: " + feature + " with "
+ mapper.getSerializationConfig().isEnabled((MapperFeature) feature));
} else if (configFeature.equals(JsonParser.class)) {
Feature parserFeature = JsonParser.Feature.valueOf(featureKey);
getLogger().info("initializing parser feature: " + parserFeature + " with " + featureValue);
mapper.configure(parserFeature, featureValue);
} else if (configFeature.equals(JsonGenerator.class)) {
com.fasterxml.jackson.core.JsonGenerator.Feature genFeature = JsonGenerator.Feature
.valueOf(featureKey);
getLogger().info("initializing parser feature: " + genFeature + " with " + featureValue);
mapper.configure(genFeature, featureValue);
}
} catch (Exception e) {
throw new RuntimeException("JsonMapperService: Error instantiating feature " + featureKey + " with "
+ featureValue , e);
}
}
});
}
}
private void initAnnotationInspectors() throws Exception {
for (Entry<String, String> entry : annotationInspectors.entrySet()) {
String key = entry.getKey();
String avClass = entry.getValue();
if (key.equals("primary") && !StringUtils.isEmpty(avClass)) {
try {
primary = (AnnotationIntrospector) Class.forName(avClass).getConstructor().newInstance();
} catch (Exception e) {
throw new InstantiationException("JsonMapperService: Error instantiating " + avClass + " for " + key);
}
} else if (key.equals("secondary") && avClass != null) {
try {
secondary = (AnnotationIntrospector) Class.forName(avClass).getConstructor().newInstance();
} catch (Exception e) {
throw new InstantiationException("JsonMapperService: Error instantiating " + avClass + " for " + key);
}
}
}
if (primary == null) {
primary = new JacksonAnnotationIntrospector(); // support default
getLogger().info("using default introspector:" + primary.getClass().getName());
mapper.setAnnotationIntrospector(primary);
} else if (primary != null && secondary != null) {
AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary, secondary);
mapper.setAnnotationIntrospector(pair);
} else {
mapper.setAnnotationIntrospector(primary);
}
if (primary instanceof LogEnabled) {
((LogEnabled) primary).enableLogging(getLogger().getChildLogger(primary.getClass().getSimpleName()));
getLogger().info("setting primary introspector logger: " + primary.getClass().getSimpleName());
}
if (secondary instanceof LogEnabled) {
((LogEnabled) secondary).enableLogging(getLogger().getChildLogger(secondary.getClass().getSimpleName()));
getLogger().info("setting secondary introspector logger: " + secondary.getClass().getSimpleName());
}
}
/**
* Loads the named class using the default class loader.
*
* @param className the name of the class to load.
* @return {@inheritDoc} the loaded class.
* @throws ClassNotFoundException if the class was not found.
*/
@SuppressWarnings("unchecked")
protected <T> Class<T> loadClass(String className) throws ClassNotFoundException {
ClassLoader loader = this.getClass().getClassLoader();
try {
Class<T> clazz;
if (loader != null) {
clazz = (Class<T>) loader.loadClass(className);
} else {
clazz = (Class<T>) Class.forName(className);
}
return clazz;
} catch (ClassNotFoundException x) {
/* Give up. */
throw x;
}
}
public ObjectMapper getMapper() {
return mapper;
}
public void setMapper(ObjectMapper mapper) {
this.mapper = mapper;
}
public boolean isCacheFilters() {
return cacheFilters;
}
public void setCacheFilters(boolean cacheFilters) {
this.cacheFilters = cacheFilters;
if (!cacheFilters)
mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, true);
}
static CharacterEscapes characterEscapes = new CharacterEscapes() {
private static final long serialVersionUID = 1L;
private final int[] asciiEscapes;
{ // instance init
int[] esc = standardAsciiEscapesForJSON();
// this avoids to get evaluated immediately
esc['<'] = CharacterEscapes.ESCAPE_STANDARD;
esc['>'] = CharacterEscapes.ESCAPE_STANDARD;
esc['&'] = CharacterEscapes.ESCAPE_STANDARD;
esc['\''] = CharacterEscapes.ESCAPE_STANDARD;
// esc['/'] = '/'; //CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes = esc;
}
@Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
@Override
public SerializableString getEscapeSequence(final int ch) {
// if ( ch == '/') {
// return new SerializedString("\\\\/");
// } else {
return null;
// }
}
};
}