mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#919 - io.ebean package initial
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
package io.ebeaninternal.server.text.csv;
|
||||
|
||||
// Original name: au.com.bytecode.opencsv.CSVReader
|
||||
// rbygrave: Made some Java Generics tweaks to remove warnings
|
||||
|
||||
/**
|
||||
* Copyright 2005 Bytecode Pty Ltd.
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Glen Smith's CSV reader released under Apache License version 2.
|
||||
*
|
||||
* @author Glen Smith
|
||||
*
|
||||
*/
|
||||
public class CsvUtilReader {
|
||||
|
||||
private final BufferedReader br;
|
||||
|
||||
private boolean hasNext = true;
|
||||
|
||||
private final char separator;
|
||||
|
||||
private final char quotechar;
|
||||
|
||||
private final int skipLines;
|
||||
|
||||
private boolean linesSkiped;
|
||||
|
||||
/** The default separator to use if none is supplied to the constructor. */
|
||||
public static final char DEFAULT_SEPARATOR = ',';
|
||||
|
||||
/**
|
||||
* The default quote character to use if none is supplied to the
|
||||
* constructor.
|
||||
*/
|
||||
public static final char DEFAULT_QUOTE_CHARACTER = '"';
|
||||
|
||||
/**
|
||||
* The default line to start reading.
|
||||
*/
|
||||
public static final int DEFAULT_SKIP_LINES = 0;
|
||||
|
||||
/**
|
||||
* Constructs CSVReader using a comma for the separator.
|
||||
*
|
||||
* @param reader
|
||||
* the reader to an underlying CSV source.
|
||||
*/
|
||||
public CsvUtilReader(Reader reader) {
|
||||
this(reader, DEFAULT_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs CSVReader with supplied separator.
|
||||
*
|
||||
* @param reader
|
||||
* the reader to an underlying CSV source.
|
||||
* @param separator
|
||||
* the delimiter to use for separating entries.
|
||||
*/
|
||||
public CsvUtilReader(Reader reader, char separator) {
|
||||
this(reader, separator, DEFAULT_QUOTE_CHARACTER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs CSVReader with supplied separator and quote char.
|
||||
*
|
||||
* @param reader
|
||||
* the reader to an underlying CSV source.
|
||||
* @param separator
|
||||
* the delimiter to use for separating entries
|
||||
* @param quotechar
|
||||
* the character to use for quoted elements
|
||||
*/
|
||||
public CsvUtilReader(Reader reader, char separator, char quotechar) {
|
||||
this(reader, separator, quotechar, DEFAULT_SKIP_LINES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs CSVReader with supplied separator and quote char.
|
||||
*
|
||||
* @param reader
|
||||
* the reader to an underlying CSV source.
|
||||
* @param separator
|
||||
* the delimiter to use for separating entries
|
||||
* @param quotechar
|
||||
* the character to use for quoted elements
|
||||
* @param line
|
||||
* the line number to skip for start reading
|
||||
*/
|
||||
public CsvUtilReader(Reader reader, char separator, char quotechar, int line) {
|
||||
this.br = new BufferedReader(reader);
|
||||
this.separator = separator;
|
||||
this.quotechar = quotechar;
|
||||
this.skipLines = line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire file into a List with each element being a String[] of
|
||||
* tokens.
|
||||
*
|
||||
* @return a List of String[], with each String[] representing a line of the
|
||||
* file.
|
||||
*
|
||||
* @throws IOException
|
||||
* if bad things happen during the read
|
||||
*/
|
||||
public List<String[]> readAll() throws IOException {
|
||||
|
||||
List<String[]> allElements = new ArrayList<>();
|
||||
while (hasNext) {
|
||||
String[] nextLineAsTokens = readNext();
|
||||
if (nextLineAsTokens != null)
|
||||
allElements.add(nextLineAsTokens);
|
||||
}
|
||||
return allElements;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next line from the buffer and converts to a string array.
|
||||
*
|
||||
* @return a string array with each comma-separated element as a separate
|
||||
* entry.
|
||||
*
|
||||
* @throws IOException
|
||||
* if bad things happen during the read
|
||||
*/
|
||||
public String[] readNext() throws IOException {
|
||||
|
||||
String nextLine = getNextLine();
|
||||
return hasNext ? parseLine(nextLine) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next line from the file.
|
||||
*
|
||||
* @return the next line from the file without trailing newline
|
||||
* @throws IOException
|
||||
* if bad things happen during the read
|
||||
*/
|
||||
private String getNextLine() throws IOException {
|
||||
if (!this.linesSkiped) {
|
||||
for (int i = 0; i < skipLines; i++) {
|
||||
br.readLine();
|
||||
}
|
||||
this.linesSkiped = true;
|
||||
}
|
||||
String nextLine = br.readLine();
|
||||
if (nextLine == null) {
|
||||
hasNext = false;
|
||||
}
|
||||
return hasNext ? nextLine : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an incoming String and returns an array of elements.
|
||||
*
|
||||
* @param nextLine
|
||||
* the string to parse
|
||||
* @return the comma-tokenized list of elements, or null if nextLine is null
|
||||
* @throws IOException if bad things happen during the read
|
||||
*/
|
||||
private String[] parseLine(String nextLine) throws IOException {
|
||||
|
||||
if (nextLine == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> tokensOnThisLine = new ArrayList<>();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
do {
|
||||
if (inQuotes) {
|
||||
// continuing a quoted section, reappend newline
|
||||
sb.append("\n");
|
||||
nextLine = getNextLine();
|
||||
if (nextLine == null)
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < nextLine.length(); i++) {
|
||||
|
||||
char c = nextLine.charAt(i);
|
||||
if (c == quotechar) {
|
||||
// this gets complex... the quote may end a quoted block, or escape another quote.
|
||||
// do a 1-char lookahead:
|
||||
if (inQuotes // we are in quotes, therefore there can be escaped quotes in here.
|
||||
&& nextLine.length() > (i + 1) // there is indeed another character to check.
|
||||
&& nextLine.charAt(i + 1) == quotechar) { // ..and that char. is a quote also.
|
||||
// we have two quote chars in a row == one quote char, so consume them both and
|
||||
// put one on the token. we do *not* exit the quoted text.
|
||||
sb.append(nextLine.charAt(i + 1));
|
||||
i++;
|
||||
} else {
|
||||
inQuotes = !inQuotes;
|
||||
// the tricky case of an embedded quote in the middle: a,bc"d"ef,g
|
||||
if (i > 2 //not on the begining of the line
|
||||
&& nextLine.charAt(i - 1) != this.separator //not at the begining of an escape sequence
|
||||
&& nextLine.length() > (i + 1) &&
|
||||
nextLine.charAt(i + 1) != this.separator //not at the end of an escape sequence
|
||||
) {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
} else if (c == separator && !inQuotes) {
|
||||
tokensOnThisLine.add(sb.toString().trim());
|
||||
sb = new StringBuilder(); // start work on next token
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
} while (inQuotes);
|
||||
tokensOnThisLine.add(sb.toString().trim());
|
||||
return tokensOnThisLine.toArray(new String[tokensOnThisLine.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying reader.
|
||||
*
|
||||
* @throws IOException if the close fails
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
br.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
package io.ebeaninternal.server.text.csv;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.ExpressionPath;
|
||||
import io.ebean.text.StringParser;
|
||||
import io.ebean.text.TextException;
|
||||
import io.ebean.text.TimeStringParser;
|
||||
import io.ebean.text.csv.CsvCallback;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.csv.DefaultCsvCallback;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.sql.Types;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Implementation of the CsvReader
|
||||
*/
|
||||
public class TCsvReader<T> implements CsvReader<T> {
|
||||
|
||||
private static final TimeStringParser TIME_PARSER = new TimeStringParser();
|
||||
|
||||
private final EbeanServer server;
|
||||
|
||||
private final BeanDescriptor<T> descriptor;
|
||||
|
||||
private final List<CsvColumn> columnList = new ArrayList<>();
|
||||
|
||||
private final CsvColumn ignoreColumn = new CsvColumn();
|
||||
|
||||
private boolean hasHeader;
|
||||
|
||||
private int logInfoFrequency = 1000;
|
||||
|
||||
private String defaultTimeFormat = "HH:mm:ss";
|
||||
private String defaultDateFormat = "yyyy-MM-dd";
|
||||
private String defaultTimestampFormat = "yyyy-MM-dd hh:mm:ss.fffffffff";
|
||||
private Locale defaultLocale = Locale.getDefault();
|
||||
|
||||
/**
|
||||
* The batch size used for JDBC statement batching.
|
||||
*/
|
||||
protected int persistBatchSize = 30;
|
||||
|
||||
private boolean addPropertiesFromHeader;
|
||||
|
||||
public TCsvReader(EbeanServer server, BeanDescriptor<T> descriptor) {
|
||||
this.server = server;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public void setDefaultLocale(Locale defaultLocale) {
|
||||
this.defaultLocale = defaultLocale;
|
||||
}
|
||||
|
||||
public void setDefaultTimeFormat(String defaultTimeFormat) {
|
||||
this.defaultTimeFormat = defaultTimeFormat;
|
||||
}
|
||||
|
||||
public void setDefaultDateFormat(String defaultDateFormat) {
|
||||
this.defaultDateFormat = defaultDateFormat;
|
||||
}
|
||||
|
||||
public void setDefaultTimestampFormat(String defaultTimestampFormat) {
|
||||
this.defaultTimestampFormat = defaultTimestampFormat;
|
||||
}
|
||||
|
||||
public void setPersistBatchSize(int persistBatchSize) {
|
||||
this.persistBatchSize = persistBatchSize;
|
||||
}
|
||||
|
||||
public void setIgnoreHeader() {
|
||||
setHasHeader(true, false);
|
||||
}
|
||||
|
||||
public void setAddPropertiesFromHeader() {
|
||||
setHasHeader(true, true);
|
||||
}
|
||||
|
||||
public void setHasHeader(boolean hasHeader, boolean addPropertiesFromHeader) {
|
||||
this.hasHeader = hasHeader;
|
||||
this.addPropertiesFromHeader = addPropertiesFromHeader;
|
||||
}
|
||||
|
||||
public void setLogInfoFrequency(int logInfoFrequency) {
|
||||
this.logInfoFrequency = logInfoFrequency;
|
||||
}
|
||||
|
||||
public void addIgnore() {
|
||||
columnList.add(ignoreColumn);
|
||||
}
|
||||
|
||||
public void addProperty(String propertyName) {
|
||||
addProperty(propertyName, null);
|
||||
}
|
||||
|
||||
public void addDateTime(String propertyName, String dateTimeFormat) {
|
||||
addDateTime(propertyName, dateTimeFormat, Locale.getDefault());
|
||||
}
|
||||
|
||||
public void addDateTime(String propertyName, String dateTimeFormat, Locale locale) {
|
||||
|
||||
ExpressionPath elProp = descriptor.getExpressionPath(propertyName);
|
||||
if (!elProp.isDateTimeCapable()) {
|
||||
throw new TextException("Property " + propertyName + " is not DateTime capable");
|
||||
}
|
||||
if (dateTimeFormat == null) {
|
||||
dateTimeFormat = getDefaultDateTimeFormat(elProp.getJdbcType());
|
||||
}
|
||||
|
||||
if (locale == null) {
|
||||
locale = defaultLocale;
|
||||
}
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat, locale);
|
||||
DateTimeParser parser = new DateTimeParser(sdf, dateTimeFormat, elProp);
|
||||
|
||||
CsvColumn column = new CsvColumn(elProp, parser);
|
||||
columnList.add(column);
|
||||
}
|
||||
|
||||
private String getDefaultDateTimeFormat(int jdbcType) {
|
||||
switch (jdbcType) {
|
||||
case Types.TIME:
|
||||
return defaultTimeFormat;
|
||||
case Types.DATE:
|
||||
return defaultDateFormat;
|
||||
case Types.TIMESTAMP:
|
||||
return defaultTimestampFormat;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Expected java.sql.Types TIME,DATE or TIMESTAMP but got [" + jdbcType + "]");
|
||||
}
|
||||
}
|
||||
|
||||
public void addProperty(String propertyName, StringParser parser) {
|
||||
|
||||
ExpressionPath elProp = descriptor.getExpressionPath(propertyName);
|
||||
if (parser == null) {
|
||||
parser = elProp.getStringParser();
|
||||
}
|
||||
CsvColumn column = new CsvColumn(elProp, parser);
|
||||
columnList.add(column);
|
||||
}
|
||||
|
||||
public void process(Reader reader) throws Exception {
|
||||
DefaultCsvCallback<T> callback = new DefaultCsvCallback<>(persistBatchSize, logInfoFrequency);
|
||||
process(reader, callback);
|
||||
}
|
||||
|
||||
public void process(Reader reader, CsvCallback<T> callback) throws Exception {
|
||||
|
||||
if (reader == null) {
|
||||
throw new NullPointerException("reader is null?");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new NullPointerException("callback is null?");
|
||||
}
|
||||
|
||||
CsvUtilReader utilReader = new CsvUtilReader(reader);
|
||||
|
||||
callback.begin(server);
|
||||
|
||||
int row = 0;
|
||||
|
||||
if (hasHeader) {
|
||||
String[] line = utilReader.readNext();
|
||||
if (addPropertiesFromHeader) {
|
||||
addPropertiesFromHeader(line);
|
||||
}
|
||||
callback.readHeader(line);
|
||||
}
|
||||
|
||||
try {
|
||||
do {
|
||||
++row;
|
||||
String[] line = utilReader.readNext();
|
||||
if (line == null) {
|
||||
--row;
|
||||
break;
|
||||
}
|
||||
|
||||
if (callback.processLine(row, line)) {
|
||||
// the line content is expected to be ok for processing
|
||||
if (line.length != columnList.size()) {
|
||||
// we have not got the expected number of columns
|
||||
String msg = "Error at line " + row + ". Expected [" + columnList.size() + "] columns "
|
||||
+ "but instead we have [" + line.length + "]. Line[" + Arrays.toString(line) + "]";
|
||||
throw new TextException(msg);
|
||||
}
|
||||
|
||||
T bean = buildBeanFromLineContent(row, line);
|
||||
|
||||
callback.processBean(row, line, bean);
|
||||
|
||||
}
|
||||
} while (true);
|
||||
|
||||
callback.end(row);
|
||||
|
||||
} catch (Exception e) {
|
||||
// notify that an error occurred so that any
|
||||
// transaction can be rolled back if required
|
||||
callback.endWithError(row, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addPropertiesFromHeader(String[] line) {
|
||||
for (String aLine : line) {
|
||||
ElPropertyValue elProp = descriptor.getElGetValue(aLine);
|
||||
if (elProp == null) {
|
||||
throw new TextException("Property [" + aLine + "] not found");
|
||||
}
|
||||
|
||||
if (Types.TIME == elProp.getJdbcType()) {
|
||||
addProperty(aLine, TIME_PARSER);
|
||||
|
||||
} else if (isDateTimeType(elProp.getJdbcType())) {
|
||||
addDateTime(aLine, null, null);
|
||||
|
||||
} else if (elProp.isAssocProperty()) {
|
||||
BeanPropertyAssocOne<?> assocOne = (BeanPropertyAssocOne<?>) elProp.getBeanProperty();
|
||||
String idProp = assocOne.getBeanDescriptor().getIdBinder().getIdProperty();
|
||||
addProperty(aLine + "." + idProp);
|
||||
} else {
|
||||
addProperty(aLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDateTimeType(int t) {
|
||||
return t == Types.TIMESTAMP || t == Types.DATE || t == Types.TIME;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T buildBeanFromLineContent(int row, String[] line) {
|
||||
|
||||
try {
|
||||
EntityBean entityBean = descriptor.createEntityBean();
|
||||
T bean = (T) entityBean;
|
||||
|
||||
int columnPos = 0;
|
||||
for (; columnPos < line.length; columnPos++) {
|
||||
convertAndSetColumn(columnPos, line[columnPos], entityBean);
|
||||
}
|
||||
|
||||
return bean;
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
String msg = "Error at line: " + row + " line[" + Arrays.toString(line) + "]";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertAndSetColumn(int columnPos, String strValue, EntityBean bean) {
|
||||
|
||||
strValue = strValue.trim();
|
||||
|
||||
if (strValue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CsvColumn c = columnList.get(columnPos);
|
||||
c.convertAndSet(strValue, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a column in the csv content.
|
||||
*/
|
||||
public static class CsvColumn {
|
||||
|
||||
private final ExpressionPath path;
|
||||
private final StringParser parser;
|
||||
|
||||
/**
|
||||
* Constructor for the IGNORE column.
|
||||
*/
|
||||
private CsvColumn() {
|
||||
this.path = null;
|
||||
this.parser = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with a property and parser.
|
||||
*/
|
||||
public CsvColumn(ExpressionPath path, StringParser parser) {
|
||||
this.path = path;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the string to the appropriate value and set it to the bean.
|
||||
*/
|
||||
public void convertAndSet(String strValue, EntityBean bean) {
|
||||
|
||||
if (parser != null && path != null) {
|
||||
Object value = parser.parse(strValue);
|
||||
path.pathSet(bean, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A StringParser for converting custom date/time/datetime strings into
|
||||
* appropriate java types (Date, Calendar, SQL Date, Time, Timestamp, JODA
|
||||
* etc).
|
||||
*/
|
||||
private static class DateTimeParser implements StringParser {
|
||||
|
||||
private final DateFormat dateFormat;
|
||||
private final ExpressionPath path;
|
||||
private final String format;
|
||||
|
||||
DateTimeParser(DateFormat dateFormat, String format, ExpressionPath path) {
|
||||
this.dateFormat = dateFormat;
|
||||
this.path = path;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public Object parse(String value) {
|
||||
try {
|
||||
Date dt = dateFormat.parse(value);
|
||||
return path.parseDateTime(dt.getTime());
|
||||
|
||||
} catch (ParseException e) {
|
||||
throw new TextException("Error parsing [" + value + "] using format[" + format + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package io.ebeaninternal.server.text.csv;
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.text.json.JsonBeanReader;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A 'context' for reading entity beans from JSON.
|
||||
* <p>
|
||||
* This is used such that a load context and persistence context can be used to span multiple marshalling requests.
|
||||
* </p>
|
||||
*/
|
||||
public class DJsonBeanReader<T> implements JsonBeanReader<T> {
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
|
||||
private final ReadJson readJson;
|
||||
|
||||
public DJsonBeanReader(BeanDescriptor<T> desc, ReadJson readJson) {
|
||||
this.desc = desc;
|
||||
this.readJson = readJson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persistenceContextPut(Object beanId, T currentBean) {
|
||||
readJson.persistenceContextPut(beanId, currentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return readJson.getPersistenceContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read() {
|
||||
try {
|
||||
return desc.jsonRead(readJson, null);
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonBeanReader<T> forJson(JsonParser moreJson, boolean resetContext) {
|
||||
return new DJsonBeanReader(desc, readJson.forJson(moreJson, resetContext));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
|
||||
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.JsonContext;
|
||||
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.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 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 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.
|
||||
*/
|
||||
public class DJsonContext implements JsonContext {
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final JsonFactory jsonFactory;
|
||||
|
||||
private final TypeManager typeManager;
|
||||
|
||||
private final Object defaultObjectMapper;
|
||||
|
||||
private final JsonConfig.Include defaultInclude;
|
||||
|
||||
private final DJsonScalar jsonScalar;
|
||||
|
||||
public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory, TypeManager typeManager) {
|
||||
this.server = server;
|
||||
this.typeManager = typeManager;
|
||||
this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory();
|
||||
this.defaultObjectMapper = this.server.getServerConfig().getObjectMapper();
|
||||
this.defaultInclude = this.server.getServerConfig().getJsonInclude();
|
||||
this.jsonScalar = new DJsonScalar(typeManager);
|
||||
}
|
||||
|
||||
public void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException {
|
||||
jsonScalar.write(generator, scalarValue);
|
||||
}
|
||||
|
||||
public boolean isSupportedType(Type genericType) {
|
||||
return server.isSupportedType(genericType);
|
||||
}
|
||||
|
||||
public JsonGenerator createGenerator(Writer writer) throws JsonIOException {
|
||||
try {
|
||||
return jsonFactory.createGenerator(writer);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonParser createParser(Reader reader) throws JsonIOException {
|
||||
try {
|
||||
return jsonFactory.createParser(reader);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public <T> T toBean(Class<T> cls, Reader jsonReader) throws JsonIOException {
|
||||
return toBean(cls, createParser(jsonReader));
|
||||
}
|
||||
|
||||
public <T> T toBean(Class<T> cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException {
|
||||
return toBean(cls, createParser(jsonReader), options);
|
||||
}
|
||||
|
||||
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException {
|
||||
return toBean(cls, parser, null);
|
||||
}
|
||||
|
||||
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
|
||||
BeanDescriptor<T> desc = getDescriptor(cls);
|
||||
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
|
||||
try {
|
||||
return desc.jsonRead(readJson, null);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DJsonBeanReader createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
|
||||
BeanDescriptor<T> desc = getDescriptor(cls);
|
||||
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
|
||||
return new DJsonBeanReader<>(desc, readJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DJsonBeanReader createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
|
||||
BeanDescriptor<T> desc = (BeanDescriptor<T>) beanType;
|
||||
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
|
||||
return new DJsonBeanReader<>(desc, readJson);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public <T> List<T> toList(Class<T> cls, Reader jsonReader) throws JsonIOException {
|
||||
return toList(cls, createParser(jsonReader));
|
||||
}
|
||||
|
||||
public <T> List<T> toList(Class<T> cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException {
|
||||
return toList(cls, createParser(jsonReader), options);
|
||||
}
|
||||
|
||||
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException {
|
||||
return toList(cls, src, null);
|
||||
}
|
||||
|
||||
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
|
||||
|
||||
BeanDescriptor<T> desc = getDescriptor(cls);
|
||||
ReadJson readJson = new ReadJson(desc, src, options, determineObjectMapper(options));
|
||||
try {
|
||||
|
||||
List<T> list = new ArrayList<>();
|
||||
|
||||
JsonToken currentToken = src.getCurrentToken();
|
||||
if (currentToken != JsonToken.START_ARRAY) {
|
||||
JsonToken event = src.nextToken();
|
||||
if (event != JsonToken.START_ARRAY) {
|
||||
throw new JsonParseException("Expecting start_array event but got " + event, src.getCurrentLocation());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public Object toObject(Type genericType, String json) throws JsonIOException {
|
||||
|
||||
return toObject(genericType, createParser(new StringReader(json)));
|
||||
}
|
||||
|
||||
public Object toObject(Type genericType, Reader json) throws JsonIOException {
|
||||
|
||||
return toObject(genericType, createParser(json));
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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).
|
||||
*/
|
||||
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 toJson(Object o) throws JsonIOException {
|
||||
return toJsonString(o, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJson(Object o, JsonWriteOptions options) throws JsonIOException {
|
||||
return toJsonString(o, options);
|
||||
}
|
||||
|
||||
private String toJsonString(Object value, JsonWriteOptions options) throws JsonIOException {
|
||||
try {
|
||||
StringWriter writer = new StringWriter(500);
|
||||
JsonGenerator gen = createGenerator(writer);
|
||||
toJsonInternal(value, gen, options);
|
||||
gen.close();
|
||||
return writer.toString();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private JsonConfig.Include determineInclude(JsonWriteOptions options) {
|
||||
if (options == null) {
|
||||
return defaultInclude;
|
||||
}
|
||||
JsonConfig.Include include = options.getInclude();
|
||||
return (include != null) ? include : defaultInclude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Default implementation of JsonScalar.
|
||||
*/
|
||||
public class DJsonScalar {
|
||||
|
||||
private final TypeManager typeManager;
|
||||
|
||||
public DJsonScalar(TypeManager typeManager) {
|
||||
this.typeManager = typeManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void write(JsonGenerator gen, Object value) throws IOException {
|
||||
|
||||
if (value instanceof String) {
|
||||
gen.writeString((String) value);
|
||||
|
||||
} else {
|
||||
ScalarType scalarType = typeManager.getScalarType(value.getClass());
|
||||
if (scalarType == null) {
|
||||
throw new IllegalArgumentException("unhandled type " + value.getClass());
|
||||
}
|
||||
scalarType.jsonWrite(gen, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
|
||||
import io.ebeaninternal.server.util.ArrayStack;
|
||||
|
||||
public class PathStack extends ArrayStack<String> {
|
||||
|
||||
public String peekFullPath(String key) {
|
||||
|
||||
String prefix = peekWithNull();
|
||||
if (prefix != null) {
|
||||
return prefix + "." + key;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public void pushPathKey(String key) {
|
||||
|
||||
String prefix = peekWithNull();
|
||||
if (prefix != null) {
|
||||
key = prefix + "." + key;
|
||||
}
|
||||
push(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.text.json.JsonReadBeanVisitor;
|
||||
import io.ebean.text.json.JsonReadOptions;
|
||||
import io.ebeaninternal.api.LoadContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.loadcontext.DLoadContext;
|
||||
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Context for JSON read processing.
|
||||
*/
|
||||
public class ReadJson {
|
||||
|
||||
private final BeanDescriptor<?> rootDesc;
|
||||
|
||||
/**
|
||||
* Jackson parser.
|
||||
*/
|
||||
private final JsonParser parser;
|
||||
|
||||
/**
|
||||
* Stack of the path - used to find the appropriate JsonReadBeanVisitor.
|
||||
*/
|
||||
private final PathStack pathStack;
|
||||
|
||||
/**
|
||||
* Map of the JsonReadBeanVisitor keyed by path.
|
||||
*/
|
||||
private final Map<String, JsonReadBeanVisitor<?>> visitorMap;
|
||||
|
||||
private final Object objectMapper;
|
||||
|
||||
private final PersistenceContext persistenceContext;
|
||||
|
||||
private final LoadContext loadContext;
|
||||
|
||||
/**
|
||||
* Construct with parser and readOptions.
|
||||
*/
|
||||
public ReadJson(BeanDescriptor<?> desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper) {
|
||||
|
||||
this.rootDesc = desc;
|
||||
this.parser = parser;
|
||||
this.objectMapper = objectMapper;
|
||||
this.persistenceContext = initPersistenceContext(readOptions);
|
||||
this.loadContext = initLoadContext(desc, readOptions);
|
||||
|
||||
// only create visitorMap, pathStack if needed ...
|
||||
this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap();
|
||||
this.pathStack = (visitorMap == null && loadContext == null) ? null : new PathStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance.
|
||||
*/
|
||||
private ReadJson(JsonParser moreJson, ReadJson source, boolean resetContext) {
|
||||
this.parser = moreJson;
|
||||
this.rootDesc = source.rootDesc;
|
||||
this.pathStack = source.pathStack;
|
||||
this.visitorMap = source.visitorMap;
|
||||
this.objectMapper = source.objectMapper;
|
||||
if (resetContext) {
|
||||
this.persistenceContext = new DefaultPersistenceContext();
|
||||
this.loadContext = source.loadContext;
|
||||
if (loadContext != null) {
|
||||
loadContext.resetPersistenceContext(persistenceContext);
|
||||
}
|
||||
} else {
|
||||
this.persistenceContext = source.persistenceContext;
|
||||
this.loadContext = source.loadContext;
|
||||
}
|
||||
}
|
||||
|
||||
private LoadContext initLoadContext(BeanDescriptor<?> desc, JsonReadOptions readOptions) {
|
||||
if (readOptions == null) return null;
|
||||
if (readOptions.isEnableLazyLoading() && readOptions.getLoadContext() == null) {
|
||||
return new DLoadContext(desc, persistenceContext);
|
||||
} else {
|
||||
return (LoadContext) readOptions.getLoadContext();
|
||||
}
|
||||
}
|
||||
|
||||
private PersistenceContext initPersistenceContext(JsonReadOptions readOptions) {
|
||||
if (readOptions != null && readOptions.getPersistenceContext() != null) {
|
||||
return readOptions.getPersistenceContext();
|
||||
}
|
||||
return new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the persistence context being used if any.
|
||||
*/
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of ReadJson using the existing context but with a new JsonParser.
|
||||
*/
|
||||
public ReadJson forJson(JsonParser moreJson, boolean resetContext) {
|
||||
return new ReadJson(moreJson, this, resetContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the bean to the persistence context.
|
||||
*/
|
||||
public <T> void persistenceContextPut(Object beanId, T currentBean) {
|
||||
|
||||
persistenceContextPutIfAbsent(beanId, (EntityBean) currentBean, rootDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the persistence context. If there is already a matching bean in the
|
||||
* persistence context then return that instance else return null.
|
||||
*/
|
||||
public Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor<?> beanDesc) {
|
||||
|
||||
if (persistenceContext == null) {
|
||||
// no persistenceContext means no lazy loading either
|
||||
return null;
|
||||
}
|
||||
|
||||
Object existing = beanDesc.contextPutIfAbsent(persistenceContext, id, bean);
|
||||
if (existing != null) {
|
||||
beanDesc.merge(bean, (EntityBean) existing);
|
||||
|
||||
} else {
|
||||
if (loadContext != null) {
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
if (ebi.isPartial()) {
|
||||
// register for further lazy loading
|
||||
String path = pathStack.peekWithNull();
|
||||
loadContext.register(path, ebi);
|
||||
beanDesc.lazyLoadRegister(path, ebi, bean, loadContext);
|
||||
}
|
||||
ebi.setLoaded();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the objectMapper used for this request.
|
||||
*/
|
||||
public ObjectMapper getObjectMapper() {
|
||||
if (objectMapper == null) {
|
||||
throw new IllegalStateException(
|
||||
"Jackson ObjectMapper required but has not set. The ObjectMapper can be set on"
|
||||
+ " either the ServerConfig or on JsonReadOptions.");
|
||||
}
|
||||
return (ObjectMapper) objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JsonParser.
|
||||
*/
|
||||
public JsonParser getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next JsonToken from the underlying parser.
|
||||
*/
|
||||
public JsonToken nextToken() throws IOException {
|
||||
return parser.nextToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the path onto the stack (traversing a 1-M or M-1 etc)
|
||||
*/
|
||||
public void pushPath(String path) {
|
||||
if (pathStack != null) {
|
||||
pathStack.pushPathKey(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop the path stack.
|
||||
*/
|
||||
public void popPath() {
|
||||
if (pathStack != null) {
|
||||
pathStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a JsonReadBeanVisitor registered to the current path then
|
||||
* call it's visit method with the bean and unmappedProperties.
|
||||
*/
|
||||
@SuppressWarnings(value = "unchecked")
|
||||
public void beanVisitor(Object bean, Map<String, Object> unmappedProperties) {
|
||||
if (visitorMap != null) {
|
||||
JsonReadBeanVisitor visitor = visitorMap.get(pathStack.peekWithNull());
|
||||
if (visitor != null) {
|
||||
visitor.visit(bean, unmappedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the property value using Jackson ObjectMapper.
|
||||
* <p/>
|
||||
* Typically this is used to read Transient properties where the type is unknown to Ebean.
|
||||
*/
|
||||
public Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException {
|
||||
return getObjectMapper().readValue(parser, propertyType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebean.text.json.EJson;
|
||||
import io.ebean.text.json.JsonIOException;
|
||||
import io.ebean.text.json.JsonWriteBeanVisitor;
|
||||
import io.ebean.text.json.JsonWriter;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.util.ArrayStack;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class WriteJson implements JsonWriter {
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final JsonGenerator generator;
|
||||
|
||||
private final FetchPath fetchPath;
|
||||
|
||||
private final Map<String, JsonWriteBeanVisitor<?>> visitors;
|
||||
|
||||
private final PathStack pathStack;
|
||||
|
||||
private final ArrayStack<Object> parentBeans;
|
||||
|
||||
private final Object objectMapper;
|
||||
|
||||
private final JsonConfig.Include include;
|
||||
|
||||
/**
|
||||
* Construct for full bean use (normal).
|
||||
*/
|
||||
public WriteJson(SpiEbeanServer server, JsonGenerator generator, FetchPath fetchPath,
|
||||
Map<String, JsonWriteBeanVisitor<?>> visitors, Object objectMapper, JsonConfig.Include include) {
|
||||
|
||||
this.server = server;
|
||||
this.generator = generator;
|
||||
this.fetchPath = fetchPath;
|
||||
this.visitors = visitors;
|
||||
this.objectMapper = objectMapper;
|
||||
this.include = include;
|
||||
this.parentBeans = new ArrayStack<>();
|
||||
this.pathStack = new PathStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for Json scalar use.
|
||||
*/
|
||||
public WriteJson(JsonGenerator generator, JsonConfig.Include include) {
|
||||
this.generator = generator;
|
||||
this.include = include;
|
||||
this.visitors = null;
|
||||
this.server = null;
|
||||
this.fetchPath = null;
|
||||
this.objectMapper = null;
|
||||
this.parentBeans = null;
|
||||
this.pathStack = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if null values should be included in JSON output.
|
||||
*/
|
||||
public boolean isIncludeNull() {
|
||||
return include == JsonConfig.Include.ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if empty collections should be included in the JSON output.
|
||||
*/
|
||||
public boolean isIncludeEmpty() {
|
||||
return include != JsonConfig.Include.NON_EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonGenerator gen() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartObject(String key) {
|
||||
try {
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
generator.writeStartObject();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartObject() {
|
||||
try {
|
||||
generator.writeStartObject();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndObject() {
|
||||
try {
|
||||
generator.writeEndObject();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartArray(String key) {
|
||||
try {
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
generator.writeStartArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartArray() {
|
||||
try {
|
||||
generator.writeStartArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndArray() {
|
||||
try {
|
||||
generator.writeEndArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRaw(String text) {
|
||||
try {
|
||||
generator.writeRaw(text);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRawValue(String text) {
|
||||
try {
|
||||
generator.writeRawValue(text);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFieldName(String name) {
|
||||
try {
|
||||
generator.writeFieldName(name);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNullField(String name) {
|
||||
if (isIncludeNull()) {
|
||||
try {
|
||||
generator.writeNullField(name);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, long value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, double value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, int value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, short value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, float value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, BigDecimal value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStringField(String name, String value) {
|
||||
try {
|
||||
generator.writeStringField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBinary(InputStream is, int length) {
|
||||
try {
|
||||
generator.writeBinary(is, length);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBinaryField(String name, byte[] value) {
|
||||
try {
|
||||
generator.writeBinaryField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBooleanField(String name, boolean value) {
|
||||
try {
|
||||
generator.writeBooleanField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean value) {
|
||||
try {
|
||||
generator.writeBoolean(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeString(String value) {
|
||||
try {
|
||||
generator.writeString(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(int value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(long value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(double value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(BigDecimal value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNull() {
|
||||
try {
|
||||
generator.writeNull();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isParentBean(Object bean) {
|
||||
return !parentBeans.isEmpty() && parentBeans.contains(bean);
|
||||
}
|
||||
|
||||
public void pushParentBeanMany(Object parentBean) {
|
||||
parentBeans.push(parentBean);
|
||||
}
|
||||
|
||||
public void popParentBeanMany() {
|
||||
parentBeans.pop();
|
||||
}
|
||||
|
||||
public void beginAssocOne(String key, Object bean) {
|
||||
parentBeans.push(bean);
|
||||
pathStack.pushPathKey(key);
|
||||
}
|
||||
|
||||
public void endAssocOne() {
|
||||
parentBeans.pop();
|
||||
pathStack.pop();
|
||||
}
|
||||
|
||||
public void beginAssocMany(String key) {
|
||||
try {
|
||||
pathStack.pushPathKey(key);
|
||||
generator.writeFieldName(key);
|
||||
generator.writeStartArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void endAssocMany() {
|
||||
try {
|
||||
pathStack.pop();
|
||||
generator.writeEndArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WriteBean createWriteBean(BeanDescriptor<?> desc, EntityBean bean) {
|
||||
|
||||
String path = pathStack.peekWithNull();
|
||||
JsonWriteBeanVisitor visitor = (visitors == null) ? null : visitors.get(path);
|
||||
if (fetchPath == null) {
|
||||
return new WriteBean(desc, bean, visitor);
|
||||
}
|
||||
|
||||
boolean explicitAllProps = false;
|
||||
Set<String> currentIncludeProps = fetchPath.getProperties(path);
|
||||
if (currentIncludeProps != null) {
|
||||
explicitAllProps = currentIncludeProps.contains("*");
|
||||
if (explicitAllProps || currentIncludeProps.isEmpty()) {
|
||||
currentIncludeProps = null;
|
||||
}
|
||||
}
|
||||
return new WriteBean(desc, explicitAllProps, currentIncludeProps, bean, visitor);
|
||||
}
|
||||
|
||||
public void writeValueUsingObjectMapper(String name, Object value) {
|
||||
|
||||
if (!isIncludeEmpty()) {
|
||||
// check for suppression of empty collection or map
|
||||
if (value instanceof Collection && ((Collection) value).isEmpty()) {
|
||||
// suppress empty collection
|
||||
return;
|
||||
} else if (value instanceof Map && ((Map) value).isEmpty()) {
|
||||
// suppress empty map
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
generator.writeFieldName(name);
|
||||
objectMapper().writeValue(generator, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectMapper objectMapper() {
|
||||
if (objectMapper == null) {
|
||||
throw new IllegalStateException("Jackson ObjectMapper required but not set. Expected to be set on either serverConfig");
|
||||
}
|
||||
return (ObjectMapper) objectMapper;
|
||||
}
|
||||
|
||||
public static class WriteBean {
|
||||
|
||||
final boolean explicitAllProps;
|
||||
final Set<String> currentIncludeProps;
|
||||
final BeanDescriptor<?> desc;
|
||||
final EntityBean currentBean;
|
||||
final JsonWriteBeanVisitor visitor;
|
||||
|
||||
WriteBean(BeanDescriptor<?> desc, EntityBean currentBean, JsonWriteBeanVisitor visitor) {
|
||||
this(desc, false, null, currentBean, visitor);
|
||||
}
|
||||
|
||||
WriteBean(BeanDescriptor<?> desc, boolean explicitAllProps, Set<String> currentIncludeProps, EntityBean currentBean, JsonWriteBeanVisitor visitor) {
|
||||
super();
|
||||
this.desc = desc;
|
||||
this.currentBean = currentBean;
|
||||
this.explicitAllProps = explicitAllProps;
|
||||
this.currentIncludeProps = currentIncludeProps;
|
||||
this.visitor = visitor;
|
||||
}
|
||||
|
||||
private boolean isReferenceOnly() {
|
||||
return !explicitAllProps && currentIncludeProps == null && currentBean._ebean_getIntercept().isReference();
|
||||
}
|
||||
|
||||
private boolean isIncludeProperty(BeanProperty prop) {
|
||||
if (explicitAllProps)
|
||||
return true;
|
||||
if (currentIncludeProps != null) {
|
||||
// explicitly controlled by pathProperties
|
||||
return currentIncludeProps.contains(prop.getName());
|
||||
} else {
|
||||
// include only loaded properties
|
||||
return currentBean._ebean_getIntercept().isLoadedProperty(prop.getPropertyIndex());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIncludeTransientProperty(BeanProperty prop) {
|
||||
if (prop.isUnmappedJson()) {
|
||||
return false;
|
||||
} else if (!explicitAllProps && currentIncludeProps != null) {
|
||||
// explicitly controlled by pathProperties
|
||||
return currentIncludeProps.contains(prop.getName());
|
||||
} else {
|
||||
// by default include transient properties
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void write(WriteJson writeJson) {
|
||||
|
||||
try {
|
||||
BeanProperty beanProp = desc.getIdProperty();
|
||||
if (beanProp != null) {
|
||||
if (isIncludeProperty(beanProp)) {
|
||||
beanProp.jsonWrite(writeJson, currentBean);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isReferenceOnly()) {
|
||||
// render all the properties and invoke lazy loading if required
|
||||
BeanProperty[] props = desc.propertiesNonTransient();
|
||||
for (BeanProperty prop1 : props) {
|
||||
if (isIncludeProperty(prop1)) {
|
||||
prop1.jsonWrite(writeJson, currentBean);
|
||||
}
|
||||
}
|
||||
props = desc.propertiesTransient();
|
||||
for (BeanProperty prop : props) {
|
||||
if (isIncludeTransientProperty(prop)) {
|
||||
prop.jsonWrite(writeJson, currentBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BeanProperty unmappedJson = desc.propertyUnmappedJson();
|
||||
if (unmappedJson != null && unmappedJson.isJsonSerialize()) {
|
||||
Map<String,Object> map = (Map<String,Object>)unmappedJson.getValue(currentBean);
|
||||
if (map != null) {
|
||||
// write to JSON at the current level
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
writeJson.writeFieldName(entry.getKey());
|
||||
EJson.write(entry.getValue(), writeJson.generator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visitor != null) {
|
||||
visitor.visit(currentBean, writeJson);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean includeMany(String key) {
|
||||
if (fetchPath != null) {
|
||||
String fullPath = pathStack.peekFullPath(key);
|
||||
return fetchPath.hasPath(fullPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void toJson(String name, Collection<?> c) {
|
||||
|
||||
try {
|
||||
beginAssocMany(name);
|
||||
|
||||
for (Object bean : c) {
|
||||
BeanDescriptor<?> d = getDescriptor(bean.getClass());
|
||||
d.jsonWrite(this, (EntityBean) bean, null);
|
||||
}
|
||||
endAssocMany();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> BeanDescriptor<T> getDescriptor(Class<T> cls) {
|
||||
BeanDescriptor<T> d = server.getBeanDescriptor(cls);
|
||||
if (d == null) {
|
||||
throw new RuntimeException("No BeanDescriptor found for " + cls);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user