package io.ebeaninternal.server.text.json;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import io.ebean.FetchPath;
import io.ebean.bean.EntityBean;
import io.ebean.config.JsonConfig;
import io.ebean.plugin.BeanType;
import io.ebean.text.json.EJson;
import io.ebean.text.json.JsonIOException;
import io.ebean.text.json.JsonReadOptions;
import io.ebean.text.json.JsonWriteBeanVisitor;
import io.ebean.text.json.JsonWriteOptions;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.util.ParamTypeHelper;
import io.ebeaninternal.util.ParamTypeHelper.ManyType;
import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

Default implementation of JsonContext.
/** * Default implementation of JsonContext. */
public class DJsonContext implements SpiJsonContext { private static final PrettyPrinter PRETTY_PRINTER = new Pretty(); private final SpiEbeanServer server; private final JsonFactory jsonFactory; private final Object defaultObjectMapper; private final JsonConfig.Include defaultInclude; private final DJsonScalar jsonScalar; private static class Pretty extends DefaultPrettyPrinter { Pretty() { _objectFieldValueSeparatorWithSpaces = ": "; } } public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory, TypeManager typeManager) { this.server = server; this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory(); this.defaultObjectMapper = this.server.getServerConfig().getObjectMapper(); this.defaultInclude = this.server.getServerConfig().getJsonInclude(); this.jsonScalar = new DJsonScalar(typeManager); } @Override public void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException { jsonScalar.write(generator, scalarValue); } @Override public boolean isSupportedType(Type genericType) { return server.isSupportedType(genericType); } @Override public JsonGenerator createGenerator(Writer writer) throws JsonIOException { try { return jsonFactory.createGenerator(writer); } catch (IOException e) { throw new JsonIOException(e); } } @Override public JsonParser createParser(Reader reader) throws JsonIOException { try { return jsonFactory.createParser(reader); } catch (IOException e) { throw new JsonIOException(e); } } @Override public <T> T toBean(Class<T> cls, String json) throws JsonIOException { return toBean(cls, new StringReader(json)); } @Override public <T> T toBean(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException { return toBean(cls, new StringReader(json), options); } @Override public <T> T toBean(Class<T> cls, Reader jsonReader) throws JsonIOException { return toBean(cls, createParser(jsonReader)); } @Override public <T> T toBean(Class<T> cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException { return toBean(cls, createParser(jsonReader), options); } @Override public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException { return toBean(cls, parser, null); } @Override public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { BeanDescriptor<T> desc = getDescriptor(cls); try { return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options)), null); } catch (IOException e) { throw new JsonIOException(e); } } @Override public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { BeanDescriptor<T> desc = getDescriptor(cls); return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options))); } @Override public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException { BeanDescriptor<T> desc = (BeanDescriptor<T>) beanType; SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options)); return new DJsonBeanReader<>(desc, readJson); } @Override public <T> List<T> toList(Class<T> cls, String json) throws JsonIOException { return toList(cls, new StringReader(json)); } @Override public <T> List<T> toList(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException { return toList(cls, new StringReader(json), options); } @Override public <T> List<T> toList(Class<T> cls, Reader jsonReader) throws JsonIOException { return toList(cls, createParser(jsonReader)); } @Override public <T> List<T> toList(Class<T> cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException { return toList(cls, createParser(jsonReader), options); } @Override public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException { return toList(cls, src, null); } @Override public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException { BeanDescriptor<T> desc = getDescriptor(cls); SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options)); try { JsonToken currentToken = src.getCurrentToken(); if (currentToken != JsonToken.START_ARRAY) { JsonToken event = src.nextToken(); if (event != JsonToken.START_ARRAY) { throw new JsonParseException(src, "Expecting start_array event but got " + event); } } List<T> list = new ArrayList<>(); do { T bean = desc.jsonRead(readJson, null); if (bean == null) { break; } else { list.add(bean); } } while (true); return list; } catch (IOException e) { throw new JsonIOException(e); } } @Override public Object toObject(Type genericType, String json) throws JsonIOException { return toObject(genericType, createParser(new StringReader(json))); } @Override public Object toObject(Type genericType, Reader json) throws JsonIOException { return toObject(genericType, createParser(json)); } @Override public Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException { TypeInfo info = ParamTypeHelper.getTypeInfo(genericType); ManyType manyType = info.getManyType(); switch (manyType) { case NONE: return toBean(info.getBeanType(), jsonParser); case LIST: return toList(info.getBeanType(), jsonParser); default: throw new JsonIOException("Type " + manyType + " not supported"); } } @Override public void toJson(Object value, JsonGenerator generator) throws JsonIOException { // generator passed in so don't close it toJsonNoClose(value, generator, null); } @Override public void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException { // generator passed in so don't close it toJsonNoClose(value, generator, JsonWriteOptions.pathProperties(fetchPath)); } @Override public void toJson(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException { // generator passed in so don't close it toJsonNoClose(o, generator, options); } @Override public void toJson(Object o, Writer writer) throws JsonIOException { // close generator toJsonWithClose(o, createGenerator(writer), null); } @Override public String toJson(Object value, FetchPath fetchPath) throws JsonIOException { return toJson(value, JsonWriteOptions.pathProperties(fetchPath)); } @Override public void toJson(Object o, Writer writer, FetchPath fetchPath) throws JsonIOException { // close generator toJsonWithClose(o, createGenerator(writer), JsonWriteOptions.pathProperties(fetchPath)); } @Override public void toJson(Object o, Writer writer, JsonWriteOptions options) throws JsonIOException { // close generator toJsonWithClose(o, createGenerator(writer), options); }
Write to the JsonGenerator and close when complete.
/** * Write to the JsonGenerator and close when complete. */
private void toJsonWithClose(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException { try { toJsonInternal(o, generator, options); generator.close(); } catch (IOException e) { throw new JsonIOException(e); } }
Write to the JsonGenerator and without closing it (as it was created externally).
/** * Write to the JsonGenerator and without closing it (as it was created externally). */
private void toJsonNoClose(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException { try { toJsonInternal(o, generator, options); } catch (IOException e) { throw new JsonIOException(e); } } @Override public String toJsonPretty(Object value) throws JsonIOException { return toJsonString(value, null, true); } @Override public String toJson(Object o) throws JsonIOException { return toJsonString(o, null, false); } @Override public String toJson(Object o, JsonWriteOptions options) throws JsonIOException { return toJsonString(o, options, false); } private String toJsonString(Object value, JsonWriteOptions options, boolean pretty) throws JsonIOException { StringWriter writer = new StringWriter(500); try (JsonGenerator gen = createGenerator(writer)) { if (pretty) { gen.setPrettyPrinter(PRETTY_PRINTER); } toJsonInternal(value, gen, options); } catch (IOException e) { throw new JsonIOException(e); } return writer.toString(); } @SuppressWarnings("unchecked") private void toJsonInternal(Object value, JsonGenerator gen, JsonWriteOptions options) throws IOException { if (value == null) { gen.writeNull(); } else if (value instanceof Number) { gen.writeNumber(((Number) value).doubleValue()); } else if (value instanceof Boolean) { gen.writeBoolean((Boolean) value); } else if (value instanceof String) { gen.writeString((String) value); // } else if (o instanceof JsonElement) { } else if (value instanceof Map<?, ?>) { toJsonFromMap((Map<Object, Object>) value, gen, options); } else if (value instanceof Collection<?>) { toJsonFromCollection((Collection<?>) value, null, gen, options); } else if (value instanceof EntityBean) { BeanDescriptor<?> d = getDescriptor(value.getClass()); WriteJson writeJson = createWriteJson(gen, options); d.jsonWrite(writeJson, (EntityBean) value, null); } else { jsonScalar.write(gen, value); } } @Override public SpiJsonReader createJsonRead(BeanType<?> beanType, String json) { BeanDescriptor<?> desc = (BeanDescriptor<?>) beanType; JsonParser parser = createParser(new StringReader(json)); return new ReadJson(desc, parser, null, defaultObjectMapper); } @Override public SpiJsonWriter createJsonWriter(Writer writer) { return createJsonWriter(createGenerator(writer), null); } @Override public SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options) { return createWriteJson(gen, options); } private WriteJson createWriteJson(JsonGenerator gen, JsonWriteOptions options) { FetchPath pathProps = (options == null) ? null : options.getPathProperties(); Map<String, JsonWriteBeanVisitor<?>> visitors = (options == null) ? null : options.getVisitorMap(); return new WriteJson(server, gen, pathProps, visitors, determineObjectMapper(options), determineInclude(options)); } private <T> void toJsonFromCollection(Collection<T> collection, String key, JsonGenerator gen, JsonWriteOptions options) throws IOException { if (key != null) { gen.writeFieldName(key); } gen.writeStartArray(); WriteJson writeJson = createWriteJson(gen, options); for (T bean : collection) { BeanDescriptor<?> d = getDescriptor(bean.getClass()); d.jsonWrite(writeJson, (EntityBean) bean, null); } gen.writeEndArray(); } private void toJsonFromMap(Map<Object, Object> map, JsonGenerator gen, JsonWriteOptions options) throws IOException { Set<Entry<Object, Object>> entrySet = map.entrySet(); Iterator<Entry<Object, Object>> it = entrySet.iterator(); WriteJson writeJson = createWriteJson(gen, options); gen.writeStartObject(); while (it.hasNext()) { Entry<Object, Object> entry = it.next(); String key = entry.getKey().toString(); Object value = entry.getValue(); if (value == null) { gen.writeNullField(key); } else { if (value instanceof Collection<?>) { toJsonFromCollection((Collection<?>) value, key, gen, options); } else if (value instanceof EntityBean) { BeanDescriptor<?> d = getDescriptor(value.getClass()); d.jsonWrite(writeJson, (EntityBean) value, key); } else { EJson.write(entry, gen); } } } gen.writeEndObject(); }
Return the BeanDescriptor for the given bean type.
/** * Return the BeanDescriptor for the given bean type. */
private <T> BeanDescriptor<T> getDescriptor(Class<T> beanType) { BeanDescriptor<T> d = server.getBeanDescriptor(beanType); if (d == null) { throw new RuntimeException("No BeanDescriptor found for " + beanType); } return d; }
Determine the object mapper to use for a JSON read request.
/** * Determine the object mapper to use for a JSON read request. */
private Object determineObjectMapper(JsonReadOptions options) { if (options == null) { return defaultObjectMapper; } Object mapper = options.getObjectMapper(); return (mapper != null) ? mapper : defaultObjectMapper; }
Determine the object mapper to use for a JSON write request.
/** * Determine the object mapper to use for a JSON write request. */
private Object determineObjectMapper(JsonWriteOptions options) { if (options == null) { return defaultObjectMapper; } Object mapper = options.getObjectMapper(); return (mapper != null) ? mapper : defaultObjectMapper; }
Determine the include mode to use for a JSON write request.
/** * Determine the include mode to use for a JSON write request. */
private JsonConfig.Include determineInclude(JsonWriteOptions options) { if (options == null) { return defaultInclude; } JsonConfig.Include include = options.getInclude(); return (include != null) ? include : defaultInclude; } }