/*
* This file is part of the GeoLatte project. This code is licenced 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.
*
* Copyright (C) 2010 - 2010 and Ownership of code is shared by:
* Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.Qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.common.dataformats.json.jackson;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.geolatte.common.Feature;
import org.geolatte.common.FeatureCollection;
import org.geolatte.geom.Geometry;
import org.geolatte.geom.GeometryCollection;
import org.geolatte.geom.LineString;
import org.geolatte.geom.MultiLineString;
import org.geolatte.geom.MultiPoint;
import org.geolatte.geom.MultiPolygon;
import org.geolatte.geom.Point;
import org.geolatte.geom.Polygon;
import org.geolatte.geom.crs.CrsId;
/**
* The JsonMapper is a class that can convert jsonstrings to objects and vice versa, and that can be extended on
* demand of the user by providing additional serializers or deserializers.
* <i>Creation-Date</i>: 20-apr-2010<br>
* <i>Creation-Time</i>: 8:09:55<br>
* <br>
*
* @author Yves Vandewoude
* @author <a href="http://www.qmino.com">Qmino bvba</a>
* @since SDK1.5
*/
public class JsonMapper {
private static final CrsId WGS84 = CrsId.valueOf(4326);
public static final int MAXIMUMDEPTH = 10;
private ObjectMapper mapper;
private int depth;
private CrsId defaultCrsId;
private boolean insideGeometryCollection;
private boolean serializeNullValues;
private boolean ignoreUnknownProperties;
/**
* Constructor of the jsonmapper
*
* @param defaultCrsId default Coordinate Referency System to use for {@code Geometry}s.
* @param serializeNullValues if set to true, null values are serialized as usual. If set to false, they are not.
* @param ignoreUnknownProperties if set to true, unknown properties in a json object will be ignored when they can not
* be mapped to a field instead of an exception being thrown.
*/
public JsonMapper(CrsId defaultCrsId, boolean serializeNullValues, boolean ignoreUnknownProperties) {
this.defaultCrsId = defaultCrsId;
this.serializeNullValues = serializeNullValues;
this.ignoreUnknownProperties = ignoreUnknownProperties;
setNewObjectMapper();
SimpleModule mod = new SimpleModule("GeolatteCommonModule");
mod.addSerializer(MultiLineString.class, new MultiLineStringSerializer(this));
mod.addSerializer(LineString.class, new LineStringSerializer(this));
mod.addSerializer(Point.class, new PointSerializer(this));
mod.addSerializer(MultiPoint.class, new MultiPointSerializer(this));
mod.addSerializer(Polygon.class, new PolygonSerializer(this));
mod.addSerializer(Feature.class, new FeatureSerializer(this));
mod.addSerializer(MultiPolygon.class, new MultiPolygonSerializer(this));
mod.addSerializer(Geometry.class, new AnyGeometrySerializer());
mod.addSerializer(GeometryCollection.class, new GeometryCollectionSerializer(this));
mod.addDeserializer(Geometry.class, new GeometryDeserializer<Geometry>(this, Geometry.class));
mod.addDeserializer(Point.class, new GeometryDeserializer<Point>(this, Point.class));
mod.addDeserializer(LineString.class, new GeometryDeserializer<LineString>(this, LineString.class));
mod.addDeserializer(MultiPoint.class, new GeometryDeserializer<MultiPoint>(this, MultiPoint.class));
mod.addDeserializer(MultiLineString.class, new GeometryDeserializer<MultiLineString>(this, MultiLineString.class));
mod.addDeserializer(Polygon.class, new GeometryDeserializer<Polygon>(this, Polygon.class));
mod.addDeserializer(MultiPolygon.class, new GeometryDeserializer<MultiPolygon>(this, MultiPolygon.class));
mod.addDeserializer(GeometryCollection.class, new GeometryDeserializer<GeometryCollection>(this, GeometryCollection.class));
mod.addDeserializer(Feature.class, new FeatureDeserializer(this));
mod.addDeserializer(FeatureCollection.class, new FeatureCollectionDeserializer(this));
mapper.registerModule(mod);
}
/**
* Constructor of the jsonmapper using as default Coordinate System WGS84 (EPSG:4326)
*
* @param serializeNullValues if set to true, null values are serialized as usual. If set to false, they are not.
* @param ignoreUnknownProperties if set to true, unknown properties in a json object will be ignored when they can not
* be mapped to a field instead of an exception being thrown.
*/
public JsonMapper(boolean serializeNullValues, boolean ignoreUnknownProperties) {
this(WGS84, serializeNullValues, ignoreUnknownProperties);
}
/**
* Default constructor. Maps all values, including null values and will throw exceptions on unknown properties;
* default coordinate reference system will be WGS84.
*/
public JsonMapper() {
this(WGS84, true, false);
}
/**
* Converts a given object into a JSON String
*
* @param input the object to convert into json
* @return the JSON string corresponding to the given object.
* @throws JsonException If the object can not be converted to a JSON string.
*/
public synchronized String toJson(Object input)
throws JsonException {
try {
// depth = 0;
return recurse(input);
} catch (IOException e) {
throw new JsonException(e);
}
}
/**
* Converts a given string into an object of the given class.
*
* @param clazz The class to which the returned object should belong
* @param jsonString the jsonstring representing the object to be parsed
* @param <T> the type of the returned object
* @return an instantiated object of class T corresponding to the given jsonstring
* @throws JsonException If deserialization failed or if the object of class T could for some reason not be
* constructed.
*/
public synchronized <T> T fromJson(String jsonString, Class<T> clazz)
throws JsonException {
if (jsonString == null) {
return null;
}
Reader reader = new StringReader(jsonString);
try {
return mapper.readValue(reader, clazz);
} catch (JsonParseException e) {
throw new JsonException(e.getMessage(), e);
} catch (JsonMappingException e) {
throw new JsonException(e.getMessage(), e);
} catch (IOException e) {
throw new JsonException(e.getMessage(), e);
}
}
/**
* Converts a JsonString into a corresponding javaobject of the requested type.
*
* @param json the inputstring to be converted to a javaobject
* @param clazz the class to which the resulting object should belong to
* @param <T> the type of the result elements
* @return An object of type T that corresponds with the given json string.
* @throws JsonException If something went wrong converting the jsonstring into an object
*/
public <T> List<T> collectionFromJson(String json, Class<T> clazz) throws JsonException {
if (json == null) {
return null;
}
try {
List<T> result = new ArrayList<T>();
List tempParseResult = (List) mapper.readValue(json, new TypeReference<List<T>>() {
});
for (Object temp : tempParseResult) {
result.add(fromJson(toJson(temp), clazz));
}
return result;
} catch (JsonParseException e) {
throw new JsonException(e.getMessage(), e);
} catch (JsonMappingException e) {
throw new JsonException(e.getMessage(), e);
} catch (IOException e) {
throw new JsonException(e.getMessage(), e);
}
}
/**
* @return Internal method that returns the current (recursion)-depth of this in the context of recursion/serialization
*/
int getDepth() {
return depth;
}
/**
* This method should only be used by serializers who need recursive calls. It prevents overflow due to circular
* references and ensures that the maximum depth of different sub-parts is limited
*
* @param input the object to serialize
* @return the jsonserialization of the given object
* @throws java.io.IOException If serialisation of the object failed.
*/
protected String recurse(Object input)
throws IOException {
increaseDepth();
if (depth > MAXIMUMDEPTH) {
decreaseDepth();
return "{ \"error\": \"maximum serialization-depth reached.\" }";
}
StringWriter writer = new StringWriter();
mapper.writeValue(writer, input);
writer.close();
decreaseDepth();
return writer.getBuffer().toString();
}
/**
* Adds a serializer to this mapper. Allows a user to alter the serialization behavior for a certain type.
*
* @param classToMap the class to map
* @param classSerializer the serializer
* @param <T> the type of objects that will be serialized by the given serializer
*/
public <T> void addClassSerializer(Class<? extends T> classToMap, JsonSerializer<T> classSerializer) {
setNewObjectMapper(); // Is this right, setting a new object mapper on each add operation?
SimpleModule mod = new SimpleModule("GeolatteCommonModule-" + classSerializer.getClass().getSimpleName());
mod.addSerializer(classToMap, classSerializer);
mapper.registerModule(mod);
}
/**
* Adds a deserializer to this mapper. Allows a user to alter the deserialization behavior for a certain type.
*
* @param classToMap the class for which the given serializer is meant
* @param classDeserializer the serializer
* @param <T> the type of objects that will be serialized by the given serializer
*/
public <T> void addClassDeserializer(Class<T> classToMap, JsonDeserializer<? extends T> classDeserializer) {
setNewObjectMapper(); // Is this right, setting a new object mapper on each add operation?
SimpleModule mod = new SimpleModule("GeolatteCommonModule-" + classDeserializer.getClass().getSimpleName());
mod.addDeserializer(classToMap, classDeserializer);
mapper.registerModule(mod);
}
/**
* We allow retrieval of the mapper for serializers
*
* @return the objectmapper used by this transformation
*/
public ObjectMapper getObjectMapper() {
return mapper;
}
/**
* Returns the default {@code CrsId} for this instance
*
* @return the {@code CrsId}
*/
public CrsId getDefaultCrsId() {
return defaultCrsId;
}
void increaseDepth() {
depth++;
}
void decreaseDepth() {
depth--;
}
/**
* Indicates whether a subgeometry is being mapped (used to know whether the CRS property needs to be generated)
*
* @return
*/
boolean insideGeometryCollection() {
return insideGeometryCollection;
}
void moveInsideGeometryCollection() {
this.insideGeometryCollection = true;
}
void moveOutsideGeometryCollection() {
this.insideGeometryCollection = false;
}
/**
* Sets a new object mapper, taking into account the original configuration parameters.
*/
private void setNewObjectMapper() {
mapper = new ObjectMapper();
if (!serializeNullValues) {
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
if (ignoreUnknownProperties) {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
}
}
}