Refactor from Jackson Core to avaje-json-core

This commit is contained in:
robin.bygrave
2026-06-18 22:00:33 +12:00
parent d42f72c0a2
commit 313fdda857
103 changed files with 1490 additions and 1652 deletions
+1
View File
@@ -183,6 +183,7 @@ database.save(customer);
| Configure database and `Database` bean | [add-ebean-postgres-database-config.md](guides/add-ebean-postgres-database-config.md) |
| Add PostgreSQL test container support | [add-ebean-postgres-test-container.md](guides/add-ebean-postgres-test-container.md) |
| Generate DB migrations | [add-ebean-db-migration-generation.md](guides/add-ebean-db-migration-generation.md) |
| Migrate JSON APIs from Jackson core to avaje-json-core | [migrating-json-jackson-core-to-avaje-json-core.md](guides/migrating-json-jackson-core-to-avaje-json-core.md) |
| Model entity beans correctly | [entity-bean-creation.md](guides/entity-bean-creation.md) |
| Use Lombok safely with entities | [lombok-with-ebean-entity-beans.md](guides/lombok-with-ebean-entity-beans.md) |
| Write type-safe query bean queries | [writing-ebean-query-beans.md](guides/writing-ebean-query-beans.md) |
+1
View File
@@ -12,6 +12,7 @@ Key guides (fetch and follow when performing the relevant task):
- Maven POM setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-maven-pom.md
- Database configuration: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-database-config.md
- Migrate to `Database.builder()`: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-to-database-builder.md
- Migrate JSON APIs from Jackson core to avaje-json-core: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-json-jackson-core-to-avaje-json-core.md
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
- Query metrics and naming: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-metrics.md
+1
View File
@@ -23,6 +23,7 @@ existing Maven project. Complete the steps in order.
| Guide | Description |
|-------|-------------|
| [Migrate to `Database.builder()`](migrating-to-database-builder.md) | Replace legacy `new DatabaseConfig()` and `DatabaseFactory.create(...)` code with `Database.builder()` and `DatabaseBuilder.build()`. Includes common rewrites, fluent builder equivalents, and manual-review cases for semi-automated upgrades |
| [Migrate JSON APIs from Jackson core to avaje-json-core](migrating-json-jackson-core-to-avaje-json-core.md) | Cut over `JsonParser`/`JsonGenerator`/`JsonFactory` usage to `JsonReader`/`JsonWriter`/`JsonStream`, including `DatabaseBuilder`/`DatabaseConfig` JSON config changes and validation checklist |
## Observability
@@ -0,0 +1,91 @@
# Guide: Migrate JSON APIs from Jackson core to avaje-json-core
## Purpose
This guide covers the one-step cutover in Ebean from Jackson core JSON APIs to
avaje-json-core APIs.
Use this when upgrading code that references:
- `com.fasterxml.jackson.core.JsonParser`
- `com.fasterxml.jackson.core.JsonGenerator`
- `com.fasterxml.jackson.core.JsonFactory`
The replacement types are:
- `io.avaje.json.JsonReader`
- `io.avaje.json.JsonWriter`
- `io.avaje.json.stream.JsonStream`
---
## Breaking API changes
| Previous API | New API |
|---|---|
| `JsonParser` | `JsonReader` |
| `JsonGenerator` | `JsonWriter` |
| `JsonFactory` | `JsonStream` |
| `DatabaseBuilder.jsonFactory(...)` | `DatabaseBuilder.jsonStream(...)` |
| `DatabaseConfig.getJsonFactory()/setJsonFactory(...)` | `DatabaseConfig.getJsonStream()/setJsonStream(...)` |
---
## Typical migration rewrites
### Parser and generator signatures
```java
// before
void read(JsonParser parser)
void write(JsonGenerator generator)
// after
void read(JsonReader parser)
void write(JsonWriter generator)
```
### Database configuration
```java
// before
Database.builder().jsonFactory(factory)
// after
Database.builder().jsonStream(stream)
```
### JSON utility calls
`EJson` and `JsonContext` APIs now operate on `JsonReader` and `JsonWriter` types.
If your code was calling those APIs with Jackson core types, switch to avaje types.
---
## Dependency and module notes
- `ebean-core` no longer requires a direct `jackson-core` dependency for JSON
parsing/writing.
- `jackson-databind` remains optional for `ObjectMapper` compatibility paths.
- `ebean-jackson-mapper` remains the compatibility bridge module for mapper-based
integrations.
---
## Behavior notes to verify during upgrade
1. Parser token handling is now based on avaje `JsonReader.Token`.
2. Scalar JSON reads (for example booleans, date-time, array scalar types) should
be validated in your tests if you previously depended on Jackson token quirks.
3. If your integration uses transient assoc-many JSON mapping with ObjectMapper,
keep ObjectMapper wiring enabled.
---
## Validation checklist
1. Compile all modules that implement or consume `io.ebean.text.json` APIs.
2. Run module tests that cover JSON scalar conversion and bean JSON round-trips.
3. Confirm no remaining `com.fasterxml.jackson.core.*` imports in migrated code.
4. Keep `ObjectMapper` compatibility tests if your project depends on mapper paths.
+4 -6
View File
@@ -70,15 +70,13 @@
<optional>true</optional>
</dependency>
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<optional>true</optional>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-core</artifactId>
<version>${avaje-json-core.version}</version>
</dependency>
<!-- provided scope for JsonNode support -->
<!-- Jackson databind remains for ObjectMapper compatibility paths -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@@ -1,6 +1,6 @@
package io.ebean;
import com.fasterxml.jackson.core.JsonFactory;
import io.avaje.json.stream.JsonStream;
import io.ebean.annotation.*;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.*;
@@ -360,18 +360,18 @@ public interface DatabaseBuilder {
DatabaseBuilder putServiceObject(Object configObject);
/**
* Set the Jackson JsonFactory to use.
* Set the JsonStream to use.
* <p>
* If not set a default implementation will be used.
*/
default DatabaseBuilder jsonFactory(JsonFactory jsonFactory) {
return setJsonFactory(jsonFactory);
default DatabaseBuilder jsonStream(JsonStream jsonStream) {
return setJsonStream(jsonStream);
}
/**
* @deprecated migrate to {@link #jsonFactory(JsonFactory)}.
* @deprecated migrate to {@link #jsonStream(JsonStream)}.
*/
DatabaseBuilder setJsonFactory(JsonFactory jsonFactory);
DatabaseBuilder setJsonStream(JsonStream jsonStream);
/**
* Set the JSON format to use for DateTime types.
@@ -2254,11 +2254,11 @@ public interface DatabaseBuilder {
boolean isAutoLoadModuleInfo();
/**
* Return the Jackson JsonFactory to use.
* Return the JsonStream to use.
* <p>
* If not set a default implementation will be used.
*/
JsonFactory getJsonFactory();
JsonStream getJsonStream();
/**
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
@@ -60,7 +60,8 @@ public class ClassLoadConfig {
}
public boolean isJacksonCorePresent() {
return isPresent("com.fasterxml.jackson.core.JsonParser");
// Legacy method name retained for compatibility; now checks avaje JSON core.
return isPresent("io.avaje.json.JsonReader");
}
/**
@@ -158,4 +159,3 @@ public class ClassLoadConfig {
}
}
}
@@ -1,7 +1,7 @@
package io.ebean.config;
import com.fasterxml.jackson.core.JsonFactory;
import io.avaje.config.Config;
import io.avaje.json.stream.JsonStream;
import io.ebean.*;
import io.ebean.annotation.MutationDetection;
import io.ebean.annotation.PersistBatch;
@@ -420,7 +420,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
* The default PersistenceContextScope used if one is not explicitly set on a query.
*/
private PersistenceContextScope persistenceContextScope = PersistenceContextScope.TRANSACTION;
private JsonFactory jsonFactory;
private JsonStream jsonStream;
private boolean localTimeWithNanos;
private boolean durationWithNanos;
private int maxCallStack = 5;
@@ -631,13 +631,13 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
}
@Override
public JsonFactory getJsonFactory() {
return jsonFactory;
public JsonStream getJsonStream() {
return jsonStream;
}
@Override
public DatabaseConfig setJsonFactory(JsonFactory jsonFactory) {
this.jsonFactory = jsonFactory;
public DatabaseConfig setJsonStream(JsonStream jsonStream) {
this.jsonStream = jsonStream;
return this;
}
@@ -1,8 +1,8 @@
package io.ebean.service;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import java.io.IOException;
import java.io.Reader;
@@ -32,12 +32,12 @@ public interface SpiJsonService extends BootstrapService {
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
void write(Object object, JsonGenerator jsonGenerator) throws IOException;
void write(Object object, JsonWriter jsonGenerator) throws IOException;
/**
* Write the collection as json array to the jsonGenerator.
*/
void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException;
void writeCollection(Collection<Object> collection, JsonWriter jsonGenerator) throws IOException;
/**
* Parse the json and return as a Map additionally specifying if the returned map should
@@ -61,17 +61,17 @@ public interface SpiJsonService extends BootstrapService {
Map<String, Object> parseObject(Reader reader) throws IOException;
/**
* Parse the json and return as a Map taking a JsonParser.
* Parse the json and return as a Map taking a JsonReader.
*/
Map<String, Object> parseObject(JsonParser parser) throws IOException;
Map<String, Object> parseObject(JsonReader parser) throws IOException;
/**
* Parse the json and return as a Map taking a JsonParser and a starting token.
* Parse the json and return as a Map taking a JsonReader and a starting token.
* <p>
* Used when the first token is checked to see if the value is null prior to calling this.
* </p>
*/
Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException;
Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException;
/**
* Parse the json and return as a modify aware List.
@@ -89,14 +89,14 @@ public interface SpiJsonService extends BootstrapService {
List<Object> parseList(Reader reader) throws IOException;
/**
* Parse the json and return as a List taking a JsonParser.
* Parse the json and return as a List taking a JsonReader.
*/
List<Object> parseList(JsonParser parser) throws IOException;
List<Object> parseList(JsonReader parser) throws IOException;
/**
* Parse the json returning as a List taking into account the current token.
*/
<T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException;
<T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException;
/**
* Parse the json and return as a List or Map.
@@ -111,7 +111,7 @@ public interface SpiJsonService extends BootstrapService {
/**
* Parse the json and return as a List or Map.
*/
Object parse(JsonParser parser) throws IOException;
Object parse(JsonReader parser) throws IOException;
/**
* Parse the json returning a Set that might be modify aware.
@@ -121,5 +121,5 @@ public interface SpiJsonService extends BootstrapService {
/**
* Parse the json returning as a Set taking into account the current token.
*/
<T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException;
<T> Set<T> parseSet(JsonReader parser, Token currentToken) throws IOException;
}
@@ -1,8 +1,7 @@
package io.ebean.text.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.ebean.XBootstrapService;
import io.ebean.service.SpiJsonService;
@@ -38,14 +37,14 @@ public class EJson {
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
public static void write(Object object, io.avaje.json.JsonWriter jsonGenerator) throws IOException {
plugin.write(object, jsonGenerator);
}
/**
* Write the collection as json array to the jsonGenerator.
*/
public static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
public static void writeCollection(Collection<Object> collection, io.avaje.json.JsonWriter jsonGenerator) throws IOException {
plugin.writeCollection(collection, jsonGenerator);
}
@@ -79,19 +78,19 @@ public class EJson {
}
/**
* Parse the json and return as a Map taking a JsonParser.
* Parse the json and return as a Map taking a JsonReader.
*/
public static Map<String, Object> parseObject(JsonParser parser) throws IOException {
public static Map<String, Object> parseObject(JsonReader parser) throws IOException {
return plugin.parseObject(parser);
}
/**
* Parse the json and return as a Map taking a JsonParser and a starting token.
* Parse the json and return as a Map taking a JsonReader and a starting token.
* <p>
* Used when the first token is checked to see if the value is null prior to calling this.
* </p>
*/
public static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
public static Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException {
return plugin.parseObject(parser, token);
}
@@ -117,16 +116,16 @@ public class EJson {
}
/**
* Parse the json and return as a List taking a JsonParser.
* Parse the json and return as a List taking a JsonReader.
*/
public static List<Object> parseList(JsonParser parser) throws IOException {
public static List<Object> parseList(JsonReader parser) throws IOException {
return plugin.parseList(parser);
}
/**
* Parse the json returning as a List taking into account the current token.
*/
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
public static <T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException {
return plugin.parseList(parser, currentToken);
}
@@ -147,7 +146,7 @@ public class EJson {
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(JsonParser parser) throws IOException {
public static Object parse(JsonReader parser) throws IOException {
return plugin.parse(parser);
}
@@ -161,7 +160,7 @@ public class EJson {
/**
* Parse the json returning as a Set taking into account the current token.
*/
public static <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
public static <T> Set<T> parseSet(JsonReader parser, Token currentToken) throws IOException {
return plugin.parseSet(parser, currentToken);
}
}
@@ -1,6 +1,6 @@
package io.ebean.text.json;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.bean.PersistenceContext;
/**
@@ -25,9 +25,9 @@ public interface JsonBeanReader<T> {
}
/**
* Create a new reader taking the context from the existing one but using a new JsonParser.
* Create a new reader taking the context from the existing one but using a new JsonReader.
*/
JsonBeanReader<T> forJson(JsonParser moreJson);
JsonBeanReader<T> forJson(JsonReader moreJson);
/**
* Add a bean explicitly to the persistence context.
@@ -1,7 +1,6 @@
package io.ebean.text.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.FetchPath;
import io.ebean.plugin.BeanType;
@@ -49,14 +48,14 @@ public interface JsonContext {
*
* @throws JsonIOException When IOException occurs
*/
<T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException;
<T> T toBean(Class<T> cls, JsonReader parser) throws JsonIOException;
/**
* Convert json parser input into a Bean of a specific type additionally using JsonReadOptions..
*
* @throws JsonIOException When IOException occurs
*/
<T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
<T> T toBean(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException;
/**
* Read json parser input into a given Bean. <br>
@@ -65,19 +64,19 @@ public interface JsonContext {
*
* @throws JsonIOException When IOException occurs
*/
<T> void toBean(T target, JsonParser parser) throws JsonIOException;
<T> void toBean(T target, JsonReader parser) throws JsonIOException;
/**
* Read json parser input into a given Bean additionally using JsonReadOptions.<br>
* See {@link #toBean(Class, JsonParser)} for details modified.
* See {@link #toBean(Class, JsonReader)} for details modified.
*
* @throws JsonIOException When IOException occurs
*/
<T> void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException;
<T> void toBean(T target, JsonReader parser, JsonReadOptions options) throws JsonIOException;
/**
* Read json reader input into a given Bean.<br>
* See {@link #toBean(Class, JsonParser)} for details
* See {@link #toBean(Class, JsonReader)} for details
*
* @throws JsonIOException When IOException occurs
*/
@@ -85,7 +84,7 @@ public interface JsonContext {
/**
* Read json reader input into a given Bean additionally using JsonReadOptions.<br>
* See {@link #toBean(Class, JsonParser)} for details modified.
* See {@link #toBean(Class, JsonReader)} for details modified.
*
* @throws JsonIOException When IOException occurs
*/
@@ -93,7 +92,7 @@ public interface JsonContext {
/**
* Read json string input into a given Bean.<br>
* See {@link #toBean(Class, JsonParser)} for details
* See {@link #toBean(Class, JsonReader)} for details
*
* @throws JsonIOException When IOException occurs
*/
@@ -101,7 +100,7 @@ public interface JsonContext {
/**
* Read json string input into a given Bean additionally using JsonReadOptions.<br>
* See {@link #toBean(Class, JsonParser)} for details
* See {@link #toBean(Class, JsonReader)} for details
*
* @throws JsonIOException When IOException occurs
*/
@@ -113,7 +112,7 @@ public interface JsonContext {
* Note that JsonOption provides an option for setting a persistence context and also enabling further lazy loading. Further lazy
* loading requires a persistence context so if that is set on then a persistence context is created if there is not one set.
*/
<T> JsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
<T> JsonBeanReader<T> createBeanReader(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException;
/**
* Create and return a new bean reading for the bean type given the JSON options and source.
@@ -122,7 +121,7 @@ public interface JsonContext {
* further lazy loading. Further lazy loading requires a persistence context so if that is set
* on then a persistence context is created if there is not one set.
*/
<T> JsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException;
<T> JsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonReader parser, JsonReadOptions options) throws JsonIOException;
/**
* Convert json string input into a list of beans of a specific type.
@@ -157,14 +156,14 @@ public interface JsonContext {
*
* @throws JsonIOException When IOException occurs
*/
<T> List<T> toList(Class<T> cls, JsonParser json) throws JsonIOException;
<T> List<T> toList(Class<T> cls, JsonReader json) throws JsonIOException;
/**
* Convert json parser input into a list of beans of a specific type additionally using JsonReadOptions.
*
* @throws JsonIOException When IOException occurs
*/
<T> List<T> toList(Class<T> cls, JsonParser json, JsonReadOptions options) throws JsonIOException;
<T> List<T> toList(Class<T> cls, JsonReader json, JsonReadOptions options) throws JsonIOException;
/**
* Use the genericType to determine if this should be converted into a List or
@@ -188,7 +187,7 @@ public interface JsonContext {
*
* @throws JsonIOException When IOException occurs
*/
Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException;
Object toObject(Type genericType, JsonReader jsonParser) throws JsonIOException;
/**
* Return the bean or collection as JSON string.
@@ -212,11 +211,11 @@ public interface JsonContext {
void toJson(Object value, Writer writer) throws JsonIOException;
/**
* Write the bean or collection to the JsonGenerator.
* Write the bean or collection to the JsonWriter.
*
* @throws JsonIOException When IOException occurs
*/
void toJson(Object value, JsonGenerator generator) throws JsonIOException;
void toJson(Object value, io.avaje.json.JsonWriter generator) throws JsonIOException;
/**
* Return the bean or collection as JSON string using FetchPath.
@@ -231,15 +230,15 @@ public interface JsonContext {
void toJson(Object value, Writer writer, FetchPath fetchPath) throws JsonIOException;
/**
* Write the bean or collection to the JsonGenerator using the FetchPath.
* Write the bean or collection to the JsonWriter using the FetchPath.
*/
void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException;
void toJson(Object value, io.avaje.json.JsonWriter generator, FetchPath fetchPath) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
* Write json to the JsonGenerator using the JsonWriteOptions.
* Write json to the JsonWriter using the JsonWriteOptions.
*/
void toJson(Object value, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException;
void toJson(Object value, io.avaje.json.JsonWriter generator, JsonWriteOptions options) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
@@ -264,27 +263,27 @@ public interface JsonContext {
boolean isSupportedType(Type genericType);
/**
* Create and return a new JsonGenerator for the given writer.
* Create and return a new JsonWriter for the given writer.
*
* @throws JsonIOException When IOException occurs
*/
JsonGenerator createGenerator(Writer writer) throws JsonIOException;
io.avaje.json.JsonWriter createGenerator(Writer writer) throws JsonIOException;
/**
* Create and return a new JsonParser for the given reader.
* Create and return a new JsonReader for the given reader.
*
* @throws JsonIOException When IOException occurs
*/
JsonParser createParser(Reader reader) throws JsonIOException;
JsonReader createParser(Reader reader) throws JsonIOException;
/**
* Write a scalar types known to Ebean to Jackson.
* Write scalar types known to Ebean to JsonWriter.
* <p>
* Ebean has built in support for java8 and Joda types as well as the other
* standard JDK types like URI, URL, UUID etc. This is a fast simple way to
* write any of those types to Jackson.
* write any of those types.
* </p>
*/
void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException;
void writeScalar(io.avaje.json.JsonWriter generator, Object scalarValue) throws IOException;
}
@@ -1,19 +1,17 @@
package io.ebean.text.json;
import com.fasterxml.jackson.core.JsonGenerator;
import java.io.InputStream;
import java.math.BigDecimal;
/**
* Wraps an underlying JsonGenerator taking into account null suppression and exposing isIncludeEmpty() etc.
* Wraps an underlying JsonWriter taking into account null suppression and exposing isIncludeEmpty() etc.
*/
public interface JsonWriter {
/**
* Return the Jackson core JsonGenerator.
* Return the underlying JsonWriter.
*/
JsonGenerator gen();
io.avaje.json.JsonWriter gen();
/**
* Return true if null values should be included in JSON output.
+1 -1
View File
@@ -8,6 +8,7 @@ module io.ebean.api {
requires transitive java.sql;
requires transitive io.avaje.config;
requires transitive io.avaje.json;
requires transitive org.jspecify;
requires transitive jakarta.persistence.api;
requires transitive io.ebean.annotation;
@@ -16,7 +17,6 @@ module io.ebean.api {
requires static org.slf4j;
requires static io.ebean.types;
requires static com.fasterxml.jackson.core;
requires static com.fasterxml.jackson.databind;
exports io.ebean;
+3 -5
View File
@@ -19,12 +19,10 @@
<version>16.11.0</version>
</dependency>
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<optional>true</optional>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-core</artifactId>
<version>${avaje-json-core.version}</version>
</dependency>
</dependencies>
@@ -1,190 +1,129 @@
package io.ebeaninternal.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import io.ebean.service.SpiJsonService;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.*;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Utility that converts between JSON content and simple java Maps/Lists.
*/
public final class DJsonService implements SpiJsonService {
/**
* Write the nested Map/List as json.
*/
@Override
public String write(Object object) throws IOException {
return EJsonWriter.write(object);
}
/**
* Write the nested Map/List as json to the writer.
*/
@Override
public void write(Object object, Writer writer) throws IOException {
EJsonWriter.write(object, writer);
}
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
@Override
public void write(Object object, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.write(object, jsonGenerator);
public void write(Object object, JsonWriter jsonWriter) throws IOException {
EJsonWriter.write(object, jsonWriter);
}
/**
* Write the collection as json array to the jsonGenerator.
*/
@Override
public void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.writeCollection(collection, jsonGenerator);
public void writeCollection(Collection<Object> collection, JsonWriter jsonWriter) throws IOException {
EJsonWriter.writeCollection(collection, jsonWriter);
}
/**
* Parse the json and return as a Map additionally specifying if the returned map should be modify
* aware meaning that it can detect when it has been modified.
*/
@Override
public Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(json, modifyAware);
}
/**
* Parse the json and return as a Map.
*/
@Override
public Map<String, Object> parseObject(String json) throws IOException {
return EJsonReader.parseObject(json);
}
/**
* Parse the json and return as a Map taking a reader.
*/
@Override
public Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(reader, modifyAware);
}
/**
* Parse the json and return as a Map taking a reader.
*/
@Override
public Map<String, Object> parseObject(Reader reader) throws IOException {
return EJsonReader.parseObject(reader);
}
/**
* Parse the json and return as a Map taking a JsonParser.
*/
@Override
public Map<String, Object> parseObject(JsonParser parser) throws IOException {
public Map<String, Object> parseObject(JsonReader parser) throws IOException {
return EJsonReader.parseObject(parser);
}
/**
* Parse the json and return as a Map taking a JsonParser and a starting token.
*
* <p>Used when the first token is checked to see if the value is null prior to calling this.
*/
@Override
public Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
public Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException {
return EJsonReader.parseObject(parser, token);
}
/**
* Parse the json and return as a modify aware List.
*/
@Override
public <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseList(json, modifyAware);
}
/**
* Parse the json and return as a List.
*/
@Override
public List<Object> parseList(String json) throws IOException {
return EJsonReader.parseList(json);
}
/**
* Parse the json and return as a List taking a Reader.
*/
@Override
public List<Object> parseList(Reader reader) throws IOException {
return EJsonReader.parseList(reader);
}
/**
* Parse the json and return as a List taking a JsonParser.
*/
@Override
public List<Object> parseList(JsonParser parser) throws IOException {
public List<Object> parseList(JsonReader parser) throws IOException {
return EJsonReader.parseList(parser, false);
}
/**
* Parse the json returning as a List taking into account the current token.
*/
@Override
@SuppressWarnings("unchecked")
public <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
public <T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException {
return (List<T>) EJsonReader.parse(parser, currentToken, false);
}
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(String json) throws IOException {
return EJsonReader.parse(json);
}
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(Reader reader) throws IOException {
return EJsonReader.parse(reader);
}
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(JsonParser parser) throws IOException {
public Object parse(JsonReader parser) throws IOException {
return EJsonReader.parse(parser);
}
/**
* Parse the json returning a Set that might be modify aware.
*/
@Override
public <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
List<T> list = parseList(json, modifyAware);
if (list == null) {
return null;
}
if (modifyAware) {
return ((ModifyAwareList<T>) list).asSet();
} else {
return new LinkedHashSet<>(list);
}
return new LinkedHashSet<>(list);
}
/**
* Parse the json returning as a Set taking into account the current token.
*/
@Override
public <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
public <T> Set<T> parseSet(JsonReader parser, Token currentToken) throws IOException {
return new LinkedHashSet<>(parseList(parser, currentToken));
}
}
@@ -1,29 +1,25 @@
package io.ebeaninternal.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.stream.JsonStream;
import io.ebean.ModifyAwareType;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
final class EJsonReader {
static final JsonFactory json = new JsonFactory();
private final JsonParser parser;
private final boolean modifyAware;
private final ModifyAwareFlag modifyAwareOwner;
private int depth;
private Stack stack;
private Context currentContext;
private EJsonReader() {
}
EJsonReader(JsonParser parser, boolean modifyAware) {
this.parser = parser;
this.modifyAware = modifyAware;
this.modifyAwareOwner = modifyAware ? new ModifyAwareFlag() : null;
private static JsonReader reader(String content) {
return JsonStream.builder().build().reader(content);
}
@SuppressWarnings("unchecked")
@@ -47,12 +43,12 @@ final class EJsonReader {
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(JsonParser parser) throws IOException {
static Map<String, Object> parseObject(JsonReader parser) throws IOException {
return (Map<String, Object>) parse(parser);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
static Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException {
return (Map<String, Object>) parse(parser, token, false);
}
@@ -72,285 +68,137 @@ final class EJsonReader {
}
@SuppressWarnings("unchecked")
static List<Object> parseList(JsonParser parser, boolean modifyAware) throws IOException {
return (List<Object>) parse(parser, modifyAware);
static List<Object> parseList(JsonReader parser, boolean modifyAware) throws IOException {
return (List<Object>) parse(parser, null, modifyAware);
}
static Object parse(String json) throws IOException {
if (json == null) {
return null;
}
return parse(new StringReader(json));
return parseRawJson(json, null);
}
static Object parse(String json, boolean modifyAware) throws IOException {
if (json == null) {
return null;
}
return parse(new StringReader(json), modifyAware);
return parseRawJson(json, modifyAware ? new ModifyAwareFlag() : null);
}
static Object parse(Reader reader) throws IOException {
return parse(json.createParser(reader));
return parseRawJson(readAll(reader), null);
}
static Object parse(Reader reader, boolean modifyAware) throws IOException {
return parse(json.createParser(reader), modifyAware);
return parseRawJson(readAll(reader), modifyAware ? new ModifyAwareFlag() : null);
}
static Object parse(JsonParser parser) throws IOException {
static Object parse(JsonReader parser) throws IOException {
return parse(parser, null, false);
}
static Object parse(JsonParser parser, boolean modifyAware) throws IOException {
static Object parse(JsonReader parser, boolean modifyAware) throws IOException {
return parse(parser, null, modifyAware);
}
static Object parse(JsonParser parser, JsonToken token, boolean modifyAware) throws IOException {
return new EJsonReader(parser, modifyAware).parseJson(token);
}
private void startArray() {
depth++;
stack.push(currentContext);
currentContext = modifyAware ? new ArrayContext(modifyAwareOwner) : new ArrayContext();
}
private void startObject() {
depth++;
stack.push(currentContext);
currentContext = modifyAware ? new ObjectContext(modifyAwareOwner) : new ObjectContext();
}
private void endArray() {
end();
}
private void endObject() {
end();
}
private void end() {
depth--;
if (!stack.isEmpty()) {
currentContext = stack.pop(currentContext);
}
if (modifyAwareOwner != null) {
modifyAwareOwner.setMarkedDirty(false);
static Object parse(JsonReader parser, Token token, boolean modifyAware) throws IOException {
ModifyAwareType owner = modifyAware ? new ModifyAwareFlag() : null;
Token effectiveToken = token == null ? parser.currentToken() : token;
if (effectiveToken == null) {
return parseRawJson(parser.readRaw(), owner);
}
return parseValue(parser, effectiveToken, owner);
}
private void setValue(Object value) {
currentContext.setValue(value);
}
private void setValueNull() {
currentContext.setValueNull();
}
private Object parseJson(JsonToken token) throws IOException {
private static Object parseValue(JsonReader parser, Token token, ModifyAwareType owner) throws IOException {
if (token == null) {
token = parser.nextToken();
// if it is a simple value just return it
switch (token) {
case VALUE_NULL:
token = parser.currentToken();
if (token == null) {
if (parser.isNullValue()) {
return null;
case VALUE_FALSE:
return Boolean.FALSE;
case VALUE_TRUE:
return Boolean.TRUE;
case VALUE_STRING:
return parser.getText();
case VALUE_NUMBER_INT:
return parser.getLongValue();
case VALUE_NUMBER_FLOAT:
return parser.getDecimalValue();
}
return parseRawJson(parser.readRaw(), owner);
}
}
// it is a object or array, process the first JsonToken
stack = new Stack();
processJsonToken(token);
// process the rest of the object or array
while (depth > 0) {
token = parser.nextToken();
processJsonToken(token);
if (token == Token.BEGIN_OBJECT) {
return parseObjectValue(parser, owner);
}
return currentContext.getValue();
if (token == Token.BEGIN_ARRAY) {
return parseArrayValue(parser, owner);
}
if (token == Token.NUMBER) {
BigDecimal value = parser.readDecimal();
return value.scale() <= 0 ? value.longValue() : value;
}
if (token == Token.STRING) {
return parser.readString();
}
if (token == Token.BOOLEAN) {
return parser.readBoolean();
}
if (token == Token.NULL) {
parser.isNullValue();
return null;
}
return parseRawJson(parser.readRaw(), owner);
}
/**
* Process the JsonToken for objects and arrays.
*/
private void processJsonToken(JsonToken token) throws IOException {
switch (token) {
case START_ARRAY:
startArray();
break;
case START_OBJECT:
startObject();
break;
case FIELD_NAME:
currentContext.setKey(parser.getCurrentName());
break;
case VALUE_STRING:
setValue(parser.getValueAsString());
break;
case VALUE_NUMBER_INT:
setValue(parser.getLongValue());
break;
case VALUE_NUMBER_FLOAT:
setValue(parser.getDecimalValue());
break;
case VALUE_TRUE:
setValue(Boolean.TRUE);
break;
case VALUE_FALSE:
setValue(Boolean.FALSE);
break;
case VALUE_NULL:
setValueNull();
break;
case END_OBJECT:
endObject();
break;
case END_ARRAY:
endArray();
break;
default:
break;
private static Object parseRawJson(String json, ModifyAwareType owner) throws IOException {
if (json == null) {
return null;
}
String content = json.trim();
if (content.isEmpty()) {
return null;
}
JsonReader parser = reader(content);
Token token = parser.currentToken();
return parseValue(parser, token, owner);
}
private static final class Stack {
private static String readAll(Reader reader) throws IOException {
StringBuilder builder = new StringBuilder();
char[] buffer = new char[2048];
int len;
while ((len = reader.read(buffer)) != -1) {
builder.append(buffer, 0, len);
}
return builder.toString();
}
private Context head;
private static Map<String, Object> parseObjectValue(JsonReader parser, ModifyAwareType owner) throws IOException {
Map<String, Object> map = owner == null
? new LinkedHashMap<>()
: new ModifyAwareMap<>(owner, new LinkedHashMap<>());
private void push(Context context) {
if (context != null) {
context.next = head;
head = context;
parser.beginObject();
while (parser.hasNextField()) {
String fieldName = parser.nextField();
map.put(fieldName, parseValue(parser, parser.currentToken(), owner));
if (owner != null) {
owner.setMarkedDirty(false);
}
}
parser.endObject();
if (owner != null) {
owner.setMarkedDirty(false);
}
return map;
}
private Context pop(Context endingContext) {
if (head == null) {
throw new NoSuchElementException();
private static List<Object> parseArrayValue(JsonReader parser, ModifyAwareType owner) throws IOException {
List<Object> list = owner == null
? new ArrayList<>()
: new ModifyAwareList<>(owner, new ArrayList<>());
parser.beginArray();
while (parser.hasNextElement()) {
Token elementToken = parser.currentToken();
Object elementValue = parseValue(parser, elementToken, owner);
list.add(elementValue);
if (owner != null) {
owner.setMarkedDirty(false);
}
Context temp = head;
head = head.next;
temp.popContext(endingContext);
return temp;
}
private boolean isEmpty() {
return head == null;
}
}
private abstract static class Context {
Context next;
abstract void popContext(Context temp);
abstract Object getValue();
abstract void setValue(Object value);
abstract void setKey(String key);
abstract void setValueNull();
}
private static class ObjectContext extends Context {
private final Map<String, Object> map;
private String key;
ObjectContext() {
map = new LinkedHashMap<>();
}
ObjectContext(ModifyAwareType owner) {
map = new ModifyAwareMap<>(owner, new LinkedHashMap<>());
}
@Override
public void popContext(Context temp) {
setValue(temp.getValue());
}
@Override
Object getValue() {
return map;
}
@Override
void setValue(Object value) {
map.put(key, value);
}
@Override
void setKey(String key) {
this.key = key;
}
@Override
void setValueNull() {
map.put(key, null);
}
}
private static class ArrayContext extends Context {
private final List<Object> values;
ArrayContext() {
values = new ArrayList<>();
}
ArrayContext(ModifyAwareType owner) {
values = new ModifyAwareList<>(owner, new ArrayList<>());
}
@Override
public void popContext(Context temp) {
values.add(temp.getValue());
}
@Override
Object getValue() {
return values;
}
@Override
void setValue(Object value) {
values.add(value);
}
@Override
void setValueNull() {
// ignore
}
@Override
void setKey(String key) {
// not expected
parser.endArray();
if (owner != null) {
owner.setMarkedDirty(false);
}
return list;
}
}
@@ -1,212 +1,149 @@
package io.ebeaninternal.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.stream.JsonStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Utility to write simple java Maps/Lists as JSON.
*/
final class EJsonWriter {
/**
* Base jsonFactory implementation used when it is not passed in.
*/
static final JsonFactory jsonFactory = new JsonFactory();
private final JsonGenerator jsonGenerator;
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
private EJsonWriter(JsonGenerator jsonGenerator) {
this.jsonGenerator = jsonGenerator;
private EJsonWriter() {
}
/**
* Convert object to Json content.
*/
static String write(Object object) throws IOException {
StringWriter writer = new StringWriter(200);
write(object, writer).close();
StringWriter writer = new StringWriter();
write(object, writer);
return writer.toString();
}
static JsonGenerator write(Object object, Writer writer) throws IOException {
JsonGenerator generator = jsonFactory.createGenerator(writer);
write(object, generator);
generator.flush();
return generator;
/**
* Convert object to Json content.
*/
static io.avaje.json.JsonWriter write(Object object, Writer writer) throws IOException {
io.avaje.json.JsonWriter jsonWriter = JSON_STREAM.writer(writer);
jsonWriter.serializeNulls(true);
write(object, jsonWriter);
jsonWriter.flush();
return jsonWriter;
}
static void write(Object object, JsonGenerator jsonGenerator) {
new EJsonWriter(jsonGenerator).writeJson(object);
}
static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
new EJsonWriter(jsonGenerator).writeCollection(null, collection);
}
private void writeJson(Object object) {
writeJson(null, object);
}
@SuppressWarnings("unchecked")
private void writeJson(String name, Object object) {
try {
if (object == null) {
writeNull(name);
} else if (object instanceof Number) {
writeNumber(name, (Number) object);
} else if (object instanceof String) {
writeString(name, (String) object);
} else if (object instanceof Map) {
writeMap(name, (Map<Object, Object>) object);
} else if (object instanceof Collection) {
writeCollection(name, (Collection<Object>) object);
} else if (object instanceof Boolean) {
writeBoolean(name, (Boolean) object);
} else if (object instanceof Date) {
writeDate(name, (Date) object);
} else if (object instanceof Map.Entry<?, ?>) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
writeJson(entry.getKey().toString(), entry.getValue());
} else {
writeString(name, object.toString());
}
} catch (IOException e) {
throw new RuntimeException(e);
/**
* Convert object to Json content.
*/
static void write(Object object, io.avaje.json.JsonWriter jsonWriter) throws IOException {
if (object == null) {
jsonWriter.nullValue();
return;
}
}
private void writeBoolean(String name, Boolean object) throws IOException {
if (name == null) {
jsonGenerator.writeBoolean(object);
} else {
jsonGenerator.writeBooleanField(name, object);
if (object instanceof String) {
jsonWriter.value((String) object);
return;
}
}
private void writeDate(String name, Date object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber(object.getTime());
} else {
jsonGenerator.writeNumberField(name, object.getTime());
if (object instanceof Integer) {
jsonWriter.value((Integer) object);
return;
}
}
private void writeNumber(String name, Number object) throws IOException {
if (object instanceof Long) {
writeLong(name, object);
} else if (object instanceof Integer) {
writeInteger(name, object);
} else if (object instanceof Double) {
writeDouble(name, object);
} else if (object instanceof BigDecimal) {
writeBigDecimal(name, object);
} else if (object instanceof BigInteger) {
writeBigInteger(name, object);
} else {
writeGeneralNumber(name, object);
jsonWriter.value((Long) object);
return;
}
if (object instanceof Double) {
jsonWriter.value((Double) object);
return;
}
if (object instanceof Float) {
jsonWriter.value((Float) object);
return;
}
if (object instanceof BigDecimal) {
jsonWriter.value((BigDecimal) object);
return;
}
if (object instanceof Boolean) {
jsonWriter.value((Boolean) object);
return;
}
if (object instanceof Map<?, ?>) {
writeMap((Map<?, ?>) object, jsonWriter);
return;
}
if (object instanceof Collection<?>) {
writeCollection((Collection<?>) object, jsonWriter);
return;
}
jsonWriter.value(object.toString());
}
private void writeGeneralNumber(String name, Number object) throws IOException {
writeBigDecimal(name, new BigDecimal(object.toString()));
/**
* Write map as Json content.
*/
private static void writeMap(Map<?, ?> map, io.avaje.json.JsonWriter jsonWriter) throws IOException {
jsonWriter.beginObject();
for (Map.Entry<?, ?> entry : map.entrySet()) {
String fieldName = (String) entry.getKey();
Object value = entry.getValue();
jsonWriter.name(fieldName);
write(value, jsonWriter);
}
jsonWriter.endObject();
}
private void writeBigDecimal(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((BigDecimal) object);
} else {
jsonGenerator.writeNumberField(name, (BigDecimal) object);
/**
* Write list as Json content.
*/
static void writeCollection(Collection<?> list, io.avaje.json.JsonWriter jsonWriter) throws IOException {
jsonWriter.beginArray();
for (Object element : list) {
write(element, jsonWriter);
}
jsonWriter.endArray();
}
private void writeBigInteger(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((BigInteger) object);
} else {
jsonGenerator.writeNumberField(name, object.longValue());
}
/**
* Convert map to Json content.
*/
static String write(Map<String, Object> map) throws IOException {
StringWriter writer = new StringWriter();
write(map, writer);
return writer.toString();
}
private void writeDouble(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Double) object);
} else {
jsonGenerator.writeNumberField(name, (Double) object);
}
/**
* Convert map to Json content.
*/
static io.avaje.json.JsonWriter write(Map<String, Object> map, Writer writer) throws IOException {
io.avaje.json.JsonWriter jsonWriter = JSON_STREAM.writer(writer);
jsonWriter.serializeNulls(true);
write(map, jsonWriter);
jsonWriter.flush();
return jsonWriter;
}
private void writeLong(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Long) object);
} else {
jsonGenerator.writeNumberField(name, (Long) object);
/**
* Convert map to Json content.
*/
static void write(Map<String, Object> map, io.avaje.json.JsonWriter jsonWriter) throws IOException {
jsonWriter.beginObject();
Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
jsonWriter.name(entry.getKey());
write(entry.getValue(), jsonWriter);
}
}
private void writeInteger(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Integer) object);
} else {
jsonGenerator.writeNumberField(name, (Integer) object);
}
}
private void writeNull(String name) throws IOException {
if (name == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeNullField(name);
}
}
private void writeString(String name, String object) throws IOException {
if (name == null) {
jsonGenerator.writeString(object);
} else {
jsonGenerator.writeStringField(name, object);
}
}
private void writeCollection(String name, Collection<Object> collection) throws IOException {
if (name != null) {
jsonGenerator.writeFieldName(name);
}
jsonGenerator.writeStartArray();
for (Object object : collection) {
writeJson(null, object);
}
jsonGenerator.writeEndArray();
}
private void writeMap(String name, Map<Object, Object> map) throws IOException {
if (name != null) {
jsonGenerator.writeFieldName(name);
}
jsonGenerator.writeStartObject();
Set<Entry<Object, Object>> entrySet = map.entrySet();
for (Entry<Object, Object> entry : entrySet) {
writeJson(entry.getKey().toString(), entry.getValue());
}
jsonGenerator.writeEndObject();
jsonWriter.endObject();
}
}
@@ -1,8 +1,7 @@
module io.ebean.core.json {
requires io.ebean.api;
requires transitive com.fasterxml.jackson.core;
requires transitive io.avaje.json;
exports io.ebeaninternal.json to io.ebean.test, io.ebean.core;
provides io.ebean.service.BootstrapService with io.ebeaninternal.json.DJsonService;
+3 -4
View File
@@ -20,10 +20,9 @@
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<optional>true</optional>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-core</artifactId>
<version>${avaje-json-core.version}</version>
</dependency>
<!-- Provided scope for Postgres JSON/JSONB support -->
@@ -1,7 +1,7 @@
package io.ebean.core.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.text.StringFormatter;
import io.ebean.text.StringParser;
@@ -177,13 +177,44 @@ public interface ScalarType<T> extends StringParser, StringFormatter, ScalarData
void writeData(DataOutput dataOutput, T value) throws IOException;
/**
* Read the value from JsonParser.
* Read the value from JsonReader.
*/
T jsonRead(JsonParser parser) throws IOException;
default T jsonRead(JsonReader parser) throws IOException {
JsonReader.Token token = parser.currentToken();
if (token == JsonReader.Token.NULL) {
parser.isNullValue();
return null;
}
if (token == JsonReader.Token.STRING) {
return parse(parser.readString());
}
return parse(parser.readRaw());
}
/**
* Write the value to the JsonGenerator.
* Write the value to the JsonWriter.
*/
void jsonWrite(JsonGenerator writer, T value) throws IOException;
default void jsonWrite(JsonWriter writer, T value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
String formatted = formatValue(value);
if (formatted == null) {
writer.nullValue();
return;
}
DocPropertyType docType = docType();
if (docType == DocPropertyType.OBJECT || docType == DocPropertyType.LIST || docType == DocPropertyType.ROOT || likelyRawJson(formatted)) {
writer.rawValue(formatted);
} else {
writer.value(formatted);
}
}
private static boolean likelyRawJson(String formatted) {
String trimmed = formatted.trim();
return !trimmed.isEmpty() && (trimmed.charAt(0) == '{' || trimmed.charAt(0) == '[');
}
}
@@ -1,8 +1,8 @@
package io.ebean.core.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import io.ebean.config.JsonConfig;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
@@ -83,20 +83,31 @@ public abstract class ScalarTypeBaseDate<T> extends ScalarTypeBase<T> {
}
@Override
public T jsonRead(JsonParser parser) throws IOException {
if (JsonToken.VALUE_NUMBER_INT == parser.getCurrentToken()) {
return convertFromMillis(parser.getLongValue());
} else {
return convertFromDate(Date.valueOf(parser.getText()));
public T jsonRead(JsonReader parser) throws IOException {
Token token = parser.currentToken();
if (Token.NUMBER == token) {
return convertFromMillis(parser.readLong());
}
if (Token.STRING == token) {
return convertFromDate(Date.valueOf(parser.readString()));
}
String raw = parser.readRaw();
if (raw == null || "null".equals(raw)) {
return null;
}
if (raw.length() > 1 && raw.charAt(0) == '"' && raw.charAt(raw.length() - 1) == '"') {
return convertFromDate(Date.valueOf(raw.substring(1, raw.length() - 1)));
}
return convertFromMillis(Long.parseLong(raw));
}
@Override
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
public void jsonWrite(JsonWriter writer, T value) throws IOException {
if (mode == JsonConfig.Date.ISO8601) {
writer.writeString(toIsoFormat(value));
writer.value(toIsoFormat(value));
} else {
writer.writeNumber(convertToMillis(value));
writer.value(convertToMillis(value));
}
}
@@ -1,7 +1,8 @@
package io.ebean.core.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import io.ebean.config.JsonConfig;
import java.io.DataInput;
@@ -99,35 +100,53 @@ public abstract class ScalarTypeBaseDateTime<T> extends ScalarTypeBase<T> {
}
@Override
public T jsonRead(JsonParser parser) throws IOException {
switch (parser.getCurrentToken()) {
case VALUE_NUMBER_INT: {
return convertFromMillis(parser.getLongValue());
}
case VALUE_NUMBER_FLOAT: {
BigDecimal value = parser.getDecimalValue();
Timestamp timestamp = ScalarTypeUtils.toTimestamp(value);
return convertFromTimestamp(timestamp);
}
default: {
return fromJsonISO8601(parser.getText());
}
public T jsonRead(JsonReader parser) throws IOException {
Token token = parser.currentToken();
if (token == Token.NUMBER) {
return readNumber(parser.readDecimal());
}
if (token == Token.STRING) {
return fromStringValue(parser.readString());
}
String raw = parser.readRaw();
if (raw == null || "null".equals(raw)) {
return null;
}
if (raw.length() > 1 && raw.charAt(0) == '"' && raw.charAt(raw.length() - 1) == '"') {
return fromStringValue(raw.substring(1, raw.length() - 1));
}
return readNumber(new BigDecimal(raw));
}
private T fromStringValue(String value) {
if (value.indexOf('-') == -1 && Character.isDigit(value.charAt(0))) {
return readNumber(new BigDecimal(value));
}
return fromJsonISO8601(value);
}
private T readNumber(BigDecimal value) {
if (value.scale() <= 0) {
return convertFromMillis(value.longValue());
}
Timestamp timestamp = ScalarTypeUtils.toTimestamp(value);
return convertFromTimestamp(timestamp);
}
@Override
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
public void jsonWrite(JsonWriter writer, T value) throws IOException {
switch (mode) {
case ISO8601: {
writer.writeString(toJsonISO8601(value));
writer.value(toJsonISO8601(value));
break;
}
case NANOS: {
writer.writeNumber(toJsonNanos(value));
writer.value(toJsonNanos(value));
break;
}
default: {
writer.writeNumber(convertToMillis(value));
writer.value(convertToMillis(value));
}
}
}
@@ -1,7 +1,7 @@
package io.ebean.core.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import java.io.DataInput;
import java.io.DataOutput;
@@ -104,13 +104,16 @@ public abstract class ScalarTypeBaseVarchar<T> extends ScalarTypeBase<T> {
}
@Override
public T jsonRead(JsonParser parser) throws IOException {
return parse(parser.getValueAsString());
public T jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
return parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
writer.writeString(format(value));
public void jsonWrite(JsonWriter writer, T value) throws IOException {
writer.value(format(value));
}
@Override
@@ -4,8 +4,7 @@ module io.ebean.core.type {
requires transitive java.sql;
requires transitive io.ebean.api;
requires transitive io.avaje.json;
requires static org.postgresql.jdbc;
requires static com.fasterxml.jackson.core;
}
-8
View File
@@ -138,14 +138,6 @@
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@@ -1,6 +1,6 @@
package io.ebeaninternal.api;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.JsonWriter;
import io.ebean.plugin.BeanType;
import io.ebean.text.json.JsonContext;
import io.ebean.text.json.JsonWriteOptions;
@@ -17,7 +17,7 @@ public interface SpiJsonContext extends JsonContext {
/**
* Create a Json Writer for writing beans as JSON.
*/
SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options);
SpiJsonWriter createJsonWriter(JsonWriter gen, JsonWriteOptions options);
/**
* Create a Json Writer for writing beans as JSON supplying a writer.
@@ -1,8 +1,8 @@
package io.ebeaninternal.api.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -14,7 +14,9 @@ public interface SpiJsonReader {
PersistenceContext persistenceContext();
SpiJsonReader forJson(JsonParser moreJson);
SpiJsonReader forJson(JsonReader moreJson);
SpiJsonReader forJson(String moreJson);
<T> void persistenceContextPut(Object beanId, T currentBean);
@@ -22,9 +24,9 @@ public interface SpiJsonReader {
ObjectMapper mapper();
JsonParser parser();
JsonReader parser();
JsonToken nextToken() throws IOException;
Token nextToken() throws IOException;
void pushPath(String path);
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.changelog;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.event.changelog.ChangeType;
@@ -15,13 +15,13 @@ import java.util.Map;
*/
final class ChangeJsonBuilder {
private final JsonFactory jsonFactory = new JsonFactory();
private final JsonStream jsonStream = JsonStream.builder().build();
/**
* Write the bean change as JSON.
*/
void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet) throws IOException {
try (JsonGenerator generator = jsonFactory.createGenerator(writer)) {
try (JsonWriter generator = jsonStream.writer(writer)) {
writeBeanChange(generator, bean, changeSet);
generator.flush();
}
@@ -30,58 +30,67 @@ final class ChangeJsonBuilder {
/**
* Write the bean change as JSON document containing the transaction header details.
*/
private void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet) throws IOException {
gen.writeStartObject();
gen.writeNumberField("ts", bean.getEventTime());
gen.writeStringField("change", bean.getEvent().getCode());
gen.writeStringField("type", bean.getType());
gen.writeStringField("id", bean.getId().toString());
private void writeBeanChange(JsonWriter gen, BeanChange bean, ChangeSet changeSet) {
gen.beginObject();
gen.name("ts");
gen.value(bean.getEventTime());
gen.name("change");
gen.value(bean.getEvent().getCode());
gen.name("type");
gen.value(bean.getType());
gen.name("id");
gen.value(bean.getId().toString());
if (bean.getTenantId() != null) {
gen.writeStringField("tenantId", bean.getTenantId().toString());
gen.name("tenantId");
gen.value(bean.getTenantId().toString());
}
writeBeanTransactionDetails(gen, changeSet);
writeBeanValues(gen, bean);
gen.writeEndObject();
gen.endObject();
}
/**
* Denormalise by writing the transaction header details.
*/
private void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet) throws IOException {
private void writeBeanTransactionDetails(JsonWriter gen, ChangeSet changeSet) {
String source = changeSet.getSource();
if (source != null) {
gen.writeStringField("source", source);
gen.name("source");
gen.value(source);
}
String userId = changeSet.getUserId();
if (userId != null) {
gen.writeStringField("userId", userId);
gen.name("userId");
gen.value(userId);
}
String userIpAddress = changeSet.getUserIpAddress();
if (userIpAddress != null) {
gen.writeStringField("userIpAddress", userIpAddress);
gen.name("userIpAddress");
gen.value(userIpAddress);
}
Map<String, String> userContext = changeSet.getUserContext();
if (userContext != null && !userContext.isEmpty()) {
gen.writeObjectFieldStart("userContext");
gen.name("userContext");
gen.beginObject();
for (Map.Entry<String, String> entry : userContext.entrySet()) {
gen.writeStringField(entry.getKey(), entry.getValue());
gen.name(entry.getKey());
gen.value(entry.getValue());
}
gen.writeEndObject();
gen.endObject();
}
}
/**
* For insert and update write the new/old values.
*/
private void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException {
private void writeBeanValues(JsonWriter gen, BeanChange bean) {
if (bean.getEvent() != ChangeType.DELETE) {
gen.writeFieldName("data");
gen.writeRaw(":");
gen.writeRaw(bean.getData());
gen.name("data");
gen.rawValue(bean.getData());
String oldData = bean.getOldData();
if (oldData != null) {
gen.writeRaw(",\"oldData\":");
gen.writeRaw(oldData);
gen.name("oldData");
gen.rawValue(oldData);
}
}
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.core;
import com.fasterxml.jackson.core.JsonFactory;
import io.avaje.json.stream.JsonStream;
import io.ebean.DatabaseBuilder;
import io.ebean.ExpressionFactory;
import io.ebean.annotation.Platform;
@@ -89,7 +89,7 @@ public final class InternalConfiguration {
private final boolean jacksonCorePresent;
private final ExpressionFactory expressionFactory;
private final SpiBackgroundExecutor backgroundExecutor;
private final JsonFactory jsonFactory;
private final JsonStream jsonStream;
private final DocStoreFactory docStoreFactory;
private final List<Plugin> plugins = new ArrayList<>();
private final MultiValueBind multiValueBind;
@@ -108,7 +108,7 @@ public final class InternalConfiguration {
this.tableModState = new TableModState();
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(service(DocStoreFactory.class));
this.jsonFactory = config.getJsonFactory();
this.jsonStream = config.getJsonStream();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.bootupClasses = bootupClasses;
@@ -292,7 +292,7 @@ public final class InternalConfiguration {
}
SpiJsonContext createJsonContext(SpiEbeanServer server) {
return jacksonCorePresent ? new DJsonContext(server, jsonFactory, typeManager) : null;
return jacksonCorePresent ? new DJsonContext(server, jsonStream, typeManager) : null;
}
AutoTuneService createAutoTuneService(SpiEbeanServer server) {
@@ -2,8 +2,11 @@ package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.bean.BeanDiffVisitor;
import io.ebean.config.JsonConfig;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.json.WriteJson;
import io.ebeaninternal.server.util.ArrayStack;
import io.avaje.json.stream.JsonStream;
import java.io.IOException;
import java.io.StringWriter;
@@ -13,26 +16,18 @@ import java.io.StringWriter;
*/
final class BeanChangeJson implements BeanDiffVisitor {
private final StringWriter newData;
private final StringWriter oldData;
private final SpiJsonWriter newJson;
private final SpiJsonWriter oldJson;
private final StringWriter data;
private final SpiJsonWriter json;
private final boolean writeNew;
private final ArrayStack<BeanDescriptor<?>> stack = new ArrayStack<>();
private BeanDescriptor<?> descriptor;
BeanChangeJson(BeanDescriptor<?> descriptor, boolean statelessUpdate) {
BeanChangeJson(BeanDescriptor<?> descriptor, boolean writeNew) {
this.descriptor = descriptor;
this.newData = new StringWriter(200);
this.newJson = descriptor.createJsonWriter(newData);
newJson.writeStartObject();
if (statelessUpdate) {
this.oldJson = null;
this.oldData = null;
} else {
this.oldData = new StringWriter(200);
this.oldJson = descriptor.createJsonWriter(oldData);
oldJson.writeStartObject();
}
this.writeNew = writeNew;
this.data = new StringWriter(200);
this.json = new WriteJson(JsonStream.builder().build().writer(data), JsonConfig.Include.ALL);
json.writeStartObject();
}
@Override
@@ -40,10 +35,7 @@ final class BeanChangeJson implements BeanDiffVisitor {
try {
BeanProperty prop = descriptor.propertiesIndex[position];
if (prop.isDbUpdatable()) {
prop.jsonWriteValue(newJson, newVal);
if (oldJson != null) {
prop.jsonWriteValue(oldJson, oldVal);
}
prop.jsonWriteValue(json, writeNew ? newVal : oldVal);
}
} catch (IOException e) {
throw new PersistenceIOException(e);
@@ -55,18 +47,12 @@ final class BeanChangeJson implements BeanDiffVisitor {
stack.push(descriptor);
BeanPropertyAssocOne<?> embedded = (BeanPropertyAssocOne<?>)descriptor.propertiesIndex[position];
descriptor = embedded.targetDescriptor();
newJson.writeStartObject(embedded.name());
if (oldJson != null) {
oldJson.writeStartObject(embedded.name());
}
json.writeStartObject(embedded.name());
}
@Override
public void visitPop() {
newJson.writeEndObject();
if (oldJson != null) {
oldJson.writeEndObject();
}
json.writeEndObject();
descriptor = stack.pop();
}
@@ -75,28 +61,14 @@ final class BeanChangeJson implements BeanDiffVisitor {
*/
void flush() {
try {
newJson.writeEndObject();
newJson.flush();
if (oldJson != null) {
oldJson.writeEndObject();
oldJson.flush();
}
json.writeEndObject();
json.flush();
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
/**
* Return the new values JSON.
*/
String newJson() {
return newData.toString();
}
/**
* Return the old values JSON.
*/
String oldJson() {
return oldData == null ? null : oldData.toString();
String json() {
return data.toString();
}
}
@@ -792,10 +792,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
*/
private BeanChange updateBeanChange(PersistRequestBean<T> request) {
try {
BeanChangeJson changeJson = new BeanChangeJson(this, request.isStatelessUpdate());
request.intercept().addDirtyPropertyValues(changeJson);
changeJson.flush();
return beanChange(ChangeType.UPDATE, request.beanId(), changeJson.newJson(), changeJson.oldJson());
BeanChangeJson newValues = new BeanChangeJson(this, true);
request.intercept().addDirtyPropertyValues(newValues);
newValues.flush();
String oldData = null;
if (!request.isStatelessUpdate()) {
BeanChangeJson oldValues = new BeanChangeJson(this, false);
request.intercept().addDirtyPropertyValues(oldValues);
oldValues.flush();
oldData = oldValues.json();
}
return beanChange(ChangeType.UPDATE, request.beanId(), newValues.json(), oldData);
} catch (RuntimeException e) {
log.log(ERROR, "Failed to write ChangeLog entry for update", e);
return null;
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.bean.EntityBean;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -49,25 +49,25 @@ class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.parser();
JsonReader parser = readJson.parser();
ElementCollector add = elementHelp.createCollector();
do {
String fieldName = parser.nextFieldName();
if (fieldName == null) {
break;
}
parser.beginObject();
while (parser.hasNextField()) {
String fieldName = parser.nextField();
if (stringKey) {
parser.nextToken();
Object val = readJsonElement(readJson, null, null); // CHECKME: Update existing map entry here?
add.addKeyValue(fieldName, val);
} else {
parser.nextFieldName();
parser.beginObject();
parser.nextField();
Object key = scalarTypeKey.jsonRead(parser);
parser.nextFieldName();
parser.nextField();
Object val = readJsonElement(readJson, null, null); // CHECKME: Update existing map entry here?
parser.endObject();
add.addKeyValue(key, val);
}
} while (true);
}
parser.endObject();
return add.collection();
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
@@ -40,15 +39,13 @@ class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.parser();
JsonReader parser = readJson.parser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
break;
}
parser.beginArray();
while (parser.hasNextElement()) {
add.addElement(scalarType.jsonRead(parser));
} while (true);
}
parser.endArray();
return add.collection();
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.bean.EntityBean;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -49,25 +49,25 @@ class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.parser();
JsonReader parser = readJson.parser();
ElementCollector add = elementHelp.createCollector();
do {
String fieldName = parser.nextFieldName();
if (fieldName == null) {
break;
}
parser.beginObject();
while (parser.hasNextField()) {
String fieldName = parser.nextField();
if (stringKey) {
parser.nextToken();
Object val = scalarTypeVal.jsonRead(parser);
add.addKeyValue(fieldName, val);
} else {
parser.nextFieldName();
parser.beginObject();
parser.nextField();
Object key = scalarTypeKey.jsonRead(parser);
parser.nextFieldName();
parser.nextField();
Object val = scalarTypeVal.jsonRead(parser);
parser.endObject();
add.addKeyValue(key, val);
}
} while (true);
}
parser.endObject();
return add.collection();
}
@@ -1,10 +1,7 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.EJson;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -34,7 +31,8 @@ final class BeanDescriptorJsonHelp<T> {
InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass());
String discValue = localInheritInfo.getDiscriminatorStringValue();
String discColumn = localInheritInfo.getDiscriminatorColumn();
writeJson.gen().writeStringField(discColumn, discValue);
writeJson.gen().name(discColumn);
writeJson.gen().value(discValue);
localInheritInfo.desc().jsonWriteProperties(writeJson, bean);
}
writeJson.writeEndObject();
@@ -66,44 +64,38 @@ final class BeanDescriptorJsonHelp<T> {
@SuppressWarnings("unchecked")
T jsonRead(SpiJsonReader jsonRead, String path, boolean withInheritance, T target) throws IOException {
JsonParser parser = jsonRead.parser();
//noinspection StatementWithEmptyBody
if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
// start object token read by Jackson already
} else {
// check for null or start object
JsonToken token = parser.nextToken();
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
return null;
}
if (JsonToken.START_OBJECT != token) {
throw new JsonParseException(parser, "Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
}
JsonReader parser = jsonRead.parser();
if (parser.isNullValue()) {
return null;
}
Token token = parser.currentToken();
if (token != Token.BEGIN_OBJECT) {
throw new IllegalStateException("Unexpected token " + token + " - expecting BEGIN_OBJECT at: " + parser.location());
}
if (desc.inheritInfo == null || !withInheritance) {
return jsonReadObject(jsonRead, path, target);
}
ObjectNode node = jsonRead.mapper().readTree(parser);
if (node.isNull()) {
Map<String, Object> node = EJson.parseObject(parser);
if (node == null) {
return null;
}
JsonParser newParser = node.traverse();
SpiJsonReader newReader = jsonRead.forJson(newParser);
// check for the discriminator value to determine the correct sub type
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
JsonNode discNode = node.get(discColumn);
if (discNode == null || discNode.isNull()) {
Object discValue = node.get(discColumn);
String rawObject = EJson.write(node);
SpiJsonReader newReader = jsonRead.forJson(rawObject);
if (discValue == null) {
if (!desc.isAbstractType()) {
return desc.jsonReadObject(newReader, path, target);
}
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(newParser, msg, parser.getCurrentLocation());
throw new IllegalStateException(msg);
}
BeanDescriptor<T> inheritDesc = (BeanDescriptor<T>) inheritInfo.readType(discNode.asText()).desc();
BeanDescriptor<T> inheritDesc = (BeanDescriptor<T>) inheritInfo.readType(String.valueOf(discValue)).desc();
return inheritDesc.jsonReadObject(newReader, path, target);
}
@@ -124,35 +116,30 @@ final class BeanDescriptorJsonHelp<T> {
if (path != null) {
readJson.pushPath(path);
}
JsonReader parser = readJson.parser();
parser.beginObject();
// unmapped properties, send to JsonReadBeanVisitor later
Map<String, Object> unmappedProperties = null;
do {
JsonParser parser = readJson.parser();
JsonToken event = parser.nextToken();
if (JsonToken.FIELD_NAME == event) {
String key = parser.getCurrentName();
BeanProperty p = desc.beanProperty(key);
if (p != null) {
if (p.isVersion() && readJson.update() ) {
// skip version prop during update
p.jsonRead(readJson);
} else {
p.jsonRead(readJson, bean);
}
while (parser.hasNextField()) {
String key = parser.nextField();
BeanProperty p = desc.beanProperty(key);
if (p != null) {
if (p.isVersion() && readJson.update()) {
// skip version prop during update
p.jsonRead(readJson);
} else {
// read an unmapped property
if (unmappedProperties == null) {
unmappedProperties = new LinkedHashMap<>();
}
unmappedProperties.put(key, EJson.parse(parser));
p.jsonRead(readJson, bean);
}
} else if (JsonToken.END_OBJECT == event) {
break;
} else {
throw new RuntimeException("Unexpected token " + event + " - expecting key or end_object at: " + parser.getCurrentLocation());
// read an unmapped property
if (unmappedProperties == null) {
unmappedProperties = new LinkedHashMap<>();
}
unmappedProperties.put(key, EJson.parse(parser));
}
} while (true);
}
parser.endObject();
if (unmappedProperties != null) {
desc.setUnmappedJson(bean, unmappedProperties);
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.ebean.DataIntegrityException;
import io.ebean.ModifyAwareType;
import io.ebean.ValuePair;
@@ -1437,14 +1438,14 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
public Object jsonRead(SpiJsonReader ctx) throws IOException {
JsonToken event = ctx.nextToken();
if (JsonToken.VALUE_NULL == event) {
JsonReader parser = ctx.parser();
if (parser.isNullValue()) {
return null;
} else {
// expect to read non-null json value
Object objValue;
if (scalarType != null) {
objValue = scalarType.jsonRead(ctx.parser());
objValue = scalarType.jsonRead(parser);
} else {
try {
objValue = ctx.readValueUsingObjectMapper(propertyType);
@@ -1,15 +1,14 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.plugin.PropertyAssocMany;
import io.ebean.text.PathProperties;
@@ -1043,9 +1042,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
*/
private Object jsonReadCollection(String json) throws IOException {
SpiJsonReader ctx = descriptor.createJsonReader(json);
JsonParser parser = ctx.parser();
JsonToken event = parser.nextToken();
if (JsonToken.VALUE_NULL == event) {
JsonReader parser = ctx.parser();
if (parser.isNullValue()) {
return null;
}
return jsonReadCollection(ctx, null, null);
@@ -1068,20 +1066,18 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
BeanCollection<?> collection = createEmpty(parentBean);
BeanCollectionAdd add = beanCollectionAdd(collection);
Map<Object, T> existingBeans = extractBeans(targets);
do {
JsonReader parser = readJson.parser();
parser.beginArray();
while (parser.hasNextElement()) {
EntityBean detailBean = getDetailBean(readJson, existingBeans);
if (detailBean == null) {
// read the entire array
break;
if (detailBean != null) {
add.addEntityBean(detailBean);
if (parentBean != null && childMasterProperty != null) {
// bind detail bean back to master via mappedBy property
childMasterProperty.setValue(detailBean, parentBean);
}
}
add.addEntityBean(detailBean);
if (parentBean != null && childMasterProperty != null) {
// bind detail bean back to master via mappedBy property
childMasterProperty.setValue(detailBean, parentBean);
}
} while (true);
}
return collection;
}
@@ -1092,18 +1088,79 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
BeanProperty idProperty = targetDescriptor.idProperty();
if (targets == null || idProperty == null) {
return (EntityBean) targetDescriptor.jsonRead(readJson, name, null);
} else {
JsonToken token = readJson.parser().nextToken();
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
return null;
}
// extract the id. We have to buffer the JSON;
ObjectNode node = readJson.mapper().readTree(readJson.parser());
SpiJsonReader jsonReader = readJson.forJson(node.traverse());
JsonNode idNode = node.get(idProperty.name());
Object id = idNode == null ? null : idProperty.jsonRead(readJson.forJson(idNode.traverse()));
return (EntityBean) targetDescriptor.jsonRead(jsonReader, name, targets.get(id));
}
/// ROB Checkme
JsonReader parser = readJson.parser();
if (parser.isNullValue()) {
return null;
}
EntityBean incomingBean = (EntityBean) targetDescriptor.jsonRead(readJson, name, null);
Object id = idProperty.getValue(incomingBean);
EntityBean existingBean = (EntityBean) targets.get(id);
if (existingBean == null) {
return incomingBean;
}
mergeLoadedBean(targetDescriptor, incomingBean, existingBean);
return existingBean;
}
private void mergeLoadedBean(BeanDescriptor<?> descriptor, EntityBean incomingBean, EntityBean existingBean) {
EntityBeanIntercept incomingIntercept = incomingBean._ebean_getIntercept();
for (BeanProperty property : descriptor.propertiesBaseScalar()) {
if (incomingIntercept.isLoadedProperty(property.propertyIndex())) {
property.setValueIntercept(existingBean, property.getValue(incomingBean));
}
}
for (BeanPropertyAssocMany<?> property : descriptor.propertiesMany()) {
if (incomingIntercept.isLoadedProperty(property.propertyIndex())) {
Object mergedValue = mergeLoadedMany(property, property.getValue(incomingBean), property.getValue(existingBean));
property.setValueIntercept(existingBean, mergedValue);
}
}
}
private Object mergeLoadedMany(BeanPropertyAssocMany<?> property, Object incomingValue, Object existingValue) {
if (!(incomingValue instanceof Collection<?>)) {
return incomingValue;
}
if (!(existingValue instanceof Collection<?>)) {
return incomingValue;
}
Collection<?> incomingCollection = (Collection<?>) incomingValue;
Collection<?> existingCollection = (Collection<?>) existingValue;
BeanProperty idProperty = property.targetDescriptor.idProperty();
if (idProperty == null) {
return incomingValue;
}
Map<Object, EntityBean> existingById = new LinkedHashMap<>();
for (Object existingElement : existingCollection) {
if (existingElement instanceof EntityBean) {
EntityBean existingBean = (EntityBean) existingElement;
Object id = idProperty.getValue(existingBean);
if (id != null) {
existingById.put(id, existingBean);
}
}
}
BeanCollection<?> mergedCollection = property.createEmpty(null);
BeanCollectionAdd add = property.beanCollectionAdd(mergedCollection);
for (Object incomingElement : incomingCollection) {
if (!(incomingElement instanceof EntityBean)) {
continue;
}
EntityBean incomingBean = (EntityBean) incomingElement;
Object id = idProperty.getValue(incomingBean);
EntityBean existingBean = id == null ? null : existingById.get(id);
if (existingBean == null) {
add.addEntityBean(incomingBean);
} else {
mergeLoadedBean(property.targetDescriptor, incomingBean, existingBean);
add.addEntityBean(existingBean);
}
}
return mergedCollection;
}
/**
@@ -1,8 +1,7 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -38,17 +37,17 @@ class BeanPropertyAssocManyJsonHelp {
if (!this.many.jsonDeserialize) {
return;
}
JsonParser parser = readJson.parser();
JsonToken event = parser.nextToken();
if (JsonToken.VALUE_NULL == event) {
JsonReader parser = readJson.parser();
if (parser.isNullValue()) {
return;
}
if (many.isTransient()) {
jsonReadTransientUsingObjectMapper(readJson, parentBean);
return;
}
if (JsonToken.START_ARRAY != event && JsonToken.START_OBJECT != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start array or object");
Token event = parser.currentToken();
if (Token.BEGIN_ARRAY != event && Token.BEGIN_OBJECT != event) {
throw new IllegalStateException("Unexpected token " + event + " - expecting start array or object");
}
if (readJson.update()) {
many.setValueIntercept(parentBean, many.jsonReadCollection(readJson, parentBean, many.getValue(parentBean)));
@@ -29,11 +29,11 @@ class BeanPropertyAssocManyJsonTransient {
TypeFactory typeFactory = mapper.getTypeFactory();
JavaType target = typeFactory.constructType(many.targetType());
MapType jacksonType = typeFactory.constructMapType(LinkedHashMap.class, TypeFactory.unknownType(), target);
value = mapper.readValue(readJson.parser(), jacksonType);
value = mapper.readValue(readJson.parser().readRaw(), jacksonType);
} else {
// read list or set using Jackson object mapper
CollectionType jacksonType = mapper.getTypeFactory().constructCollectionType(manyType.getCollectionType(), many.targetType());
value = mapper.readValue(readJson.parser(), jacksonType);
value = mapper.readValue(readJson.parser().readRaw(), jacksonType);
}
many.setValue(parentBean, value);
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.json;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.PersistenceIOException;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.json.JsonBeanReader;
@@ -45,7 +45,7 @@ public final class DJsonBeanReader<T> implements JsonBeanReader<T> {
}
@Override
public JsonBeanReader<T> forJson(JsonParser moreJson) {
public JsonBeanReader<T> forJson(JsonReader moreJson) {
return new DJsonBeanReader<>(desc, readJson.forJson(moreJson));
}
}
@@ -1,12 +1,9 @@
package io.ebeaninternal.server.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.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.FetchPath;
import io.ebean.bean.EntityBean;
import io.ebean.config.JsonConfig;
@@ -28,7 +25,6 @@ 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;
@@ -45,30 +41,22 @@ import java.util.Set;
*/
public final class DJsonContext implements SpiJsonContext {
private static final PrettyPrinter PRETTY_PRINTER = new Pretty();
private final SpiEbeanServer server;
private final JsonFactory jsonFactory;
private final JsonStream jsonStream;
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) {
public DJsonContext(SpiEbeanServer server, JsonStream jsonStream, TypeManager typeManager) {
this.server = server;
this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory();
this.jsonStream = jsonStream;
this.defaultObjectMapper = this.server.config().getObjectMapper();
this.defaultInclude = this.server.config().getJsonInclude();
this.jsonScalar = new DJsonScalar(typeManager);
}
@Override
public void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException {
public void writeScalar(JsonWriter generator, Object scalarValue) throws IOException {
jsonScalar.write(generator, scalarValue);
}
@@ -78,31 +66,34 @@ public final class DJsonContext implements SpiJsonContext {
}
@Override
public JsonGenerator createGenerator(Writer writer) throws JsonIOException {
public JsonWriter createGenerator(Writer writer) throws JsonIOException {
JsonWriter jsonWriter = stream().writer(writer);
jsonWriter.serializeNulls(defaultInclude == JsonConfig.Include.ALL);
jsonWriter.serializeEmpty(defaultInclude != JsonConfig.Include.NON_EMPTY);
return jsonWriter;
}
@Override
public JsonReader createParser(Reader reader) throws JsonIOException {
try {
return jsonFactory.createGenerator(writer);
return createParser(readAll(reader));
} 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);
}
private JsonStream stream() {
return (jsonStream != null) ? jsonStream : JsonStream.builder().build();
}
@Override
public <T> T toBean(Class<T> cls, String json) throws JsonIOException {
return toBean(cls, new StringReader(json));
return toBean(cls, createParser(json));
}
@Override
public <T> T toBean(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException {
return toBean(cls, new StringReader(json), options);
return toBean(cls, createParser(json), options);
}
@Override
@@ -116,15 +107,15 @@ public final class DJsonContext implements SpiJsonContext {
}
@Override
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException {
public <T> T toBean(Class<T> cls, JsonReader parser) throws JsonIOException {
return toBean(cls, parser, null);
}
@Override
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
public <T> T toBean(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
try {
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), false), null, null);
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), false, stream()), null, null);
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -132,12 +123,12 @@ public final class DJsonContext implements SpiJsonContext {
@Override
public <T> void toBean(T target, String json) throws JsonIOException {
toBean(target, new StringReader(json));
toBean(target, createParser(json));
}
@Override
public <T> void toBean(T target, String json, JsonReadOptions options) throws JsonIOException {
toBean(target, new StringReader(json), options);
toBean(target, createParser(json), options);
}
@Override
@@ -151,42 +142,42 @@ public final class DJsonContext implements SpiJsonContext {
}
@Override
public <T> void toBean(T target, JsonParser parser) throws JsonIOException {
public <T> void toBean(T target, JsonReader parser) throws JsonIOException {
toBean(target, parser, null);
}
@SuppressWarnings("unchecked")
@Override
public <T> void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException {
public <T> void toBean(T target, JsonReader parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = (BeanDescriptor<T>) getDescriptor(target.getClass());
try {
desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), target != null), null, target);
desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), target != null, stream()), null, target);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
@Override
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options), false));
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options), false, stream()));
}
@Override
public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException {
public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonReader parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = (BeanDescriptor<T>) beanType;
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options), false);
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options), false, stream());
return new DJsonBeanReader<>(desc, readJson);
}
@Override
public <T> List<T> toList(Class<T> cls, String json) throws JsonIOException {
return toList(cls, new StringReader(json));
return toList(cls, createParser(json));
}
@Override
public <T> List<T> toList(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException {
return toList(cls, new StringReader(json), options);
return toList(cls, createParser(json), options);
}
@Override
@@ -200,35 +191,30 @@ public final class DJsonContext implements SpiJsonContext {
}
@Override
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException {
public <T> List<T> toList(Class<T> cls, JsonReader src) throws JsonIOException {
return toList(cls, src, null);
}
@Override
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
public <T> List<T> toList(Class<T> cls, JsonReader src, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options), false);
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options), false, stream());
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);
}
if (src.isNullValue()) {
return null;
}
Token currentToken = src.currentToken();
if (currentToken != Token.BEGIN_ARRAY) {
throw new JsonIOException("Expecting BEGIN_ARRAY but got " + currentToken);
}
List<T> list = new ArrayList<>();
do {
// CHECKME: Should we update the list
src.beginArray();
while (src.hasNextElement()) {
T bean = desc.jsonRead(readJson, null, null);
if (bean == null) {
break;
} else {
if (bean != null) {
list.add(bean);
}
} while (true);
}
return list;
} catch (IOException e) {
throw new JsonIOException(e);
@@ -237,7 +223,7 @@ public final class DJsonContext implements SpiJsonContext {
@Override
public Object toObject(Type genericType, String json) throws JsonIOException {
return toObject(genericType, createParser(new StringReader(json)));
return toObject(genericType, createParser(json));
}
@Override
@@ -245,8 +231,22 @@ public final class DJsonContext implements SpiJsonContext {
return toObject(genericType, createParser(json));
}
private JsonReader createParser(String json) {
return stream().reader(json);
}
private String readAll(Reader reader) throws IOException {
StringBuilder builder = new StringBuilder();
char[] buffer = new char[2048];
int len;
while ((len = reader.read(buffer)) != -1) {
builder.append(buffer, 0, len);
}
return builder.toString();
}
@Override
public Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException {
public Object toObject(Type genericType, JsonReader jsonParser) throws JsonIOException {
TypeInfo info = ParamTypeHelper.getTypeInfo(genericType);
ManyType manyType = info.getManyType();
switch (manyType) {
@@ -262,19 +262,19 @@ public final class DJsonContext implements SpiJsonContext {
}
@Override
public void toJson(Object value, JsonGenerator generator) throws JsonIOException {
public void toJson(Object value, JsonWriter 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 {
public void toJson(Object value, JsonWriter 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 {
public void toJson(Object o, JsonWriter generator, JsonWriteOptions options) throws JsonIOException {
// generator passed in so don't close it
toJsonNoClose(o, generator, options);
}
@@ -303,9 +303,9 @@ public final class DJsonContext implements SpiJsonContext {
}
/**
* Write to the JsonGenerator and close when complete.
* Write to the JsonWriter and close when complete.
*/
private void toJsonWithClose(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException {
private void toJsonWithClose(Object o, JsonWriter generator, JsonWriteOptions options) throws JsonIOException {
try {
toJsonInternal(o, generator, options);
generator.close();
@@ -315,9 +315,9 @@ public final class DJsonContext implements SpiJsonContext {
}
/**
* Write to the JsonGenerator and without closing it (as it was created externally).
* Write to the JsonWriter and without closing it (as it was created externally).
*/
private void toJsonNoClose(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException {
private void toJsonNoClose(Object o, JsonWriter generator, JsonWriteOptions options) throws JsonIOException {
try {
toJsonInternal(o, generator, options);
} catch (IOException e) {
@@ -342,9 +342,9 @@ public final class DJsonContext implements SpiJsonContext {
private String toJsonString(Object value, JsonWriteOptions options, boolean pretty) throws JsonIOException {
StringWriter writer = new StringWriter(500);
try (JsonGenerator gen = createGenerator(writer)) {
try (JsonWriter gen = createGenerator(writer)) {
if (pretty) {
gen.setPrettyPrinter(PRETTY_PRINTER);
gen.pretty(true);
}
toJsonInternal(value, gen, options);
} catch (IOException e) {
@@ -354,29 +354,23 @@ public final class DJsonContext implements SpiJsonContext {
}
@SuppressWarnings("unchecked")
private void toJsonInternal(Object value, JsonGenerator gen, JsonWriteOptions options) throws IOException {
private void toJsonInternal(Object value, JsonWriter gen, JsonWriteOptions options) throws IOException {
if (value == null) {
gen.writeNull();
gen.nullValue();
} else if (value instanceof Number) {
gen.writeNumber(((Number) value).doubleValue());
gen.jsonValue(value);
} else if (value instanceof Boolean) {
gen.writeBoolean((Boolean) value);
gen.value((Boolean) value);
} else if (value instanceof String) {
gen.writeString((String) value);
// } else if (o instanceof JsonElement) {
gen.value((String) value);
} 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);
}
@@ -385,8 +379,8 @@ public final class DJsonContext implements SpiJsonContext {
@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, false);
JsonReader parser = createParser(json);
return new ReadJson(desc, parser, null, defaultObjectMapper, false, stream());
}
@Override
@@ -395,11 +389,11 @@ public final class DJsonContext implements SpiJsonContext {
}
@Override
public SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options) {
public SpiJsonWriter createJsonWriter(JsonWriter gen, JsonWriteOptions options) {
return createWriteJson(gen, options);
}
private WriteJson createWriteJson(JsonGenerator gen, JsonWriteOptions options) {
private WriteJson createWriteJson(JsonWriter gen, JsonWriteOptions options) {
FetchPath pathProps = (options == null) ? null : options.getPathProperties();
Map<String, JsonWriteBeanVisitor<?>> visitors = (options == null) ? null : options.getVisitorMap();
return new WriteJson(server,
@@ -411,46 +405,50 @@ public final class DJsonContext implements SpiJsonContext {
options == null || options.isIncludeLoadedImplicit());
}
private <T> void toJsonFromCollection(Collection<T> collection, String key, JsonGenerator gen, JsonWriteOptions options) throws IOException {
private <T> void toJsonFromCollection(Collection<T> collection, String key, JsonWriter gen, JsonWriteOptions options) throws IOException {
if (key != null) {
gen.writeFieldName(key);
gen.name(key);
}
gen.writeStartArray();
gen.beginArray();
WriteJson writeJson = createWriteJson(gen, options);
for (T bean : collection) {
BeanDescriptor<?> d = getDescriptor(bean.getClass());
d.jsonWrite(writeJson, (EntityBean) bean, null);
if (bean == null) {
gen.nullValue();
} else if (bean instanceof EntityBean) {
BeanDescriptor<?> d = getDescriptor(bean.getClass());
d.jsonWrite(writeJson, (EntityBean) bean, null);
} else {
EJson.write(bean, gen);
}
}
gen.writeEndArray();
gen.endArray();
}
private void toJsonFromMap(Map<Object, Object> map, JsonGenerator gen, JsonWriteOptions options) throws IOException {
private void toJsonFromMap(Map<Object, Object> map, JsonWriter 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();
gen.beginObject();
while (it.hasNext()) {
Entry<Object, Object> entry = it.next();
String key = entry.getKey().toString();
Object value = entry.getValue();
if (value == null) {
gen.writeNullField(key);
gen.name(key);
gen.nullValue();
} 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 {
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.name(key);
EJson.write(value, gen);
}
}
gen.writeEndObject();
gen.endObject();
}
/**
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.json;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
@@ -19,21 +19,18 @@ public final class DJsonScalar {
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void write(JsonGenerator gen, Object value) throws IOException {
public void write(JsonWriter gen, Object value) throws IOException {
if (value instanceof String) {
gen.writeString((String) value);
gen.value((String) value);
} else if (value instanceof List) {
// expected for @DbArray values
List list = (List)value;
gen.writeRaw('[');
gen.beginArray();
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
gen.writeRaw(',');
}
write(gen, list.get(i));
}
gen.writeRaw(']');
gen.endArray();
} else {
ScalarType scalarType = typeManager.type(value.getClass());
@@ -1,8 +1,10 @@
package io.ebeaninternal.server.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.stream.BufferRecycleStrategy;
import io.avaje.json.stream.JsonStream;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
@@ -22,8 +24,9 @@ import java.util.Map;
*/
public final class ReadJson implements SpiJsonReader {
private final JsonStream jsonStream;
private final BeanDescriptor<?> rootDesc;
private final JsonParser parser;
private final JsonReader parser;
private final PathStack pathStack;
/**
* Map of the JsonReadBeanVisitor keyed by path.
@@ -38,9 +41,10 @@ public final class ReadJson implements SpiJsonReader {
/**
* Construct with parser and readOptions.
*/
public ReadJson(BeanDescriptor<?> desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper, boolean update) {
public ReadJson(BeanDescriptor<?> desc, JsonReader parser, JsonReadOptions readOptions, Object objectMapper, boolean update, JsonStream jsonStream) {
this.rootDesc = desc;
this.parser = parser;
this.jsonStream = jsonStream;
this.objectMapper = objectMapper;
this.persistenceContext = initPersistenceContext(readOptions);
this.loadContext = initLoadContext(desc, readOptions);
@@ -54,7 +58,8 @@ public final class ReadJson implements SpiJsonReader {
/**
* Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance.
*/
private ReadJson(JsonParser moreJson, ReadJson source) {
private ReadJson(JsonReader moreJson, ReadJson source) {
this.jsonStream = source.jsonStream;
this.parser = moreJson;
this.rootDesc = source.rootDesc;
this.pathStack = source.pathStack;
@@ -91,13 +96,22 @@ public final class ReadJson implements SpiJsonReader {
}
/**
* Return a new instance of ReadJson using the existing context but with a new JsonParser.
* Return a new instance of ReadJson using the existing context but with a new JsonReader.
*/
@Override
public SpiJsonReader forJson(JsonParser moreJson) {
public SpiJsonReader forJson(JsonReader moreJson) {
return new ReadJson(moreJson, this);
}
@Override
public SpiJsonReader forJson(String moreJson) {
JsonReader nestedReader = JsonStream.builder()
.bufferRecycling(BufferRecycleStrategy.NO_RECYCLING)
.build()
.reader(moreJson);
return new ReadJson(nestedReader, this);
}
/**
* Add the bean to the persistence context.
*/
@@ -152,19 +166,19 @@ public final class ReadJson implements SpiJsonReader {
}
/**
* Return the JsonParser.
* Return the JsonReader.
*/
@Override
public JsonParser parser() {
public JsonReader parser() {
return parser;
}
/**
* Return the next JsonToken from the underlying parser.
* Return the current token from the underlying parser.
*/
@Override
public JsonToken nextToken() throws IOException {
return parser.nextToken();
public Token nextToken() throws IOException {
return parser.currentToken();
}
/**
@@ -209,7 +223,7 @@ public final class ReadJson implements SpiJsonReader {
*/
@Override
public Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException {
return mapper().readValue(parser, propertyType);
return mapper().readValue(parser.readRaw(), propertyType);
}
/**
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.avaje.json.JsonWriter;
import io.ebean.FetchPath;
import io.ebean.bean.EntityBean;
import io.ebean.config.JsonConfig;
@@ -24,7 +24,7 @@ import java.util.Set;
public final class WriteJson implements SpiJsonWriter {
private final SpiEbeanServer server;
private final JsonGenerator generator;
private final JsonWriter generator;
private final FetchPath fetchPath;
private final Map<String, JsonWriteBeanVisitor<?>> visitors;
private final PathStack pathStack;
@@ -37,7 +37,7 @@ public final class WriteJson implements SpiJsonWriter {
/**
* Construct for full bean use (normal).
*/
public WriteJson(SpiEbeanServer server, JsonGenerator generator, FetchPath fetchPath,
public WriteJson(SpiEbeanServer server, JsonWriter generator, FetchPath fetchPath,
Map<String, JsonWriteBeanVisitor<?>> visitors, Object objectMapper, JsonConfig.Include include,
boolean includeLoadedImplicit) {
@@ -50,12 +50,14 @@ public final class WriteJson implements SpiJsonWriter {
this.includeLoadedImplicit = includeLoadedImplicit;
this.parentBeans = new ArrayStack<>();
this.pathStack = new PathStack();
this.generator.serializeNulls(isIncludeNull());
this.generator.serializeEmpty(isIncludeEmpty());
}
/**
* Construct for Json scalar use.
*/
public WriteJson(JsonGenerator generator, JsonConfig.Include include) {
public WriteJson(JsonWriter generator, JsonConfig.Include include) {
this.generator = generator;
this.include = include;
this.includeLoadedImplicit = true;
@@ -65,6 +67,8 @@ public final class WriteJson implements SpiJsonWriter {
this.objectMapper = null;
this.parentBeans = null;
this.pathStack = null;
this.generator.serializeNulls(isIncludeNull());
this.generator.serializeEmpty(isIncludeEmpty());
}
/**
@@ -84,7 +88,7 @@ public final class WriteJson implements SpiJsonWriter {
}
@Override
public JsonGenerator gen() {
public JsonWriter gen() {
return generator;
}
@@ -95,171 +99,113 @@ public final class WriteJson implements SpiJsonWriter {
@Override
public void writeStartObject(String key) {
try {
if (key != null) {
generator.writeFieldName(key);
}
generator.writeStartObject();
} catch (IOException e) {
throw new JsonIOException(e);
if (key != null) {
generator.name(key);
}
generator.beginObject();
}
@Override
public void writeStartObject() {
try {
generator.writeStartObject();
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.beginObject();
}
@Override
public void writeEndObject() {
try {
generator.writeEndObject();
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.endObject();
}
@Override
public void writeStartArray(String key) {
try {
if (key != null) {
generator.writeFieldName(key);
}
generator.writeStartArray();
} catch (IOException e) {
throw new JsonIOException(e);
if (key != null) {
generator.name(key);
}
generator.beginArray();
}
@Override
public void writeStartArray() {
try {
generator.writeStartArray();
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.beginArray();
}
@Override
public void writeEndArray() {
try {
generator.writeEndArray();
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.endArray();
}
@Override
public void writeRaw(String text) {
try {
generator.writeRaw(text);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.rawChunkStart();
generator.rawChunk(text);
generator.rawChunkEnd();
}
@Override
public void writeRawValue(String text) {
try {
generator.writeRawValue(text);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.rawValue(text);
}
@Override
public void writeFieldName(String name) {
try {
generator.writeFieldName(name);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
}
@Override
public void writeNullField(String name) {
if (isIncludeNull()) {
try {
generator.writeNullField(name);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.nullValue();
}
}
@Override
public void writeNumberField(String name, long value) {
try {
generator.writeNumberField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeNumberField(String name, double value) {
try {
generator.writeNumberField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeNumberField(String name, int value) {
try {
generator.writeNumberField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeNumberField(String name, short value) {
try {
generator.writeNumberField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeNumberField(String name, float value) {
try {
generator.writeNumberField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value((double) value);
}
@Override
public void writeNumberField(String name, BigDecimal value) {
try {
generator.writeNumberField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeStringField(String name, String value) {
try {
generator.writeStringField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeBinary(InputStream is, int length) {
try {
generator.writeBinary(is, length);
generator.value(is.readNBytes(length));
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -267,83 +213,49 @@ public final class WriteJson implements SpiJsonWriter {
@Override
public void writeBinaryField(String name, byte[] value) {
try {
generator.writeBinaryField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeBooleanField(String name, boolean value) {
try {
generator.writeBooleanField(name, value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.name(name);
generator.value(value);
}
@Override
public void writeBoolean(boolean value) {
try {
generator.writeBoolean(value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.value(value);
}
@Override
public void writeString(String value) {
try {
generator.writeString(value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.value(value);
}
@Override
public void writeNumber(int value) {
try {
generator.writeNumber(value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.value(value);
}
@Override
public void writeNumber(long value) {
try {
generator.writeNumber(value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.value(value);
}
@Override
public void writeNumber(double value) {
try {
generator.writeNumber(value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.value(value);
}
@Override
public void writeNumber(BigDecimal value) {
try {
generator.writeNumber(value);
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.value(value);
}
@Override
public void writeNull() {
try {
generator.writeNull();
} catch (IOException e) {
throw new JsonIOException(e);
}
generator.nullValue();
}
@Override
@@ -375,55 +287,39 @@ public final class WriteJson implements SpiJsonWriter {
@Override
public void beginAssocMany(String key) {
try {
pathStack.pushPathKey(key);
if (key != null) {
generator.writeFieldName(key);
}
generator.writeStartArray();
} catch (IOException e) {
throw new JsonIOException(e);
pathStack.pushPathKey(key);
if (key != null) {
generator.name(key);
}
generator.beginArray();
}
@Override
public void endAssocMany() {
try {
pathStack.pop();
generator.writeEndArray();
} catch (IOException e) {
throw new JsonIOException(e);
}
pathStack.pop();
generator.endArray();
}
@Override
public void beginAssocManyMap(String key, boolean elementCollection) {
try {
pathStack.pushPathKey(key);
if (key != null) {
generator.writeFieldName(key);
}
if (elementCollection) {
generator.writeStartObject();
} else {
generator.writeStartArray();
}
} catch (IOException e) {
throw new JsonIOException(e);
pathStack.pushPathKey(key);
if (key != null) {
generator.name(key);
}
if (elementCollection) {
generator.beginObject();
} else {
generator.beginArray();
}
}
@Override
public void endAssocManyMap(boolean elementCollection) {
try {
pathStack.pop();
if (elementCollection) {
generator.writeEndObject();
} else {
generator.writeEndArray();
}
} catch (IOException e) {
throw new JsonIOException(e);
pathStack.pop();
if (elementCollection) {
generator.endObject();
} else {
generator.endArray();
}
}
@@ -465,8 +361,8 @@ public final class WriteJson implements SpiJsonWriter {
}
}
try {
generator.writeFieldName(name);
objectMapper().writeValue(generator, value);
generator.name(name);
generator.rawValue(objectMapper().writeValueAsString(value));
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.readaudit;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.applog.AppLog;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditQueryPlan;
import io.ebean.event.readaudit.ReadEvent;
@@ -24,7 +24,7 @@ public class DefaultReadAuditLogger implements ReadAuditLogger {
private static final System.Logger queryLogger = AppLog.getLogger("io.ebean.ReadAuditQuery");
private static final System.Logger auditLogger = AppLog.getLogger("io.ebean.ReadAudit");
protected final JsonFactory jsonFactory = new JsonFactory();
protected final JsonStream jsonStream = JsonStream.builder().build();
protected final int defaultQueryBuffer = 500;
protected final int defaultReadBuffer = 150;
@@ -34,25 +34,26 @@ public class DefaultReadAuditLogger implements ReadAuditLogger {
@Override
public void queryPlan(ReadAuditQueryPlan queryPlan) {
StringWriter writer = new StringWriter(defaultQueryBuffer);
try (JsonGenerator gen = jsonFactory.createGenerator(writer)) {
gen.writeStartObject();
try (JsonWriter gen = jsonStream.writer(writer)) {
gen.beginObject();
String beanType = queryPlan.getBeanType();
if (beanType != null) {
gen.writeStringField("beanType", beanType);
gen.name("beanType");
gen.value(beanType);
}
String queryKey = queryPlan.getQueryKey();
if (queryKey != null) {
gen.writeStringField("queryKey", queryKey);
gen.name("queryKey");
gen.value(queryKey);
}
String sql = queryPlan.getSql();
if (sql != null) {
gen.writeStringField("sql", sql);
gen.name("sql");
gen.value(sql);
}
gen.writeEndObject();
gen.endObject();
gen.flush();
queryLogger.log(INFO, writer.toString());
} catch (IOException e) {
CoreLog.log.log(ERROR, "Error writing Read audit event", e);
}
}
@@ -73,56 +74,64 @@ public class DefaultReadAuditLogger implements ReadAuditLogger {
}
protected void writeEvent(ReadEvent event) {
try {
StringWriter writer = new StringWriter(defaultReadBuffer);
JsonGenerator gen = jsonFactory.createGenerator(writer);
StringWriter writer = new StringWriter(defaultReadBuffer);
try (JsonWriter gen = jsonStream.writer(writer)) {
writeDetails(gen, event);
auditLogger.log(INFO, writer.toString());
} catch (IOException e) {
CoreLog.log.log(ERROR, "Error writing Read audit event", e);
return;
}
auditLogger.log(INFO, writer.toString());
}
/**
* Write the details for the read bean or read many beans event.
*/
protected void writeDetails(JsonGenerator gen, ReadEvent event) throws IOException {
gen.writeStartObject();
protected void writeDetails(JsonWriter gen, ReadEvent event) throws IOException {
gen.beginObject();
String source = event.getSource();
if (source != null) {
gen.writeStringField("source", source);
gen.name("source");
gen.value(source);
}
String userId = event.getUserId();
if (userId != null) {
gen.writeStringField("userId", userId);
gen.name("userId");
gen.value(userId);
}
String userIpAddress = event.getUserIpAddress();
if (userIpAddress != null) {
gen.writeStringField("userIpAddress", userIpAddress);
gen.name("userIpAddress");
gen.value(userIpAddress);
}
Map<String, String> userContext = event.getUserContext();
if (userContext != null && !userContext.isEmpty()) {
gen.writeObjectFieldStart("userContext");
gen.name("userContext");
gen.beginObject();
for (Map.Entry<String, String> entry : userContext.entrySet()) {
gen.writeStringField(entry.getKey(), entry.getValue());
gen.name(entry.getKey());
gen.value(entry.getValue());
}
gen.writeEndObject();
gen.endObject();
}
gen.writeNumberField("eventTime", event.getEventTime());
gen.writeStringField("beanType", event.getBeanType());
gen.writeStringField("queryKey", event.getQueryKey());
gen.writeStringField("bindLog", event.getBindLog());
gen.name("eventTime");
gen.value(event.getEventTime());
gen.name("beanType");
gen.value(event.getBeanType());
gen.name("queryKey");
gen.value(event.getQueryKey());
gen.name("bindLog");
gen.value(event.getBindLog());
Object id = event.getId();
if (id != null) {
gen.writeFieldName("id");
gen.name("id");
EJson.write(id, gen);
} else {
gen.writeFieldName("ids");
gen.name("ids");
EJson.writeCollection(event.getIds(), gen);
}
gen.writeEndObject();
gen.endObject();
gen.flush();
gen.close();
}
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
@@ -173,12 +173,16 @@ class ScalarTypeArrayList extends ScalarTypeArrayBase<List> implements ScalarTyp
}
@Override
public List jsonRead(JsonParser parser) throws IOException {
return convert(EJson.parseList(parser, parser.getCurrentToken()));
public List jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
List<Object> rawList = EJson.parseList(parser, parser.currentToken());
return convert(rawList);
}
@Override
public void jsonWrite(JsonGenerator writer, List value) throws IOException {
public void jsonWrite(JsonWriter writer, List value) throws IOException {
EJson.write(value, writer);
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
@@ -152,12 +152,16 @@ class ScalarTypeArraySet extends ScalarTypeArrayBase<Set> implements ScalarTypeA
}
@Override
public Set jsonRead(JsonParser parser) throws IOException {
return convert(EJson.parseList(parser, parser.getCurrentToken()));
public Set jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
List<Object> rawList = EJson.parseList(parser, parser.currentToken());
return convert(rawList);
}
@Override
public void jsonWrite(JsonGenerator writer, Set value) throws IOException {
public void jsonWrite(JsonWriter writer, Set value) throws IOException {
EJson.write(value, writer);
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -78,13 +78,13 @@ class ScalarTypeBigDecimal extends ScalarTypeBase<BigDecimal> {
}
@Override
public BigDecimal jsonRead(JsonParser parser) throws IOException {
return parser.getDecimalValue();
public BigDecimal jsonRead(JsonReader parser) throws IOException {
return parser.readDecimal();
}
@Override
public void jsonWrite(JsonGenerator writer, BigDecimal value) throws IOException {
writer.writeNumber(value);
public void jsonWrite(JsonWriter writer, BigDecimal value) throws IOException {
writer.value(value);
}
@Override
@@ -1,8 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -348,13 +347,39 @@ public final class ScalarTypeBoolean {
}
@Override
public Boolean jsonRead(JsonParser parser) {
return JsonToken.VALUE_TRUE == parser.getCurrentToken() ? Boolean.TRUE : Boolean.FALSE;
public Boolean jsonRead(JsonReader parser) {
if (parser.isNullValue()) {
return null;
}
var token = parser.currentToken();
if (token == JsonReader.Token.BOOLEAN) {
return parser.readBoolean();
}
if (token == JsonReader.Token.NUMBER) {
return parser.readDecimal().intValue() == 1;
}
if (token == JsonReader.Token.STRING) {
return parse(parser.readString());
}
String raw = parser.readRaw();
if (raw == null || "null".equals(raw)) {
return null;
}
if ("true".equals(raw) || "1".equals(raw)) {
return Boolean.TRUE;
}
if ("false".equals(raw) || "0".equals(raw)) {
return Boolean.FALSE;
}
if (raw.length() > 1 && raw.charAt(0) == '"' && raw.charAt(raw.length() - 1) == '"') {
return parse(raw.substring(1, raw.length() - 1));
}
return parse(raw);
}
@Override
public void jsonWrite(JsonGenerator writer, Boolean value) throws IOException {
writer.writeBoolean(value);
public void jsonWrite(JsonWriter writer, Boolean value) throws IOException {
writer.value(value);
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -9,7 +9,6 @@ import io.ebean.core.type.ScalarTypeBase;
import io.ebean.text.TextException;
import io.ebean.core.type.BasicTypeConverter;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@@ -55,15 +54,13 @@ final class ScalarTypeByte extends ScalarTypeBase<Byte> {
}
@Override
public void jsonWrite(JsonGenerator writer, Byte value) throws IOException {
writer.writeBinary(new byte[]{value});
public void jsonWrite(JsonWriter writer, Byte value) throws IOException {
writer.value(new byte[]{value});
}
@Override
public Byte jsonRead(JsonParser parser) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
parser.readBinaryValue(os);
byte[] bytes = os.toByteArray();
public Byte jsonRead(JsonReader parser) throws IOException {
byte[] bytes = parser.readBinary();
if (bytes.length == 0) {
return null;
} else {
@@ -1,13 +1,12 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarTypeBase;
import io.ebean.text.TextException;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@@ -85,15 +84,13 @@ public abstract class ScalarTypeBytesBase extends ScalarTypeBase<byte[]> {
}
@Override
public void jsonWrite(JsonGenerator writer, byte[] value) throws IOException {
writer.writeBinary(value);
public void jsonWrite(JsonWriter writer, byte[] value) throws IOException {
writer.value(value);
}
@Override
public byte[] jsonRead(JsonParser parser) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(500);
parser.readBinaryValue(out);
return out.toByteArray();
public byte[] jsonRead(JsonReader parser) throws IOException {
return parser.readBinary();
}
@Override
@@ -1,13 +1,12 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@@ -58,15 +57,13 @@ public final class ScalarTypeBytesEncrypted implements ScalarType<byte[]> {
}
@Override
public void jsonWrite(JsonGenerator writer, byte[] value) throws IOException {
writer.writeBinary(value);
public void jsonWrite(JsonWriter writer, byte[] value) throws IOException {
writer.value(value);
}
@Override
public byte[] jsonRead(JsonParser parser) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(500);
parser.readBinaryValue(out);
return out.toByteArray();
public byte[] jsonRead(JsonReader parser) throws IOException {
return parser.readBinary();
}
@Override
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarTypeBaseVarchar;
@@ -72,7 +72,7 @@ final class ScalarTypeCharArray extends ScalarTypeBaseVarchar<char[]> {
}
@Override
public char[] jsonRead(JsonParser parser) throws IOException {
return parser.getValueAsString().toCharArray();
public char[] jsonRead(JsonReader parser) throws IOException {
return parser.readString().toCharArray();
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -77,13 +77,13 @@ final class ScalarTypeDouble extends ScalarTypeBase<Double> {
}
@Override
public Double jsonRead(JsonParser parser) throws IOException {
return parser.getDoubleValue();
public Double jsonRead(JsonReader parser) throws IOException {
return parser.readDouble();
}
@Override
public void jsonWrite(JsonGenerator writer, Double value) throws IOException {
writer.writeNumber(value);
public void jsonWrite(JsonWriter writer, Double value) throws IOException {
writer.value(value);
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.*;
import java.io.DataInput;
@@ -90,13 +90,13 @@ class ScalarTypeDuration extends ScalarTypeBase<Duration> {
}
@Override
public Duration jsonRead(JsonParser parser) throws IOException {
return Duration.parse(parser.getValueAsString());
public Duration jsonRead(JsonReader parser) throws IOException {
return Duration.parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, Duration value) throws IOException {
writer.writeString(value.toString());
public void jsonWrite(JsonWriter writer, Duration value) throws IOException {
writer.value(value.toString());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -120,12 +120,12 @@ public final class ScalarTypeEncryptedWrapper<T> implements ScalarType<T>, Local
}
@Override
public T jsonRead(JsonParser parser) throws IOException {
public T jsonRead(JsonReader parser) throws IOException {
return wrapped.jsonRead(parser);
}
@Override
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
public void jsonWrite(JsonWriter writer, T value) throws IOException {
wrapped.jsonWrite(writer, value);
}
@@ -1,11 +1,11 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarTypeBase;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import jakarta.persistence.EnumType;
import java.io.DataInput;
@@ -228,20 +228,19 @@ final class ScalarTypeEnumStandard {
}
@Override
public Object jsonRead(JsonParser parser) throws IOException {
if (parser.getCodec() != null) {
return parser.readValueAs(enumType);
} else {
return parse(parser.getValueAsString());
public Object jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
return parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, Object value) throws IOException {
if (writer.getCodec() != null) {
writer.writeObject(value);
public void jsonWrite(JsonWriter writer, Object value) throws IOException {
if (value == null) {
writer.nullValue();
} else {
writer.writeString(formatValue(value));
writer.value(formatValue(value));
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -102,16 +102,17 @@ final class ScalarTypeFile extends ScalarTypeBase<File> {
}
@Override
public void jsonWrite(JsonGenerator writer, File value) throws IOException {
InputStream is = getInputStream(value);
writer.writeBinary(is, (int) value.length());
public void jsonWrite(JsonWriter writer, File value) throws IOException {
try (InputStream is = getInputStream(value)) {
writer.value(is.readAllBytes());
}
}
@Override
public File jsonRead(JsonParser parser) throws IOException {
public File jsonRead(JsonReader parser) throws IOException {
File tempFile = File.createTempFile(prefix, suffix, directory);
try (OutputStream os = getOutputStream(tempFile)) {
parser.readBinaryValue(os);
os.write(parser.readBinary());
os.flush();
}
return tempFile;
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -77,13 +77,13 @@ final class ScalarTypeFloat extends ScalarTypeBase<Float> {
}
@Override
public Float jsonRead(JsonParser parser) throws IOException {
return parser.getFloatValue();
public Float jsonRead(JsonReader parser) throws IOException {
return (float) parser.readDouble();
}
@Override
public void jsonWrite(JsonGenerator writer, Float value) throws IOException {
writer.writeNumber(value);
public void jsonWrite(JsonWriter writer, Float value) throws IOException {
writer.value(value.doubleValue());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -87,13 +87,13 @@ final class ScalarTypeInteger extends ScalarTypeBase<Integer> {
}
@Override
public Integer jsonRead(JsonParser parser) throws IOException {
return parser.getIntValue();
public Integer jsonRead(JsonReader parser) throws IOException {
return parser.readInt();
}
@Override
public void jsonWrite(JsonGenerator writer, Integer value) throws IOException {
writer.writeNumber(value);
public void jsonWrite(JsonWriter writer, Integer value) throws IOException {
writer.value(value);
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.*;
import io.ebean.text.TextException;
@@ -182,12 +182,12 @@ final class ScalarTypeJsonList {
}
@Override
public final List jsonRead(JsonParser parser) throws IOException {
return EJson.parseList(parser, parser.getCurrentToken());
public final List jsonRead(JsonReader parser) throws IOException {
return EJson.parseList(parser, parser.currentToken());
}
@Override
public final void jsonWrite(JsonGenerator writer, List value) throws IOException {
public final void jsonWrite(JsonWriter writer, List value) throws IOException {
EJson.write(value, writer);
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
@@ -235,13 +235,13 @@ abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
}
@Override
public final void jsonWrite(JsonGenerator writer, Map value) throws IOException {
public final void jsonWrite(JsonWriter writer, Map value) throws IOException {
EJson.write(value, writer);
}
@Override
public final Map jsonRead(JsonParser parser) throws IOException {
return EJson.parseObject(parser, parser.getCurrentToken());
public final Map jsonRead(JsonReader parser) throws IOException {
return EJson.parseObject(parser, parser.currentToken());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.*;
import io.ebean.text.TextException;
@@ -179,12 +179,12 @@ final class ScalarTypeJsonSet {
}
@Override
public final Set jsonRead(JsonParser parser) throws IOException {
return convertList(EJson.parseList(parser, parser.getCurrentToken()));
public final Set jsonRead(JsonReader parser) throws IOException {
return convertList(EJson.parseList(parser, parser.currentToken()));
}
@Override
public final void jsonWrite(JsonGenerator writer, Set value) throws IOException {
public final void jsonWrite(JsonWriter writer, Set value) throws IOException {
EJson.write(value, writer);
}
@@ -1,7 +1,8 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import io.ebean.config.JsonConfig;
import io.ebean.config.dbplatform.ExtraDbTypes;
import io.ebean.core.type.ScalarTypeBaseDateTime;
@@ -48,13 +49,19 @@ final class ScalarTypeLocalDateTime extends ScalarTypeBaseDateTime<LocalDateTime
}
@Override
public LocalDateTime jsonRead(JsonParser parser) throws IOException {
return parse(parser.getText());
public LocalDateTime jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
if (parser.currentToken() == Token.NUMBER) {
return parse(parser.readDecimal().toPlainString());
}
return parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, LocalDateTime value) throws IOException {
writer.writeString(value.toString());
public void jsonWrite(JsonWriter writer, LocalDateTime value) throws IOException {
writer.value(value.toString());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -87,13 +87,13 @@ class ScalarTypeLocalTime extends ScalarTypeBase<LocalTime> {
}
@Override
public LocalTime jsonRead(JsonParser parser) throws IOException {
return LocalTime.parse(parser.getValueAsString());
public LocalTime jsonRead(JsonReader parser) throws IOException {
return LocalTime.parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, LocalTime value) throws IOException {
writer.writeString(value.toString());
public void jsonWrite(JsonWriter writer, LocalTime value) throws IOException {
writer.value(value.toString());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -87,13 +87,13 @@ final class ScalarTypeLong extends ScalarTypeBase<Long> {
}
@Override
public Long jsonRead(JsonParser parser) throws IOException {
return parser.getLongValue();
public Long jsonRead(JsonReader parser) throws IOException {
return parser.readLong();
}
@Override
public void jsonWrite(JsonGenerator writer, Long value) throws IOException {
writer.writeNumber(value);
public void jsonWrite(JsonWriter writer, Long value) throws IOException {
writer.value(value);
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -82,13 +82,13 @@ final class ScalarTypeMathBigInteger extends ScalarTypeBase<BigInteger> {
}
@Override
public BigInteger jsonRead(JsonParser parser) throws IOException {
return parser.getDecimalValue().toBigInteger();
public BigInteger jsonRead(JsonReader parser) throws IOException {
return parser.readBigInteger();
}
@Override
public void jsonWrite(JsonGenerator writer, BigInteger value) throws IOException {
writer.writeNumber(value.longValue());
public void jsonWrite(JsonWriter writer, BigInteger value) throws IOException {
writer.value(value);
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -102,13 +102,13 @@ final class ScalarTypeMonthDay extends ScalarTypeBase<MonthDay> {
}
@Override
public MonthDay jsonRead(JsonParser parser) throws IOException {
return parse(parser.getValueAsString());
public MonthDay jsonRead(JsonReader parser) throws IOException {
return parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, MonthDay value) throws IOException {
writer.writeString(format(value));
public void jsonWrite(JsonWriter writer, MonthDay value) throws IOException {
writer.value(format(value));
}
@Override
@@ -5,8 +5,8 @@ import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
@@ -84,12 +84,12 @@ class ScalarTypeNotFound implements ScalarType<Void> {
}
@Override
public Void jsonRead(JsonParser parser) throws IOException {
public Void jsonRead(JsonReader parser) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void jsonWrite(JsonGenerator writer, Void value) throws IOException {
public void jsonWrite(JsonWriter writer, Void value) throws IOException {
throw new UnsupportedOperationException();
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
@@ -102,16 +102,16 @@ final class ScalarTypePostgresHstore extends ScalarTypeBase<Map> {
}
@Override
public void jsonWrite(JsonGenerator writer, Map value) throws IOException {
public void jsonWrite(JsonWriter writer, Map value) throws IOException {
EJson.write(value, writer);
}
@Override
public Map jsonRead(JsonParser parser) throws IOException {
public Map jsonRead(JsonReader parser) throws IOException {
// at this point the BeanProperty has read the START_OBJECT token
// to check for a null value. Pass the START_OBJECT token through to
// the EJson parsing so that it knows the first token has been read
return EJson.parseObject(parser, parser.getCurrentToken());
return EJson.parseObject(parser, parser.currentToken());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -77,13 +77,13 @@ final class ScalarTypeShort extends ScalarTypeBase<Short> {
}
@Override
public Short jsonRead(JsonParser parser) throws IOException {
return parser.getShortValue();
public Short jsonRead(JsonReader parser) throws IOException {
return (short) parser.readInt();
}
@Override
public void jsonWrite(JsonGenerator writer, Short value) throws IOException {
writer.writeNumber(value);
public void jsonWrite(JsonWriter writer, Short value) throws IOException {
writer.value(value.intValue());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -77,13 +77,16 @@ abstract class ScalarTypeStringBase extends ScalarTypeBase<String> {
}
@Override
public String jsonRead(JsonParser parser) throws IOException {
return parser.getValueAsString();
public String jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
return parser.readString();
}
@Override
public void jsonWrite(JsonGenerator writer, String value) throws IOException {
writer.writeString(value);
public void jsonWrite(JsonWriter writer, String value) throws IOException {
writer.value(value);
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -83,13 +83,13 @@ final class ScalarTypeTime extends ScalarTypeBase<Time> {
}
@Override
public Time jsonRead(JsonParser parser) throws IOException {
return parse(parser.getValueAsString());
public Time jsonRead(JsonReader parser) throws IOException {
return parse(parser.readString());
}
@Override
public void jsonWrite(JsonGenerator writer, Time value) throws IOException {
writer.writeString(format(value));
public void jsonWrite(JsonWriter writer, Time value) throws IOException {
writer.value(format(value));
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarTypeBase;
@@ -70,13 +70,13 @@ abstract class ScalarTypeUUIDBase extends ScalarTypeBase<UUID> implements Scalar
}
@Override
public void jsonWrite(JsonGenerator writer, UUID value) throws IOException {
writer.writeString(formatValue(value));
public void jsonWrite(JsonWriter writer, UUID value) throws IOException {
writer.value(formatValue(value));
}
@Override
public UUID jsonRead(JsonParser parser) throws IOException {
return parse(parser.getValueAsString());
public UUID jsonRead(JsonReader parser) throws IOException {
return parse(parser.readString());
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.config.ScalarTypeConverter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
@@ -156,13 +156,13 @@ public class ScalarTypeWrapper<B, S> implements ScalarType<B> {
}
@Override
public B jsonRead(JsonParser parser) throws IOException {
public B jsonRead(JsonReader parser) throws IOException {
S object = scalarType.jsonRead(parser);
return converter.wrapValue(object);
}
@Override
public void jsonWrite(JsonGenerator writer, B beanValue) throws IOException {
public void jsonWrite(JsonWriter writer, B beanValue) throws IOException {
S unwrapValue = converter.unwrapValue(beanValue);
scalarType.jsonWrite(writer, unwrapValue);
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -82,13 +82,13 @@ final class ScalarTypeYear extends ScalarTypeBase<Year> {
}
@Override
public Year jsonRead(JsonParser parser) throws IOException {
return Year.of(parser.getIntValue());
public Year jsonRead(JsonReader parser) throws IOException {
return Year.of(parser.readInt());
}
@Override
public void jsonWrite(JsonGenerator writer, Year value) throws IOException {
writer.writeNumber(value.getValue());
public void jsonWrite(JsonWriter writer, Year value) throws IOException {
writer.value(value.getValue());
}
@Override
@@ -1,105 +1,121 @@
package io.ebeaninternal.server.util;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import io.avaje.json.JsonReader;
import io.avaje.json.stream.JsonStream;
/**
* Compute an order-independent structural hash of JSON content using Jackson's streaming parser.
* Compute an order-independent structural hash of JSON content.
* <p>
* Object key ordering does NOT affect the hash value (handles PostgreSQL JSONB key reordering),
* while array element ordering DOES affect it (array position is semantically significant).
* <p>
* This is significantly faster than a full parse/format roundtrip because it performs
* zero object allocation beyond the parser itself — no tree building, no reflection,
* no type conversion. Single-pass O(n) time with O(depth) stack space.
* Object key ordering does not affect the hash value, while array element ordering does.
*/
public final class JsonContentHash {
private static final JsonFactory FACTORY = new JsonFactory();
private static final JsonStream STREAM = JsonStream.builder().build();
// Type markers to distinguish empty object {}, empty array [], and null
private static final long OBJECT_SEED = 0x7A5662B4E8B10FA3L;
private static final long ARRAY_SEED = 0x3C6EF372FE94F82BL;
private static final long TRUE_HASH = 0x9E3779B97F4A7C15L;
private static final long FALSE_HASH = 0x517CC1B727220A95L;
private static final long NULL_HASH = 0x6C62272E07BB0142L;
private JsonContentHash() {
}
/**
* Compute an order-independent hash of JSON content.
* Two JSON strings with identical content but different key ordering
* will produce the same hash value.
*/
public static long hash(String json) {
if (json == null || json.isEmpty()) {
return 0L;
}
try (JsonParser parser = FACTORY.createParser(json)) {
parser.nextToken();
return computeHash(parser);
} catch (IOException e) {
try (JsonReader reader = STREAM.reader(json)) {
return hash(reader, firstNonWhitespace(json));
} catch (RuntimeException e) {
// Fallback to regular string hash if JSON is malformed.
// This is safe: two identical malformed strings produce the same hash,
// and a malformed string won't falsely match a valid one.
return stringHash(json);
}
}
private static long computeHash(JsonParser parser) throws IOException {
JsonToken token = parser.currentToken();
if (token == null) {
return 0L;
}
switch (token) {
case START_OBJECT:
return hashObject(parser);
case START_ARRAY:
return hashArray(parser);
case VALUE_STRING:
return mix(stringHash(parser.getText()));
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
// Use text representation for numeric consistency across int/long/double
return mix(stringHash(parser.getText()));
case VALUE_TRUE:
return 0x9E3779B97F4A7C15L;
case VALUE_FALSE:
return 0x517CC1B727220A95L;
case VALUE_NULL:
return 0x6C62272E07BB0142L;
private static long hash(JsonReader reader, char firstToken) {
switch (firstToken) {
case '{':
reader.beginObject();
return hashObject(reader);
case '[':
reader.beginArray();
return hashArray(reader);
case '"':
return mix(stringHash(reader.readString()));
case 't':
case 'f':
return reader.readBoolean() ? TRUE_HASH : FALSE_HASH;
case 'n':
if (reader.isNullValue()) {
return NULL_HASH;
}
throw new IllegalStateException("Invalid null token");
default:
return 0L;
return mix(stringHash(reader.readDecimal().toString()));
}
}
// Type markers to distinguish empty object {}, empty array [], and null
private static final long OBJECT_SEED = 0x7A5662B4E8B10FA3L;
private static final long ARRAY_SEED = 0x3C6EF372FE94F82BL;
private static long hashValue(JsonReader reader) {
JsonReader.Token token = reader.currentToken();
switch (token) {
case BEGIN_OBJECT:
reader.beginObject();
return hashObject(reader);
case BEGIN_ARRAY:
reader.beginArray();
return hashArray(reader);
case STRING:
return mix(stringHash(reader.readString()));
case NUMBER:
return mix(stringHash(reader.readDecimal().toString()));
case BOOLEAN:
return reader.readBoolean() ? TRUE_HASH : FALSE_HASH;
case NULL:
if (reader.isNullValue()) {
return NULL_HASH;
}
throw new IllegalStateException("Invalid null token");
default:
throw new IllegalStateException("Unhandled token " + token);
}
}
/**
* Hash an object using commutative addition of entry hashes.
* Addition is commutative (a + b == b + a), so the result is
* independent of the order in which keys appear in the JSON.
*/
private static long hashObject(JsonParser parser) throws IOException {
private static long hashObject(JsonReader reader) {
long hash = OBJECT_SEED;
while (parser.nextToken() != JsonToken.END_OBJECT) {
long keyHash = stringHash(parser.currentName());
parser.nextToken();
long valueHash = computeHash(parser);
// Mix key+value into a single entry hash, then add (commutative)
while (reader.hasNextField()) {
String key = reader.nextField();
long keyHash = stringHash(key);
long valueHash = hashValue(reader);
hash += mix(keyHash * 0x9E3779B97F4A7C15L + valueHash);
}
reader.endObject();
return hash;
}
/**
* Hash an array using position-dependent combination.
* Array element order IS semantically significant in JSON.
*/
private static long hashArray(JsonParser parser) throws IOException {
private static long hashArray(JsonReader reader) {
long hash = ARRAY_SEED;
while (parser.nextToken() != JsonToken.END_ARRAY) {
hash = hash * 31 + computeHash(parser);
while (reader.hasNextElement()) {
hash = hash * 31 + hashValue(reader);
}
reader.endArray();
return mix(hash);
}
private static char firstNonWhitespace(String json) {
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
if (!Character.isWhitespace(c)) {
return c;
}
}
return 0;
}
/**
* 64-bit FNV-1a inspired string hash for better distribution than String.hashCode().
*/
@@ -113,11 +129,7 @@ public final class JsonContentHash {
}
/**
* Mixing/finalizer function to improve hash distribution and break
* additive symmetry (prevents collisions when values are swapped between keys).
* <p>
* This is fmix64 from MurmurHash3 by Austin Appleby (public domain).
* See: https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
* Mixing/finalizer function to improve hash distribution and break additive symmetry.
*/
private static long mix(long h) {
h ^= (h >>> 33);
+1 -1
View File
@@ -29,12 +29,12 @@ module io.ebean.core {
requires org.antlr.antlr4.runtime;
requires io.avaje.classpath.scanner.api;
requires io.avaje.classpath.scanner;
requires io.avaje.json;
requires io.ebean.types;
requires static io.avaje.jsr305x;
requires static io.ebean.core.json;
requires static com.fasterxml.jackson.annotation;
requires static com.fasterxml.jackson.core;
requires static com.fasterxml.jackson.databind;
requires static jakarta.validation;
requires static jakarta.transaction;
@@ -1,9 +1,8 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.config.JsonConfig;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.server.json.WriteJson;
@@ -12,13 +11,14 @@ import java.io.IOException;
import java.io.StringWriter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Base class to help json testing.
*/
public class JsonTester<T> {
protected JsonFactory factory = new JsonFactory();
protected JsonStream jsonStream = JsonStream.builder().build();
protected ScalarType<T> type;
@@ -29,24 +29,21 @@ public class JsonTester<T> {
public String test(T value) throws IOException {
StringWriter writer = new StringWriter();
JsonGenerator generator = factory.createGenerator(writer);
generator.writeStartObject();
JsonWriter generator = jsonStream.writer(writer);
generator.beginObject();
WriteJson writeJson = new WriteJson(generator, JsonConfig.Include.ALL);
writeJson.writeFieldName("key");
type.jsonWrite(generator, value);
generator.writeEndObject();
generator.endObject();
generator.flush();
JsonParser parser = factory.createParser(writer.toString());
JsonToken token = parser.nextToken();
assertEquals(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertEquals(JsonToken.FIELD_NAME, token);
parser.nextToken();
JsonReader parser = jsonStream.reader(writer.toString());
parser.beginObject();
assertTrue(parser.hasNextField());
assertEquals("key", parser.nextField());
T val1 = type.jsonRead(parser);
assertEquals(value, val1);
parser.endObject();
return writer.toString();
}
@@ -1,8 +1,7 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.DB;
import io.avaje.json.JsonReader;
import io.avaje.json.stream.JsonStream;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarType;
import io.ebean.text.json.EJson;
@@ -10,7 +9,6 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@@ -143,9 +141,10 @@ public class ScalarTypeArrayListTest extends BasePlatformArrayTypeFactoryTest {
input.add(UUID.randomUUID());
String asJson = EJson.write(input);
JsonParser parser = DB.json().createParser(new StringReader(asJson));
Object parsed = scalarType.jsonRead(parser);
assertThat(parsed).isEqualTo(input);
try (JsonReader parser = JsonStream.builder().build().reader(asJson)) {
parser.beginArray();
Object parsed = scalarType.jsonRead(parser);
assertThat(parsed).isEqualTo(input);
}
}
}
@@ -1,13 +1,12 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.DB;
import io.avaje.json.JsonReader;
import io.avaje.json.stream.JsonStream;
import io.ebean.core.type.ScalarType;
import io.ebean.text.json.EJson;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -130,9 +129,10 @@ public class ScalarTypeArraySetH2Test extends BasePlatformArrayTypeFactoryTest {
input.add(UUID.randomUUID());
String asJson = EJson.write(input);
JsonParser parser = DB.json().createParser(new StringReader(asJson));
Object parsed = scalarType.jsonRead(parser);
assertThat(parsed).isEqualTo(input);
try (JsonReader parser = JsonStream.builder().build().reader(asJson)) {
parser.beginArray();
Object parsed = scalarType.jsonRead(parser);
assertThat(parsed).isEqualTo(input);
}
}
}
@@ -1,12 +1,7 @@
package io.ebeaninternal.server.type;
import io.ebean.DB;
import io.ebean.text.json.JsonContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.TOne;
import java.io.IOException;
import java.sql.Types;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,34 +9,16 @@ import static org.junit.jupiter.api.Assertions.*;
public class ScalarTypeBooleanTest {
JsonContext jsonContext = DB.getDefault().json();
@Test
public void json_true() {
TOne bean = new TOne();
bean.setId(42);
bean.setActive(true);
String json = jsonContext.toJson(bean);
TOne tOne = jsonContext.toBean(TOne.class, json);
Assertions.assertTrue(tOne.isActive());
assertEquals(json, "{\"id\":42,\"active\":true}");
public void json_true() throws IOException {
String json = new JsonTester<>(new ScalarTypeBoolean.Native()).test(true);
assertEquals(json, "{\"key\":true}");
}
@Test
public void json_false() {
TOne bean = new TOne();
bean.setId(42);
bean.setActive(false);
String json = jsonContext.toJson(bean);
TOne tOne = jsonContext.toBean(TOne.class, json);
Assertions.assertFalse(tOne.isActive());
assertEquals(json, "{\"id\":42,\"active\":false}");
public void json_false() throws IOException {
String json = new JsonTester<>(new ScalarTypeBoolean.Native()).test(false);
assertEquals(json, "{\"key\":false}");
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.config.JsonConfig;
import org.junit.jupiter.api.Test;
@@ -21,7 +21,7 @@ public class ScalarTypeLocalDateTimeTest {
private final ScalarTypeLocalDateTime type = new ScalarTypeLocalDateTime(JsonConfig.DateTime.MILLIS);
private final JsonFactory factory = new JsonFactory();
private final JsonStream jsonStream = JsonStream.builder().build();
// warm up
private final LocalDateTime warmUp = LocalDateTime.now();
@@ -91,7 +91,7 @@ public class ScalarTypeLocalDateTimeTest {
ScalarTypeLocalDateTime typeIso = new ScalarTypeLocalDateTime(JsonConfig.DateTime.ISO8601);
StringWriter writer = new StringWriter();
JsonGenerator generator = factory.createGenerator(writer);
JsonWriter generator = jsonStream.writer(writer);
typeIso.jsonWrite(generator, of);
generator.flush();
@@ -132,18 +132,15 @@ public class ScalarTypeLocalDateTimeTest {
@Test
public void testParseEbean11() throws IOException {
ScalarTypeLocalDateTime type = new ScalarTypeLocalDateTime(JsonConfig.DateTime.ISO8601);
JsonFactory factory = new JsonFactory();
JsonParser parser11 = factory.createParser("1517627106000"); // its a number!
JsonParser parser13 = factory.createParser("\"2022-01-01T01:00:00\"");
JsonReader parser11 = jsonStream.reader("1517627106000"); // ebean 11 style number
// test parsing an ebean 11/13 timestamp, we do not expect an exception
LocalDateTime p = type.parse("1517627106000");
parser11.nextToken();
LocalDateTime q = type.jsonRead(parser11);
assertThat(p).isEqualTo(q);
JsonReader parser13 = jsonStream.reader("\"2022-01-01T01:00:00\"");
p = type.parse("2022-01-01T01:00:00");
parser13.nextToken();
q = type.jsonRead(parser13);
assertThat(p).isEqualTo(q);
TimeZone tz = TimeZone.getDefault();
@@ -1,9 +1,9 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonReader.Token;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebeaninternal.json.ModifyAwareMap;
import org.junit.jupiter.api.Test;
@@ -19,7 +19,7 @@ public class ScalarTypePostgresHstoreTest {
ScalarTypePostgresHstore hstore = new ScalarTypePostgresHstore();
JsonFactory jsonFactory = new JsonFactory();
JsonStream jsonStream = JsonStream.builder().build();
@Test
public void testIsMutable() {
@@ -72,25 +72,25 @@ public class ScalarTypePostgresHstoreTest {
@SuppressWarnings("unchecked")
private Map<String, Object> parseHstore(String json) throws IOException {
JsonParser parser = jsonFactory.createParser(json);
JsonReader parser = jsonStream.reader(json);
// BeanProperty reads the first token checking for null so
// simulate that here
JsonToken token = parser.nextToken();
assertEquals(JsonToken.START_OBJECT, token);
Token token = parser.currentToken();
assertEquals(Token.BEGIN_OBJECT, token);
return (Map<String, Object>) hstore.jsonRead(parser);
}
private String generateJson(Map<String, Object> map) throws IOException {
StringWriter writer = new StringWriter();
JsonGenerator generator = jsonFactory.createGenerator(writer);
JsonWriter generator = jsonStream.writer(writer);
// wrap in an object to form proper json
generator.writeStartObject();
generator.writeFieldName("key");
generator.beginObject();
generator.name("key");
hstore.jsonWrite(generator, map);
generator.writeEndObject();
generator.endObject();
generator.flush();
return writer.toString();
+7
View File
@@ -19,6 +19,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-json-core</artifactId>
<version>${avaje-json-core.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
@@ -1,13 +1,13 @@
package io.ebean.jackson.mapper;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.annotation.MutationDetection;
import io.ebean.core.type.*;
import io.ebean.text.TextException;
@@ -221,13 +221,20 @@ public final class ScalarJsonJacksonMapper implements ScalarJsonMapper {
}
@Override
public final T jsonRead(JsonParser parser) throws IOException {
return objectReader.readValue(parser, deserType);
public final T jsonRead(JsonReader parser) throws IOException {
if (parser.isNullValue()) {
return null;
}
return objectReader.readValue(parser.readRaw(), deserType);
}
@Override
public final void jsonWrite(JsonGenerator writer, T value) throws IOException {
objectWriter.writeValue(writer, value);
public final void jsonWrite(JsonWriter writer, T value) throws IOException {
if (value == null) {
writer.nullValue();
} else {
writer.rawValue(objectWriter.writeValueAsString(value));
}
}
@Override
@@ -2,6 +2,7 @@ import io.ebean.jackson.mapper.ScalarJsonJacksonMapper;
module io.ebean.jackson.mapper {
requires io.avaje.json;
requires io.ebean.core.type;
requires com.fasterxml.jackson.annotation;
requires com.fasterxml.jackson.core;
@@ -1,7 +1,7 @@
package io.ebean.postgis;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -107,12 +107,12 @@ abstract class ScalarTypePgisBase<T extends Geometry> implements ScalarType<T> {
}
@Override
public T jsonRead(JsonParser parser) {
public T jsonRead(JsonReader parser) {
return null;
}
@Override
public void jsonWrite(JsonGenerator writer, T value) {
public void jsonWrite(JsonWriter writer, T value) {
}
}
@@ -1,7 +1,7 @@
package io.ebean.pgvector;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -85,12 +85,12 @@ abstract class ScalarTypePGbase<T extends PGobject> implements ScalarType<T> {
}
@Override
public T jsonRead(JsonParser parser) {
public T jsonRead(JsonReader parser) {
return null;
}
@Override
public void jsonWrite(JsonGenerator writer, T value) {
public void jsonWrite(JsonWriter writer, T value) {
}
}
@@ -1,7 +1,7 @@
package io.ebean.postgis;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -107,12 +107,12 @@ abstract class ScalarTypePgisBase<T extends Geometry> implements ScalarType<T> {
}
@Override
public T jsonRead(JsonParser parser) {
public T jsonRead(JsonReader parser) {
return null;
}
@Override
public void jsonWrite(JsonGenerator writer, T value) {
public void jsonWrite(JsonWriter writer, T value) {
}
}
@@ -1,7 +1,7 @@
package io.ebean.postgis.latte;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -103,12 +103,12 @@ abstract class ScalarTypeGeoLatteBase<T extends Geometry> implements ScalarType<
}
@Override
public T jsonRead(JsonParser parser) {
public T jsonRead(JsonReader parser) {
return null;
}
@Override
public void jsonWrite(JsonGenerator writer, T value) {
public void jsonWrite(JsonWriter writer, T value) {
}
}
@@ -102,8 +102,8 @@ public class DbJson {
public PrettyJson replace(String... propertyNames) {
for (String propertyName : propertyNames) {
String placeholder = "\"" + propertyName + "\": " + placeHolder;
rawJson = rawJson.replaceAll("\"" + propertyName + "\": (\\d+)", placeholder);
rawJson = rawJson.replaceAll("\"" + propertyName + "\": \"(.*?)\"", placeholder);
rawJson = rawJson.replaceAll("\"" + propertyName + "\":\\s*(\\d+)", placeholder);
rawJson = rawJson.replaceAll("\"" + propertyName + "\":\\s*\"(.*?)\"", placeholder);
}
return this;
}
@@ -134,7 +134,9 @@ public class DbJson {
* Normalise line ending characters to just use new line.
*/
private String lineEnd(String content) {
return content.replace("\r\n", "\n");
return content
.replace("\r\n", "\n")
.replaceAll("\"([^\"]+)\":\\s+", "\"$1\": ");
}
/**
@@ -1,7 +1,7 @@
package io.ebean.xtest.internal.server.text.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.platform.h2.H2Platform;
@@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class DJsonScalarTest {
private final DJsonScalar jsonScalar;
private final JsonStream jsonStream = JsonStream.builder().build();
public DJsonScalarTest() {
var serverConfig = new DatabaseConfig();
@@ -33,37 +34,35 @@ public class DJsonScalarTest {
@Test
public void writeBasicTypes() throws IOException {
StringWriter writer = new StringWriter();
JsonGenerator generator = createGenerator(writer);
JsonWriter generator = createGenerator(writer);
UUID uuid = UUID.randomUUID();
LocalDate today = LocalDate.now();
generator.writeRaw("[");
generator.rawChunk('[');
jsonScalar.write(generator, "hello");
generator.writeRaw(",");
generator.rawChunk(',');
jsonScalar.write(generator, uuid);
generator.writeRaw(",");
generator.rawChunk(',');
jsonScalar.write(generator, today);
generator.writeRaw("]");
generator.rawChunk(']');
generator.flush();
generator.close();
String json = writer.toString();
assertThat(json).contains("hello");
assertThat(json).contains(uuid.toString());
}
private JsonGenerator createGenerator(StringWriter writer) throws IOException {
JsonFactory factory = new JsonFactory();
return factory.createGenerator(writer);
private JsonWriter createGenerator(StringWriter writer) {
return jsonStream.writer(writer);
}
@Test
public void writeDbArrayTypes() throws IOException {
StringWriter writer = new StringWriter();
JsonGenerator generator = createGenerator(writer);
JsonWriter generator = createGenerator(writer);
List<UUID> list = new ArrayList<>();
list.add(UUID.randomUUID());
@@ -72,10 +71,9 @@ public class DJsonScalarTest {
jsonScalar.write(generator, list);
generator.flush();
generator.close();
String json = writer.toString();
assertThat(json).isEqualTo("[\""+list.get(0)+"\", \""+list.get(1)+"\"]");
assertThat(json).isEqualTo("[\"" + list.get(0) + "\",\"" + list.get(1) + "\"]");
}
}
@@ -1,7 +1,7 @@
package io.ebean.xtest.internal.server.text.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.JsonWriter;
import io.avaje.json.stream.JsonStream;
import io.ebean.DB;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -20,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class WriteJsonDirtyTest {
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
@Test
public void test() throws IOException {
@@ -40,14 +42,12 @@ public class WriteJsonDirtyTest {
boolean[] dirtyProperties = entityBean._ebean_getIntercept().dirtyProperties();
StringWriter writer = new StringWriter();
JsonFactory jsonFactory = new JsonFactory();
JsonGenerator generator = jsonFactory.createGenerator(writer);
JsonWriter generator = JSON_STREAM.writer(writer);
WriteJson writeJson = new WriteJson(server, generator, null, null, null, null, true);
descriptor.jsonWriteDirty(writeJson, entityBean, dirtyProperties);
generator.flush();
generator.close();
String jsonContent = writer.toString();
assertTrue(jsonContent.contains("\"name\":"));
@@ -28,8 +28,8 @@ class WriteJsonTest {
String json = DB.json().toJsonPretty(list);
assertThat(json).contains("\"customer\": {");
assertThat(json).contains("\"billingAddress\": {");
assertThat(json).contains("\"details\": [ {");
assertThat(json).contains("\"customer\":");
assertThat(json).contains("\"billingAddress\":");
assertThat(json).contains("\"details\": [");
}
}
@@ -1,7 +1,7 @@
package io.ebean.xtest.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.stream.JsonStream;
import io.ebean.ModifyAwareType;
import io.ebean.text.json.EJson;
import io.ebean.util.IOUtils;
@@ -20,14 +20,13 @@ import static org.junit.jupiter.api.Assertions.*;
public class EJsonTests {
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
@Test
public void test_map_simple() throws IOException {
JsonFactory factory = new JsonFactory();
String jsonInput = "{\"name\":\"rob\",\"age\":12}";
JsonParser jsonParser = factory.createParser(jsonInput);
JsonReader jsonParser = JSON_STREAM.reader(jsonInput);
Object result = EJson.parse(jsonParser);
@@ -56,11 +55,8 @@ public class EJsonTests {
@Test
public void test_parseObject() throws IOException {
JsonFactory factory = new JsonFactory();
String jsonInput = "{\"name\":\"rob\",\"age\":12}";
JsonParser jsonParser = factory.createParser(jsonInput);
JsonReader jsonParser = JSON_STREAM.reader(jsonInput);
Map<String, Object> map = EJson.parseObject(jsonParser);
@@ -168,8 +164,7 @@ public class EJsonTests {
String jsonInput = "[\"name\",\"rob\",12,13]";
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createParser(jsonInput);
JsonReader parser = JSON_STREAM.reader(jsonInput);
List<Object> list = EJson.parseList(parser);
@@ -1,6 +1,6 @@
package io.ebean.xtest.json;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.bean.PersistenceContext;
@@ -22,7 +22,7 @@ public class JsonBeanReaderTest extends BaseTestCase {
@Test
public void read() {
JsonParser parser = getParser();
JsonReader parser = getParser();
JsonBeanReader<Customer> beanReader = json.createBeanReader(Customer.class, parser, null);
Customer customer = beanReader.read();
@@ -30,7 +30,7 @@ public class JsonBeanReaderTest extends BaseTestCase {
assertThat(customer.getName()).isEqualTo("dummy");
}
private JsonParser getParser() {
private JsonReader getParser() {
Customer customer = new Customer();
customer.setId(42);
@@ -45,11 +45,11 @@ public class JsonBeanReaderTest extends BaseTestCase {
@Test
public void forJson() {
JsonParser parser = getParser();
JsonReader parser = getParser();
JsonBeanReader<Customer> beanReader = json.createBeanReader(Customer.class, parser, null);
beanReader.read();
JsonParser more = getParser();
JsonReader more = getParser();
JsonBeanReader<Customer> moreReader = beanReader.forJson(more);
Customer customer = moreReader.read();
@@ -60,7 +60,7 @@ public class JsonBeanReaderTest extends BaseTestCase {
@Test
public void persistenceContextPut_when_noPC() throws Exception {
JsonParser parser = getParser();
JsonReader parser = getParser();
JsonBeanReader<Customer> beanReader = json.createBeanReader(Customer.class, parser, null);
beanReader.read();
@@ -75,7 +75,7 @@ public class JsonBeanReaderTest extends BaseTestCase {
JsonReadOptions options = new JsonReadOptions().setEnableLazyLoading(true);
JsonParser parser = getParser();
JsonReader parser = getParser();
JsonBeanReader<Customer> beanReader = json.createBeanReader(Customer.class, parser, options);
Customer customer = beanReader.read();
@@ -1,6 +1,6 @@
package io.ebean.xtest.json;
import com.fasterxml.jackson.core.JsonGenerator;
import io.avaje.json.JsonWriter;
import io.ebean.DB;
import io.ebean.text.PathProperties;
import io.ebean.text.json.JsonContext;
@@ -90,8 +90,8 @@ public class JsonContextTest {
.findList();
String json = DB.json().toJsonPretty(orders);
assertThat(json).contains("[ {");
assertThat(json).contains("\"customer\": {");
assertThat(json).contains("[");
assertThat(json).contains("\"customer\":");
}
@Test
@@ -185,7 +185,7 @@ public class JsonContextTest {
StringWriter writer = new StringWriter();
JsonContext json = DB.json();
JsonGenerator generator = json.createGenerator(writer);
JsonWriter generator = json.createGenerator(writer);
Customer customer = new Customer();
customer.setId(1);
@@ -194,10 +194,10 @@ public class JsonContextTest {
// we can use the generator before and after our json.toJson() call
// ... confirming we are not closing the generator
generator.writeStartArray();
generator.beginArray();
json.toJson(customer, generator, PathProperties.parse("id,name"));
generator.writeEndArray();
generator.close();
generator.endArray();
generator.flush();
String jsonString = writer.toString();
assertThat(jsonString).startsWith("[");
@@ -210,17 +210,18 @@ public class JsonContextTest {
StringWriter writer = new StringWriter();
JsonContext json = DB.json();
JsonGenerator generator = json.createGenerator(writer);
JsonWriter generator = json.createGenerator(writer);
// test that we can write anything via writeRaw()
generator.writeRaw("START");
generator.writeStartArray();
generator.writeStartObject();
generator.writeNumberField("count", 12);
generator.writeEndObject();
generator.writeEndArray();
generator.writeRaw("END");
generator.close();
generator.rawChunk("START");
generator.beginArray();
generator.beginObject();
generator.name("count");
generator.value(12);
generator.endObject();
generator.endArray();
generator.rawChunk("END");
generator.flush();
assertEquals("START[{\"count\":12}]END", writer.toString());
}
@@ -1,6 +1,7 @@
package org.tests.text.json;
import com.fasterxml.jackson.core.JsonParser;
import io.avaje.json.JsonReader;
import io.avaje.json.stream.JsonStream;
import io.ebean.BeanState;
import io.ebean.DB;
import io.ebean.xtest.BaseTestCase;
@@ -185,9 +186,9 @@ public class TestJsonBeanDescriptorParse extends BaseTestCase {
private SpiJsonReader createRead(SpiEbeanServer server, BeanDescriptor<Customer> descriptor) {
StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello Rob\"}");
JsonParser parser = server.json().createParser(reader);
JsonReader parser = server.json().createParser(reader);
return new ReadJson(descriptor, parser, null, null, false);
return new ReadJson(descriptor, parser, null, null, false, JsonStream.builder().build());
}
}

Some files were not shown because too many files have changed in this diff Show More