/**
* Singleton Jackson ObjectMapper
*/
package com.almende.util.jackson;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.BitSet;
import java.util.LinkedHashMap;
import com.almende.util.URIUtil;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper;
/**
* The Class JOM.
*/
public final class JOM {
private static final ObjectMapper MAPPER = createInstance();
/**
* Instantiates a new jom.
*/
protected JOM() {}
/**
* Gets the single instance of JOM.
*
* @return single instance of JOM
*/
public static ObjectMapper getInstance() {
return MAPPER;
}
/**
* Creates the object node.
*
* @return the object node
*/
public static ObjectNode createObjectNode() {
return getInstance().createObjectNode();
}
/**
* Creates the array node.
*
* @return the array node
*/
public static ArrayNode createArrayNode() {
return getInstance().createArrayNode();
}
/**
* Creates the null node.
*
* @return the null node
*/
public static NullNode createNullNode() {
return NullNode.getInstance();
}
/**
* Creates the instance.
*
* @return the object mapper
*/
private static synchronized ObjectMapper createInstance() {
final ObjectMapper mapper = new ObjectMapper();
mapper.setNodeFactory(new JsonNodeFactory() {
private static final long serialVersionUID = -1340917885113347742L;
@Override
public ObjectNode objectNode() {
return new ObjectNode(this,
new LinkedHashMap<String, JsonNode>(2));
}
});
// set configuration
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
mapper.configure(
DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, false);
mapper.getFactory().configure(
JsonFactory.Feature.CANONICALIZE_FIELD_NAMES, false);
// Needed for o.a. JsonFileState
mapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
mapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
// Needed for NaN/Infinity:
mapper.configure(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS, false);
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
// Convenient for JSON configuration documents
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
mapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
mapper.registerModule(new JodaModule());
mapper.registerModule(new JsonOrgModule());
SimpleModule throwableModule = new SimpleModule("ThrowableModule",
new Version(1, 0, 0, null, null, null)) {
private static final long serialVersionUID = -7028757133086455336L;
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Throwable.class,
ThrowableMixin.class);
}
};
mapper.registerModule(throwableModule);
SimpleModule bitSetModule = new SimpleModule("BitSetModule",
new Version(1, 0, 0, null, null, null));
bitSetModule.addSerializer(new CustomBitSetSerializer());
bitSetModule.addDeserializer(BitSet.class,
new JOM().new CustomBitSetDeserializer());
mapper.registerModule(bitSetModule);
SimpleModule uriModule = new SimpleModule("UriModule", new Version(1,
0, 0, null, null, null));
uriModule.addDeserializer(URI.class,
new JOM().new CustomURIDeserializer());
mapper.registerModule(uriModule);
return mapper;
}
/**
* Gets the type factory.
*
* @return the type factory
*/
public static TypeFactory getTypeFactory() {
return getInstance().getTypeFactory();
}
/**
* Gets the type schema.
*
* @param c
* the c
* @return the type schema
* @throws JsonMappingException
* the json mapping exception
*/
public static ObjectNode getTypeSchema(final Type c)
throws JsonMappingException {
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
getInstance().acceptJsonFormatVisitor(getInstance().constructType(c),
visitor);
JsonSchema jsonSchema = visitor.finalSchema();
return getInstance().valueToTree(jsonSchema);
}
/**
* The Class CustomBitSetSerializer.
*/
public static class CustomBitSetSerializer extends StdSerializer<BitSet> {
private static final long serialVersionUID = 7215238140499196910L;
/**
* Instantiates a new custom bit set serializer.
*/
public CustomBitSetSerializer() {
super(BitSet.class, true);
}
@Override
public void serialize(BitSet value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonGenerationException {
jgen.writeStartObject();
jgen.writeNumberField("size", value.size());
jgen.writeStringField("hex", bytesToHex(value.toByteArray()));
jgen.writeEndObject();
}
}
/**
* The Class CustomBitSetDeserializer.
*/
public class CustomBitSetDeserializer extends StdDeserializer<BitSet> {
private static final long serialVersionUID = 8734051359812526123L;
/**
* Instantiates a new custom bit set deserializer.
*/
public CustomBitSetDeserializer() {
super(BitSet.class);
}
@Override
public BitSet deserialize(JsonParser jpar, DeserializationContext ctx)
throws IOException, JsonProcessingException {
final JsonNode node = jpar.readValueAsTree();
if (!node.isObject()) {
throw ctx.mappingException(BitSet.class);
}
final ObjectNode obj = (ObjectNode) node;
final int size = obj.get("size").asInt();
final byte[] value = hexToBytes(obj.get("hex").asText());
final BitSet result = BitSet.valueOf(value);
result.set(result.length(), size, false);
return result;
}
}
/**
* The Class CustomBitSetDeserializer.
*/
public class CustomURIDeserializer extends StdDeserializer<URI> {
private static final long serialVersionUID = 8734051359812526123L;
/**
* Instantiates a new custom bit set deserializer.
*/
public CustomURIDeserializer() {
super(URI.class);
}
@Override
public URI deserialize(JsonParser jpar, DeserializationContext ctx)
throws IOException, JsonProcessingException {
final JsonNode node = jpar.readValueAsTree();
if (node.isTextual()) {
try {
return URIUtil.parse(node.asText());
} catch (URISyntaxException e) {
throw ctx.mappingException(URI.class);
}
}
if (node.isObject()) {
final String string = node.get("string").textValue();
try {
return URIUtil.parse(string);
} catch (URISyntaxException e) {
throw ctx.mappingException(URI.class);
}
}
throw ctx.mappingException(URI.class);
}
}
// From: http://stackoverflow.com/a/9855338
private static final char[] HEXARRAY = "0123456789ABCDEF".toCharArray();
private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEXARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEXARRAY[v & 0x0F];
}
return new String(hexChars);
}
// From: http://stackoverflow.com/a/140861
private static byte[] hexToBytes(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
}