blob: 50a4b0fff82c4c30e545b9c031dc0d2f2ccff3e8 [file] [log] [blame]
/*
* 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.
*/
package org.apache.tuscany.sca.databinding.json.jackson;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import org.apache.tuscany.sca.databinding.TransformationContext;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.AnnotationIntrospector;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.MappingJsonFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.deser.BeanDeserializerFactory;
import org.codehaus.jackson.map.deser.StdDeserializerProvider;
import org.codehaus.jackson.map.introspect.AnnotatedClass;
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
import org.codehaus.jackson.map.module.SimpleDeserializers;
import org.codehaus.jackson.map.ser.BeanPropertyWriter;
import org.codehaus.jackson.map.ser.CustomSerializerFactory;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
import org.codehaus.jackson.map.util.StdDateFormat;
import org.codehaus.jackson.xc.JaxbAnnotationIntrospector;
import org.codehaus.jackson.xc.XmlAdapterJsonDeserializer;
import org.codehaus.jackson.xc.XmlAdapterJsonSerializer;
import org.json.JSONObject;
import com.fasterxml.jackson.module.jsonorg.JsonOrgModule;
/**
* Helper class for Jackson
*/
public class JacksonHelper {
public static final String TUSCANY_FILTER = "tuscanyFilter";
public static final String EXCLUDED_FIELDS = "excludedFields";
public static final String INCLUDED_FIELDS = "includedFields";
private final static SimpleBeanPropertyFilter DEFAULT_FILTER = SimpleBeanPropertyFilter.serializeAllExcept();
/**
* The default instance of Jackson ObjectMapper
*/
public final static ObjectMapper MAPPER = createMapper();
private final static JsonFactory FACTORY = new MappingJsonFactory(createMapper());
public static ObjectMapper createMapper() {
return createObjectMapper(null);
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static ObjectMapper createObjectMapper(Class<?> cls) {
ObjectMapper mapper = null;
if (cls != null) {
// Workaround for http://jira.codehaus.org/browse/JACKSON-413
Package pkg = cls.getPackage();
if (pkg != null) {
XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class);
if (adapters != null) {
CustomSerializerFactory serializerFactory = new CustomSerializerFactory();
BeanDeserializerFactory deserializerFactory = new BeanDeserializerFactory(null);
for (XmlJavaTypeAdapter a : adapters.value()) {
XmlAdapter xmlAdapter = null;
try {
xmlAdapter = a.value().newInstance();
} catch (Throwable e) {
// Ignore
}
if (xmlAdapter != null) {
XmlAdapterJsonDeserializer deserializer = new XmlAdapterJsonDeserializer(xmlAdapter);
XmlAdapterJsonSerializer serializer = new XmlAdapterJsonSerializer(xmlAdapter);
SimpleDeserializers deserializers = new SimpleDeserializers();
deserializers.addDeserializer(a.type(), deserializer);
deserializerFactory.withAdditionalDeserializers(deserializers);
serializerFactory.addGenericMapping(a.type(), serializer);
StdDeserializerProvider deserializerProvider =
new StdDeserializerProvider(deserializerFactory);
mapper = new ObjectMapper();
mapper.registerModule(new JsonOrgModule());
mapper.setSerializerFactory(serializerFactory);
mapper.setDeserializerProvider(deserializerProvider);
}
}
}
}
}
if (cls != null && mapper == null) {
return MAPPER;
}
if (mapper == null) {
mapper = new ObjectMapper();
mapper.registerModule(new JsonOrgModule());
}
// Let's honor the Jackson annotations first
AnnotationIntrospector primary = new JacksonAnnotationIntrospector() {
@Override
public Object findFilterId(AnnotatedClass annotatedClass) {
Object filterId = super.findFilterId(annotatedClass);
return filterId == null ? TUSCANY_FILTER : filterId;
}
};
AnnotationIntrospector secondary = new JaxbAnnotationIntrospector();
AnnotationIntrospector pair = new AnnotationIntrospector.Pair(primary, secondary);
mapper.setDeserializationConfig(mapper.getDeserializationConfig().withAnnotationIntrospector(pair)
.without(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES)
.withDateFormat(StdDateFormat.getBlueprintISO8601Format()));
mapper.setSerializationConfig(mapper.getSerializationConfig().withAnnotationIntrospector(pair)
.withSerializationInclusion(JsonSerialize.Inclusion.NON_NULL)
.withDateFormat(StdDateFormat.getBlueprintISO8601Format()));
mapper.setFilters(new SimpleFilterProvider().addFilter(TUSCANY_FILTER, DEFAULT_FILTER));
return mapper;
}
public static JsonFactory getJsonFactory() {
return FACTORY;
}
public static String toString(JsonNode node) {
try {
return MAPPER.writeValueAsString(node);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static String toString(JsonParser parser) {
try {
JsonFactory jsonFactory = getJsonFactory();
StringWriter sw = new StringWriter();
JsonGenerator generator = jsonFactory.createJsonGenerator(sw);
JsonNode node = parser.readValueAs(JsonNode.class);
generator.writeTree(node);
return sw.toString();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static JsonParser createJsonParser(String content) {
JsonFactory jsonFactory = getJsonFactory();
try {
return jsonFactory.createJsonParser(content);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static JsonParser createJsonParser(InputStream content) {
JsonFactory jsonFactory = getJsonFactory();
try {
return jsonFactory.createJsonParser(content);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static JsonParser createJsonParser(Reader content) {
JsonFactory jsonFactory = getJsonFactory();
try {
return jsonFactory.createJsonParser(content);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static void write(JsonNode node, OutputStream out) {
try {
JsonFactory jsonFactory = getJsonFactory();
JsonGenerator generator = jsonFactory.createJsonGenerator(out, JsonEncoding.UTF8);
generator.writeTree(node);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static void write(JsonParser parser, OutputStream out) {
try {
JsonFactory jsonFactory = getJsonFactory();
JsonGenerator generator = jsonFactory.createJsonGenerator(out, JsonEncoding.UTF8);
JsonNode node = parser.readValueAs(JsonNode.class);
generator.writeTree(node);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static JSONObject read(InputStream is) throws IOException {
try {
return MAPPER.readValue(is, JSONObject.class);
} catch (Exception e) {
throw new IOException(e);
}
}
/**
* Read from String into a org.json.JSONObject
* @param json
* @return
*/
public static JSONObject read(String json) {
try {
return MAPPER.readValue(json, JSONObject.class);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public static void write(JSONObject json, OutputStream out) throws IOException {
try {
MAPPER.writeValue(out, json);
} catch (Exception e) {
throw new IOException(e);
}
}
public static String write(JSONObject json) throws IOException {
try {
return MAPPER.writeValueAsString(json);
} catch (Exception e) {
throw new IOException(e);
}
}
public static FilterProvider configureFilterProvider(TransformationContext context) {
SimpleBeanPropertyFilter filter = DEFAULT_FILTER;
if (context != null) {
Set<String> included = (Set<String>)context.getMetadata().get(INCLUDED_FIELDS);
Set<String> excluded = (Set<String>)context.getMetadata().get(EXCLUDED_FIELDS);
// Class<?> type = context.getSourceDataType() == null ? null : context.getSourceDataType().getPhysical();
filter = new TuscanyBeanPropertyFilter(included, excluded);
}
FilterProvider filters = new SimpleFilterProvider().addFilter(TUSCANY_FILTER, filter);
return filters;
}
private static class TuscanyBeanPropertyFilter extends SimpleBeanPropertyFilter {
private List<String> includedFields;
private List<String> excludedFields;
private Stack<String> path = new Stack<String>();
public TuscanyBeanPropertyFilter(Set<String> includedFields, Set<String> excludedFields) {
if (includedFields == null) {
includedFields = new HashSet<String>();
includedFields.add(""); // Allows any fields
}
if (excludedFields == null) {
excludedFields = Collections.emptySet();
}
this.includedFields = new ArrayList<String>(includedFields);
Collections.sort(this.includedFields, Collections.reverseOrder());
this.excludedFields = new ArrayList<String>(excludedFields);
Collections.sort(this.excludedFields, Collections.reverseOrder());
}
@Override
public void serializeAsField(Object bean,
JsonGenerator jgen,
SerializerProvider provider,
BeanPropertyWriter writer) throws Exception {
path.push(writer.getName());
String fname = getFullName(path);
try {
// System.out.println(path);
if (isAllowed(fname, includedFields, excludedFields)) {
// Matching includes, write
writer.serializeAsField(bean, jgen, provider);
}
} finally {
path.pop();
}
}
/**
* Check the target string is a prefix of the source separated by .
* @param source
* @param target
* @return
*/
private boolean matches(String source, String target) {
int index = source.indexOf(target);
if (index == -1) {
return false;
}
if (target.length() == source.length() || source.charAt(target.length()) == '.') {
return true;
}
return false;
}
/**
* Check if the path matches the one of the patterns
* @param path
* @param patterns
* @param included
* @return
*/
private boolean isAllowed(String fullName, List<String> included, List<String> excluded) {
String ex = null;
for (String p : excluded) {
if (matches(fullName, p)) {
ex = p;
break;
}
}
for (String p : included) {
if (matches(fullName, p) // If the parent element is included
|| matches(p, fullName) // If one of the child elements is included
) {
if (ex != null && ex.length() > p.length()) {
// We already have an exclusion pattern that's more matching
return false;
}
return true;
}
}
return ex == null && included.contains("");
}
private String getFullName(Stack<String> path) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < path.size(); i++) {
builder.append(path.get(i));
if (i != path.size() - 1) {
builder.append(".");
}
}
String qname = builder.toString();
return qname;
}
}
}