/*
* Copyright 2014, The Sporting Exchange Limited
*
* Licensed 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 com.betfair.cougar.marshalling.impl.databinding.json;
import java.io.IOException;
import java.util.logging.Level;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.betfair.cougar.marshalling.api.databinding.*;
public class JSONBindingFactory implements DataBindingFactory {
private final static Logger LOGGER = LoggerFactory.getLogger(JSONBindingFactory.class);
private final ObjectMapper objectMapper;
private final JSONMarshaller marshaller;
private final JSONUnMarshaller unMarshaller;
private boolean enableAfterburner = true;
private boolean useOptimizedBeanDeserializer = true;
private boolean useValueClassLoader = true;
public JSONBindingFactory() {
LOGGER.info("Initialising JSONBindingFactory");
objectMapper = createBaseObjectMapper();
marshaller = new JSONMarshaller(objectMapper);
unMarshaller = new JSONUnMarshaller(objectMapper);
}
public ObjectMapper createBaseObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
JSONDateFormat jdf=new JSONDateFormat();
mapper.setDateFormat(jdf);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
if (enableAfterburner) {
AfterburnerModule module = new AfterburnerModule();
module.setUseOptimizedBeanDeserializer(useOptimizedBeanDeserializer);
module.setUseValueClassLoader(useValueClassLoader);
mapper.registerModule(module);
}
applyNumericRangeBugfixes(mapper);
return mapper;
}
/**
* Fixes problem in Jackson's StdDeserializer. with _parseInteger and _parseLong.
* The provided implementation allowed out-of-range numbers to be shoe-horned into types, ignoring under/overflow.
* E.g. 21474836470 would be deserialized into an Integer as -10.
* E.g. 92233720368547758080 would be deserialized into a Long as 0.
*/
private static void applyNumericRangeBugfixes(ObjectMapper mapper) {
// Create a custom module
SimpleModule customModule = new SimpleModule("CustomModule", new Version(1, 0, 0, null, null, null));
// Register a deserializer for Integer that overrides default buggy version
customModule.addDeserializer(Integer.class, new IntegerDeserializer());
customModule.addDeserializer(int.class, new IntegerDeserializer());
// Register a deserializer for Long that overrides default buggy version
customModule.addDeserializer(Long.class, new LongDeserializer());
customModule.addDeserializer(long.class, new LongDeserializer());
// Register a deserializer for Byte that overrides default buggy version
customModule.addDeserializer(Byte.class, new ByteDeserializer());
customModule.addDeserializer(byte.class, new ByteDeserializer());
// Add the module to the mapper
mapper.registerModule(customModule);
}
@Override
public UnMarshaller getUnMarshaller() {
return unMarshaller;
}
@Override
public Marshaller getMarshaller() {
return marshaller;
}
@Override
public FaultMarshaller getFaultMarshaller() {
return marshaller;
}
@Override
public FaultUnMarshaller getFaultUnMarshaller() {
return unMarshaller;
}
public void setEnableAfterburner(boolean enableAfterburner) {
this.enableAfterburner = enableAfterburner;
}
public void setUseOptimizedBeanDeserializer(boolean useOptimizedBeanDeserializer) {
this.useOptimizedBeanDeserializer = useOptimizedBeanDeserializer;
}
public void setUseValueClassLoader(boolean useValueClassLoader) {
this.useValueClassLoader = useValueClassLoader;
}
/**
* A deserializer for Integer that properly checks whether the value is within range.
* Needed because Jackson's StdDeserializer._parseInteger has a bug in it that allows underflow and overflow.
* This deserializer is a copy of Jackson's own StdDeserializer._parseInteger with the fix applied.
* When registered with a mapper it overrides the buggy default deserialization of Integer.
*/
static class IntegerDeserializer extends StdDeserializer<Integer> {
public IntegerDeserializer() {
super(Integer.class);
}
@Override
public Integer deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonToken t = parser.getCurrentToken();
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
return rangeCheckedInteger(parser, context);
}
if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
String text = parser.getText().trim();
try {
int len = text.length();
if (len > 9) {
return rangeCheckedInteger(parser, context);
}
if (len == 0) {
return null;
}
return Integer.valueOf(NumberInput.parseInt(text));
} catch (IllegalArgumentException iae) {
throw context.weirdStringException(_valueClass, "not a valid Integer value");//NOSONAR
}
}
if (t == JsonToken.VALUE_NULL) {
return null;
}
// Otherwise, no can do:
throw context.mappingException(_valueClass);
}
private Integer rangeCheckedInteger(JsonParser parser, DeserializationContext context)
throws IOException {
long l = parser.getLongValue();
if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
throw context.weirdStringException(
_valueClass,
"Over/underflow: numeric value (" + l +") out of range of Integer ("
+ Integer.MIN_VALUE + " to " + Integer.MAX_VALUE + ")"
);
}
return Integer.valueOf((int) l);
}
}
/**
* A deserializer for Long that properly checks whether the value is within range.
* Needed because Jackson's StdDeserializer._parseLong has a bug in it that allows underflow and overflow.
* This deserializer is a copy of Jackson's own StdDeserializer._parseLong with the fix applied.
* When registered with a mapper it overrides the buggy default deserialization of Long.
*/
static class LongDeserializer extends StdDeserializer<Long> {
public LongDeserializer() {
super(Long.class);
}
@Override
public Long deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonToken t = parser.getCurrentToken();
// it should be ok to coerce (although may fail, too)
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT || t == JsonToken.VALUE_STRING) {
return rangeCheckedLong(parser, context);
}
if (t == JsonToken.VALUE_NULL) {
return null;
}
// Otherwise, no can do:
throw context.mappingException(_valueClass);
}
private Long rangeCheckedLong(JsonParser parser, DeserializationContext context)
throws IOException {
String text = parser.getText().trim();
if (text.length() == 0) {
return null;
}
try {
return Long.valueOf(NumberInput.parseLong(text));
}
catch (Exception e) {
throw context.weirdStringException(//NOSONAR
_valueClass,
"Over/underflow: numeric value (" + text +") out of range of Long ("
+ Long.MIN_VALUE + " to " + Long.MAX_VALUE + ")"
);
}
}
}
/**
* A deserializer for Long that properly checks whether the value is within range.
* Needed because Jackson's StdDeserializer._parseByte uses JsonParser.getByteValue() which is too permissive,
* allowing values in the range -128 to +255, but we want byte to be -128 to +127
* See also: [JACKSON-804]
* When registered with a mapper it overrides the buggy default deserialization of Long.
*/
static class ByteDeserializer extends StdDeserializer<Byte> {
public ByteDeserializer() {
super(Byte.class);
}
@Override
public Byte deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
return _parseByte(jp, ctxt);
}
@Override
protected Byte _parseByte(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonToken t = jp.getCurrentToken();
Integer value = null;
if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
value = jp.getIntValue();
}
else if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
String text = jp.getText().trim();
try {
int len = text.length();
if (len == 0) {
return getEmptyValue();
}
value = NumberInput.parseInt(text);
} catch (IllegalArgumentException iae) {
throw ctxt.weirdStringException(_valueClass, "not a valid Byte value");//NOSONAR
}
}
if (value != null) {
// So far so good: but does it fit?
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw ctxt.weirdStringException(_valueClass, "overflow, value can not be represented as 8-bit value");
}
return (byte) (int) value;
}
if (t == JsonToken.VALUE_NULL) {
return getNullValue();
}
throw ctxt.mappingException(_valueClass, t);
}
}
}