Compare commits

...
10 Commits
Author SHA1 Message Date
Rob Bygrave a7fddf1981 Version 18.0.0 2026-06-22 21:11:45 +12:00
Rob Bygrave f610d03a1d Dependency: Bump ebean-agent to 18.0.0 2026-06-22 21:09:10 +12:00
Rob BygraveandGitHub 852f199ffb Merge pull request #3789 from ebean-orm/feature/avaje-json-core
Refactor from Jackson Core to avaje-json-core
2026-06-22 21:02:52 +12:00
robin.bygrave 56d33b1f1b Update documentation for the DbJson mapping support 2026-06-22 10:06:34 +12:00
robin.bygrave e9b00cbbd1 Add documentation for the DbJson mapping support 2026-06-19 22:10:54 +12:00
robin.bygrave 230d7a36a7 Simplify @DbJson Map dispatch and support Map<Enum,String>
Replace isMapValueTypeObject + isMapStringString with isBuiltinJsonMap:
 the built-in JSON map handles a String or enum key with a String, Object
 or wildcard value. This also routes Map<Enum,String> to the built-in type
 and fixes non-String/enum keys with Object value (e.g. Map<Integer,Object>)
 being mis-routed to the built-in type (ClassCastException) instead of the
 object mapper.
2026-06-19 21:47:20 +12:00
robin.bygrave 10bf5dbbbd Support Map<Enum,Object> in @DbJson(B) fields
Addresses #3735. Enum-keyed JSON maps previously failed (enum key cast to
 String). Add ScalarTypeJsonMapEnum which converts enum keys via their
 ScalarType (honouring @DbEnumValue) and reuses ScalarTypeJsonMap for all
 storage/platform handling, so it works for VARCHAR/CLOB/BLOB and Postgres
 JSON/JSONB with no per-storage variants.

 - JsonStorage now reports jdbcType(), enabling a shared storageFor(...) reused
   by the plain and enum-key Map types
 - DefaultTypeManager routes Map<Enum,Object> to the new type and calls
   setAccessible on the @DbEnumValue method (supports nested/non-public enums)
 - add TypeReflectHelper.getMapKeyTypeRaw and TestEnumKeyMap
2026-06-19 20:03:27 +12:00
robin.bygrave bffe741642 Simplify @DbJson(B) Map/List/Set type handling via JsonStorage strategy
Collapse the per-storage and per-platform ScalarType subclass explosion for the built-in JSON value types into two orthogonal strategies.

 - Add JsonStorage strategy (VARCHAR / CLOB / BLOB / Postgres) encapsulating
   how the raw JSON string is read from / bound to JDBC. Postgres vs
   non-Postgres is now a single reusable strategy rather than a subclass per
   value type.
 - Add ScalarTypeJsonValue<T> base holding the shared read / bind / L2-cache /
   json plumbing once, plus ScalarTypeJsonCollectionValue<T> adding the
   ScalarTypeArray (DB array column definition) aspect for List/Set.
 - Rewrite ScalarTypeJsonMap, ScalarTypeJsonList and ScalarTypeJsonSet as thin
   types: a typeFor factory selecting a JsonStorage + value marshalling that
   delegates to the avaje-JsonMapper-backed EJson facade.
 - Remove ScalarTypeJsonMapPostgres and ~16 nested storage/platform classes.
2026-06-19 17:27:19 +12:00
robin.bygrave a8189567dd Replace EJson internals with avaje JsonMapper
Reworks the DJsonService (the SpiJsonService SPI behind io.ebean.text.json.EJson) to use avaje JsonMapper instead of the bespoke EJsonReader/EJsonWriter, consolidating all read/write logic into a single JsonAdapter.

Changes

 - New EbeanJsonAdapter — a JsonAdapter<Object> that materializes JSON into plain or modify-aware Map/List/Set, with two shared singletons (PLAIN, MODIFY_AWARE). Preserves existing EJson semantics: - Integral numbers → Long, decimals → BigDecimal
 - Write coverage for String/Integer/Long/Double/Float/BigDecimal/Boolean/Map/Collection with a toString() fallback for other types (UUID, enum, etc.)
 - Modify-aware loads share a single ModifyAwareFlag owner per root, reset to non-dirty once after the load completes
 - DJsonService now builds one JsonMapper + two JsonMapper.Type<Object> and routes parse/write through them. Null serialization is retained via a serializeNulls(true) writer; null/blank-input guards, token-honoring entry points, and parseSet (modify-aware asSet()) are preserved.
 - EJsonReader/EJsonWriter are now unused and should be deleted (couldn't remove them in this environment).

Why

Simplifies Ebean's JSON handling by reusing avaje-json-core's JsonMapper rather than maintaining a parallel reader/writer, while keeping behavior identical.
2026-06-19 16:19:35 +12:00
robin.bygrave 313fdda857 Refactor from Jackson Core to avaje-json-core 2026-06-18 22:00:33 +12:00
166 changed files with 2750 additions and 2666 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<artifactId>composites</artifactId>
+2
View File
@@ -183,6 +183,8 @@ 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) |
| Know which `@DbJson` types need Jackson vs built-in | [dbjson-mapping-support.md](guides/dbjson-mapping-support.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
+2
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
@@ -38,6 +39,7 @@ existing Maven project. Complete the steps in order.
|-------|-------------|
| [Entity Bean Creation](entity-bean-creation.md) | How to generate clean, idiomatic Ebean entity beans for AI agents; patterns and anti-patterns; field visibility and accessor guidance; minimal boilerplate |
| [Lombok with Ebean entity beans](lombok-with-ebean-entity-beans.md) | Which Lombok annotations to use and avoid on entity beans; why `@Data` is incompatible with Ebean; how to use `@Getter` + `@Setter` + `@Accessors(chain = true)` |
| [`@DbJson` mapping support (built-in vs Jackson)](dbjson-mapping-support.md) | Which `@DbJson` / `@DbJsonB` property types are handled by the built-in avaje-json-core support versus which require `ebean-jackson-mapper` (Jackson `ObjectMapper`); supported `String`/`List`/`Set`/`Map` matrix; enum-key and `@DbArray` notes |
## Querying
+116
View File
@@ -0,0 +1,116 @@
# Guide: `@DbJson` / `@DbJsonB` mapping support — built-in vs Jackson ObjectMapper
## Purpose
Ebean can map `@DbJson` and `@DbJsonB` properties in two ways:
- **Built-in** JSON support, backed by **avaje-json-core** — no extra dependency.
- **Jackson `ObjectMapper`**, provided by the **`ebean-jackson-mapper`** module — used
for everything the built-in support does not handle.
This guide lists exactly which property types are handled built-in and which require
`ebean-jackson-mapper`.
> If a property type is **not** handled built-in and `ebean-jackson-mapper` is not on the
> classpath, Ebean fails fast at startup:
>
> ```text
> Unsupported @DbJson mapping - Missing dependency ebean-jackson-mapper?
> Jackson ObjectMapper not present for <property>
> ```
---
## Quick reference
| Property type | Built-in (avaje-json-core) | Needs `ebean-jackson-mapper` |
|---|:---:|:---:|
| `String` | ✅ | |
| `List<String>`, `List<Long>` | ✅ | |
| `Set<String>`, `Set<Long>` | ✅ | |
| `Map<String, Object>`, `Map<String, ?>` | ✅ | |
| `Map<String, String>` | ✅ | |
| `Map<Enum, Object>`, `Map<Enum, String>` | ✅ | |
| `List`/`Set` of any other element type (`Integer`, `Double`, `UUID`, `LocalDate`, an enum, a POJO, …) | | ✅ |
| `Map` with a typed value other than `String`/`Object` (`Map<String,Integer>`, `Map<String,UUID>`, …) | | ✅ |
| `Map` with a key other than `String` or an enum (`Map<Integer, …>`, `Map<UUID, …>`) | | ✅ |
| POJOs, records, or any other type | | ✅ |
---
## Built-in support (no Jackson required)
The built-in path materialises JSON into the *natural* JSON value types
(`String`, `Long`, `BigDecimal`, `Boolean`, `Map`, `List`). It is therefore type-safe only
for the following declared property types:
- **`String`** — stored as raw JSON text.
- **`List<String>`** and **`List<Long>`**.
- **`Set<String>`** and **`Set<Long>`**.
- **`Map<K, V>`** where:
- the key `K` is `String` or an **enum**, and
- the value `V` is `Object`, `String`, or a wildcard `?`.
So `Map<String,Object>`, `Map<String,String>`, `Map<Enum,Object>` and `Map<Enum,String>`
are all built-in.
These mappings work across all supported storage types — `VARCHAR`, `CLOB`, `BLOB`, and
Postgres `json` / `jsonb` — without `ebean-jackson-mapper`.
---
## Everything else → Jackson `ObjectMapper`
Any other `@DbJson` / `@DbJsonB` property routes to the Jackson `ObjectMapper` path, which
requires `ebean-jackson-mapper`:
- **Typed collections** — `List`/`Set` whose element type is not `String` or `Long`
(for example `List<Integer>`, `List<UUID>`, `List<LocalDate>`, `List<MyEnum>`, `List<MyPojo>`).
- **Typed-value maps** — a `Map` value type other than `String`/`Object`
(for example `Map<String,Integer>`, `Map<String,UUID>`, `Map<String,MyPojo>`).
- **Non-`String`/non-enum map keys** — for example `Map<Integer,Object>`, `Map<UUID,String>`.
- **POJOs, records, and any other custom type.**
> **Jackson marker annotation override:** if the **field or getter** carries a Jackson annotation
> (anything meta-annotated with `com.fasterxml.jackson.annotation.JacksonAnnotation`), Ebean
> uses the `ObjectMapper` path even when the type would otherwise be handled built-in.
---
## Adding `ebean-jackson-mapper`
```xml
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>${ebean.version}</version>
</dependency>
```
A Jackson `ObjectMapper` must be available (via `jackson-databind`). Ebean detects it and
registers the mapper-based JSON support automatically.
---
## Notes
- **Enum map keys** are serialised using the enum `name()` (for example `ACTIVE`), not any
`@DbEnumValue` mapping. Round-trips are correct; the DB value mapping is not applied to
JSON keys.
- **`@DbArray` alternative:** for typed *scalar* collections (`List`/`Set` of `Integer`,
`Long`, `UUID`, `Double`, an enum, …) consider `@DbArray`, which maps to a native DB array
(with a JSON fallback on platforms without array support) and supports more element types
than built-in `@DbJson` collections.
- The reason typed value/element collections need a real mapper is that the built-in path
only produces natural JSON types — for example a JSON number always parses to `Long`, so a
declared `List<Integer>` or `Map<String,Integer>` could not be populated safely without a
type-aware mapper.
---
## Choosing
- Prefer the **built-in** mappings for the common cases (`String`, string/long lists and sets,
object/string maps) to avoid pulling in Jackson.
- Add **`ebean-jackson-mapper`** when you need rich POJO JSON columns or typed collections /
typed-value maps.
@@ -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.
+5 -7
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<name>ebean api</name>
@@ -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;
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<artifactId>ebean-bench</artifactId>
+28 -28
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<!-- platforms -->
@@ -193,91 +193,91 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
</dependencies>
+5 -7
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<artifactId>ebean-core-json</artifactId>
<name>ebean-core-json</name>
@@ -16,15 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.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,170 @@
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.avaje.json.mapper.JsonMapper;
import io.avaje.json.stream.JsonStream;
import io.ebean.service.SpiJsonService;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
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.
* <p>
* Backed by avaje {@link JsonMapper} using {@link EbeanJsonAdapter} which
* preserves Ebean's modify-aware collection and number semantics.
*/
public final class DJsonService implements SpiJsonService {
/**
* Write the nested Map/List as json.
*/
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
private static final JsonMapper MAPPER = JsonMapper.builder().jsonStream(JSON_STREAM).build();
private static final JsonMapper.Type<Object> PLAIN = MAPPER.type(EbeanJsonAdapter.PLAIN);
private static final JsonMapper.Type<Object> MODIFY_AWARE = MAPPER.type(EbeanJsonAdapter.MODIFY_AWARE);
private static JsonMapper.Type<Object> type(boolean modifyAware) {
return modifyAware ? MODIFY_AWARE : PLAIN;
}
private static boolean blank(String content) {
return content == null || content.trim().isEmpty();
}
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();
}
@Override
public String write(Object object) throws IOException {
return EJsonWriter.write(object);
StringWriter writer = new StringWriter();
write(object, writer);
return writer.toString();
}
/**
* Write the nested Map/List as json to the writer.
*/
@Override
public void write(Object object, Writer writer) throws IOException {
EJsonWriter.write(object, writer);
JsonWriter jsonWriter = JSON_STREAM.writer(writer);
jsonWriter.serializeNulls(true);
PLAIN.toJson(object, jsonWriter);
jsonWriter.flush();
}
/**
* 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 {
PLAIN.toJson(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 {
EbeanJsonAdapter.writeCollection(jsonWriter, collection);
}
/**
* 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 {
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 {
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 {
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 {
return (List<T>) EJsonReader.parse(parser, currentToken, false);
public Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
return blank(json) ? null : (Map<String, Object>) type(modifyAware).fromJson(json);
}
@Override
public Map<String, Object> parseObject(String json) throws IOException {
return parseObject(json, false);
}
@Override
public Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
return parseObject(readAll(reader), modifyAware);
}
@Override
public Map<String, Object> parseObject(Reader reader) throws IOException {
return parseObject(reader, false);
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> parseObject(JsonReader parser) throws IOException {
return (Map<String, Object>) PLAIN.fromJson(parser);
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException {
return (Map<String, Object>) EbeanJsonAdapter.read(parser, token, false);
}
@Override
@SuppressWarnings("unchecked")
public <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return blank(json) ? null : (List<T>) type(modifyAware).fromJson(json);
}
@Override
@SuppressWarnings("unchecked")
public List<Object> parseList(String json) throws IOException {
return (List<Object>) parseList(json, false);
}
@Override
public List<Object> parseList(Reader reader) throws IOException {
return parseList(readAll(reader));
}
@Override
@SuppressWarnings("unchecked")
public List<Object> parseList(JsonReader parser) throws IOException {
return (List<Object>) PLAIN.fromJson(parser);
}
@Override
@SuppressWarnings("unchecked")
public <T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException {
return (List<T>) EbeanJsonAdapter.read(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);
return blank(json) ? null : PLAIN.fromJson(json);
}
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(Reader reader) throws IOException {
return EJsonReader.parse(reader);
return parse(readAll(reader));
}
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(JsonParser parser) throws IOException {
return EJsonReader.parse(parser);
public Object parse(JsonReader parser) throws IOException {
return PLAIN.fromJson(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,356 +0,0 @@
package io.ebeaninternal.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ModifyAwareType;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
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;
EJsonReader(JsonParser parser, boolean modifyAware) {
this.parser = parser;
this.modifyAware = modifyAware;
this.modifyAwareOwner = modifyAware ? new ModifyAwareFlag() : null;
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
return (Map<String, Object>) parse(json, modifyAware);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(String json) throws IOException {
return (Map<String, Object>) parse(json);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(Reader reader) throws IOException {
return (Map<String, Object>) parse(reader);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
return (Map<String, Object>) parse(reader, modifyAware);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(JsonParser parser) throws IOException {
return (Map<String, Object>) parse(parser);
}
@SuppressWarnings("unchecked")
static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
return (Map<String, Object>) parse(parser, token, false);
}
@SuppressWarnings("unchecked")
static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return (List<T>) parse(json, modifyAware);
}
@SuppressWarnings("unchecked")
static List<Object> parseList(String json) throws IOException {
return (List<Object>) parse(json);
}
@SuppressWarnings("unchecked")
static List<Object> parseList(Reader reader) throws IOException {
return (List<Object>) parse(reader);
}
@SuppressWarnings("unchecked")
static List<Object> parseList(JsonParser parser, boolean modifyAware) throws IOException {
return (List<Object>) parse(parser, modifyAware);
}
static Object parse(String json) throws IOException {
if (json == null) {
return null;
}
return parse(new StringReader(json));
}
static Object parse(String json, boolean modifyAware) throws IOException {
if (json == null) {
return null;
}
return parse(new StringReader(json), modifyAware);
}
static Object parse(Reader reader) throws IOException {
return parse(json.createParser(reader));
}
static Object parse(Reader reader, boolean modifyAware) throws IOException {
return parse(json.createParser(reader), modifyAware);
}
static Object parse(JsonParser parser) throws IOException {
return parse(parser, null, false);
}
static Object parse(JsonParser 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);
}
}
private void setValue(Object value) {
currentContext.setValue(value);
}
private void setValueNull() {
currentContext.setValueNull();
}
private Object parseJson(JsonToken token) throws IOException {
if (token == null) {
token = parser.nextToken();
// if it is a simple value just return it
switch (token) {
case VALUE_NULL:
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();
}
}
// 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);
}
return currentContext.getValue();
}
/**
* 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 final class Stack {
private Context head;
private void push(Context context) {
if (context != null) {
context.next = head;
head = context;
}
}
private Context pop(Context endingContext) {
if (head == null) {
throw new NoSuchElementException();
}
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
}
}
}
@@ -1,212 +0,0 @@
package io.ebeaninternal.json;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
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.Map;
import java.util.Map.Entry;
import java.util.Set;
final class EJsonWriter {
/**
* Base jsonFactory implementation used when it is not passed in.
*/
static final JsonFactory jsonFactory = new JsonFactory();
private final JsonGenerator jsonGenerator;
private EJsonWriter(JsonGenerator jsonGenerator) {
this.jsonGenerator = jsonGenerator;
}
static String write(Object object) throws IOException {
StringWriter writer = new StringWriter(200);
write(object, writer).close();
return writer.toString();
}
static JsonGenerator write(Object object, Writer writer) throws IOException {
JsonGenerator generator = jsonFactory.createGenerator(writer);
write(object, generator);
generator.flush();
return generator;
}
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);
}
}
private void writeBoolean(String name, Boolean object) throws IOException {
if (name == null) {
jsonGenerator.writeBoolean(object);
} else {
jsonGenerator.writeBooleanField(name, object);
}
}
private void writeDate(String name, Date object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber(object.getTime());
} else {
jsonGenerator.writeNumberField(name, object.getTime());
}
}
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);
}
}
private void writeGeneralNumber(String name, Number object) throws IOException {
writeBigDecimal(name, new BigDecimal(object.toString()));
}
private void writeBigDecimal(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((BigDecimal) object);
} else {
jsonGenerator.writeNumberField(name, (BigDecimal) object);
}
}
private void writeBigInteger(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((BigInteger) object);
} else {
jsonGenerator.writeNumberField(name, object.longValue());
}
}
private void writeDouble(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Double) object);
} else {
jsonGenerator.writeNumberField(name, (Double) object);
}
}
private void writeLong(String name, Number object) throws IOException {
if (name == null) {
jsonGenerator.writeNumber((Long) object);
} else {
jsonGenerator.writeNumberField(name, (Long) object);
}
}
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();
}
}
@@ -0,0 +1,181 @@
package io.ebeaninternal.json;
import io.avaje.json.JsonAdapter;
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.ModifyAwareType;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Ebean specific {@link JsonAdapter} that materializes JSON into plain Java
* Map/List/scalar values - optionally wrapped in modify-aware collections so
* that mutations after load are tracked as dirty.
* <p>
* This consolidates the prior EJsonReader/EJsonWriter behavior into a single
* adapter that plugs into avaje {@code JsonMapper}.
*/
final class EbeanJsonAdapter implements JsonAdapter<Object> {
static final EbeanJsonAdapter PLAIN = new EbeanJsonAdapter(false);
static final EbeanJsonAdapter MODIFY_AWARE = new EbeanJsonAdapter(true);
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
private final boolean modifyAware;
private EbeanJsonAdapter(boolean modifyAware) {
this.modifyAware = modifyAware;
}
@Override
public Object fromJson(JsonReader reader) {
return read(reader, null, modifyAware);
}
@Override
public void toJson(JsonWriter writer, Object value) {
write(writer, value);
}
/**
* Read a value honoring an explicitly supplied current token (or current token when null).
*/
static Object read(JsonReader parser, Token token, boolean modifyAware) {
ModifyAwareType owner = modifyAware ? new ModifyAwareFlag() : null;
Token effectiveToken = token == null ? parser.currentToken() : token;
Object value;
if (effectiveToken == null) {
value = parseRawJson(parser.readRaw(), owner);
} else {
value = parseValue(parser, effectiveToken, owner);
}
if (owner != null) {
owner.setMarkedDirty(false);
}
return value;
}
private static Object parseValue(JsonReader parser, Token token, ModifyAwareType owner) {
if (token == null) {
token = parser.currentToken();
if (token == null) {
if (parser.isNullValue()) {
return null;
}
return parseRawJson(parser.readRaw(), owner);
}
}
switch (token) {
case BEGIN_OBJECT:
return parseObjectValue(parser, owner);
case BEGIN_ARRAY:
return parseArrayValue(parser, owner);
case NUMBER:
BigDecimal value = parser.readDecimal();
return value.scale() <= 0 ? value.longValue() : value;
case STRING:
return parser.readString();
case BOOLEAN:
return parser.readBoolean();
case NULL:
parser.isNullValue();
return null;
default:
return parseRawJson(parser.readRaw(), owner);
}
}
private static Object parseRawJson(String json, ModifyAwareType owner) {
if (json == null) {
return null;
}
String content = json.trim();
if (content.isEmpty()) {
return null;
}
try (JsonReader parser = JSON_STREAM.reader(content)) {
return parseValue(parser, parser.currentToken(), owner);
}
}
private static Map<String, Object> parseObjectValue(JsonReader parser, ModifyAwareType owner) {
Map<String, Object> map = owner == null
? new LinkedHashMap<>()
: new ModifyAwareMap<>(owner, new LinkedHashMap<>());
parser.beginObject();
while (parser.hasNextField()) {
String fieldName = parser.nextField();
map.put(fieldName, parseValue(parser, parser.currentToken(), owner));
}
parser.endObject();
return map;
}
private static List<Object> parseArrayValue(JsonReader parser, ModifyAwareType owner) {
List<Object> list = owner == null
? new ArrayList<>()
: new ModifyAwareList<>(owner, new ArrayList<>());
parser.beginArray();
while (parser.hasNextElement()) {
list.add(parseValue(parser, parser.currentToken(), owner));
}
parser.endArray();
return list;
}
/**
* Write the value to an existing JsonWriter (used for the raw stream paths).
*/
static void write(JsonWriter jsonWriter, Object object) {
if (object == null) {
jsonWriter.nullValue();
} else if (object instanceof String) {
jsonWriter.value((String) object);
} else if (object instanceof Integer) {
jsonWriter.value((Integer) object);
} else if (object instanceof Long) {
jsonWriter.value((Long) object);
} else if (object instanceof Double) {
jsonWriter.value((Double) object);
} else if (object instanceof Float) {
jsonWriter.value((Float) object);
} else if (object instanceof BigDecimal) {
jsonWriter.value((BigDecimal) object);
} else if (object instanceof Boolean) {
jsonWriter.value((Boolean) object);
} else if (object instanceof Map<?, ?>) {
writeMap(jsonWriter, (Map<?, ?>) object);
} else if (object instanceof Collection<?>) {
writeCollection(jsonWriter, (Collection<?>) object);
} else {
jsonWriter.value(object.toString());
}
}
private static void writeMap(JsonWriter jsonWriter, Map<?, ?> map) {
jsonWriter.beginObject();
for (Map.Entry<?, ?> entry : map.entrySet()) {
jsonWriter.name((String) entry.getKey());
write(jsonWriter, entry.getValue());
}
jsonWriter.endObject();
}
static void writeCollection(JsonWriter jsonWriter, Collection<?> collection) {
jsonWriter.beginArray();
for (Object element : collection) {
write(jsonWriter, element);
}
jsonWriter.endArray();
}
}
@@ -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;
+5 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,14 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</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;
}
+7 -15
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.11.1</version>
<version>18.0.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-json</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -52,7 +52,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
</dependency>
<dependency>
@@ -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>
@@ -165,21 +157,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.11.1</version>
<version>18.0.0</version>
<scope>test</scope>
</dependency>
@@ -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();
}
}
@@ -319,7 +319,11 @@ public final class DefaultTypeManager implements TypeManager {
if (type.equals(Set.class) && isValueTypeSimple(genericType)) {
return ScalarTypeJsonSet.typeFor(postgres, dbType, docType(genericType), prop.isNullable(), keepSource(prop));
}
if (type.equals(Map.class) && isMapValueTypeObject(genericType)) {
if (type.equals(Map.class) && isBuiltinJsonMap(genericType)) {
Type keyType = TypeReflectHelper.getMapKeyTypeRaw(genericType);
if (isEnumType(keyType)) {
return enumJsonMapType(postgres, dbType, keyType, keepSource(prop));
}
return ScalarTypeJsonMap.typeFor(postgres, dbType, keepSource(prop));
}
if (objectMapperPresent && prop.getMutationDetection() == MutationDetection.DEFAULT) {
@@ -338,6 +342,13 @@ public final class DefaultTypeManager implements TypeManager {
return prop.getMutationDetection() == MutationDetection.SOURCE;
}
@SuppressWarnings("unchecked")
private ScalarType<?> enumJsonMapType(boolean postgres, int dbType, Type keyType, boolean keepSource) {
Class<? extends Enum<?>> enumClass = asEnumClass(keyType);
ScalarType<? extends Enum<?>> enumScalarType = (ScalarType<? extends Enum<?>>) enumType(enumClass, null);
return ScalarTypeJsonMapEnum.typeFor(postgres, dbType, enumScalarType, keepSource);
}
private DocPropertyType docPropertyType(DeployBeanProperty prop, Class<?> type) {
return type.equals(List.class) || type.equals(Set.class) ? docType(prop.getGenericType()) : DocPropertyType.OBJECT;
}
@@ -365,11 +376,24 @@ public final class DefaultTypeManager implements TypeManager {
}
/**
* Return true if value parameter type of the map is Object.
* Return true if the Map is handled by the built-in JSON support: a String or enum key
* with a String, Object or wildcard value. Such values round-trip through EJson without
* type coercion. Other maps (typed values, or non-String/enum keys) use the object mapper.
*/
private boolean isMapValueTypeObject(Type genericType) {
private boolean isBuiltinJsonMap(Type genericType) {
if (!(genericType instanceof ParameterizedType)) {
return false;
}
Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
return Object.class.equals(typeArgs[1]) || "?".equals(typeArgs[1].toString());
return isJsonMapKeyType(typeArgs[0]) && isJsonMapValueType(typeArgs[1]);
}
private boolean isJsonMapKeyType(Type keyType) {
return String.class.equals(keyType) || isEnumType(keyType);
}
private boolean isJsonMapValueType(Type valueType) {
return Object.class.equals(valueType) || String.class.equals(valueType) || "?".equals(valueType.toString());
}
private ScalarType<?> createJsonObjectMapperType(DeployBeanProperty prop, int dbType, DocPropertyType docType) {
@@ -560,6 +584,7 @@ public final class DefaultTypeManager implements TypeManager {
*/
private ScalarTypeEnum<?> enumTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType, int length, boolean withConstraint) {
Map<String, String> nameValueMap = new LinkedHashMap<>();
method.setAccessible(true);
for (Enum<?> enumConstant : enumType.getEnumConstants()) {
try {
Object value = method.invoke(enumConstant);
@@ -0,0 +1,166 @@
package io.ebeaninternal.server.type;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.PostgresHelper;
import io.ebean.util.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.sql.Types;
/**
* Strategy for reading and binding the raw JSON string of a {@code @DbJson} property.
* <p>
* This collapses the previously duplicated VARCHAR / CLOB / BLOB and Postgres JSON / JSONB
* variants (which differed only in how the raw JSON is read from / bound to JDBC) into a
* small set of reusable strategies shared by all JSON value types (Map, List, Set).
*/
interface JsonStorage {
/** VARCHAR storage - read/bind as a String. */
JsonStorage VARCHAR = new Varchar();
/** CLOB storage - read via stream, bind as a String. */
JsonStorage CLOB = new Clob();
/** BLOB storage - read/bind as UTF-8 bytes. */
JsonStorage BLOB = new Blob();
/** Postgres JSON / JSONB storage binding via a PGobject of the given type. */
static JsonStorage postgres(String pgType) {
return new Postgres(pgType);
}
/**
* Read the raw JSON content (or null) from the DB.
*/
String read(DataReader reader) throws SQLException;
/**
* The JDBC type reported by a ScalarType using this storage.
*/
int jdbcType();
/**
* Bind the given non-null raw JSON content.
*/
void bind(DataBinder binder, String rawJson) throws SQLException;
/**
* Bind an SQL null value.
*/
void bindNull(DataBinder binder) throws SQLException;
final class Varchar implements JsonStorage {
@Override
public String read(DataReader reader) throws SQLException {
return reader.getString();
}
@Override
public int jdbcType() {
return Types.VARCHAR;
}
@Override
public void bind(DataBinder binder, String rawJson) throws SQLException {
binder.setString(rawJson);
}
@Override
public void bindNull(DataBinder binder) throws SQLException {
binder.setNull(Types.VARCHAR);
}
}
final class Clob implements JsonStorage {
@Override
public String read(DataReader reader) throws SQLException {
return reader.getStringFromStream();
}
@Override
public int jdbcType() {
return Types.CLOB;
}
@Override
public void bind(DataBinder binder, String rawJson) throws SQLException {
binder.setString(rawJson);
}
@Override
public void bindNull(DataBinder binder) throws SQLException {
binder.setNull(Types.VARCHAR);
}
}
final class Blob implements JsonStorage {
@Override
public String read(DataReader reader) throws SQLException {
InputStream is = reader.getBinaryStream();
if (is == null) {
return null;
}
try (Reader streamReader = IOUtils.newReader(is)) {
StringBuilder builder = new StringBuilder();
char[] buffer = new char[2048];
int nRead;
while ((nRead = streamReader.read(buffer)) >= 0) {
builder.append(buffer, 0, nRead);
}
return builder.toString();
} catch (IOException e) {
throw new SQLException("Error reading Blob stream from DB", e);
}
}
@Override
public int jdbcType() {
return Types.BLOB;
}
@Override
public void bind(DataBinder binder, String rawJson) throws SQLException {
binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8));
}
@Override
public void bindNull(DataBinder binder) throws SQLException {
binder.setNull(Types.BLOB);
}
}
final class Postgres implements JsonStorage {
private final String pgType;
private final int jdbcType;
Postgres(String pgType) {
this.pgType = pgType;
this.jdbcType = PostgresHelper.JSONB_TYPE.equals(pgType) ? DbPlatformType.JSONB : DbPlatformType.JSON;
}
@Override
public String read(DataReader reader) throws SQLException {
return reader.getString();
}
@Override
public int jdbcType() {
return jdbcType;
}
@Override
public void bind(DataBinder binder, String rawJson) throws SQLException {
binder.setObject(PostgresHelper.asObject(pgType, rawJson));
}
@Override
public void bindNull(DataBinder binder) throws SQLException {
binder.setObject(PostgresHelper.asObject(pgType, null));
}
}
}
@@ -30,7 +30,7 @@ class PlatformArrayTypeJsonList implements PlatformArrayTypeFactory {
// TODO: keepSource for @DbArray?
return new ScalarTypeJsonList.VarcharWithConverter(DocPropertyType.UUID, nullable, false, ArrayElementConverter.UUID);
}
return new ScalarTypeJsonList.Varchar(docType(valueType), nullable, false);
return new ScalarTypeJsonList(java.sql.Types.VARCHAR, JsonStorage.VARCHAR, docType(valueType), nullable, false);
}
@Override
@@ -30,7 +30,7 @@ class PlatformArrayTypeJsonSet implements PlatformArrayTypeFactory {
// TODO: keepSource for @DbArray?
return new ScalarTypeJsonSet.VarcharWithConverter(DocPropertyType.UUID, nullable, false, ArrayElementConverter.UUID);
}
return new ScalarTypeJsonSet.Varchar(docType(valueType), nullable, false);
return new ScalarTypeJsonSet(java.sql.Types.VARCHAR, JsonStorage.VARCHAR, docType(valueType), nullable, false);
}
@Override
@@ -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
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.type;
import io.ebean.core.type.DocPropertyType;
/**
* Base for the JSON collection value types (List, Set).
* <p>
* Adds the {@link ScalarTypeArray} aspect (logical DB array column definition) used when the
* same type backs a {@code @DbArray} mapping on a platform without native array support.
*/
abstract class ScalarTypeJsonCollectionValue<T> extends ScalarTypeJsonValue<T> implements ScalarTypeArray {
ScalarTypeJsonCollectionValue(Class<T> type, int jdbcType, JsonStorage storage, boolean keepSource,
boolean nullable, DocPropertyType docType) {
super(type, jdbcType, storage, keepSource, nullable, "[]", docType);
}
@Override
public final String getDbColumnDefn() {
switch (docType()) {
case SHORT:
case INTEGER:
case LONG:
return "integer[]";
case DOUBLE:
case FLOAT:
return "decimal[]";
default:
return "varchar[]";
}
}
}
@@ -1,218 +1,122 @@
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.core.type.DocPropertyType;
import io.ebean.core.type.PostgresHelper;
import io.ebean.core.type.ScalarType;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeaninternal.json.ModifyAwareList;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
/**
* Types for mapping List in JSON format to DB types VARCHAR, JSON and JSONB.
* Type which maps a List in JSON format to VARCHAR or Postgres JSON / JSONB.
*/
final class ScalarTypeJsonList {
@SuppressWarnings("rawtypes")
class ScalarTypeJsonList extends ScalarTypeJsonCollectionValue<List> {
/**
* Return the appropriate ScalarType based requested dbType and if Postgres.
* Return the appropriate ScalarType for the requested dbType and platform.
*/
static ScalarType<?> typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) {
if (postgres) {
switch (dbType) {
case DbPlatformType.JSONB:
return new ScalarTypeJsonList.JsonB(docType, nullable, keepSource);
return new ScalarTypeJsonList(DbPlatformType.JSONB, JsonStorage.postgres(PostgresHelper.JSONB_TYPE), docType, nullable, keepSource);
case DbPlatformType.JSON:
return new ScalarTypeJsonList.Json(docType, nullable, keepSource);
return new ScalarTypeJsonList(DbPlatformType.JSON, JsonStorage.postgres(PostgresHelper.JSON_TYPE), docType, nullable, keepSource);
}
}
return new ScalarTypeJsonList.Varchar(docType, nullable, keepSource);
return new ScalarTypeJsonList(Types.VARCHAR, JsonStorage.VARCHAR, docType, nullable, keepSource);
}
@SuppressWarnings("rawtypes")
static final class VarcharWithConverter extends ScalarTypeJsonList.Base {
ScalarTypeJsonList(int jdbcType, JsonStorage storage, DocPropertyType docType, boolean nullable, boolean keepSource) {
super(List.class, jdbcType, storage, keepSource, nullable, docType);
}
@Override
List readJson(String rawJson) {
try {
// parse JSON into a modifyAware list
return EJson.parseList(rawJson, true);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", rawJson, e);
}
}
@Override
public List parse(String value) {
try {
return EJson.parseList(value, false);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", value, e);
}
}
@Override
public String formatValue(List value) {
try {
return EJson.write(value);
} catch (IOException e) {
throw new PersistenceException("Failed to format List into JSON content", e);
}
}
@Override
public List jsonRead(JsonReader parser) throws IOException {
return EJson.parseList(parser, parser.currentToken());
}
@Override
public void jsonWrite(JsonWriter writer, List value) throws IOException {
EJson.write(value, writer);
}
/**
* List mapped to VARCHAR with element conversion - used as the {@code @DbArray} fallback
* on platforms without native array support.
*/
static final class VarcharWithConverter extends ScalarTypeJsonList {
private final ArrayElementConverter converter;
VarcharWithConverter(DocPropertyType docType, boolean nullable, boolean keepSource, ArrayElementConverter converter) {
super(Types.VARCHAR, docType, nullable, keepSource);
super(Types.VARCHAR, JsonStorage.VARCHAR, docType, nullable, keepSource);
this.converter = converter;
}
@Override
List readJsonConvert(String json) {
try {
return convertElements(EJson.parseList(json, false));
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", json, e);
}
List readJson(String rawJson) {
return convert(rawJson);
}
@Override
public List parse(String value) {
return convert(value);
}
@SuppressWarnings("unchecked")
private List convertElements(List<Object> rawList) {
if (rawList == null) {
return null;
}
final List result = new ArrayList<>(rawList.size());
for (Object o : rawList) {
result.add(converter.fromSerialized(o));
}
return new ModifyAwareList(result);
}
@Override
public List parse(String value) {
private List convert(String json) {
try {
return convertElements(EJson.parseList(value, false));
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", value, e);
}
}
}
/**
* List mapped to DB VARCHAR.
*/
static final class Varchar extends ScalarTypeJsonList.Base {
Varchar(DocPropertyType docType, boolean nullable, boolean keepSource) {
super(Types.VARCHAR, docType, nullable, keepSource);
}
}
/**
* List mapped to Postgres JSON.
*/
private final static class Json extends ScalarTypeJsonList.PgBase {
Json(DocPropertyType docType, boolean nullable, boolean keepSource) {
super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable, keepSource);
}
}
/**
* List mapped to Postgres JSONB.
*/
private static final class JsonB extends ScalarTypeJsonList.PgBase {
JsonB(DocPropertyType docType, boolean nullable, boolean keepSource) {
super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable, keepSource);
}
}
/**
* Base class for List handling.
*/
@SuppressWarnings("rawtypes")
private abstract static class Base extends ScalarTypeJsonCollection<List> {
final boolean keepSource;
private Base(int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) {
super(List.class, dbType, docType, nullable);
this.keepSource = keepSource;
}
@Override
public final boolean jsonMapper() {
return keepSource;
}
@Override
public final List read(DataReader reader) throws SQLException {
String json = reader.getString();
if (keepSource) {
reader.pushJson(json);
}
return readJsonConvert(json);
}
List readJsonConvert(String json) {
try {
// parse JSON into modifyAware list
return EJson.parseList(json, true);
List<Object> rawList = EJson.parseList(json, false);
if (rawList == null) {
return null;
}
final List result = new ArrayList<>(rawList.size());
for (Object o : rawList) {
result.add(converter.fromSerialized(o));
}
return new ModifyAwareList(result);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", json, e);
}
}
@Override
public final void bind(DataBinder binder, List value) throws SQLException {
String rawJson = keepSource ? binder.popJson() : null;
if (rawJson == null && value != null) {
rawJson = formatValue(value);
}
if (value == null) {
bindNull(binder);
} else {
bindRawJson(binder, rawJson);
}
}
@Override
protected void bindNull(DataBinder binder) throws SQLException {
if (nullable) {
binder.setNull(Types.VARCHAR);
} else {
binder.setString("[]");
}
}
protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException {
binder.setString(rawJson);
}
@Override
public final String formatValue(List value) {
try {
return EJson.write(value);
} catch (IOException e) {
throw new PersistenceException("Failed to format List into JSON content", e);
}
}
@Override
public List parse(String value) {
try {
return EJson.parseList(value, false);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", value, e);
}
}
@Override
public final List jsonRead(JsonParser parser) throws IOException {
return EJson.parseList(parser, parser.getCurrentToken());
}
@Override
public final void jsonWrite(JsonGenerator writer, List value) throws IOException {
EJson.write(value, writer);
}
}
/**
* Postgres extension to base List handling.
*/
private static class PgBase extends ScalarTypeJsonList.Base {
final String pgType;
PgBase(int jdbcType, String pgType, DocPropertyType docType, boolean nullable, boolean keepSource) {
super(jdbcType, docType, nullable, keepSource);
this.pgType = pgType;
}
@Override
protected final void bindRawJson(DataBinder binder, String rawJson) throws SQLException {
binder.setObject(PostgresHelper.asObject(pgType, rawJson));
}
@Override
protected final void bindNull(DataBinder binder) throws SQLException {
binder.setObject(PostgresHelper.asObject(pgType, nullable ? null : "[]"));
}
}
}
@@ -1,204 +1,63 @@
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;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarTypeBase;
import io.ebean.core.type.PostgresHelper;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebean.util.IOUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.io.IOException;
import java.sql.Types;
import java.util.Map;
/**
* Type which maps Map<String,Object> to various DB types (Clob, Varchar, Blob) in JSON format.
* Type which maps {@code Map<String,Object>} to JSON stored in VARCHAR, CLOB, BLOB,
* or Postgres JSON / JSONB.
*/
@SuppressWarnings("rawtypes")
abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
class ScalarTypeJsonMap extends ScalarTypeJsonValue<Map> {
/**
* Return the ScalarType for the requested dbType and postgres.
* Return the ScalarType for the requested dbType and platform.
*/
static ScalarTypeJsonMap typeFor(boolean postgres, int dbType, boolean keepSource) {
return new ScalarTypeJsonMap(storageFor(postgres, dbType), keepSource);
}
/**
* Select the storage strategy for the given dbType and platform. Shared with the
* enum-key Map variant.
*/
static JsonStorage storageFor(boolean postgres, int dbType) {
switch (dbType) {
case Types.VARCHAR:
return new ScalarTypeJsonMap.Varchar(keepSource);
return JsonStorage.VARCHAR;
case Types.BLOB:
return new ScalarTypeJsonMap.Blob(keepSource);
return JsonStorage.BLOB;
case Types.CLOB:
return new ScalarTypeJsonMap.Clob(keepSource);
return JsonStorage.CLOB;
case DbPlatformType.JSONB:
return postgres ? new ScalarTypeJsonMapPostgres.JSONB(keepSource) : new ScalarTypeJsonMap.Clob(keepSource);
return postgres ? JsonStorage.postgres(PostgresHelper.JSONB_TYPE) : JsonStorage.CLOB;
case DbPlatformType.JSON:
return postgres ? new ScalarTypeJsonMapPostgres.JSON(keepSource) : new ScalarTypeJsonMap.Clob(keepSource);
return postgres ? JsonStorage.postgres(PostgresHelper.JSON_TYPE) : JsonStorage.CLOB;
default:
throw new IllegalStateException("Unknown dbType " + dbType);
}
}
private static final class Clob extends ScalarTypeJsonMap {
Clob(boolean keepSource) {
super(Types.CLOB, keepSource);
}
@Override
protected String readJson(DataReader reader) throws SQLException {
return reader.getStringFromStream();
}
}
private static final class Varchar extends ScalarTypeJsonMap {
Varchar(boolean keepSource) {
super(Types.VARCHAR, keepSource);
}
}
private static final class Blob extends ScalarTypeJsonMap {
Blob(boolean keepSource) {
super(Types.BLOB, keepSource);
}
@Override
public Map read(DataReader reader) throws SQLException {
InputStream is = reader.getBinaryStream();
if (is == null) {
if (keepSource) {
reader.pushJson(null);
}
return null;
}
try {
if (keepSource) {
StringWriter jsonBuffer = new StringWriter();
try (Reader streamReader = IOUtils.newReader(is)) {
transferTo(streamReader, jsonBuffer);
}
String rawJson = jsonBuffer.toString();
reader.pushJson(rawJson);
return parse(rawJson);
} else {
try (Reader streamReader = IOUtils.newReader(is)) {
return parse(streamReader);
}
}
} catch (IOException e) {
throw new SQLException("Error reading Blob stream from DB", e);
}
}
private static void transferTo(Reader reader, Writer out) throws IOException {
char[] buffer = new char[2048];
int nRead;
while ((nRead = reader.read(buffer, 0, 2048)) >= 0) {
out.write(buffer, 0, nRead);
}
}
@Override
protected void bindNull(DataBinder binder) throws SQLException {
binder.setNull(Types.BLOB);
}
@Override
protected void bindJson(DataBinder binder, String rawJson) throws SQLException {
binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8));
}
}
final boolean keepSource;
ScalarTypeJsonMap(int jdbcType, boolean keepSource) {
super(Map.class, false, jdbcType);
this.keepSource = keepSource;
}
/**
* Map is a mutable type. Use the isDirty() method to check for dirty state.
*/
@Override
public final boolean mutable() {
return true;
}
/**
* Return true if the value should be considered dirty (and included in an update).
*/
@Override
public boolean isDirty(Object value) {
return TypeJsonManager.checkIsDirty(value);
ScalarTypeJsonMap(JsonStorage storage, boolean keepSource) {
super(Map.class, storage.jdbcType(), storage, keepSource, true, null, DocPropertyType.OBJECT);
}
@Override
public final boolean jsonMapper() {
return keepSource;
}
@Override
public Map read(DataReader reader) throws SQLException {
String rawJson = readJson(reader);
if (keepSource) {
reader.pushJson(rawJson);
}
if (rawJson == null) {
return null;
}
Map readJson(String rawJson) {
return parse(rawJson);
}
protected String readJson(DataReader reader) throws SQLException {
return reader.getString();
}
@Override
public final void bind(DataBinder binder, Map value) throws SQLException {
String rawJson = keepSource ? binder.popJson() : null;
if (rawJson == null && value != null) {
rawJson = formatValue(value);
}
if (value == null) {
bindNull(binder);
} else {
bindJson(binder, rawJson);
}
}
protected void bindNull(DataBinder binder) throws SQLException {
binder.setNull(Types.VARCHAR);
}
protected void bindJson(DataBinder binder, String rawJson) throws SQLException {
binder.setString(rawJson);
}
@Override
public final Object toJdbcType(Object value) {
return value;
}
@Override
public final Map toBeanType(Object value) {
return (Map) value;
}
@Override
public final String formatValue(Map v) {
try {
return EJson.write(v);
} catch (IOException e) {
throw new TextException(e);
}
}
@Override
public final Map parse(String value) {
public Map parse(String value) {
try {
// return a modify aware map
return EJson.parseObject(value, true);
@@ -207,46 +66,22 @@ abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
}
}
public final Map parse(Reader reader) {
@Override
public String formatValue(Map value) {
try {
// return a modify aware map
return EJson.parseObject(reader, true);
return EJson.write(value);
} catch (IOException e) {
throw new TextException(e);
}
}
@Override
public final Map readData(DataInput dataInput) throws IOException {
if (!dataInput.readBoolean()) {
return null;
} else {
return parse(dataInput.readUTF());
}
public Map jsonRead(JsonReader parser) throws IOException {
return EJson.parseObject(parser, parser.currentToken());
}
@Override
public final void writeData(DataOutput dataOutput, Map map) throws IOException {
if (map == null) {
dataOutput.writeBoolean(false);
} else {
ScalarHelp.writeUTF(dataOutput, format(map));
}
}
@Override
public final void jsonWrite(JsonGenerator writer, Map value) throws IOException {
public 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());
}
@Override
public final DocPropertyType docType() {
return DocPropertyType.OBJECT;
}
}
@@ -0,0 +1,90 @@
package io.ebeaninternal.server.type;
import io.avaje.json.JsonReader;
import io.avaje.json.JsonWriter;
import io.ebean.core.type.ScalarType;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeaninternal.json.ModifyAwareMap;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Type which maps {@code Map<Enum,Object>} to JSON.
* <p>
* The enum keys are (de)serialized via their {@link ScalarType} (honouring custom
* {@code @DbEnumValue} mappings), then delegated to {@link ScalarTypeJsonMap} for all
* storage, platform and value handling.
*/
@SuppressWarnings("rawtypes")
final class ScalarTypeJsonMapEnum<T extends Enum<T>> extends ScalarTypeJsonMap {
private final ScalarType<T> enumType;
static ScalarType<?> typeFor(boolean postgres, int dbType, ScalarType<? extends Enum<?>> enumType, boolean keepSource) {
return new ScalarTypeJsonMapEnum<>(storageFor(postgres, dbType), enumType, keepSource);
}
@SuppressWarnings("unchecked")
private ScalarTypeJsonMapEnum(JsonStorage storage, ScalarType<? extends Enum> enumType, boolean keepSource) {
super(storage, keepSource);
this.enumType = (ScalarType<T>) enumType;
}
@Override
Map readJson(String rawJson) {
return parse(rawJson);
}
@Override
public Map parse(String value) {
try {
return toEnumKeys(EJson.parseObject(value, true));
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as Map with enum keys", value, e);
}
}
@Override
public String formatValue(Map value) {
try {
return EJson.write(toStringKeys(value));
} catch (IOException e) {
throw new TextException(e);
}
}
@Override
public Map jsonRead(JsonReader parser) throws IOException {
return toEnumKeys(EJson.parseObject(parser, parser.currentToken()));
}
@Override
public void jsonWrite(JsonWriter writer, Map value) throws IOException {
EJson.write(toStringKeys(value), writer);
}
@SuppressWarnings("unchecked")
private Map<String, Object> toStringKeys(Map value) {
Map<String, Object> stringKeyMap = new LinkedHashMap<>();
for (Object o : value.entrySet()) {
Map.Entry e = (Map.Entry) o;
stringKeyMap.put(enumType.formatValue((T) e.getKey()), e.getValue());
}
return stringKeyMap;
}
@SuppressWarnings("unchecked")
private Map toEnumKeys(Map<String, Object> stringKeyMap) {
if (stringKeyMap == null) {
return null;
}
Map enumKeyMap = new LinkedHashMap();
for (Map.Entry<String, Object> e : stringKeyMap.entrySet()) {
enumKeyMap.put(enumType.parse(e.getKey()), e.getValue());
}
return stringKeyMap instanceof ModifyAwareMap ? new ModifyAwareMap(enumKeyMap) : enumKeyMap;
}
}
@@ -1,50 +0,0 @@
package io.ebeaninternal.server.type;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.PostgresHelper;
import java.sql.SQLException;
/**
* Support for the Postgres DB types JSON and JSONB.
*/
abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap {
private final String postgresType;
ScalarTypeJsonMapPostgres(int jdbcType, String postgresType, boolean keepSource) {
super(jdbcType, keepSource);
this.postgresType = postgresType;
}
@Override
protected final void bindNull(DataBinder binder) throws SQLException {
binder.setObject(PostgresHelper.asObject(postgresType, null));
}
@Override
protected final void bindJson(DataBinder binder, String rawJson) throws SQLException {
binder.setObject(PostgresHelper.asObject(postgresType, rawJson));
}
/**
* ScalarType mapping java Map type to Postgres JSON database type.
*/
static final class JSON extends ScalarTypeJsonMapPostgres {
JSON(boolean keepSource) {
super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, keepSource);
}
}
/**
* ScalarType mapping java Map type to Postgres JSONB database type.
*/
static final class JSONB extends ScalarTypeJsonMapPostgres {
JSONB(boolean keepSource) {
super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, keepSource);
}
}
}
@@ -1,220 +1,124 @@
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.core.type.DocPropertyType;
import io.ebean.core.type.PostgresHelper;
import io.ebean.core.type.ScalarType;
import io.ebean.text.TextException;
import io.ebean.text.json.EJson;
import io.ebeaninternal.json.ModifyAwareSet;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Types for mapping List in JSON format to DB types VARCHAR, JSON and JSONB.
* Type which maps a Set in JSON format to VARCHAR or Postgres JSON / JSONB.
*/
final class ScalarTypeJsonSet {
@SuppressWarnings("rawtypes")
class ScalarTypeJsonSet extends ScalarTypeJsonCollectionValue<Set> {
/**
* Return the appropriate ScalarType for the requested dbType and Postgres.
* Return the appropriate ScalarType for the requested dbType and platform.
*/
static ScalarType<?> typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) {
static ScalarType<?> typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) {
if (postgres) {
switch (dbType) {
case DbPlatformType.JSONB:
return new ScalarTypeJsonSet.JsonB(docPropertyType, nullable, keepSource);
return new ScalarTypeJsonSet(DbPlatformType.JSONB, JsonStorage.postgres(PostgresHelper.JSONB_TYPE), docType, nullable, keepSource);
case DbPlatformType.JSON:
return new ScalarTypeJsonSet.Json(docPropertyType, nullable, keepSource);
return new ScalarTypeJsonSet(DbPlatformType.JSON, JsonStorage.postgres(PostgresHelper.JSON_TYPE), docType, nullable, keepSource);
}
}
return new ScalarTypeJsonSet.Varchar(docPropertyType, nullable, keepSource);
return new ScalarTypeJsonSet(Types.VARCHAR, JsonStorage.VARCHAR, docType, nullable, keepSource);
}
@SuppressWarnings("rawtypes")
static final class VarcharWithConverter extends ScalarTypeJsonSet.Base {
ScalarTypeJsonSet(int jdbcType, JsonStorage storage, DocPropertyType docType, boolean nullable, boolean keepSource) {
super(Set.class, jdbcType, storage, keepSource, nullable, docType);
}
@Override
Set readJson(String rawJson) {
try {
// parse JSON into a modifyAware set
return EJson.parseSet(rawJson, true);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as Set", rawJson, e);
}
}
@Override
@SuppressWarnings("unchecked")
public Set parse(String value) {
try {
return new LinkedHashSet(EJson.parseList(value));
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as Set: " + value, e);
}
}
@Override
public String formatValue(Set value) {
try {
return EJson.write(value);
} catch (IOException e) {
throw new PersistenceException("Failed to format Set into JSON content", e);
}
}
@Override
@SuppressWarnings("unchecked")
public Set jsonRead(JsonReader parser) throws IOException {
return new LinkedHashSet(EJson.parseList(parser, parser.currentToken()));
}
@Override
public void jsonWrite(JsonWriter writer, Set value) throws IOException {
EJson.write(value, writer);
}
/**
* Set mapped to VARCHAR with element conversion - used as the {@code @DbArray} fallback
* on platforms without native array support.
*/
static final class VarcharWithConverter extends ScalarTypeJsonSet {
private final ArrayElementConverter converter;
VarcharWithConverter(DocPropertyType docType, boolean nullable, boolean keepSource, ArrayElementConverter converter) {
super(Types.VARCHAR, docType, nullable, keepSource);
super(Types.VARCHAR, JsonStorage.VARCHAR, docType, nullable, keepSource);
this.converter = converter;
}
@Override
Set readJsonConvert(String json) {
try {
return convertElements(EJson.parseSet(json, false));
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", json, e);
}
}
@SuppressWarnings("unchecked")
private Set convertElements(Set<Object> rawSet) {
if (rawSet == null) {
return null;
}
final Set result = new LinkedHashSet(rawSet.size());
for (Object o : rawSet) {
result.add(converter.fromSerialized(o));
}
return new ModifyAwareSet(result);
Set readJson(String rawJson) {
return convert(rawJson);
}
@Override
public Set parse(String value) {
return convert(value);
}
@SuppressWarnings("unchecked")
private Set convert(String json) {
try {
return convertElements(EJson.parseSet(value, false));
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as Set: " + value, e);
}
}
}
/**
* List mapped to DB VARCHAR.
*/
static final class Varchar extends ScalarTypeJsonSet.Base {
public Varchar(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) {
super(Types.VARCHAR, docPropertyType, nullable, keepSource);
}
}
/**
* List mapped to Postgres JSON.
*/
private static final class Json extends ScalarTypeJsonSet.PgBase {
private Json(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) {
super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable, keepSource);
}
}
/**
* List mapped to Postgres JSONB.
*/
private static final class JsonB extends ScalarTypeJsonSet.PgBase {
private JsonB(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) {
super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable, keepSource);
}
}
@SuppressWarnings("rawtypes")
private abstract static class Base extends ScalarTypeJsonCollection<Set> {
final boolean keepSource;
private Base(int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) {
super(Set.class, dbType, docPropertyType, nullable);
this.keepSource = keepSource;
}
@Override
public final boolean jsonMapper() {
return keepSource;
}
@Override
public final Set read(DataReader reader) throws SQLException {
String json = reader.getString();
if (keepSource) {
reader.pushJson(json);
}
return readJsonConvert(json);
}
Set readJsonConvert(String json) {
try {
return EJson.parseSet(json, true);
Set<Object> rawSet = EJson.parseSet(json, false);
if (rawSet == null) {
return null;
}
final Set result = new LinkedHashSet(rawSet.size());
for (Object o : rawSet) {
result.add(converter.fromSerialized(o));
}
return new ModifyAwareSet(result);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as Set", json, e);
}
}
@Override
public final void bind(DataBinder binder, Set value) throws SQLException {
String rawJson = keepSource ? binder.popJson() : null;
if (rawJson == null && value != null) {
rawJson = formatValue(value);
}
if (value == null) {
bindNull(binder);
} else {
bindRawJson(binder, rawJson);
}
}
@Override
protected void bindNull(DataBinder binder) throws SQLException {
if (nullable) {
binder.setNull(Types.VARCHAR);
} else {
binder.setString("[]");
}
}
protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException {
binder.setString(rawJson);
}
@Override
public final String formatValue(Set value) {
try {
return EJson.write(value);
} catch (IOException e) {
throw new PersistenceException("Failed to format List into JSON content", e);
}
}
@Override
public Set parse(String value) {
try {
return convertList(EJson.parseList(value));
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as Set: " + value, e);
}
}
@Override
public final Set jsonRead(JsonParser parser) throws IOException {
return convertList(EJson.parseList(parser, parser.getCurrentToken()));
}
@Override
public final void jsonWrite(JsonGenerator writer, Set value) throws IOException {
EJson.write(value, writer);
}
@SuppressWarnings("unchecked")
private Set convertList(List list) {
return new LinkedHashSet(list);
}
}
/**
* Postgres extension to base List handling.
*/
private static class PgBase extends ScalarTypeJsonSet.Base {
final String pgType;
PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) {
super(jdbcType, docPropertyType, nullable, keepSource);
this.pgType = pgType;
}
@Override
protected final void bindRawJson(DataBinder binder, String rawJson) throws SQLException {
binder.setObject(PostgresHelper.asObject(pgType, rawJson));
}
@Override
protected final void bindNull(DataBinder binder) throws SQLException {
binder.setObject(PostgresHelper.asObject(pgType, nullable ? null : "[]"));
}
}
}
@@ -0,0 +1,128 @@
package io.ebeaninternal.server.type;
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 java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
/**
* Base for the built-in JSON value types (Map, List, Set).
* <p>
* Holds the common read / bind / L2 cache plumbing and delegates:
* <ul>
* <li>the storage concern (VARCHAR / CLOB / BLOB / Postgres) to a {@link JsonStorage}</li>
* <li>the value marshalling concern to the subclass (which uses the avaje JsonMapper
* backed {@code EJson} facade)</li>
* </ul>
* This removes the previous explosion of per-storage and per-platform subclasses.
*/
abstract class ScalarTypeJsonValue<T> extends ScalarTypeBase<T> {
protected final JsonStorage storage;
protected final boolean keepSource;
private final boolean nullable;
private final String emptyJson;
private final DocPropertyType docType;
/**
* @param emptyJson JSON bound when the value is null and the property is not nullable
* (e.g. {@code "[]"} for collections), or null to always bind SQL null.
*/
ScalarTypeJsonValue(Class<T> type, int jdbcType, JsonStorage storage, boolean keepSource,
boolean nullable, String emptyJson, DocPropertyType docType) {
super(type, false, jdbcType);
this.storage = storage;
this.keepSource = keepSource;
this.nullable = nullable;
this.emptyJson = emptyJson;
this.docType = docType;
}
/**
* Marshal the raw JSON read from the DB into the bean value (the load path - typically
* returns a modify-aware collection).
*/
abstract T readJson(String rawJson);
@Override
public final boolean mutable() {
return true;
}
@Override
public final boolean isDirty(Object value) {
return TypeJsonManager.checkIsDirty(value);
}
@Override
public final boolean jsonMapper() {
return keepSource;
}
@Override
public final T read(DataReader reader) throws SQLException {
String rawJson = storage.read(reader);
if (keepSource) {
reader.pushJson(rawJson);
}
if (rawJson == null) {
return null;
}
return readJson(rawJson);
}
@Override
public final void bind(DataBinder binder, T value) throws SQLException {
String rawJson = keepSource ? binder.popJson() : null;
if (rawJson == null && value != null) {
rawJson = formatValue(value);
}
if (value == null) {
if (nullable || emptyJson == null) {
storage.bindNull(binder);
} else {
storage.bind(binder, emptyJson);
}
} else {
storage.bind(binder, rawJson);
}
}
@Override
public final Object toJdbcType(Object value) {
return value;
}
@Override
@SuppressWarnings("unchecked")
public final T toBeanType(Object value) {
return (T) value;
}
@Override
public final DocPropertyType docType() {
return docType;
}
@Override
public final T readData(DataInput dataInput) throws IOException {
if (!dataInput.readBoolean()) {
return null;
}
return parse(dataInput.readUTF());
}
@Override
public final void writeData(DataOutput dataOutput, T value) throws IOException {
if (value == null) {
dataOutput.writeBoolean(false);
} else {
ScalarHelp.writeUTF(dataOutput, formatValue(value));
}
}
}
@@ -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();
}

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