package de.galan.verjson.core;
import static de.galan.commons.time.Instants.*;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Preconditions;
import de.galan.verjson.step.ProcessStepException;
import de.galan.verjson.step.Step;
import de.galan.verjson.util.MetaWrapper;
import de.galan.verjson.util.ReadException;
/**
* Versionized transformable/evolvable objectgraphs<br/>
* TODO documentation
*
* @author daniel
* @param <T> Type of Objects to be transformed
*/
public class Verjson<T> {
/** Optional user-defined namespace to distinguish between different types */
String namespace;
/** Type of the serialized objects */
Class<T> valueClass;
Map<Long, ? extends Step> steps;
ObjectMapper mapper;
/** Highest version available in added transformers, starting with 1. */
long highestSourceVersion;
/** Include the creational timestamp in each serialized object */
boolean includeTimestamp;
public static <T> Verjson<T> create(Class<T> valueClass, Versions versions) {
return new Verjson<T>(valueClass, versions);
}
public Verjson(Class<T> valueClass, Versions versions) {
this.valueClass = Preconditions.checkNotNull(valueClass, "valueClass can not be null");
Versions vs = (versions != null) ? versions : new Versions();
this.namespace = vs.getNamespace();
configure(vs);
}
protected void configure(Versions versions) {
versions.configure();
includeTimestamp = versions.isIncludeTimestamp();
mapper = new ObjectMapperFactory().create(versions);
steps = createStepSequencer().sequence(versions.getSteps());
highestSourceVersion = determineHighestSourceVersion();
}
protected long determineHighestSourceVersion() {
Preconditions.checkNotNull(steps, "Steps can not be null, at least one NoopStep has to exist");
Set<Long> keys = steps.keySet();
Preconditions.checkElementIndex(0, keys.toArray().length, "Steps can not be empty, at least one NoopStep has to exist");
return Collections.max(keys);
}
protected StepSequencer createStepSequencer() {
return new DefaultStepSequencer();
}
protected String getNamespace() {
return namespace;
}
protected ObjectMapper getMapper() {
return mapper;
}
//TODO own exception
//TODO check only Validation and Transformation (for now)
/** Serializes the given object to a String */
public String write(T obj) throws JsonProcessingException {
Date ts = includeTimestamp ? Date.from(now()) : null;
MetaWrapper wrapper = new MetaWrapper(getHighestSourceVersion(), getNamespace(), obj, ts);
return mapper.writeValueAsString(wrapper);
}
/**
* Serializes the given object without any metadata to a String. This is basically the raw serialized object from
* the data element. Since the metadata is missing, this Json can not be read using the read(..) methods, except the
* readPlain-method - if the version is manually passed.
*/
public String writePlain(T obj) throws JsonProcessingException {
return mapper.writeValueAsString(obj);
}
public JsonNode readTree(String json) throws IOException {
return getMapper().readTree(json);
}
protected JsonNode wrapPlainNode(JsonNode node, long version) {
ObjectNode wrapper = new ObjectNode(JsonNodeFactory.instance);
wrapper.put(MetaWrapper.ID_VERSION, version);
wrapper.set(MetaWrapper.ID_DATA, node);
return wrapper;
}
public T readPlain(JsonNode node, long version) throws VersionNotSupportedException, NamespaceMismatchException, ProcessStepException, IOReadException {
return read(wrapPlainNode(node, version));
}
public T read(String json) throws VersionNotSupportedException, NamespaceMismatchException, ProcessStepException, IOReadException {
T result = null;
try {
result = read(readTree(json));
}
catch (IOException ex) {
throw new IOReadException("Reading json failed: " + ex.getMessage(), ex);
}
return result;
}
public T read(JsonNode node) throws VersionNotSupportedException, NamespaceMismatchException, ProcessStepException, IOReadException {
T result = null;
try {
verifyNamespace(node);
Long jsonVersion = verifyVersion(node);
steps.get(jsonVersion).process(node);
JsonNode data = MetaWrapper.getData(node);
result = getMapper().treeToValue(data, getValueClass());
}
catch (ReadException ex) {
throw ex;
}
catch (IOException ex) {
throw new IOReadException("Reading json failed: " + ex.getMessage(), ex);
}
return result;
}
protected Long verifyVersion(JsonNode node) throws VersionNotSupportedException {
Long sourceVersion = MetaWrapper.getVersion(node);
if (sourceVersion > getHighestSourceVersion()) {
throw new VersionNotSupportedException(getHighestSourceVersion(), sourceVersion, getValueClass());
}
return sourceVersion;
}
protected String verifyNamespace(JsonNode node) throws NamespaceMismatchException {
// verify namespace
String ns = MetaWrapper.getNamespace(node);
if (!StringUtils.equals(ns, getNamespace())) {
throw new NamespaceMismatchException(getNamespace(), ns);
}
return ns;
}
protected Class<T> getValueClass() {
return valueClass;
}
protected long getHighestSourceVersion() {
return highestSourceVersion;
}
}