org.apache.fulcrum.json.jackson.JacksonMapperService Maven / Gradle / Ivy
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
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
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.fulcrum.json.JsonService;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.AnnotationIntrospector;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.Module;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectMapper.DefaultTyping;
import org.codehaus.jackson.map.ObjectReader;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.StdSerializerProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
/**
* Jackson 1 Impl of @link {@link JsonService}.
*
* By default multiple serialization of the same object in a single thread is
* not support (e.g filter + mixin or default + filter for the same bean /
* object).
*
* Note: Filters could not easily unregistered. Try setting @link
* {@link #cacheFilters} to false
.
*
*
* @author gk
* @version $Id: JacksonMapperService.java 1800593 2017-07-03 06:36:53Z gk $
*
*/
public class JacksonMapperService 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";
ObjectMapper mapper;
AnnotationIntrospector primary; // support default
AnnotationIntrospector secondary;
public String ANNOTATIONINSPECTOR = "annotationInspectors";
private Hashtable annotationInspectors = null;
private Hashtable features = null;
private Map filters;
private String dateFormat;
final String DEFAULTDATEFORMAT = "MM/dd/yyyy";
final boolean defaultType = false;
public boolean cacheFilters = true; // more efficient if not using multiple
// serialization in one thread
String[] defaultTypeDefs = null;
@Override
public synchronized String ser(Object src) throws Exception {
return ser(src, false);
}
@Override
public String ser(Object src, Class type) throws Exception {
return ser(src, type, false);
}
public synchronized String ser(Object src, FilterProvider filters)
throws Exception {
if (filters == null) {
getLogger().debug("ser class::" + src.getClass() + " without filter ");
return ser(src);
}
getLogger().debug("ser::" + src + " with filters " + filters);
String serResult = mapper.writer(filters).writeValueAsString(src);
return serResult;
}
@Override
public String ser(Object src, Boolean cleanCache) throws Exception {
if (src.getClass() != null && filters.containsKey(src.getClass().getName())) {
getLogger().warn(
"Found registered filter - using instead of default view 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());
}
if (!cacheFilters || cleanCache) {
cleanSerializerCache();
}
return mapper.writer().writeValueAsString(src);
}
@Override
public String ser(Object src, Class type, Boolean cleanCache)
throws Exception {
getLogger().debug("ser::" + src + " with type" + type);
if (src.getClass() != null && filters.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());
}
if (!cacheFilters || cleanCache) {
cleanSerializerCache();
}
return mapper.writerWithView(type).writeValueAsString(src);
}
@Override
public T deSer(String src, Class type) throws Exception {
ObjectReader reader = mapper.reader(type);
return (T) reader.readValue(src);
}
@Override
public Collection deSerCollection(String json,
Object collectionType, Class elementType) throws Exception {
return mapper.readValue(json, mapper.getTypeFactory()
.constructCollectionType(((Collection)collectionType).getClass(), elementType));
}
public T deSer(String json, Class extends Collection> collectionType,
Class type) throws Exception {
return (T) mapper.readValue(json, mapper.getTypeFactory()
.constructCollectionType(collectionType, type));
}
@Override
public String serializeAllExceptFilter(Object src, String... filterAttr)
throws Exception {
return serializeAllExceptFilter(src, src.getClass(), true, filterAttr);
}
@Override
public String serializeAllExceptFilter(Object src, Boolean refresh,
String... filterAttr) throws Exception {
return serializeAllExceptFilter(src, src.getClass(), refresh, filterAttr);
}
@Override
public synchronized String serializeAllExceptFilter(Object src,
Class filterClass, String... filterAttr) throws Exception {
return serializeAllExceptFilter(src, filterClass, true, filterAttr);
}
@Override
public String serializeAllExceptFilter(Object src,
Class filterClass, Boolean refreshFilter, String... filterAttr)
throws Exception {
setCustomIntrospectorWithExternalFilterId(filterClass);
FilterProvider filter = null;
if ( filterClass != null) {
if (filterAttr != null && filterAttr.length > 0 &&
(refreshFilter || !this.filters.containsKey(filterClass.getName()))) {
filter = new SimpleFilterProvider().addFilter(
filterClass.getName(),
SimpleBeanPropertyFilter.serializeAllExcept(filterAttr));
this.filters.put(filterClass.getName(), filter);
} else {
filter = this.filters.get(filterClass.getName());
}
}
String serialized = ser(src, filter);
if (!cacheFilters || refreshFilter) {
removeFilterClass(filterClass);
cleanSerializerCache();
}
return serialized;
}
@Override
public String serializeOnlyFilter(Object src, String... filterAttr)
throws Exception {
return serializeOnlyFilter(src, src.getClass(), true, filterAttr);
}
@Override
public String serializeOnlyFilter(Object src, Boolean refresh,
String... filterAttr) throws Exception {
return serializeOnlyFilter(src, src.getClass(), refresh, filterAttr);
}
@Override
public synchronized String serializeOnlyFilter(Object src,
Class filterClass, String... filterAttr) throws Exception {
return serializeOnlyFilter(src, filterClass, true, filterAttr);
}
@Override
public String serializeOnlyFilter(Object src, Class filterClass,
Boolean refreshFilter, String... filterAttr) throws Exception {
setCustomIntrospectorWithExternalFilterId(filterClass);
FilterProvider filter = null;
if (filterClass == null && src != null && src.getClass() != null) {
filterClass =(Class) src.getClass();
}
if ( filterClass != null) {
if (!this.filters.containsKey(filterClass.getName())) {
getLogger().debug("filterClass::" + filterClass.getName() + " with filterAttr: " + filterAttr);
if (filterAttr != null) {
filter = new SimpleFilterProvider().addFilter(
filterClass.getName(),
SimpleBeanPropertyFilter.filterOutAllExcept(filterAttr));
this.filters.put(filterClass.getName(), filter);
} else {
filter = new SimpleFilterProvider();
this.filters.put(filterClass.getName(),filter);
}
} else {
filter = this.filters.get(filterClass.getName());
}
}
String serialized = ser(src, filter);
getLogger().debug("serialized " + serialized);
if (!cacheFilters || refreshFilter) {
removeFilterClass(filterClass);
cleanSerializerCache();
}
return serialized;
}
private void removeFilterClass(Class filterClass) {
if (this.filters.containsKey(filterClass.getName())) {
removeCustomIntrospectorWithExternalFilterId(filterClass);
this.filters.remove(filterClass.getName());
mapper.getSerializerProvider().flushCachedSerializers();
getLogger().debug(
"removed from SimpleFilterProvider filters "
+ filterClass.getName());
}
}
private void cleanSerializerCache() {
if (mapper.getSerializerProvider() instanceof StdSerializerProvider) {
int cachedSerProvs = ((StdSerializerProvider) mapper
.getSerializerProvider()).cachedSerializersCount();
if (cachedSerProvs > 0) {
getLogger()
.debug("flushing cachedSerializersCount:"
+ cachedSerProvs);
((StdSerializerProvider) mapper.getSerializerProvider())
.flushCachedSerializers();
}
}
}
private void setCustomIntrospectorWithExternalFilterId(
Class externalFilterId) {
if (primary instanceof CustomIntrospector && externalFilterId != null) {
((CustomIntrospector) primary)
.setExternalFilterClasses(externalFilterId);
getLogger().debug(
"added class from filters "
+ externalFilterId.getName());
}
}
private void removeCustomIntrospectorWithExternalFilterId(
Class externalFilterId) {
if (primary instanceof CustomIntrospector && externalFilterId != null) {
((CustomIntrospector) primary)
.removeExternalFilterClass(externalFilterId);
getLogger().debug(
"removed from introspector filter id "
+ externalFilterId.getName());
}
}
public JacksonMapperService registerModule(Module module) {
mapper.withModule(module);
return this;
}
public void addSimpleModule(SimpleModule module, Class type,
JsonSerializer ser) {
module.addSerializer(type, ser);
}
public void addSimpleModule(SimpleModule module, Class type,
JsonDeserializer deSer) {
module.addDeserializer(type, deSer);
}
@Override
public void setDateFormat(final DateFormat df) {
mapper.setDateFormat(df);
}
@Override
public JsonService addAdapter(String name, Class target, Object mixin)
throws Exception {
return addAdapter(name, target, mixin.getClass());
}
@Override
public JsonService addAdapter(String name, Class target, Class mixin)
throws Exception {
Module mx = new MixinModule(name, target, mixin);
getLogger().debug("registering module " + mx + " for: " + mixin);
mapper.withModule(mx);
return this;
}
/**
* Avalon component lifecycle method
*/
@Override
public void configure(Configuration conf) throws ConfigurationException {
getLogger().debug("conf.getName()" + conf.getName());
this.annotationInspectors = new Hashtable();
final Configuration configuredAnnotationInspectors = conf.getChild(
ANNOTATIONINSPECTOR, false);
if (configuredAnnotationInspectors != null) {
Configuration[] nameVal = configuredAnnotationInspectors
.getChildren();
for (int i = 0; i < nameVal.length; i++) {
String key = nameVal[i].getName();
getLogger().debug("configured key: " + key);
if (key.equals("features")) {
this.features = new Hashtable();
Configuration[] features = nameVal[i].getChildren();
for (int j = 0; j < features.length; j++) {
boolean featureValue = features[j]
.getAttributeAsBoolean("value", false);
String feature = features[j].getValue();
getLogger().debug(
"configuredAnnotationInspectors " + feature
+ ":" + featureValue);
this.features.put(feature, featureValue);
}
} else {
String val = nameVal[i].getValue();
getLogger()
.debug("configuredAnnotationInspectors " + key
+ ":" + val);
this.annotationInspectors.put(key, val);
}
}
}
final Configuration configuredDateFormat = conf.getChild(DATE_FORMAT,
true);
this.dateFormat = configuredDateFormat.getValue(DEFAULTDATEFORMAT);
final Configuration configuredKeepFilter = conf.getChild(CACHE_FILTERS,
false);
if (configuredKeepFilter != null) {
this.cacheFilters = configuredKeepFilter.getValueAsBoolean();
}
final Configuration configuredDefaultType = conf.getChild(
DEFAULT_TYPING, false);
if (configuredDefaultType != null) {
defaultTypeDefs = new String[] {
configuredDefaultType.getAttribute("type"),
configuredDefaultType.getAttribute("key") };
}
}
@Override
public void initialize() throws Exception {
mapper = new ObjectMapper();
Enumeration enumKey = annotationInspectors.keys();
while (enumKey.hasMoreElements()) {
String key = enumKey.nextElement();
String avClass = annotationInspectors.get(key);
if (key.equals("primary") && avClass != null) {
try {
primary = (AnnotationIntrospector) Class.forName(avClass).getConstructor()
.newInstance();
} catch (Exception e) {
throw new Exception(
"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 Exception(
"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 {
AnnotationIntrospector pair = new AnnotationIntrospector.Pair(
primary, secondary);
mapper.setAnnotationIntrospector(pair);
}
// mapper.enableDefaultTypingAsProperty(DefaultTyping.OBJECT_AND_NON_CONCRETE,
// "type");
if (features != null) {
Enumeration enumFeatureKey = features.keys();
while (enumFeatureKey.hasMoreElements()) {
String featureKey = enumFeatureKey.nextElement();
Boolean featureValue = features.get(featureKey);
Feature feature;
if (featureKey != null && featureValue != null) {
try {
String[] featureParts = featureKey.split("\\.");
getLogger().info(
"initializing mapper feature: "
+ featureParts[featureParts.length - 1]
+ " with " + featureValue);
feature = Feature
.valueOf(featureParts[featureParts.length - 1]);
mapper.configure(feature, featureValue);
assert mapper.getSerializationConfig().isEnabled(
feature) == featureValue;
} catch (Exception e) {
throw new Exception(
"JsonMapperService: Error instantiating feature "
+ featureKey + " with " + featureValue,
e);
}
}
}
}
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]);
}
getLogger().info("setting date format to:" + dateFormat);
getLogger().info("keepFilters is:" + cacheFilters);
mapper.setDateFormat(new SimpleDateFormat(dateFormat));
filters = Collections
.synchronizedMap(new HashMap());
getLogger().info("initialized: mapper:" + mapper);
}
public ObjectMapper getMapper() {
return mapper;
}
public void setMapper(ObjectMapper mapper) {
this.mapper = mapper;
}
public static class MixinModule extends SimpleModule {
public final Class> clazz;
public final Class> mixin;
public MixinModule(String name, Class clazz, Class mixin) {
super(name, new Version(1, 0, 0, null));
this.clazz = clazz;
this.mixin = mixin;
}
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(this.clazz, this.mixin);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy