Compare commits

..
Author SHA1 Message Date
Robin Bygrave 2b00899513 [maven-release-plugin] prepare release avaje-ebeanorm-7.2.3 2016-03-23 18:20:35 +13:00
Robin Bygrave 6fe557e237 #616 - No effective change - rename methods 2016-03-23 18:11:25 +13:00
Robin Bygrave a227f8c469 #616 - ElasticSearch expression - IllegalArgumentException: unhandled type class org.example.domain.Customer 2016-03-23 18:08:37 +13:00
Robin Bygrave a9d7ea0299 #614 - 'Matches for the concatenated key columns where not found' - fix test to run on Postgres 2016-03-23 17:11:39 +13:00
Robin Bygrave 0c4a2d5664 #614 - 'Matches for the concatenated key columns where not found' - fix 2016-03-23 17:03:55 +13:00
Robin Bygrave 577dbad9bd #614 - 'Matches for the concatenated key columns where not found' - tests 2016-03-23 17:01:43 +13:00
Robin Bygrave ae28599825 No effective change - use existing CamelCaseHelper implementation 2016-03-23 16:10:50 +13:00
Robin Bygrave 0627976f6d No effective change - format 2016-03-23 14:39:24 +13:00
Robin Bygrave 7d89b97e2b #615 - ENH: Change DDL generation with @SoftDelete to add 'default false' to column definition 2016-03-23 11:10:35 +13:00
Robin Bygrave 90ee903c52 No effective change - alter tests to run against Oracle 2016-03-23 10:41:55 +13:00
Robin Bygrave ffa68f6815 No effective change - alter tests to run against Oracle 2016-03-23 10:41:42 +13:00
Robin Bygrave 2cb0794a01 #612 - ENH: Add deletePermanent() to com.avaje.ebean.Model 2016-03-22 12:06:39 +13:00
Robin Bygrave 67a267f843 #604 - @SoftDelete with deletePermanent() on lazy loaded @OneToOne 2016-03-22 11:57:44 +13:00
Robin Bygrave e57ce7a6c4 #611 - findRowCount ignores @SoftDelete, does not add predicate 2016-03-22 10:35:20 +13:00
Robin Bygrave 5d3d73aac8 [maven-release-plugin] prepare for next development iteration 2016-03-22 09:47:16 +13:00
Robin Bygrave 436988694d [maven-release-plugin] prepare release avaje-ebeanorm-7.2.2 2016-03-22 09:46:58 +13:00
Robin Bygrave 90f786babd #610 - ManyToOne returns wrong ID value (prior ManyToOne with inheritance and null value). Regression introduced in 7.2.1. 2016-03-22 09:46:12 +13:00
Robin Bygrave 0d8146d453 [maven-release-plugin] prepare for next development iteration 2016-03-21 12:20:17 +13:00
Robin Bygrave 0eca613b41 [maven-release-plugin] prepare release avaje-ebeanorm-7.2.1 2016-03-21 12:19:57 +13:00
Robin Bygrave dcde54fca4 #100 - SQLITE (365) : ADD CONSTRAINT DDL SQLite Incompatibility 2016-03-21 11:06:52 +13:00
Robin Bygrave 49b8efbc30 #609 - SQLite DDL - disable use of comments and alter table add foreign key (as these are not supported by SQLite) 2016-03-21 10:31:57 +13:00
Robin Bygrave fc70161909 No effective change - bump SQLite jdbc driver for testing 2016-03-21 10:31:42 +13:00
Rob Bygrave 44ba6d3bb2 Merge pull request #608 from btimo/fixSQLiteDdl
SQLite Ddl : autoincrement attribute
2016-03-21 10:14:05 +13:00
Robin Bygrave d56e071f53 No effective change - javadoc only 2016-03-21 09:20:11 +13:00
Robin Bygrave ce33c230c7 No effective change - javadoc only 2016-03-21 08:43:33 +13:00
btimo 2da6a6960e - fix SQLiteDdl : https://www.sqlite.org/autoinc.html 2016-03-18 17:55:29 +01:00
Robin Bygrave 62f88c9e4f #607 - bool conversion for and(), or() and not() 2016-03-18 16:19:41 +13:00
Robin Bygrave 42f65ead47 #607 - must(), should(), mustNot() always mark setUseDocStore(true); 2016-03-18 16:17:32 +13:00
Robin Bygrave 8e98e88e8a #607 - Tidy "text" extensions to criteria API - remove endMust(), endShould(), endMustNot() ... just use endJunction() 2016-03-18 14:24:07 +13:00
Robin Bygrave 5de67e1939 #606 - Add not() as a junction expression (like and() and or()) to the query criteria API 2016-03-18 14:11:51 +13:00
Robin Bygrave 3ddd7099b5 #605 - Add and() and or() ... as synonyms for conjunction() and disjunction() ... to the "Query Criteria API" 2016-03-18 13:06:10 +13:00
Robin Bygrave 5d889790c2 No effective change - move related methods together 2016-03-18 12:53:15 +13:00
Robin Bygrave 636acc61e5 #594 ElasticSearch - Add match / TextExpression support - move expressions onto ExpressionList (remove TextExpressionList) 2016-03-18 12:51:39 +13:00
Robin Bygrave cb347ff5fa #602 - Elastic - Add support for ebean.docstore.generateMapping 2016-03-15 17:10:03 +13:00
Robin Bygrave db9d39ef61 #583 - MSSQL Server - Delete query syntax error, mssql does not like the table alias in the delete statement 2016-03-15 01:03:08 +13:00
Robin Bygrave 5216014d50 #601 - Second Nested filterMany not executed but joined 2016-03-14 23:54:34 +13:00
Robin Bygrave c40715733e #600 - Unique index or primary key violation - double insert attempted on ManyToMany intersection 2016-03-14 22:32:03 +13:00
Robin Bygrave 9bcc8a77fe No effective change - imports 2016-03-14 21:28:14 +13:00
Robin Bygrave 7be3a01823 No effective change - tidy up tests 2016-03-14 21:06:48 +13:00
Robin Bygrave 0ca4f92b26 #592 - Fetched empty collection of null child of parent entity 2016-03-14 20:10:07 +13:00
Robin Bygrave b99f002968 #599 - ElasticSearch - add query join support for find, findList, findPagedList, findEach, findEachWhile 2016-03-14 17:40:26 +13:00
Robin Bygrave fa451078b7 No effective change - cleanup imports 2016-03-11 21:56:11 +13:00
Robin Bygrave 49121a9c1f #595 - Refactor - persistence context root type handling - fully expect root type 2016-03-11 20:54:57 +13:00
Robin Bygrave 33f69902dc #595 - Refactor - persistence context root type handling - contextGet() and contextRef() 2016-03-11 20:26:03 +13:00
Robin Bygrave cf783fbbb7 #595 - Refactor - persistence context root type handling - AssocOne split simple and inherit 2016-03-11 19:40:02 +13:00
Robin Bygrave cf62ea4c2f #595 - Refactor - persistence context root type handling 2016-03-11 17:34:45 +13:00
Robin Bygrave 049bf76cc4 #594 - ElasticSearch - Add support for TextSimple, TextQueryString and TextCommon full text search expressions 2016-03-11 14:52:27 +13:00
Robin Bygrave 75a84af3f8 #594 - ElasticSearch - Use AbstractTextExpression 2016-03-10 11:30:42 +13:00
Robin Bygrave 93680f5d19 Bump agent to 4.9.2 2016-03-10 11:29:51 +13:00
Robin Bygrave faf7df1c69 #594 - ElasticSearch - multi match support 2016-03-09 23:44:23 +13:00
Robin Bygrave 34dcc98908 #594 - ElasticSearch - Add match / TextExpression support (Full text search expression) 2016-03-09 21:48:22 +13:00
Robin Bygrave 3c63b79324 #589 - ElasticSearch - Improve update notification when not mapped but has nested invalidation 2016-03-08 22:14:23 +13:00
Robin Bygrave 2d93d592c1 #593 - Refactor SPI - Move methods from ElPropertyValue to ExpressionPath 2016-03-08 21:34:27 +13:00
Robin Bygrave 57836af5b5 #593 - Refactor SPI - Move methods from ElPropertyValue to ExpressionPath 2016-03-08 21:21:46 +13:00
Robin Bygrave ba871d6684 #593 - Refactor SPI - Move methods from ElPropertyValue to ExpressionPath 2016-03-08 21:09:49 +13:00
Robin Bygrave 583fc11945 #593 - Refactor SPI - Move methods from ElPropertyValue to ExpressionPath 2016-03-08 21:02:39 +13:00
Robin Bygrave 3ba4660bbb No effective change - format code 2016-03-08 18:33:32 +13:00
Robin Bygrave 37b86f2191 #589 - ElasticSearch - refactor to DocQueryContext 2016-03-08 00:00:02 +13:00
Robin Bygrave da8766533f #589 - ElasticSearch - extend mapping options 2016-03-07 22:00:41 +13:00
Robin Bygrave ae31117b8f #590 - Refactor internals - Remove duplicate isBatch() logic in JdbcTransaction 2016-03-07 19:23:50 +13:00
Robin Bygrave cf825f7ac4 #589 - ElasticSearch, Transaction - rename DocStoreMode and batch size methods 2016-03-07 19:15:08 +13:00
Robin Bygrave a0609b1726 #589 - ElasticSearch - DocStoreMode renamed from DocStoreEvent 2016-03-07 19:10:15 +13:00
Robin Bygrave 32bcd5e853 #589 - ElasticSearch - JsonReader given BeanType 2016-03-07 16:27:16 +13:00
Robin Bygrave f082fce1ff Bump pom to 7.2.1-SNAPSHOT 2016-03-07 16:26:18 +13:00
Robin Bygrave 7fc58a1842 #589 - ElasticSearch - DocStoreConfig ... mapping path, suffix, resources 2016-03-07 14:21:09 +13:00
Robin Bygrave f2ffbb2c5b #589 - ElasticSearch - Add copyIndex() via Query, Add findEachWhile() 2016-03-07 13:49:23 +13:00
Robin Bygrave 5c0fb7c8cd #584 - ElasticSearch: Internal changes - Enum and UUID tests 2016-03-04 00:04:58 +13:00
Robin Bygrave 04c9db1e42 #586 - Refactor the UUID ScalarTypes (Native, Varchar and Binary) to have a common base 2016-03-04 00:00:05 +13:00
Robin Bygrave 8f7de950e2 No effective change - reformat, early check for UUID type 2016-03-03 23:56:30 +13:00
Robin Bygrave 53dd810b9a #587 - Refactor - Change JsonContext.getScalar() to JsonContext.writeScalar() 2016-03-03 23:38:09 +13:00
Robin Bygrave 290ca6ba90 #586 - Refactor the UUID ScalarTypes (Native, Varchar and Binary) to have a common base 2016-03-03 22:10:28 +13:00
Robin Bygrave 992f5797a7 #586 - Refactor the UUID ScalarTypes (Native, Varchar and Binary) to have a common base 2016-03-03 22:10:04 +13:00
Robin Bygrave 798976fbd2 #585 - Refactor of ScalarType jsonWrite() method such that it only takes JsonGenerator - part 4, tidy up 2016-03-03 21:39:10 +13:00
Robin Bygrave c315067ff0 #585 - Refactor of ScalarType jsonWrite() method such that it only takes JsonGenerator - part 3, remove JsonToken from jsonRead() 2016-03-03 21:06:09 +13:00
Robin Bygrave a4f0e3936a #585 - Refactor of ScalarType jsonWrite() method such that it only takes JsonGenerator - part 2, use JsonGenerator 2016-03-03 20:27:13 +13:00
Robin Bygrave fc96685ed0 #585 - Refactor of ScalarType jsonWrite() method such that it only takes JsonGenerator - part 1, remove field name 2016-03-03 19:51:07 +13:00
Robin Bygrave 5b10d06348 #584 - ElasticSearch: Internal changes for config, expressions and JSON conversion 2016-03-03 17:46:32 +13:00
Robin Bygrave ca73eff519 #582 - ElasticSearch: Add setUseDocStore(true) on ExpressionList ... for fluid query style 2016-03-02 23:25:58 +13:00
Robin Bygrave 646de50ae8 #581 - ElasticSearch - DocStoreConfig, read dropCreate not read from properties file 2016-03-02 23:22:13 +13:00
Robin Bygrave e2558a1e24 #578 - Deprecated Joda Time API - LocalDate.toDateMidnight() usage causes exception to be thrown, change to LocalDate.toDateTimeAtStartOfDay() 2016-03-02 08:12:53 +13:00
Robin Bygrave ad596a2043 #580 - @Enumerated(EnumType.STRING) on Enum uses toString() rather than name() 2016-03-01 17:35:55 +13:00
Robin Bygrave 0a60cd4e58 [maven-release-plugin] prepare for next development iteration 2016-03-01 16:17:14 +13:00
Robin Bygrave ec38137e5c [maven-release-plugin] prepare release avaje-ebeanorm-7.1.1 2016-03-01 16:16:53 +13:00
Robin Bygrave bfcbd335c8 Reduce logging via logback-test.xml for CI etc 2016-03-01 16:14:31 +13:00
Robin Bygrave 2cafdf709d bump pom to 7.1.1-SNAPSHOT 2016-03-01 16:12:40 +13:00
Robin Bygrave ed2ea13deb #578 - Deprecated Joda Time API - LocalDate.toDateMidnight() usage causes exception to be thrown, change to LocalDate.toDateTimeAtStartOfDay() 2016-03-01 16:09:57 +13:00
Robin Bygrave 8d3b4a0407 #304 - ElasticSearch integration part 1 / doc store integration 2016-03-01 15:36:44 +13:00
Robin Bygrave cb1fe297d6 #579 - Postgres History DDL - Revert of #544 such that Postgres @History DB trigger uses current_timestamp (transaction start time) 2016-03-01 13:15:19 +13:00
Robin Bygrave 6561c118ec #575 - Refactor PathProperties, extract FetchPath interface, rename get(path) -> getProperties(path) 2016-02-29 10:22:59 +13:00
Robin Bygrave eda7fe24c1 #576 - DateTimeJsonParser error when date time has no millisecond precision -java.text.ParseException: Unparseable date: "2016-02-28T20:39:00Z" 2016-02-29 10:14:17 +13:00
Robin Bygrave 2353049c6f #574 - Refactor - rename .plugin objects; SpiBeanType -> BeanType and SpiServerPlugin -> Plugin 2016-02-29 09:19:44 +13:00
Robin Bygrave 6c49ac0857 [maven-release-plugin] prepare for next development iteration 2016-02-17 21:50:48 +13:00
373 changed files with 14138 additions and 3573 deletions
+8 -8
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.18.1</version>
<version>7.2.3</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -73,7 +73,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.3</version>
<version>2.6.5</version>
<scope>provided</scope>
</dependency>
@@ -81,7 +81,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.3</version>
<version>2.6.5</version>
</dependency>
<dependency>
@@ -138,8 +138,8 @@
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.1.0</version>
<scope>test</scope>
<version>2.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -152,7 +152,7 @@
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>4.9.1</version>
<version>4.9.2</version>
<scope>test</scope>
</dependency>
@@ -168,7 +168,7 @@
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.7.2</version>
<version>3.8.11.2</version>
<scope>test</scope>
</dependency>
@@ -219,7 +219,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.9.1</version>
<version>4.9.2</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -0,0 +1,94 @@
package com.avaje.ebean;
/**
* Bean holding the details to update the document store.
*/
public final class DocStoreQueueEntry {
/**
* Action to either update or delete a document from the index.
*/
public enum Action {
/**
* Action is to update a document in the doc store.
*/
INDEX(1),
/**
* Action is to delete a document from the doc store..
*/
DELETE(2),
/**
* An update is required based on a change to a nested/embedded object at a given path.
*/
NESTED(3);
int value;
Action(int value) {
this.value = value;
}
/**
* Return the value associated with this action type.
*/
public int getValue() {
return value;
}
}
final Action type;
final String queueId;
final String path;
final Object beanId;
/**
* Construct for an INDEX or DELETE action.
*/
public DocStoreQueueEntry(Action type, String queueId, Object beanId) {
this(type, queueId, null, beanId);
}
/**
* Construct for an NESTED/embedded path invalidation action.
*/
public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) {
this.type = type;
this.queueId = queueId;
this.path = path;
this.beanId = beanId;
}
/**
* Return the event type.
*/
public Action getType() {
return type;
}
/**
* Return the associate queueId.
*/
public String getQueueId() {
return queueId;
}
/**
* Return the path if this is a nested update.
*/
public String getPath() {
return path;
}
/**
* Return the bean id (which matches the document id).
*/
public Object getBeanId() {
return beanId;
}
}
@@ -0,0 +1,270 @@
package com.avaje.ebean;
import com.avaje.ebeanservice.docstore.api.DocQueryRequest;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
/**
* Document storage operations.
*/
public interface DocumentStore {
/**
* Update the associated document store using the result of the query.
* <p>
* This will execute the query against the database creating a document for each
* bean graph and sending this to the document store.
* </p>
* <p>
* Note that the select and fetch paths of the query is set for you to match the
* document structure needed based on <code>@DocStore</code> and <code>@DocStoreEmbedded</code>
* so what this query requires is the predicates only.
* </p>
* <p>
* This query will be executed using findEach so it is safe to use a query
* that will fetch a lot of beans. The default bulkBatchSize is used.
* </p>
*
* @param query The query that selects object to send to the document store.
*/
<T> void indexByQuery(Query<T> query);
/**
* Update the associated document store index using the result of the query additionally specifying a
* bulkBatchSize to use for sending the messages to ElasticSearch.
*
* @param query The query that selects object to send to the document store.
* @param bulkBatchSize The batch size to use when bulk sending to the document store.
*/
<T> void indexByQuery(Query<T> query, int bulkBatchSize);
/**
* Update the document store for all beans of this type.
* <p>
* This is the same as indexByQuery where the query has no predicates and so fetches all rows.
* </p>
*/
void indexAll(Class<?> beanType);
/**
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findUnique() on the query.
* </p>
* <pre>{@code
*
* Customer customer =
* server.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findUnique();
*
* }</pre>
*/
@Nullable
<T> T find(DocQueryRequest<T> request);
/**
* Execute the find list query. This request is prepared to execute secondary queries.
* <p>
* Typically this is called indirectly by findList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* List<Customer> newCustomers =
* server.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .findList();
*
* }</pre>
*/
<T> List<T> findList(DocQueryRequest<T> request);
/**
* Execute the query against the document store returning the paged list.
* <p>
* The query should have <code>firstRow</code> or <code>maxRows</code> set prior to calling this method.
* </p>
* <p>
* Typically this is called indirectly by findPagedList() on the query that has setUseDocStore(true).
* </p>
*
* <pre>{@code
*
* PagedList<Customer> newCustomers =
* server.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .setMaxRows(50)
* .findPagedList();
*
* }</pre>
*
*/
<T> PagedList<T> findPagedList(DocQueryRequest<T> request);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEach() on the query that has setUseDocStore(true).
* </p>
*
* <pre>{@code
*
* server.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEach(new QueryEachConsumer<Order>() {
* @Override
* public void accept(Order bean) {
* // process the bean
* }
* });
*
* }</pre>
*/
<T> void findEach(DocQueryRequest<T> query, QueryEachConsumer<T> consumer);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* Unlike findEach() this provides the opportunity to stop iterating through the large query.
* </p>
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEachWhile() on the query that has setUseDocStore(true).
* </p>
*
*
* <pre>{@code
*
* server.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new QueryEachWhileConsumer<Order>() {
* @Override
* public void accept(Order bean) {
* // process the bean
*
* // return true to continue, false to stop
* // boolean shouldContinue = ...
* return shouldContinue;
* }
* });
*
* }</pre>
*/
<T> void findEachWhile(DocQueryRequest<T> query, QueryEachWhileConsumer<T> consumer);
/**
* Process the queue entries sending updates to the document store or queuing them for later processing.
*/
long process(List<DocStoreQueueEntry> queueEntries) throws IOException;
/**
* Drop the index from the document store (similar to DDL drop table).
*
* <pre>{@code
*
* DocumentStore documentStore = server.docStore();
*
* documentStore.dropIndex("product_copy");
*
* }</pre>
*
*/
void dropIndex(String indexName);
/**
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
*
* <pre>{@code
*
* DocumentStore documentStore = server.docStore();
*
* // uses product_copy.mapping.json resource
* // ... to define mappings for the index
*
* documentStore.createIndex("product_copy", null);
*
* }</pre>
*
* @param indexName the name of the new index
* @param alias the alias of the index
*/
void createIndex(String indexName, String alias);
/**
* Copy the index to a new index.
* <p>
* This copy process does not use the database but instead will copy from the source index to a destination index.
* </p>
*
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy");
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex);
/**
* Copy entries from an index to a new index but limiting to documents that have been
* modified since the sinceEpochMillis time.
* <p>
* To support this the document needs to have a <code>@WhenModified</code> property.
* </p>
*
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy", sinceMillis);
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex, long sinceEpochMillis);
/**
* Copy from a source index to a new index taking only the documents
* matching the given query.
*
* <pre>{@code
*
* // predicates to select the source documents to copy
* Query<Product> query = server.find(Product.class)
* .where()
* .ge("whenModified", new Timestamp(since))
* .ge("name", "A")
* .lt("name", "D")
* .query();
*
* // copy from the source index to "product_copy" index
* long copyCount = documentStore.copyIndex(query, "product_copy", 1000);
*
* }</pre>
*
* @param query The query to select the source documents to copy
* @param newIndex The target index to copy the documents to
* @param bulkBatchSize The ElasticSearch bulk batch size, if 0 uses the default.
* @return The number of documents copied to the new index.
*/
long copyIndex(Query<?> query, String newIndex, int bulkBatchSize);
}
@@ -1886,11 +1886,16 @@ public interface EbeanServer {
*
* }</pre>
*
* @see com.avaje.ebean.text.PathProperties
* @see Query#apply(com.avaje.ebean.text.PathProperties)
* @see FetchPath
* @see Query#apply(FetchPath)
*/
JsonContext json();
/**
* Return the Document store.
*/
DocumentStore docStore();
/**
* Publish a single bean given its type and id returning the resulting live bean.
* <p>
@@ -1,10 +1,8 @@
package com.avaje.ebean;
import java.io.Serializable;
/**
* An expression that is part of a WHERE or HAVING clause.
*/
public interface Expression extends Serializable {
public interface Expression {
}
@@ -1,5 +1,11 @@
package com.avaje.ebean;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -285,6 +291,31 @@ public interface ExpressionFactory {
*/
Expression raw(String raw);
/**
* Create a Text Match expression (currently doc store/Elastic only).
*/
Expression textMatch(String propertyName, String search, Match options);
/**
* Create a Text Multi match expression (currently doc store/Elastic only).
*/
Expression textMultiMatch(String query, MultiMatch options);
/**
* Create a text simple query expression (currently doc store/Elastic only).
*/
Expression textSimple(String search, TextSimple options);
/**
* Create a text query string expression (currently doc store/Elastic only).
*/
Expression textQueryString(String search, TextQueryString options);
/**
* Create a text common terms expression (currently doc store/Elastic only).
*/
Expression textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -319,4 +350,13 @@ public interface ExpressionFactory {
* Return a list of expressions that will be joined by OR's.
*/
<T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent);
/**
* Return a Text query junction for MUST, SHOULD or MUST NOT.
* <p>
* This is doc store Elastic only.
* </p>
*/
<T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent);
}
@@ -1,10 +1,13 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.search.Match;
import com.avaje.ebean.search.MultiMatch;
import com.avaje.ebean.search.TextCommonTerms;
import com.avaje.ebean.search.TextQueryString;
import com.avaje.ebean.search.TextSimple;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
@@ -31,7 +34,7 @@ import java.util.Set;
*
* @see Query#where()
*/
public interface ExpressionList<T> extends Serializable {
public interface ExpressionList<T> {
/**
* Return the query that owns this expression list.
@@ -95,7 +98,7 @@ public interface ExpressionList<T> extends Serializable {
/**
* Apply the path properties to the query replacing the select and fetch clauses.
*/
Query<T> apply(PathProperties pathProperties);
Query<T> apply(FetchPath fetchPath);
/**
* Perform an 'As of' query using history tables to return the object graph
@@ -381,6 +384,32 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> setUseQueryCache(boolean useCache);
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
* </p>
*/
Query<T> setUseDocStore(boolean useDocsStore);
/**
* Set true if you want to disable lazy loading.
* <p>
* That is, once the object graph is returned further lazy loading is disabled.
* </p>
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Disable read auditing for this query.
* <p>
* This is intended to be used when the query is not a user initiated query and instead
* part of the internal processing in an application to load a cache or document store etc.
* In these cases we don't want the query to be part of read auditing.
* </p>
*/
Query<T> setDisableReadAuditing();
/**
* Add expressions to the having clause.
* <p>
@@ -803,6 +832,47 @@ public interface ExpressionList<T> extends Serializable {
*/
ExpressionList<T> raw(String raw);
/**
* Add a match expression.
*
* @param propertyName The property name for the match
* @param search The search value
*/
ExpressionList<T> match(String propertyName, String search);
/**
* Add a match expression with options.
*
* @param propertyName The property name for the match
* @param search The search value
*/
ExpressionList<T> match(String propertyName, String search, Match options);
/**
* Add a multi-match expression.
*/
ExpressionList<T> multiMatch(String search, String... properties);
/**
* Add a multi-match expression using options.
*/
ExpressionList<T> multiMatch(String search, MultiMatch options);
/**
* Add a simple query string expression.
*/
ExpressionList<T> textSimple(String search, TextSimple options);
/**
* Add a query string expression.
*/
ExpressionList<T> textQueryString(String search, TextQueryString options);
/**
* Add common terms expression.
*/
ExpressionList<T> textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
@@ -819,20 +889,168 @@ public interface ExpressionList<T> extends Serializable {
ExpressionList<T> not(Expression exp);
/**
* Return a list of expressions that will be joined by AND's.
* Start a list of expressions that will be joined by AND's
* returning the expression list the expressions are added to.
* <p>
* This is exactly the same as conjunction();
* </p>
* <p>
* Use endJunction() to end the AND junction.
* </p>
* <p>
* Note that a where() clause defaults to an AND junction so
* typically you only explicitly need to use the and() junction
* when it is nested inside an or() or not() junction.
* </p>
*
* <pre>{@code
*
* // Example: Nested and()
*
* Ebean.find(Customer.class)
* .where()
* .or()
* .and() // nested and
* .startsWith("name", "r")
* .eq("anniversary", onAfter)
* .endJunction() // end AND junction
* .and()
* .eq("status", Customer.Status.ACTIVE)
* .gt("id", 0)
* .endJunction() // end AND junction
* .orderBy().asc("name")
* .findList();
* }</pre>
*/
Junction<T> and();
/**
* Return a list of expressions that will be joined by OR's.
* This is exactly the same as disjunction();
*
* <p>
* Use endJunction() to end the OR junction.
* </p>
*
* <pre>{@code
*
* // Example: Use or() to join
* // two nested and() expressions
*
* Ebean.find(Customer.class)
* .where()
* .or()
* .and()
* .startsWith("name", "r")
* .eq("anniversary", onAfter)
* .endJunction() // end AND junction
* .and()
* .eq("status", Customer.Status.ACTIVE)
* .gt("id", 0)
* .endJunction() // end AND junction
* .orderBy().asc("name")
* .findList();
*
* }</pre>
*/
Junction<T> or();
/**
* Return a list of expressions that will be wrapped by NOT.
* <p>
* Use endJunction() to end expressions being added to the
* NOT expression list.
* </p>
*
* <pre>@{code
*
* .where()
* .not()
* .gt("id", 1)
* .eq("anniversary", onAfter)
* .endJunction() // end the not expressions
*
* }</pre>
*
* <pre>@{code
*
* // Example: nested not()
*
* Ebean.find(Customer.class)
* .where()
* .eq("status", Customer.Status.ACTIVE)
* .not()
* .gt("id", 1)
* .eq("anniversary", onAfter)
* .endJunction() // end the not expressions
* .orderBy()
* .asc("name")
* .findList();
*
* }</pre>
*/
Junction<T> not();
/**
* Start (and return) a list of expressions that will be joined by AND's.
* <p>
* This is the same as and().
* </p>
*/
Junction<T> conjunction();
/**
* Return a list of expressions that will be joined by OR's.
* Start (and return) a list of expressions that will be joined by OR's.
* <p>
* This is the same as or().
* </p>
*/
Junction<T> disjunction();
/**
* End a Conjunction or Disjunction returning the parent expression list.
* Start a list of expressions that will be joined by MUST.
* <p>
* Alternatively you can always use where() to return the top level expression
* list.
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to and().
* </p>
*/
Junction<T> must();
/**
* Start a list of expressions that will be joined by SHOULD.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to or().
* </p>
*/
Junction<T> should();
/**
* Start a list of expressions that will be joined by MUST NOT.
* <p>
* This automatically makes the query a useDocStore(true) query that
* will execute against the document store (ElasticSearch etc).
* </p>
* <p>
* This is logically similar to not().
* </p>
*/
Junction<T> mustNot();
/**
* End a junction returning the parent expression list.
* <p>
* Ends a and(), or(), not(), must(), mustNot() or should() junction
* such that you get the parent expression.
* </p>
* <p>
* Alternatively you can always use where() to return the top level expression list.
* </p>
*/
ExpressionList<T> endJunction();
@@ -0,0 +1,26 @@
package com.avaje.ebean;
import java.util.Set;
/**
* Provides paths and properties for an object graph that can be used to control what parts of the object graph
* is fetching (select and fetch clauses) and also can be used to control JSON marshalling (what parts of the object
* graph are included in the JSON).
*/
public interface FetchPath {
/**
* Return true if the path is included in this FetchPath.
*/
boolean hasPath(String path);
/**
* Return the properties at the given path.
*/
Set<String> getProperties(String path);
/**
* Apply the fetch path to the query.
*/
<T> void apply(Query<T> query);
}
+104 -45
View File
@@ -9,70 +9,129 @@ package com.avaje.ebean;
* <p>
* Note: where() always takes you to the top level WHERE expression list.
* </p>
*
* <pre class="code">
*
* <pre>{@code
* Query q =
* Ebean.find(Person.class)
* .where().disjunction()
* .like(&quot;name&quot;, &quot;Rob%&quot;)
* .eq(&quot;status&quot;, Status.NEW)
*
* // where() returns us to the top level expression list
* .where().gt(&quot;id&quot;, 10);
*
* .where()
* .or()
* .like("name", "Rob%")
* .eq("status", Status.NEW)
*
* // where() returns us to the top level expression list
* .where().gt("id", 10);
*
* // read as...
* // where ( ((name like Rob%) or (status = NEW)) AND (id &gt; 10) )
* </pre>
*
* }</pre>
*
* <p>
* Note: endJunction() takes you to the parent expression list
* </p>
*
* <pre class="code">
*
* <pre>{@code
* Query q =
* Ebean.find(Person.class)
* .where().disjunction()
* .like(&quot;name&quot;, &quot;Rob%&quot;)
* .eq(&quot;status&quot;, Status.NEW)
* .endJunction()
*
* // endJunction().. takes us to the 'parent' expression list
* // which in this case is the top level (same as where())
*
* .gt(&quot;id&quot;, 10);
*
* .where()
* .or()
* .like("name", "Rob%")
* .eq("status", Status.NEW)
* .endJunction()
*
* // endJunction().. takes us to the 'parent' expression list
* // which in this case is the top level (same as where())
*
* .gt("id", 10);
*
* // read as...
* // where ( ((name like Rob%) or (status = NEW)) AND (id &gt; 10) )
* </pre>
*
* // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
* }</pre>
*
* <p>
* Example of a nested disjunction.
* </p>
*
* <pre class="code">
* Query&lt;Customer&gt; q =
*
* <pre>{@code
* Query<Customer> q =
* Ebean.find(Customer.class)
* .where()
* .disjunction()
* .conjunction()
* .startsWith(&quot;name&quot;, &quot;r&quot;)
* .eq(&quot;anniversary&quot;, onAfter)
* .endJunction()
* .conjunction()
* .eq(&quot;status&quot;, Customer.Status.ACTIVE)
* .gt(&quot;id&quot;, 0)
* .endJunction()
* .order().asc(&quot;name&quot;);
*
* .or()
* .and()
* .startsWith("name", "r")
* .eq("anniversary", onAfter)
* .endJunction()
* .and()
* .eq("status", Customer.Status.ACTIVE)
* .gt("id", 0)
* .endJunction()
* .order().asc("name");
*
* q.findList();
* String s = q.getGeneratedSql();
*
*
* // this produces an expression like:
*
* ( name like ? and c.anniversary = ? ) or (c.status = ? and c.id &gt; ? )
*
* </pre>
* ( name like ? and c.anniversary = ? ) or (c.status = ? and c.id > ? )
*
* }</pre>
*/
public interface Junction<T> extends Expression, ExpressionList<T> {
/**
* The type of Junction used in full text expressions.
*/
enum Type {
/**
* AND group.
*/
AND(" and ", ""),
/**
* OR group.
*/
OR(" or ", ""),
/**
* NOT group.
*/
NOT(" and ", "not "),
/**
* Text search AND group.
*/
MUST("must", ""),
/**
* Text search NOT group.
*/
MUST_NOT("must_not", ""),
/**
* Text search OR group.
*/
SHOULD("should", "");
String prefix;
String literal;
Type(String literal, String prefix) {
this.literal = literal;
this.prefix = prefix;
}
/**
* Return the literal value for this type.
*/
public String literal() {
return literal;
}
/**
* Return the prefix value for this type.
*/
public String prefix() {
return prefix;
}
}
}
+16 -4
View File
@@ -1,7 +1,6 @@
package com.avaje.ebean;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.util.ClassUtil;
import org.jetbrains.annotations.Nullable;
@@ -271,6 +270,19 @@ public abstract class Model {
return db().delete(this);
}
/**
* Delete a bean permanently without soft delete.
* <p>
* This is used when the bean contains a <code>@SoftDelete</code> property and we
* want to perform a hard/permanent delete.
* </p>
*
* @see EbeanServer#deletePermanent(Object)
*/
public boolean deletePermanent() {
return db().deletePermanent(this);
}
/**
* Perform an update using this entity against the specified server.
*/
@@ -588,10 +600,10 @@ public abstract class Model {
/**
* Creates a query applying the path properties to set the select and fetch clauses.
* <p>
* Equivalent to {@link Query#apply(com.avaje.ebean.text.PathProperties)}
* Equivalent to {@link Query#apply(FetchPath)}
*/
public Query<T> apply(PathProperties pathProperties) {
return db().find(type).apply(pathProperties);
public Query<T> apply(FetchPath fetchPath) {
return db().find(type).apply(fetchPath);
}
/**
@@ -0,0 +1,18 @@
package com.avaje.ebean;
import javax.persistence.PersistenceException;
/**
* Captures and wraps IOException's occurring during ElasticSearch processing etc.
*/
public class PersistenceIOException extends PersistenceException {
public PersistenceIOException(String msg, Exception cause) {
super(msg, cause);
}
public PersistenceIOException(Exception cause) {
super(cause);
}
}
+33 -5
View File
@@ -1,10 +1,8 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
@@ -275,7 +273,7 @@ import java.util.Set;
* @param <T>
* the type of Entity bean this query will fetch.
*/
public interface Query<T> extends Serializable {
public interface Query<T> {
/**
* Return the RawSql that was set to use for this query.
@@ -522,10 +520,10 @@ public interface Query<T> extends Serializable {
/**
* Apply the path properties replacing the select and fetch clauses.
* <p>
* This is typically used when the PathProperties is applied to both the query and the JSON output.
* This is typically used when the FetchPath is applied to both the query and the JSON output.
* </p>
*/
Query<T> apply(PathProperties pathProperties);
Query<T> apply(FetchPath fetchPath);
/**
* Execute the query returning the list of Id's.
@@ -1043,6 +1041,27 @@ public interface Query<T> extends Serializable {
*/
ExpressionList<T> where();
/**
* Add Full text search expressions for Document store queries.
* <p>
* This is currently ElasticSearch only and provides the full text
* expressions such as Match and Multi-Match.
* </p>
* <p>
* This automatically makes this query a "Doc Store" query and will execute
* against the document store (ElasticSearch).
* </p>
* <p>
* Expressions added here are added to the "query" section of an ElasticSearch
* query rather than the "filter" section.
* </p>
* <p>
* Expressions added to the where() are added to the "filter" section of an
* ElasticSearch query.
* </p>
*/
ExpressionList<T> text();
/**
* This applies a filter on the 'many' property list rather than the root
* level objects.
@@ -1286,6 +1305,14 @@ public interface Query<T> extends Serializable {
*/
Query<T> setUseQueryCache(boolean useQueryCache);
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
* </p>
*/
Query<T> setUseDocStore(boolean useDocStore);
/**
* When set to true when you want the returned beans to be read only.
*/
@@ -1365,4 +1392,5 @@ public interface Query<T> extends Serializable {
* </p>
*/
Set<String> validate();
}
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.annotation.DocStoreMode;
import com.avaje.ebean.config.DocStoreConfig;
import com.avaje.ebean.config.PersistBatch;
import javax.persistence.PersistenceException;
@@ -81,6 +83,29 @@ public interface Transaction extends Closeable {
*/
boolean isActive();
/**
* Set the behavior for document store updates on this transaction.
* <p>
* For example, set the mode to DocStoreEvent.IGNORE for this transaction and
* then any changes via this transaction are not sent to the doc store. This
* would be used when doing large bulk inserts into the database and we want
* to control how that is sent to the document store.
* </p>
*/
void setDocStoreMode(DocStoreMode mode);
/**
* Set the batch size to use for sending messages to the document store.
* <p>
* You might set this if you know the changes in this transaction result in especially large or
* especially small payloads and want to adjust the batch size to match.
* </p>
* <p>
* Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
* </p>
*/
void setDocStoreBatchSize(int batchSize);
/**
* Explicitly turn off or on the cascading nature of save() and delete(). This
* gives the developer exact control over what beans are saved and deleted
@@ -0,0 +1,32 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to indicate that a particular string property should be treated as a 'code' and not analysed for text searching.
* <p>
* By default all Id properties and all Enum properties are treated as 'code' and not analysed.
* </p>
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DocCode {
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
}
@@ -0,0 +1,24 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the entity type maps to a document store (like ElasticSearch).
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocMapping {
/**
* The property name the mapping applies to.
*/
String name();
/**
* Mapping options.
*/
DocProperty options();
}
@@ -0,0 +1,110 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the entity type maps to a document store (like ElasticSearch).
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocProperty {
/**
* Set this to true to indicate that this property should be un-analysed.
*/
boolean code() default false;
/**
* Set this to true to get an additional un-analysed 'raw' field to use for sorting etc.
*/
boolean sortable() default false;
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
/**
* Set this to false to exclude this from the _all property.
*/
boolean includeInAll() default true;
/**
* The analyzer to use.
*/
String analyzer() default "";
/**
* The analyzer to use for searches.
*/
String searchAnalyzer() default "";
/**
* The index options for this property.
*/
Option options() default Option.DEFAULT;
/**
* Set this to false such that doc values are not stored separately for this property.
*/
boolean docValues() default true;
/**
* Set a copyTo field.
*/
String copyTo() default "";
/**
* Set to false to disable the field from indexing, it will only be get/set in _source.
*/
boolean enabled() default true;
/**
* Set to false such that norms are not stored.
*/
boolean norms() default true;
/**
* Index options for a property.
*/
enum Option {
/**
* Only index the doc number.
*/
DOCS,
/**
* Doc number and term frequencies are indexed.
*/
FREQS,
/**
* Doc number, term frequencies and term positions are indexed.
*/
POSITIONS,
/**
* Doc number, term frequencies, term positions and start/end offsets are indexed.
*/
OFFSETS,
/**
* Use the default which means analysed string properties use POSITIONS as the default and all other types use DOCS.
*/
DEFAULT
}
}
@@ -0,0 +1,34 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to indicate that a particular string property should support sorting. What this typically means is that
* for ElasticSearch an additional 'raw' field is added that stores the un-analysed value. This un-analysed value
* can be used for sorting etc and the original field used for text searching.
* <p>
* For example, customer name and product name are good candidates for marking with @DocSortable.
* </p>
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocSortable {
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
}
@@ -0,0 +1,88 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the entity type maps to a document store (like ElasticSearch).
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocStore {
/**
* A unique Id used when queuing reindex events.
*/
String queueId() default "";
/**
* The ElasticSearch index name. If left unspecified the short name of the bean type is used.
*/
String indexName() default "";
/**
* The ElasticSearch index type. If left unspecified the short name of the bean type is used.
*/
String indexType() default "";
/**
* The number of shards this index should use.
*/
int shards() default 0;
/**
* The number of replicas this index should use.
*/
int replicas() default 0;
/**
* Additional mapping that can be defined on the properties.
*/
DocMapping[] mapping() default {};
/**
* Specify the behavior when bean Insert, Update, Delete events occur.
*/
DocStoreMode persist() default DocStoreMode.DEFAULT;
/**
* Specify the behavior when bean Insert occurs.
*/
DocStoreMode insert() default DocStoreMode.DEFAULT;
/**
* Specify the behavior when bean Update occurs.
*/
DocStoreMode update() default DocStoreMode.DEFAULT;
/**
* Specify the behavior when bean Delete occurs.
*/
DocStoreMode delete() default DocStoreMode.DEFAULT;
/**
* Specify to include only some properties in the doc store document.
* <p>
* If this is left as default then all scalar properties are included,
* all @ManyToOne properties are included with just the nested id property
* and no @OneToMany properties are included.
* </p>
* <p>
* Note that typically DocStoreEmbedded is used on @ManyToOne and @OneToMany
* properties to indicate what part of the nested document should be included.
* </p>
*
* <h3>Example:</h3>
* <pre>{@code
*
* // only include the customer id and name
* @DocStore(doc = "id,name")
* @Entity @Table(name = "o_order")
* public class Customer {
*
* }</pre>
*/
String doc() default "";
}
@@ -0,0 +1,37 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the property is included in the parent document store index.
*
* <pre>{@code
*
*
* @DocStore
* @Entity @Table(name = "o_order")
* public class Order {
*
* ...
* // include some customer details including
* // nested billingAddress
* @DocStoreEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
* @ManyToOne
* Customer customer;
*
*
* }</pre>
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocStoreEmbedded {
/**
* The properties on the embedded bean to include in the index.
*/
String doc() default "";
}
@@ -0,0 +1,38 @@
package com.avaje.ebean.annotation;
/**
* Defines the behavior options when a Insert, Update or Delete event occurs
* on a bean with an associated ElasticSearch index.
* <p>
* For some indexes or some transactions it can be beneficial to queue the event
* for later processing rather than look to update ElasticSearch at that time.
* </p>
*/
public enum DocStoreMode {
/**
* Add the event to the queue for processing later (delaying the update to the document store).
*/
QUEUE,
/**
* Update the document store when transaction succeeds.
*/
UPDATE,
/**
* Ignore the event and not update the document store.
* <p>
* This can be used on a index or for a transaction where you want to have more
* manual programmatic control over the updating of the document store. Say you want to
* IGNORE on a particular transaction and instead manually queue a bulk update.
* </p>
*/
IGNORE,
/**
* The actual mode of QUEUE, UPDATE or IGNORE is set from the default configuration.
*/
DEFAULT
}
@@ -31,6 +31,11 @@ public interface BeanCollection<E> extends Serializable {
ALL
}
/**
* Load bean from another collection.
*/
void loadFrom(BeanCollection<?> other);
/**
* Add a bean to the list/set with modifyListen notification.
*/
@@ -219,6 +219,18 @@ public final class EntityBeanIntercept implements Serializable {
this.fullyLoadedBean = fullyLoadedBean;
}
/**
* Check each property to see if the bean is partially loaded.
*/
public boolean isPartial() {
for (int i = 0; i < loadedProps.length; i++) {
if (!loadedProps[i]) {
return true;
}
}
return false;
}
/**
* Return true if this bean has been directly modified (it has oldValues) or
* if any embedded beans are either new or dirty (and hence need saving).
@@ -469,7 +481,7 @@ public final class EntityBeanIntercept implements Serializable {
public void setLoadedProperty(int propertyIndex) {
loadedProps[propertyIndex] = true;
}
/**
* Return true if the property is loaded.
*/
@@ -560,6 +572,23 @@ public final class EntityBeanIntercept implements Serializable {
return props;
}
/**
* Return the array of flags indicating the dirty properties.
*/
public boolean[] getDirtyProperties() {
int len = getPropertyLength();
boolean[] dirties = new boolean[len];
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
dirties[i] = true;
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
dirties[i] = true;
}
}
return dirties;
}
/**
* Return the set of dirty properties.
*/
@@ -10,31 +10,31 @@ package com.avaje.ebean.bean;
public interface PersistenceContext {
/**
* Put the entity bean into the PersistanceContext.
* Put the entity bean into the PersistenceContext.
*/
void put(Object id, Object bean);
void put(Class<?> rootType, Object id, Object bean);
/**
* Put the entity bean into the PersistanceContext if one is not already
* Put the entity bean into the PersistenceContext if one is not already
* present (for this id).
* <p>
* Returns an existing entity bean (if one is already there) and otherwise
* returns null.
* </p>
*/
Object putIfAbsent(Object id, Object bean);
Object putIfAbsent(Class<?> rootType, Object id, Object bean);
/**
* Return an object given its type and unique id.
*/
Object get(Class<?> beanType, Object uid);
Object get(Class<?> rootType, Object uid);
/**
* Get the bean from the persistence context also checked to see if it had
* been previously deleted (if so then you also can't hit the L2 cache to
* fetch the bean for this particular persistence context).
*/
WithOption getWithOption(Class<?> beanType, Object uid);
WithOption getWithOption(Class<?> rootType, Object uid);
/**
* Clear all the references.
@@ -44,22 +44,22 @@ public interface PersistenceContext {
/**
* Clear all the references for a given type of entity bean.
*/
void clear(Class<?> beanType);
void clear(Class<?> rootType);
/**
* Clear the reference to a specific entity bean.
*/
void clear(Class<?> beanType, Object uid);
void clear(Class<?> rootType, Object uid);
/**
* Clear the reference as a result of an entity being deleted.
*/
void deleted(Class<?> beanType, Object id);
void deleted(Class<?> rootType, Object id);
/**
* Return the number of beans of the given type in the persistence context.
*/
int size(Class<?> beanType);
int size(Class<?> rootType);
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
@@ -0,0 +1,21 @@
package com.avaje.ebean.bean;
import javax.persistence.Entity;
/**
* Utility to find the root bean type.
*/
public class PersistenceContextUtil {
/**
* Find and return the root bean type for the given class.
*/
public static Class<?> root(Class<?> beanType) {
Class<?> parent = beanType.getSuperclass();
while (parent != null && parent.isAnnotationPresent(Entity.class)) {
beanType = parent;
parent = parent.getSuperclass();
}
return beanType;
}
}
@@ -1,5 +1,6 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
@@ -64,6 +65,15 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
list.add((E) bean);
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
if (list == null) {
list = new ArrayList<E>();
}
list.addAll((Collection<? extends E>) other.getActualDetails());
}
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (list == null) {
@@ -1,5 +1,6 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
@@ -51,6 +52,14 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
return !touched && (map == null || map.isEmpty());
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
BeanMap<K,E> otherMap = (BeanMap<K,E>)other;
internalPutNull();
map.putAll(otherMap.getActualMap());
}
public void internalPutNull() {
if (map == null) {
map = new LinkedHashMap<K, E>();
@@ -1,5 +1,6 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
@@ -57,6 +58,15 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
set.add((E) bean);
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
if (set == null) {
set = new LinkedHashSet<E>();
}
set.addAll((Collection<? extends E>) other.getActualDetails());
}
@Override
public void internalAddWithCheck(Object bean) {
if (set == null || !set.contains(bean)) {
@@ -90,6 +90,13 @@ public class ClassLoadConfig {
return context.forName(name);
}
/**
* Return the classLoader to use for service loading etc.
*/
public ClassLoader getClassLoader() {
return context.getClassLoader();
}
/**
* Wraps the preferred, caller and context class loaders.
*/
@@ -138,6 +145,9 @@ public class ClassLoadConfig {
return Class.forName(name, true, classLoader);
}
ClassLoader getClassLoader() {
return preferredLoader != null ? preferredLoader : contextLoader;
}
}
}
@@ -0,0 +1,248 @@
package com.avaje.ebean.config;
import com.avaje.ebean.annotation.DocStoreMode;
/**
* Configuration for the Document store integration (e.g. ElasticSearch).
*/
public class DocStoreConfig {
/**
* True when the Document store integration is active/on.
*/
protected boolean active;
/**
* Set to true means Ebean will generate mapping files on startup.
*/
protected boolean generateMapping;
/**
* When true the Document store should drop and re-create document indexes.
*/
protected boolean dropCreate;
/**
* When true the Document store should create any document indexes that don't already exist.
*/
protected boolean create;
/**
* The URL of the Document store server. For example: http://localhost:9200.
*/
protected String url;
/**
* The default mode used by indexes.
*/
protected DocStoreMode persist = DocStoreMode.UPDATE;
/**
* The default batch size to use for the Bulk API calls.
*/
protected int bulkBatchSize = 1000;
/**
* Resource path for the Document store mapping files.
*/
protected String mappingPath;
/**
* Suffix used for mapping files.
*/
protected String mappingSuffix;
/**
* Location of resources that mapping files are generated into.
*/
protected String pathToResources = "src/main/resources";
/**
* Return true if the Document store (ElasticSearch) integration is active.
*/
public boolean isActive() {
String systemValue = System.getProperty("ebean.docstore.active");
if (systemValue != null) {
return Boolean.parseBoolean(systemValue);
}
return active;
}
/**
* Set to true to make the Document store (ElasticSearch) integration active.
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* Return the URL to the Document store.
*/
public String getUrl() {
String systemValue = System.getProperty("ebean.docstore.url");
if (systemValue != null) {
return systemValue;
}
return url;
}
/**
* Set the URL to the Document store server.
* <p>
* For a local ElasticSearch server this would be: http://localhost:9200
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Return true if Ebean should generate mapping files on server startup.
*/
public boolean isGenerateMapping() {
return generateMapping;
}
/**
* Set to true if Ebean should generate mapping files on server startup.
*/
public void setGenerateMapping(boolean generateMapping) {
this.generateMapping = generateMapping;
}
/**
* Return true if the document store should recreate mapped indexes.
*/
public boolean isDropCreate() {
return dropCreate;
}
/**
* Set to true if the document store should recreate mapped indexes.
*/
public void setDropCreate(boolean dropCreate) {
this.dropCreate = dropCreate;
}
/**
* Create true if the document store should create mapped indexes that don't yet exist.
* This is only used if dropCreate is false.
*/
public boolean isCreate() {
return create;
}
/**
* Set to true if the document store should create mapped indexes that don't yet exist.
* This is only used if dropCreate is false.
*/
public void setCreate(boolean create) {
this.create = create;
}
/**
* Return the default batch size to use for calls to the Bulk API.
*/
public int getBulkBatchSize() {
return bulkBatchSize;
}
/**
* Set the default batch size to use for calls to the Bulk API.
* <p>
* The batch size can be set on a transaction via {@link com.avaje.ebean.Transaction#setDocStoreBatchSize(int)}.
* </p>
*/
public void setBulkBatchSize(int bulkBatchSize) {
this.bulkBatchSize = bulkBatchSize;
}
/**
* Return the mapping path.
*/
public String getMappingPath() {
return mappingPath;
}
/**
* Set the mapping path.
*/
public void setMappingPath(String mappingPath) {
this.mappingPath = mappingPath;
}
/**
* Return the mapping suffix.
*/
public String getMappingSuffix() {
return mappingSuffix;
}
/**
* Set the mapping suffix.
*/
public void setMappingSuffix(String mappingSuffix) {
this.mappingSuffix = mappingSuffix;
}
/**
* Return the relative file system path to resources when generating mapping files.
*/
public String getPathToResources() {
return pathToResources;
}
/**
* Set the relative file system path to resources when generating mapping files.
*/
public void setPathToResources(String pathToResources) {
this.pathToResources = pathToResources;
}
/**
* Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
*/
public DocStoreMode getPersist() {
return persist;
}
/**
* Set the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
* <ul>
* <li>DocStoreEvent.UPDATE - build and send message to Bulk API</li>
* <li>DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing</li>
* <li>DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index</li>
* </ul>
* <p>
* You might choose to use QUEUE if that particular index data is updating very frequently or the cost of indexing
* is expensive. Setting it to QUEUE can mean many changes can be batched together potentially coalescing multiple
* updates for an index entry into a single update.
* </p>
* <p>
* You might choose to use IGNORE when you have your own external process for updating the indexes. In this case
* you don't want Ebean to do anything when the data changes.
* </p>
*/
public void setPersist(DocStoreMode persist) {
this.persist = persist;
}
/**
* Load settings specified in properties files.
*/
public void loadSettings(PropertiesWrapper properties) {
active = properties.getBoolean("docstore.active", active);
url = properties.get("docstore.url", url);
persist = properties.getEnum(DocStoreMode.class, "docstore.persist", persist);
bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize);
generateMapping = properties.getBoolean("docstore.generateMapping", generateMapping);
dropCreate = properties.getBoolean("docstore.dropCreate", dropCreate);
create = properties.getBoolean("docstore.create", create);
mappingPath = properties.get("docstore.mappingPath", mappingPath);
mappingSuffix = properties.get("docstore.mappingSuffix", mappingSuffix);
pathToResources = properties.get("docstore.pathToResources", pathToResources);
}
}
@@ -25,8 +25,10 @@ import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.ServiceLoader;
/**
* The configuration used for creating a EbeanServer.
@@ -129,6 +131,11 @@ public class ServerConfig {
*/
private String classPathReaderClassName;
/**
* Configuration for the ElasticSearch integration.
*/
private DocStoreConfig docStoreConfig = new DocStoreConfig();
/**
* This is used to populate @WhoCreated, @WhoModified and
* support other audit features (who executed a query etc).
@@ -283,7 +290,7 @@ public class ServerConfig {
private DbConstraintNaming constraintNaming = new DbConstraintNaming();
/**
* Behaviour of update to include on the change properties.
* Behaviour of update to include on the change properties.
*/
private boolean updateChangesOnly = true;
@@ -1153,6 +1160,20 @@ public class ServerConfig {
this.namingConvention = namingConvention;
}
/**
* Return the configuration for the ElasticSearch integration.
*/
public DocStoreConfig getDocStoreConfig() {
return docStoreConfig;
}
/**
* Set the configuration for the ElasticSearch integration.
*/
public void setDocStoreConfig(DocStoreConfig docStoreConfig) {
this.docStoreConfig = docStoreConfig;
}
/**
* Return the constraint naming convention used in DDL generation.
*/
@@ -2134,6 +2155,23 @@ public class ServerConfig {
this.classLoadConfig = classLoadConfig;
}
/**
* Return the service loader using the classLoader defined in ClassLoadConfig.
*/
public <T> ServiceLoader<T> serviceLoad(Class<T> spiService) {
return ServiceLoader.load(spiService, classLoadConfig.getClassLoader());
}
/**
* Return the first service using the service loader (or null).
*/
public <T> T service(Class<T> spiService) {
ServiceLoader<T> load = serviceLoad(spiService);
Iterator<T> serviceInstances = load.iterator();
return serviceInstances.hasNext() ? serviceInstances.next() : null;
}
/**
* Load settings from ebean.properties.
*/
@@ -2212,7 +2250,14 @@ public class ServerConfig {
}
/**
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
* This is broken out to allow overridden behaviour.
*/
protected void loadDocStoreSettings(PropertiesWrapper p) {
docStoreConfig.loadSettings(p);
}
/**
* This is broken out to allow overridden behaviour.
*/
protected void loadAutoTuneSettings(PropertiesWrapper p) {
autoTuneConfig.loadSettings(p);
@@ -2239,6 +2284,11 @@ public class ServerConfig {
}
loadDataSourceSettings(p);
if (docStoreConfig == null) {
docStoreConfig = new DocStoreConfig();
}
loadDocStoreSettings(p);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
@@ -1,5 +1,7 @@
package com.avaje.ebean.config;
import com.avaje.ebean.util.CamelCaseHelper;
/**
* Converts between Camel Case and Underscore based names for both table and
* column names (and is the default naming convention in Ebean).
@@ -141,21 +143,6 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
* Convert and return the from string from underscore to camel case.
*/
protected String toCamelFromUnderscore(String underscore) {
StringBuilder result = new StringBuilder(underscore.length());
String[] vals = underscore.split("_");
for (int i = 0; i < vals.length; i++) {
String lower = vals[i].toLowerCase();
if (i > 0) {
char c = Character.toUpperCase(lower.charAt(0));
result.append(c);
result.append(lower.substring(1));
} else {
result.append(lower);
}
}
return result.toString();
return CamelCaseHelper.toCamelFromUnderscore(underscore);
}
}
@@ -18,7 +18,7 @@ public class DB2Platform extends DatabasePlatform {
this.maxTableNameLength = 18;
this.maxConstraintNameLength = 18;
this.sqlLimiter = new Db2SqlLimiter();
this.platformDdl = new DB2Ddl(dbTypeMap, dbIdentity);
this.platformDdl = new DB2Ddl(this);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
@@ -18,7 +18,6 @@ public class DatabasePlatform {
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
/**
* Behavior used when ending a query only transaction (at read committed isolation level).
*/
@@ -72,6 +71,11 @@ public class DatabasePlatform {
*/
protected DbTypeMap dbTypeMap = new DbTypeMap();
/**
* Default values for DB columns.
*/
protected DbDefaultValue dbDefaultValue = new DbDefaultValue();
/**
* Set to true if the DB has native UUID type support.
*/
@@ -291,6 +295,13 @@ public class DatabasePlatform {
return dbTypeMap;
}
/**
* Return the mapping for DB column default values.
*/
public DbDefaultValue getDbDefaultValue() {
return dbDefaultValue;
}
/**
* Return the column alias prefix.
*/
@@ -425,6 +436,20 @@ public class DatabasePlatform {
return sqlLimiter;
}
/**
* Set the DB TRUE literal (from the registered boolean ScalarType)
*/
public void setDbTrueLiteral(String dbTrueLiteral) {
this.dbDefaultValue.setTrue(dbTrueLiteral);
}
/**
* Set the DB FALSE literal (from the registered boolean ScalarType)
*/
public void setDbFalseLiteral(String dbFalseLiteral) {
this.dbDefaultValue.setFalse(dbFalseLiteral);
}
/**
* Convert backticks to the platform specific open quote and close quote
* <p>
@@ -0,0 +1,70 @@
package com.avaje.ebean.config.dbplatform;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* DB Column default values mapping to database platform specific literals.
*/
public class DbDefaultValue {
/**
* The key for FALSE.
*/
public static final String FALSE = "false";
/**
* The key for TRUE.
*/
public static final String TRUE = "true";
/**
* The key for the NOW / current timestamp.
*/
public static final String NOW = "now";
protected Map<String,String> map = new LinkedHashMap<String,String>();
/**
* Set the DB now function.
*/
public void setNow(String dbFunction) {
put(NOW, dbFunction);
}
/**
* Set the DB false literal.
*/
public void setFalse(String dbFalseLiteral) {
put(FALSE, dbFalseLiteral);
}
/**
* Set the DB true literal.
*/
public void setTrue(String dbTrueLiteral) {
put(TRUE, dbTrueLiteral);
}
/**
* Add an translation entry.
*/
public void put(String dbLiteral, String dbTranslated) {
map.put(dbLiteral, dbTranslated);
}
/**
* Convert the DB default literal to platform specific type or function.
* <p>
* This is intended for the DB column default clause in DDL.
* </p>
*/
public String convert(String dbDefaultLiteral) {
if (dbDefaultLiteral == null) {
return null;
}
String val = map.get(dbDefaultLiteral);
return val != null ? val : dbDefaultLiteral;
}
}
@@ -14,9 +14,10 @@ public class H2Platform extends DatabasePlatform {
super();
this.name = "h2";
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
this.platformDdl = new H2Ddl(this);
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
this.dbDefaultValue.setNow("now()");
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
@@ -15,7 +15,7 @@ public class HsqldbPlatform extends DatabasePlatform {
super();
this.name = "hsqldb";
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new HsqldbDdl(dbTypeMap, dbIdentity);
this.platformDdl = new HsqldbDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
@@ -25,7 +25,7 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
this.idInExpandedForm = true;
this.selectCountWithAlias = true;
this.sqlLimiter = new MsSqlServer2005SqlLimiter();
this.platformDdl = new MsSqlServerDdl(dbTypeMap, dbIdentity);
this.platformDdl = new MsSqlServerDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -25,7 +25,7 @@ public class MySqlPlatform extends DatabasePlatform {
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.dbEncrypt = new MySqlDbEncrypt();
this.platformDdl = new MySqlDdl(this.dbTypeMap, this.dbIdentity);
this.platformDdl = new MySqlDdl(this);
this.historySupport = new MySqlHistorySupport();
this.dbIdentity.setIdType(IdType.IDENTITY);
@@ -16,11 +16,9 @@ public class OraclePlatform extends DatabasePlatform {
this.name = "oracle";
this.maxTableNameLength = 30;
this.maxConstraintNameLength = 30;
// OnQueryOnly.CLOSE as a performance optimisation on Oracle
this.onQueryOnly = OnQueryOnly.CLOSE;
this.dbEncrypt = new OracleDbEncrypt();
this.sqlLimiter = new RownumSqlLimiter();
this.platformDdl = new Oracle10Ddl(this.dbTypeMap, this.dbIdentity);
this.platformDdl = new Oracle10Ddl(this);
this.historySupport = new OracleDbHistorySupport();
// Not using getGeneratedKeys as instead we will
@@ -19,9 +19,6 @@ public class PostgresPlatform extends DatabasePlatform {
public PostgresPlatform() {
super();
this.name = "postgres";
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
this.onQueryOnly = OnQueryOnly.CLOSE;
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
@@ -30,7 +27,7 @@ public class PostgresPlatform extends DatabasePlatform {
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
this.platformDdl = new PostgresDdl(this.dbTypeMap, this.dbIdentity);
this.platformDdl = new PostgresDdl(this);
// Use Identity and getGeneratedKeys
this.dbIdentity.setIdType(IdType.IDENTITY);
@@ -9,7 +9,7 @@ public class SQLitePlatform extends DatabasePlatform {
public SQLitePlatform() {
super();
this.name = "sqlite";
this.platformDdl = new SQLiteDdl(dbTypeMap, dbIdentity);
this.platformDdl = new SQLiteDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(false);
@@ -123,6 +123,9 @@ public class BaseTableDdl implements TableDdl {
// defined on the columns
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
}
if (platformDdl.isInlineForeignKeys()) {
writeInlineForeignKeys(writer, createTable);
}
apply.newLine().append(")");
addTableCommentInline(apply, createTable);
@@ -151,8 +154,9 @@ public class BaseTableDdl implements TableDdl {
apply.end();
writer.dropAll().end();
writeAddForeignKeys(writer, createTable);
if (!platformDdl.isInlineForeignKeys()) {
writeAddForeignKeys(writer, createTable);
}
}
/**
@@ -235,6 +239,43 @@ public class BaseTableDdl implements TableDdl {
platformDdl.createWithHistory(writer, table);
}
protected void writeInlineForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
for (Column column : createTable.getColumn()) {
String references = column.getReferences();
if (hasValue(references)) {
writeInlineForeignKey(write, column);
}
}
writeInlineCompoundForeignKeys(write, createTable);
}
protected void writeInlineForeignKey(DdlWrite write, Column column) throws IOException {
String references = column.getReferences();
int pos = references.lastIndexOf('.');
if (pos == -1) {
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
}
String refTableName = references.substring(0, pos);
String refColumnName = references.substring(pos + 1);
String fkConstraint = platformDdl.tableInlineForeignKey(new String[]{column.getName()}, refTableName, new String[]{refColumnName});
write.apply().append(",").newLine().append(" ").append(fkConstraint);
}
protected void writeInlineCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
List<ForeignKey> foreignKey = createTable.getForeignKey();
for (ForeignKey key : foreignKey) {
String refTableName = key.getRefTableName();
String[] cols = toColumnNamesSplit(key.getColumnNames());
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
String fkConstraint = platformDdl.tableInlineForeignKey(cols, refTableName, refColumns);
write.apply().append(",").newLine().append(" ").append(fkConstraint);
}
}
protected void writeAddForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
String tableName = createTable.getName();
@@ -309,7 +350,10 @@ public class BaseTableDdl implements TableDdl {
protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException {
buffer.append(platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns)).endOfStatement();
String fkConstraint = platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns);
if (fkConstraint != null && !fkConstraint.isEmpty()) {
buffer.append(fkConstraint).endOfStatement();
}
}
protected void appendColumns(String[] columns, DdlBuffer buffer) throws IOException {
@@ -1,15 +1,14 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
/**
* DB2 platform specific DDL.
*/
public class DB2Ddl extends PlatformDdl {
public DB2Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public DB2Ddl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = " generated by default as identity";
}
@@ -1,15 +1,14 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
/**
* H2 platform specific DDL.
*/
public class H2Ddl extends PlatformDdl {
public H2Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public H2Ddl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new H2HistoryDdl();
}
@@ -1,15 +1,14 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
/**
* Hsqldb platform specific DDL.
*/
public class HsqldbDdl extends PlatformDdl {
public HsqldbDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public HsqldbDdl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = " generated by default as identity (start with 1) ";
}
@@ -1,7 +1,6 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
import com.avaje.ebean.dbmigration.migration.AlterColumn;
@@ -12,8 +11,8 @@ import java.io.IOException;
*/
public class MsSqlServerDdl extends PlatformDdl {
public MsSqlServerDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public MsSqlServerDdl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = " identity(1,1)";
this.foreignKeyRestrict = "";
this.inlineUniqueOneToOne = false;
@@ -1,7 +1,6 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
import com.avaje.ebean.dbmigration.migration.AlterColumn;
import com.avaje.ebean.dbmigration.migration.Column;
@@ -14,8 +13,8 @@ import java.io.IOException;
*/
public class MySqlDdl extends PlatformDdl {
public MySqlDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public MySqlDdl(DatabasePlatform platform) {
super(platform);
this.alterColumn = "modify";
this.dropUniqueConstraint = "drop index";
this.historyDdl = new MySqlHistoryDdl();
@@ -1,15 +1,14 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
/**
* Oracle platform specific DDL.
*/
public class Oracle10Ddl extends PlatformDdl {
public Oracle10Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public Oracle10Ddl(DatabasePlatform platform) {
super(platform);
this.dropTableIfExists = "drop table ";
this.dropSequenceIfExists = "drop sequence ";
this.dropTableCascade = " cascade constraints purge";
@@ -2,8 +2,9 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.DbConstraintNaming;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbDefaultValue;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.dbmigration.ddlgeneration.BaseDdlHandler;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
@@ -85,9 +86,17 @@ public class PlatformDdl {
protected DbConstraintNaming naming;
public PlatformDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
this.dbIdentity = dbIdentity;
this.typeConverter = new PlatformTypeConverter(platformTypes);
/**
* Generally not desired as then they are not named (used with SQLite).
*/
protected boolean inlineForeignKeys;
protected final DbDefaultValue dbDefaultValue;
public PlatformDdl(DatabasePlatform platform) {
this.dbIdentity = platform.getDbIdentity();
this.dbDefaultValue = platform.getDbDefaultValue();
this.typeConverter = new PlatformTypeConverter(platform.getDbTypeMap());
}
/**
@@ -128,6 +137,14 @@ public class PlatformDdl {
return inlineComments;
}
/**
* Return true if foreign key reference constraints need to inlined with create table.
* Ideally we don't do this as then the constraints are not named. Do this for SQLite.
*/
public boolean isInlineForeignKeys() {
return inlineForeignKeys;
}
/**
* Write all the table columns converting to platform types as necessary.
*/
@@ -152,6 +169,12 @@ public class PlatformDdl {
buffer.append(" ");
buffer.append(lowerColumnName(column.getName()), 29);
buffer.append(platformType);
if (!typeContainsDefault(platformType)) {
String defaultValue = convertDefaultValue(column.getDefaultValue());
if (defaultValue != null) {
buffer.append(" default ").append(defaultValue);
}
}
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
buffer.append(" not null");
}
@@ -160,6 +183,20 @@ public class PlatformDdl {
// so that the database can potentially provide a nice SQL error
}
/**
* Return true if the type definition already contains a default value.
*/
private boolean typeContainsDefault(String platformType) {
return platformType.toLowerCase().contains(" default");
}
/**
* Convert the DB column default literal to platform specific.
*/
private String convertDefaultValue(String dbDefault) {
return dbDefaultValue.convert(dbDefault);
}
/**
* Return the drop foreign key clause.
*/
@@ -255,6 +292,20 @@ public class PlatformDdl {
return buffer.toString();
}
/**
* Return the foreign key constraint when used inline with create table.
*/
public String tableInlineForeignKey(String[] columns, String refTable, String[] refColumns) {
StringBuilder buffer = new StringBuilder(90);
buffer.append("foreign key");
appendColumns(columns, buffer);
buffer.append(" references ").append(lowerTableName(refTable));
appendColumns(refColumns, buffer);
appendWithSpace(foreignKeyRestrict, buffer);
return buffer.toString();
}
/**
* Add foreign key.
*/
@@ -1,15 +1,14 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
/**
* Postgres specific DDL.
*/
public class PostgresDdl extends PlatformDdl {
public PostgresDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
public PostgresDdl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new PostgresHistoryDdl();
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
@@ -14,7 +14,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
public PostgresHistoryDdl() {
this.currentTimestamp = "statement_timestamp()";
this.currentTimestamp = "current_timestamp";
}
/**
@@ -1,16 +1,34 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
import java.io.IOException;
/**
* DB2 platform specific DDL.
*/
public class SQLiteDdl extends PlatformDdl {
public SQLiteDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
this.identitySuffix = " autoincrement";
public SQLiteDdl(DatabasePlatform platform) {
super(platform);
this.identitySuffix = "";
this.inlineForeignKeys = true;
}
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
// not supported
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
// not supported
}
@Override
public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) {
// not supported
return null;
}
}
@@ -38,7 +38,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
table.setComment(descriptor.getDbComment());
if (descriptor.isHistorySupport()) {
table.setWithHistory(true);
BeanProperty whenCreated = descriptor.findWhenCreatedProperty();
BeanProperty whenCreated = descriptor.getWhenCreatedProperty();
if (whenCreated != null) {
table.setWhenCreatedColumn(whenCreated.getDbColumn());
}
@@ -230,6 +230,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
col.setDefaultValue(p.getDbColumnDefault());
col.setComment(p.getDbComment());
col.setDraftOnly(p.isDraftOnly());
@@ -0,0 +1,72 @@
package com.avaje.ebean.plugin;
import com.avaje.ebean.FetchPath;
import com.avaje.ebean.Query;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
import java.io.IOException;
/**
* Doc store functions for a specific entity bean type.
*
* @param <T> The type of entity bean
*/
public interface BeanDocType<T> {
/**
* Return the doc store index type for this bean type.
*/
String getIndexType();
/**
* Return the doc store index name for this bean type.
*/
String getIndexName();
/**
* Apply the appropriate fetch path to the query such that the query returns beans matching
* the document store structure with the expected embedded properties.
*/
void applyPath(Query<T> spiQuery);
/**
* Return the FetchPath for the embedded document.
*/
FetchPath getEmbedded(String path);
/**
* For embedded 'many' properties we need a FetchPath relative to the root which is used to
* build and replace the embedded list.
*/
FetchPath getEmbeddedManyRoot(String path);
/**
* Return a 'raw' property mapped for the given property.
* If none exists the given property is returned.
*/
String rawProperty(String property);
/**
* Store the bean in the doc store index.
* <p>
* This somewhat assumes the bean is fetched with appropriate path properties
* to match the expected document structure.
*/
void index(Object idValue, T bean, DocStoreUpdateContext txn) throws IOException;
/**
* Add a delete by Id to the doc store.
*/
void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException;
/**
* Add a embedded document update to the doc store.
*
* @param idValue the Id value of the bean holding the embedded document
* @param embeddedProperty the embedded property
* @param embeddedRawContent the content of the embedded document in JSON form
* @param txn the doc store transaction to add the update to
*/
void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException;
}
@@ -0,0 +1,179 @@
package com.avaje.ebean.plugin;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping;
import java.util.Collection;
/**
* Information and methods on BeanDescriptors made available to plugins.
*/
public interface BeanType<T> {
/**
* Return the short name of the bean type.
*/
String getName();
/**
* Return the full name of the bean type.
*/
String getFullName();
/**
* Return the class type this BeanDescriptor describes.
*/
Class<T> getBeanType();
/**
* Return the type bean for an OneToMany or ManyToOne or ManyToMany property.
*/
BeanType<?> getBeanTypeAtPath(String propertyName);
/**
* Return all the properties for this bean type.
*/
Collection<? extends Property> allProperties();
/**
* Return the Id property.
*/
Property getIdProperty();
/**
* Return the when modified property if there is one defined.
*/
Property getWhenModifiedProperty();
/**
* Return the when created property if there is one defined.
*/
Property getWhenCreatedProperty();
/**
* Return the Property to read values from a bean.
*/
Property getProperty(String propertyName);
/**
* Return the ExpressionPath for a given property path.
* <p>
* This can return a property or nested property path.
* </p>
*/
ExpressionPath getExpressionPath(String path);
/**
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
String getBaseTable();
/**
* Create a new instance of the bean.
*/
T createBean();
/**
* Return the bean id. This is the same as getBeanId() but without the generic type.
*/
Object beanId(Object bean);
/**
* Return the id value for the given bean.
*/
Object getBeanId(T bean);
/**
* Set the id value to the bean.
*/
void setBeanId(T bean, Object idValue);
/**
* Return the bean persist controller.
*/
BeanPersistController getPersistController();
/**
* Return the bean persist listener.
*/
BeanPersistListener getPersistListener();
/**
* Return the beanFinder. Usually null unless overriding the finder.
*/
BeanFindController getFindController();
/**
* Return the BeanQueryAdapter or null if none is defined.
*/
BeanQueryAdapter getQueryAdapter();
/**
* Return the identity generation type.
*/
IdType getIdType();
/**
* Return the sequence name associated to this entity bean type (if there is one).
*/
String getSequenceName();
/**
* Return true if this bean type has doc store backing.
*/
boolean isDocStoreMapped();
/**
* Return the DocumentMapping for this bean type.
* <p>
* This is the document structure and mapping options for how this bean type is mapped
* for the document store.
* </p>
*/
DocumentMapping getDocMapping();
/**
* Return the doc store queueId for this bean type.
*/
String getDocStoreQueueId();
/**
* Return the doc store support for this bean type.\
*/
BeanDocType<T> docStore();
/**
* Add the discriminator value to the query if needed.
*/
void addInheritanceWhere(SpiQuery<?> query);
/**
* Return the root bean type for an inheritance hierarchy.
*/
BeanType<?> root();
/**
* Return true if this bean type has an inheritance hierarchy.
*/
boolean hasInheritance();
/**
* Return the discriminator column.
*/
String getDiscColumn();
/**
* Create a bean given the discriminator value.
*/
T createBeanUsingDisc(Object discValue);
}
@@ -0,0 +1,76 @@
package com.avaje.ebean.plugin;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.StringParser;
/**
* A dot notation expression path.
*/
public interface ExpressionPath {
/**
* Return true if there is a property on the path that is a many property.
*/
boolean containsMany();
/**
* Return the value from a given entity bean.
*/
Object pathGet(Object bean);
/**
* Set a value to the bean for this expression path.
*
* @param bean the bean to set the value on
* @param value the value to set
*/
void pathSet(Object bean, Object value);
/**
* Convert the value to the expected type.
* <p>
* Typically useful for converting strings to the appropriate number type etc.
* </p>
*/
Object convert(Object value);
/**
* Return the default StringParser for the scalar property.
*/
StringParser getStringParser();
/**
* For DateTime capable scalar types convert the long systemTimeMillis into
* an appropriate java time (Date,Timestamp,Time,Calendar, JODA type etc).
*/
Object parseDateTime(long systemTimeMillis);
/**
* Return true if the last type is "DateTime capable" - can support
* {@link #parseDateTime(long)}.
*/
boolean isDateTimeCapable();
/**
* Return the underlying JDBC type or 0 if this is not a scalar type.
*/
int getJdbcType();
/**
* Return true if this is an ManyToOne or OneToOne associated bean property.
*/
boolean isAssocId();
/**
* Return the Id expression string.
* <p>
* Typically used to produce id = ? expression strings.
* </p>
*/
String getAssocIdExpression(String propName, String bindOperator);
/**
* Return the Id values for the given bean value.
*/
Object[] getAssocIdValues(EntityBean bean);
}
@@ -3,7 +3,7 @@ package com.avaje.ebean.plugin;
/**
* A 'plugin' that wants to be configured on startup so it can use features of the EbeanServer itself.
*/
public interface SpiServerPlugin {
public interface Plugin {
/**
* Configure the plugin.
@@ -0,0 +1,22 @@
package com.avaje.ebean.plugin;
/**
* Property of a entity bean that can be read.
*/
public interface Property {
/**
* Return the name of the property.
*/
String getName();
/**
* Return the value of the property on the given bean.
*/
Object getVal(Object bean);
/**
* Return true if this is a OneToMany or ManyToMany property.
*/
boolean isMany();
}
@@ -1,64 +0,0 @@
package com.avaje.ebean.plugin;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
/**
* Information and methods on BeanDescriptors made available to plugins.
*/
public interface SpiBeanType<T> {
/**
* Return the class type this BeanDescriptor describes.
*/
Class<T> getBeanType();
/**
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
String getBaseTable();
/**
* Return the id value for the given bean.
*/
Object getBeanId(T bean);
/**
* Return the bean persist controller.
*/
BeanPersistController getPersistController();
/**
* Return the bean persist listener.
*/
BeanPersistListener getPersistListener();
/**
* Return the beanFinder. Usually null unless overriding the finder.
*/
BeanFindController getFindController();
/**
* Return the BeanQueryAdapter or null if none is defined.
*/
BeanQueryAdapter getQueryAdapter();
/**
* Return the identity generation type.
*/
IdType getIdType();
/**
* Return the sequence name associated to this entity bean type (if there is one).
*/
String getSequenceName();
}
@@ -24,16 +24,20 @@ public interface SpiServer extends EbeanServer {
/**
* Return all the bean types registered on this server instance.
*/
List<? extends SpiBeanType<?>> getBeanTypes();
List<? extends BeanType<?>> getBeanTypes();
/**
* Return the bean type for a given entity bean class.
*/
<T> SpiBeanType<T> getBeanType(Class<T> beanClass);
<T> BeanType<T> getBeanType(Class<T> beanClass);
/**
* Return the bean types mapped to the given base table.
*/
List<? extends SpiBeanType<?>> getBeanTypes(String baseTableName);
List<? extends BeanType<?>> getBeanTypes(String baseTableName);
/**
* Return the bean type for a given doc store queueId.
*/
BeanType<?> getBeanTypeForQueueId(String queueId);
}
@@ -0,0 +1,98 @@
package com.avaje.ebean.search;
/**
* Options for the text match and multi match expressions.
*/
public abstract class AbstractMatch {
protected boolean operatorAnd;
protected String analyzer;
protected double boost;
protected String minShouldMatch;
protected int maxExpansions;
protected String zeroTerms;
protected double cutoffFrequency;
protected String fuzziness;
protected int prefixLength;
protected String rewrite;
/**
* Return true if using the AND operator otherwise using the OR operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the zero terms option.
*/
public String getZeroTerms() {
return zeroTerms;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the max expansions.
*/
public int getMaxExpansions() {
return maxExpansions;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the prefix length.
*/
public int getPrefixLength() {
return prefixLength;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
}
@@ -0,0 +1,117 @@
package com.avaje.ebean.search;
/**
* Options for the text match expression.
*/
public class Match extends AbstractMatch {
protected boolean phrase;
protected boolean phrasePrefix;
public Match() {
}
/**
* Set this to be a "Phrase" type expression.
*/
public Match phrase() {
phrase = true;
return this;
}
/**
* Set this to be a "Phrase Prefix" type expression.
*/
public Match phrasePrefix() {
phrasePrefix = true;
return this;
}
/**
* Use the AND operator (rather than OR).
*/
public Match opAnd() {
operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public Match opOr() {
operatorAnd = false;
return this;
}
/**
* Set the zero terms.
*/
public Match zeroTerms(String zeroTerms) {
this.zeroTerms = zeroTerms;
return this;
}
/**
* Set the cutoff frequency.
*/
public Match cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set the max expansions (for phrase prefix only).
*/
public Match maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions;
return this;
}
/**
* Set the Analyzer to use for this expression.
*/
public Match analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the boost.
*/
public Match boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the rewrite to use.
*/
public Match minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the rewrite to use.
*/
public Match rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return true if this is a phrase query.
*/
public boolean isPhrase() {
return phrase;
}
/**
* Return true if this is a phrase prefix query.
*/
public boolean isPhrasePrefix() {
return phrasePrefix;
}
}
@@ -0,0 +1,148 @@
package com.avaje.ebean.search;
/**
* Options for the text match expression.
*/
public class MultiMatch extends AbstractMatch {
/**
* The MultiMatch type.
*/
public enum Type {
BEST_FIELDS,
MOST_FIELDS,
CROSS_FIELDS,
PHRASE,
PHRASE_PREFIX
}
protected final String[] fields;
protected Type type = Type.BEST_FIELDS;
protected double tieBreaker;
/**
* Create with the given fields.
*/
public static MultiMatch fields(String... fields) {
return new MultiMatch(fields);
}
/**
* Construct with a set of fields.
*/
public MultiMatch(String... fields) {
this.fields = fields;
}
/**
* Set the type of query.
*/
public MultiMatch type(Type type) {
this.type = type;
return this;
}
/**
* Set the tieBreaker to use.
*/
public MultiMatch tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Use the AND operator (rather than OR).
*/
public MultiMatch opAnd() {
operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public MultiMatch opOr() {
operatorAnd = false;
return this;
}
/**
* Set the minimum should match value.
*/
public MultiMatch minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the boost.
*/
public MultiMatch boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the zero terms.
*/
public MultiMatch zeroTerms(String zeroTerms) {
this.zeroTerms = zeroTerms;
return this;
}
/**
* Set the cutoff frequency.
*/
public MultiMatch cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set the max expansions (for phrase prefix only).
*/
public MultiMatch maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions;
return this;
}
/**
* Set the Analyzer to use for this expression.
*/
public MultiMatch analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the rewrite to use.
*/
public MultiMatch rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the type.
*/
public Type getType() {
return type;
}
/**
* Return the fields to search.
*/
public String[] getFields() {
return fields;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
}
@@ -0,0 +1,141 @@
package com.avaje.ebean.search;
/**
* Text common terms query.
* <p>
* This maps to an ElasticSearch "common terms query".
* </p>
* <pre>{@code
*
* TextCommonTerms options = new TextCommonTerms()
* .cutoffFrequency(0.001)
* .minShouldMatch("50%")
* .lowFreqOperatorAnd(true)
* .highFreqOperatorAnd(true);
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textCommonTerms("the brown", options)
* .findList();
*
* }</pre>
*
* <pre>{@code
*
* // ElasticSearch expression
*
* "common": {
* "body": {
* "query": "the brown",
* "cutoff_frequency": 0.001,
* "low_freq_operator": "and",
* "high_freq_operator": "and",
* "minimum_should_match": "50%"
* }
* }
*
* }</pre>
*/
public class TextCommonTerms {
protected double cutoffFrequency;
protected boolean lowFreqOperatorAnd;
protected boolean highFreqOperatorAnd;
protected String minShouldMatch;
protected String minShouldMatchLowFreq;
protected String minShouldMatchHighFreq;
/**
* Set the cutoff frequency.
*/
public TextCommonTerms cutoffFrequency(double cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency;
return this;
}
/**
* Set to true if low frequency terms should use AND operator.
*/
public TextCommonTerms lowFreqOperatorAnd(boolean opAnd) {
this.lowFreqOperatorAnd = opAnd;
return this;
}
/**
* Set to true if high frequency terms should use AND operator.
*/
public TextCommonTerms highFreqOperatorAnd(boolean opAnd) {
this.highFreqOperatorAnd = opAnd;
return this;
}
/**
* Set the minimum should match.
*/
public TextCommonTerms minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the minimum should match for low frequency terms.
*/
public TextCommonTerms minShouldMatchLowFreq(String minShouldMatchLowFreq) {
this.minShouldMatchLowFreq = minShouldMatchLowFreq;
return this;
}
/**
* Set the minimum should match for high frequency terms.
*/
public TextCommonTerms minShouldMatchHighFreq(String minShouldMatchHighFreq) {
this.minShouldMatchHighFreq = minShouldMatchHighFreq;
return this;
}
/**
* Return true if low freq should use the AND operator.
*/
public boolean isLowFreqOperatorAnd() {
return lowFreqOperatorAnd;
}
/**
* Return true if high freq should use the AND operator.
*/
public boolean isHighFreqOperatorAnd() {
return highFreqOperatorAnd;
}
/**
* Return the cutoff frequency.
*/
public double getCutoffFrequency() {
return cutoffFrequency;
}
/**
* Return the minimum to match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the minimum to match for high frequency.
*/
public String getMinShouldMatchHighFreq() {
return minShouldMatchHighFreq;
}
/**
* Return the minimum to match for low frequency.
*/
public String getMinShouldMatchLowFreq() {
return minShouldMatchLowFreq;
}
}
@@ -0,0 +1,436 @@
package com.avaje.ebean.search;
/**
* Text query string options.
* <p>
* This maps to an ElasticSearch "query string query".
* </p>
*
* <pre>{@code
*
* TextQueryString options = new TextQueryString()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*
*
* <pre>{@code
*
* // just use default options
* TextQueryString options = new TextQueryString();
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*
*/
public class TextQueryString {
public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = 50;
public static final int DEFAULT_MAX_DETERMINIZED_STATES = 10000;
protected final String[] fields;
/**
* Only used when multiple fields set.
*/
protected boolean useDisMax = true;
/**
* Only used when multiple fields set.
*/
protected double tieBreaker;
protected String defaultField;
protected boolean operatorAnd;
protected String analyzer;
protected boolean allowLeadingWildcard = true;
protected boolean lowercaseExpandedTerms = true;
protected boolean enablePositionIncrements = true;
protected int fuzzyMaxExpansions = DEFAULT_FUZZY_MAX_EXPANSIONS;
protected String fuzziness;
protected int fuzzyPrefixLength;
protected double phraseSlop;
protected double boost;
protected boolean analyzeWildcard;
protected boolean autoGeneratePhraseQueries;
protected int maxDeterminizedStates = DEFAULT_MAX_DETERMINIZED_STATES;
protected String minShouldMatch;
protected boolean lenient;
protected String locale;
protected String timeZone;
protected String rewrite;
/**
* Create with given fields.
*/
public static TextQueryString fields(String... fields) {
return new TextQueryString(fields);
}
/**
* Construct with the fields to use.
*/
public TextQueryString(String... fields) {
this.fields = fields;
}
/**
* Use the AND operator (rather than OR).
*/
public TextQueryString opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use the OR operator (rather than AND).
*/
public TextQueryString opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the locale.
*/
public TextQueryString locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set lenient mode.
*/
public TextQueryString lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextQueryString minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Set the analyzer.
*/
public TextQueryString analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set useDisMax option (when multiple fields only).
*/
public TextQueryString useDisMax(boolean useDisMax) {
this.useDisMax = useDisMax;
return this;
}
/**
* Set tieBreaker option (when multiple fields only).
*/
public TextQueryString tieBreaker(double tieBreaker) {
this.tieBreaker = tieBreaker;
return this;
}
/**
* Set the default field.
*/
public TextQueryString defaultField(String defaultField) {
this.defaultField = defaultField;
return this;
}
/**
* Set allow leading wildcard mode.
*/
public TextQueryString allowLeadingWildcard(boolean allowLeadingWildcard) {
this.allowLeadingWildcard = allowLeadingWildcard;
return this;
}
/**
* Set lowercase expanded terms mode.
*/
public TextQueryString lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set enable position increments mode.
*/
public TextQueryString enablePositionIncrements(boolean enablePositionIncrements) {
this.enablePositionIncrements = enablePositionIncrements;
return this;
}
/**
* Set fuzzy max expansions.
*/
public TextQueryString fuzzyMaxExpansions(int fuzzyMaxExpansions) {
this.fuzzyMaxExpansions = fuzzyMaxExpansions;
return this;
}
/**
* Set fuzziness.
*/
public TextQueryString fuzziness(String fuzziness) {
this.fuzziness = fuzziness;
return this;
}
/**
* Set the fuzzy prefix length.
*/
public TextQueryString fuzzyPrefixLength(int fuzzyPrefixLength) {
this.fuzzyPrefixLength = fuzzyPrefixLength;
return this;
}
/**
* Set the phrase slop.
*/
public TextQueryString phraseSlop(double phraseSlop) {
this.phraseSlop = phraseSlop;
return this;
}
/**
* Set the boost.
*/
public TextQueryString boost(double boost) {
this.boost = boost;
return this;
}
/**
* Set the analyze wildcard mode.
*/
public TextQueryString analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the auto generate phrase queries mode.
*/
public TextQueryString autoGeneratePhraseQueries(boolean autoGeneratePhraseQueries) {
this.autoGeneratePhraseQueries = autoGeneratePhraseQueries;
return this;
}
/**
* Set the max determinized states.
*/
public TextQueryString maxDeterminizedStates(int maxDeterminizedStates) {
this.maxDeterminizedStates = maxDeterminizedStates;
return this;
}
/**
* Set the time zone.
*/
public TextQueryString timeZone(String timeZone) {
this.timeZone = timeZone;
return this;
}
/**
* Set the rewrite option.
*/
public TextQueryString rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}
/**
* Return the rewrite option.
*/
public String getRewrite() {
return rewrite;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return true if AND is the default operator.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
/**
* Return the useDixMax mode.
*/
public boolean isUseDisMax() {
return useDisMax;
}
/**
* Return the tie breaker.
*/
public double getTieBreaker() {
return tieBreaker;
}
/**
* Return the default field.
*/
public String getDefaultField() {
return defaultField;
}
/**
* Return the allow leading wildcard mode.
*/
public boolean isAllowLeadingWildcard() {
return allowLeadingWildcard;
}
/**
* Return the lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return the enable position increments mode.
*/
public boolean isEnablePositionIncrements() {
return enablePositionIncrements;
}
/**
* Return the fuzzy max expansions.
*/
public int getFuzzyMaxExpansions() {
return fuzzyMaxExpansions;
}
/**
* Return the fuzziness.
*/
public String getFuzziness() {
return fuzziness;
}
/**
* Return the fuzzy prefix length.
*/
public int getFuzzyPrefixLength() {
return fuzzyPrefixLength;
}
/**
* Return the phrase slop.
*/
public double getPhraseSlop() {
return phraseSlop;
}
/**
* Return the analyze wildcard mode.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return the boost.
*/
public double getBoost() {
return boost;
}
/**
* Return the auto generate phase queries mode.
*/
public boolean isAutoGeneratePhraseQueries() {
return autoGeneratePhraseQueries;
}
/**
* Return the max determinized states.
*/
public int getMaxDeterminizedStates() {
return maxDeterminizedStates;
}
/**
* Return the time zone.
*/
public String getTimeZone() {
return timeZone;
}
}
@@ -0,0 +1,194 @@
package com.avaje.ebean.search;
/**
* Simple text query options.
* <p>
* This maps to an ElasticSearch "simple text query".
* </p>
*
* <pre>{@code
*
* TextSimple options = new TextSimple()
* .analyzeWildcard(true)
* .fields("name")
* .lenient(true)
* .opAnd();
*
* List<Customer> customers = server.find(Customer.class)
* .text()
* .textSimple("quick brown", options)
* .findList();
*
* }</pre>
*/
public class TextSimple {
protected String[] fields;
protected boolean operatorAnd;
protected String analyzer;
protected String flags;
protected boolean lowercaseExpandedTerms = true;
protected boolean analyzeWildcard;
protected String locale;
protected boolean lenient;
protected String minShouldMatch;
/**
* Construct
*/
public TextSimple() {
}
/**
* Set the fields.
*/
public TextSimple fields(String... fields) {
this.fields = fields;
return this;
}
/**
* Use AND as the default operator.
*/
public TextSimple opAnd() {
this.operatorAnd = true;
return this;
}
/**
* Use OR as the default operator.
*/
public TextSimple opOr() {
this.operatorAnd = false;
return this;
}
/**
* Set the analyzer
*/
public TextSimple analyzer(String analyzer) {
this.analyzer = analyzer;
return this;
}
/**
* Set the flags.
*/
public TextSimple flags(String flags) {
this.flags = flags;
return this;
}
/**
* Set the false to not use lowercase expanded terms.
*/
public TextSimple lowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
this.lowercaseExpandedTerms = lowercaseExpandedTerms;
return this;
}
/**
* Set to true to use analyze wildcard.
*/
public TextSimple analyzeWildcard(boolean analyzeWildcard) {
this.analyzeWildcard = analyzeWildcard;
return this;
}
/**
* Set the locale.
*/
public TextSimple locale(String locale) {
this.locale = locale;
return this;
}
/**
* Set the lenient mode.
*/
public TextSimple lenient(boolean lenient) {
this.lenient = lenient;
return this;
}
/**
* Set the minimum should match.
*/
public TextSimple minShouldMatch(String minShouldMatch) {
this.minShouldMatch = minShouldMatch;
return this;
}
/**
* Return lenient mode.
*/
public boolean isLenient() {
return lenient;
}
/**
* Return true to analyse wildcard.
*/
public boolean isAnalyzeWildcard() {
return analyzeWildcard;
}
/**
* Return lowercase expanded terms mode.
*/
public boolean isLowercaseExpandedTerms() {
return lowercaseExpandedTerms;
}
/**
* Return true if the default operator should be AND.
*/
public boolean isOperatorAnd() {
return operatorAnd;
}
/**
* Return the analyzer to use.
*/
public String getAnalyzer() {
return analyzer;
}
/**
* Return the fields.
*/
public String[] getFields() {
return fields;
}
/**
* Return the locale.
*/
public String getLocale() {
return locale;
}
/**
* Return the flags.
*/
public String getFlags() {
return flags;
}
/**
* Return the minimum should match.
*/
public String getMinShouldMatch() {
return minShouldMatch;
}
}
@@ -1,14 +1,16 @@
package com.avaje.ebean.text;
import com.avaje.ebean.FetchPath;
import com.avaje.ebean.Query;
import com.avaje.ebeaninternal.server.query.SplitName;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import com.avaje.ebean.Query;
import java.util.Set;
/**
* This is a Tree like structure of paths and properties that can be used for
@@ -20,7 +22,7 @@ import com.avaje.ebean.Query;
* render (JAX-RS JSON / XML).
* </p>
*/
public class PathProperties {
public class PathProperties implements FetchPath {
private final Map<String, Props> pathMap;
@@ -44,39 +46,6 @@ public class PathProperties {
this.pathMap.put(null, rootProps);
}
/**
* Construct for creating copy.
*/
private PathProperties(PathProperties orig) {
this.rootProps = orig.rootProps.copy(this);
this.pathMap = new LinkedHashMap<String, Props>(orig.pathMap.size());
Set<Entry<String, Props>> entrySet = orig.pathMap.entrySet();
for (Entry<String, Props> e : entrySet) {
pathMap.put(e.getKey(), e.getValue().copy(this));
}
}
/**
* Create a copy of this instance so that it can be modified.
* <p>
* For example, you may want to create a copy to add extra properties to a
* path so that they are fetching in a ORM query but perhaps not rendered by
* default. That is, use a PathProperties for JSON or XML rendering, but
* create a copy, add some extra properties and then use that copy to define
* an ORM query.
* </p>
*/
public PathProperties copy() {
return new PathProperties(this);
}
/**
* Return true if there are no paths defined.
*/
public boolean isEmpty() {
return pathMap.isEmpty();
}
public String toString() {
return pathMap.toString();
}
@@ -84,6 +53,7 @@ public class PathProperties {
/**
* Return true if the path is defined and has properties.
*/
@Override
public boolean hasPath(String path) {
Props props = pathMap.get(path);
return props != null && !props.isEmpty();
@@ -92,40 +62,38 @@ public class PathProperties {
/**
* Get the properties for a given path.
*/
public LinkedHashSet<String> get(String path) {
@Override
public Set<String> getProperties(String path) {
Props props = pathMap.get(path);
return props == null ? null : props.getProperties();
}
public void addToPath(String path, String property) {
getProps(path).getProperties().add(property);
}
public void addNested(String prefix, PathProperties pathProps) {
for (Entry<String, Props> entry : pathProps.pathMap.entrySet()) {
String path = pathAdd(prefix, entry.getKey());
String[] split = SplitName.split(path);
getProps(split[0]).addProperty(split[1]);
getProps(path).addProps(entry.getValue());
}
}
private String pathAdd(String prefix, String key) {
return key == null ? prefix : prefix + "." + key;
}
Props getProps(String path) {
Props props = pathMap.get(path);
if (props == null) {
props = new Props(this, null, path);
pathMap.put(path, props);
}
props.getProperties().add(property);
}
/**
* Set the properties for a given path.
*/
public void put(String path, LinkedHashSet<String> properties) {
pathMap.put(path, new Props(this, null, path, properties));
}
/**
* Remove a path returning the properties set for that path.
*/
public Set<String> remove(String path) {
Props props = pathMap.remove(path);
return props == null ? null : props.getProperties();
}
/**
* Return a shallow copy of the paths.
*/
public Set<String> getPaths() {
return new LinkedHashSet<String>(pathMap.keySet());
return props;
}
public Collection<Props> getPathProps() {
@@ -135,7 +103,7 @@ public class PathProperties {
/**
* Apply these path properties as fetch paths to the query.
*/
public void apply(Query<?> query) {
public <T> void apply(Query<T> query) {
for (Entry<String, Props> entry : pathMap.entrySet()) {
String path = entry.getKey();
@@ -153,6 +121,40 @@ public class PathProperties {
return rootProps;
}
/**
* Return true if the property (dot notation) is included in the PathProperties.
*/
public boolean includesProperty(String name) {
String[] split = SplitName.split(name);
Props props = pathMap.get(split[0]);
return (props != null && props.includes(split[1]));
}
/**
* Return true if the property is included using a prefix.
*/
public boolean includesProperty(String prefix, String name) {
return includesProperty(SplitName.add(prefix, name));
}
/**
* Return true if the fetch path is included in the PathProperties.
* <p>
* The fetch path is a OneToMany or ManyToMany path in dot notation.
* </p>
*/
public boolean includesPath(String path) {
return pathMap.containsKey(path);
}
/**
* Return true if the path is included using a prefix.
*/
public boolean includesPath(String prefix, String name) {
return includesPath(SplitName.add(prefix, name));
}
public static class Props {
private final PathProperties owner;
@@ -173,13 +175,6 @@ public class PathProperties {
this(owner, parentPath, path, new LinkedHashSet<String>());
}
/**
* Create a shallow copy of this Props instance.
*/
public Props copy(PathProperties newOwner) {
return new Props(newOwner, parentPath, path, new LinkedHashSet<String>(propSet));
}
public String getPath() {
return path;
}
@@ -228,15 +223,15 @@ public class PathProperties {
/**
* Add a child Property set.
*/
protected Props addChild(String subpath) {
protected Props addChild(String subPath) {
subpath = subpath.trim();
addProperty(subpath);
subPath = subPath.trim();
addProperty(subPath);
// build the subpath
String p = path == null ? subpath : path + "." + subpath;
Props nested = new Props(owner, path, p);
owner.pathMap.put(p, nested);
// build the subPath
String fullPath = path == null ? subPath : path + "." + subPath;
Props nested = new Props(owner, path, fullPath);
owner.pathMap.put(fullPath, nested);
return nested;
}
@@ -246,6 +241,14 @@ public class PathProperties {
protected void addProperty(String property) {
propSet.add(property.trim());
}
private void addProps(Props value) {
propSet.addAll(value.propSet);
}
private boolean includes(String prop) {
return propSet.isEmpty() || propSet.contains(prop) || propSet.contains("*");
}
}
}
@@ -3,7 +3,7 @@ package com.avaje.ebean.text;
/**
* Parses Uri segments like :(id,name,shippingAddress(*),contacts(*)) so that
* the response can be customised for performance.
*
*
* @author rbygrave
*/
class PathPropertiesParser {
@@ -50,12 +50,12 @@ class PathPropertiesParser {
do {
char c1 = chars[pos++];
switch (c1) {
case '(':
return currentWord();
default:
if (pos == 1) {
return "";
}
case '(':
return currentWord();
default:
if (pos == 1) {
return "";
}
}
} while (pos < eof);
throw new RuntimeException("Hit EOF while reading sectionTitle from " + startPos);
@@ -72,28 +72,30 @@ class PathPropertiesParser {
}
private void parseSection() {
do {
char c1 = chars[pos++];
switch (c1) {
case '(':
addSubpath();
break;
case ',':
addCurrentProperty();
break;
case ':':
// start new section
startPos = pos;
return;
case ')':
// end of section
addCurrentProperty();
popSubpath();
break;
default:
}
if (pos < eof) {
do {
char c1 = chars[pos++];
switch (c1) {
case '(':
addSubpath();
break;
case ',':
addCurrentProperty();
break;
case ':':
// start new section
startPos = pos;
return;
case ')':
// end of section
addCurrentProperty();
popSubpath();
break;
default:
}
} while (pos < eof);
} while (pos < eof);
}
if (startPos < pos) {
String currentWord = source.substring(startPos, pos);
currentPathProps.addProperty(currentWord);
@@ -0,0 +1,35 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.bean.PersistenceContext;
import com.fasterxml.jackson.core.JsonParser;
/**
* Provides a JSON reader that can hold a persistence context and load context while reading JSON.
* <p>
* This provides a mechanism such that an object loaded from JSON can have unique instances
* by using a persistence context and also support further lazy loading (via a load context).
* </p>
*/
public interface JsonBeanReader<T> {
/**
* Read the JSON returning a bean.
*/
T read();
/**
* Create a new reader taking the context from the existing one but using a new JsonParser.
*/
JsonBeanReader<T> forJson(JsonParser moreJson, boolean resetContext);
/**
* Add a bean explicitly to the persistence context.
*/
void persistenceContextPut(Object beanId, T currentBean);
/**
* Return the persistence context if one is being used.
*/
PersistenceContext getPersistenceContext();
}
@@ -1,9 +1,11 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.FetchPath;
import com.avaje.ebean.plugin.BeanType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
@@ -56,6 +58,26 @@ public interface JsonContext {
*/
<T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
/**
* Create and return a new bean reading for the bean type given the JSON options and source.
* <p>
* 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.
* </p>
*/
<T> JsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
/**
* Create and return a new bean reading for the bean type given the JSON options and source.
* <p>
* 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.
* </p>
*/
<T> JsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException;
/**
* Convert json string input into a list of beans of a specific type.
*
@@ -144,21 +166,21 @@ public interface JsonContext {
void toJson(Object value, JsonGenerator generator) throws JsonIOException;
/**
* Return the bean or collection as JSON string using PathProperties.
* Return the bean or collection as JSON string using FetchPath.
*
* @throws JsonIOException When IOException occurs
*/
String toJson(Object value, PathProperties pathProperties) throws JsonIOException;
String toJson(Object value, FetchPath fetchPath) throws JsonIOException;
/**
* Write the bean or collection as json to the writer using the PathProperties.
* Write the bean or collection as json to the writer using the FetchPath.
*/
void toJson(Object value, Writer writer, PathProperties pathProperties) throws JsonIOException;
void toJson(Object value, Writer writer, FetchPath fetchPath) throws JsonIOException;
/**
* Write the bean or collection to the JsonGenerator using the PathProperties.
* Write the bean or collection to the JsonGenerator using the FetchPath.
*/
void toJson(Object value, JsonGenerator generator, PathProperties pathProperties) throws JsonIOException;
void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
@@ -203,13 +225,13 @@ public interface JsonContext {
JsonParser createParser(Reader reader) throws JsonIOException;
/**
* Return a helper that can write scalar types known to Ebean to Jackson.
* Write a scalar types known to Ebean to Jackson.
* <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.
* </p>
*/
JsonScalar getScalar(JsonGenerator generator);
void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException;
}
@@ -1,5 +1,7 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.bean.PersistenceContext;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -17,6 +19,12 @@ public class JsonReadOptions {
protected Object objectMapper;
protected boolean enableLazyLoading;
protected PersistenceContext persistenceContext;
protected Object loadContext;
/**
* Default constructor.
*/
@@ -46,6 +54,25 @@ public class JsonReadOptions {
return this;
}
/**
* Return true if lazy loading is enabled after the objects are loaded.
*/
public boolean isEnableLazyLoading() {
return enableLazyLoading;
}
/**
* Set to true to enable lazy loading on partially populated beans.
* <p>
* If this is set to true a persistence context will be created if one has
* not already been supplied.
* </p>
*/
public JsonReadOptions setEnableLazyLoading(boolean enableLazyLoading) {
this.enableLazyLoading = enableLazyLoading;
return this;
}
/**
* Return the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig).
*/
@@ -56,7 +83,38 @@ public class JsonReadOptions {
/**
* Set the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig).
*/
public void setObjectMapper(Object objectMapper) {
public JsonReadOptions setObjectMapper(Object objectMapper) {
this.objectMapper = objectMapper;
return this;
}
/**
* Set the persistence context to use when building the object graph from the JSON.
*/
public JsonReadOptions setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
return this;
}
/**
* Return the persistence context to use when marshalling JSON.
*/
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
/**
* Return the load context to use.
*/
public Object getLoadContext() {
return loadContext;
}
/**
* Set the load context to use.
*/
public void setLoadContext(Object loadContext) {
this.loadContext = loadContext;
}
}
@@ -1,11 +0,0 @@
package com.avaje.ebean.text.json;
import java.io.IOException;
/**
* Writes any scalar type known to Ebean the Jackson generator.
*/
public interface JsonScalar {
void write(String name, Object value) throws IOException;
}
@@ -1,5 +1,6 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.FetchPath;
import com.avaje.ebean.config.JsonConfig;
import com.avaje.ebean.text.PathProperties;
@@ -15,7 +16,7 @@ import java.util.Map;
*/
public class JsonWriteOptions {
protected PathProperties pathProperties;
protected FetchPath pathProperties;
protected Object objectMapper;
@@ -38,7 +39,7 @@ public class JsonWriteOptions {
/**
* Construct JsonWriteOptions with the given pathProperties.
*/
public static JsonWriteOptions pathProperties(PathProperties pathProperties) {
public static JsonWriteOptions pathProperties(FetchPath pathProperties) {
JsonWriteOptions o = new JsonWriteOptions();
o.setPathProperties(pathProperties);
return o;
@@ -47,14 +48,14 @@ public class JsonWriteOptions {
/**
* Set the Map of properties to include by path.
*/
public void setPathProperties(PathProperties pathProperties) {
public void setPathProperties(FetchPath pathProperties) {
this.pathProperties = pathProperties;
}
/**
* Return the properties to include by path.
*/
public PathProperties getPathProperties() {
public FetchPath getPathProperties() {
return pathProperties;
}
@@ -135,6 +135,11 @@ public interface JsonWriter {
*/
void writeNumber(long value);
/**
* Write a double value.
*/
void writeNumber(double value);
/**
* Write a BigDecimal value (typically inside a list).
*/
@@ -5,9 +5,6 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.util.List;
/**
* Controls the loading of reference objects for a query instance.
@@ -19,38 +16,40 @@ public interface LoadContext {
*/
int getSecondaryQueriesMinBatchSize(int defaultQueryBatch);
/**
* Execute any secondary (+query) queries if there are any defined.
* @param parentRequest the originating query request
*/
void executeSecondaryQueries(OrmQueryRequest<?> parentRequest);
/**
* Execute any secondary (+query) queries if there are any defined.
*
* @param parentRequest the originating query request
* @param forEach set true when using findEach iteration
*/
void executeSecondaryQueries(OrmQueryRequest<?> parentRequest, boolean forEach);
/**
* Return the node for a given path which is used by AutoTune profiling.
*/
ObjectGraphNode getObjectGraphNode(String path);
* Return the node for a given path which is used by AutoTune profiling.
*/
ObjectGraphNode getObjectGraphNode(String path);
/**
* Return the persistence context used by this query and future lazy loading.
*/
PersistenceContext getPersistenceContext();
/**
* Return the persistence context used by this query and future lazy loading.
*/
PersistenceContext getPersistenceContext();
/**
* Set the persistence context used by this query and future lazy loading.
* <p>
* Used by query iterator when processing large result sets.
* </p>
*/
void resetPersistenceContext(PersistenceContext persistenceContext);
/**
* Set the persistence context used by this query and future lazy loading.
* <p>
* Used by query iterator when processing large result sets.
* </p>
*/
void resetPersistenceContext(PersistenceContext persistenceContext);
/**
* Register a Bean for lazy loading.
*/
void register(String path, EntityBeanIntercept ebi);
/**
* Register a Bean for lazy loading.
*/
void register(String path, EntityBeanIntercept ebi);
/**
* Register a collection for lazy loading.
*/
void register(String path, BeanCollection<?> bc);
/**
* Register a collection for lazy loading.
*/
void register(String path, BeanCollection<?> bc);
}
@@ -29,4 +29,5 @@ public interface LoadManyBuffer {
void configureQuery(SpiQuery<?> query);
boolean isUseDocStore();
}
@@ -144,7 +144,7 @@ public class LoadManyRequest extends LoadRequest {
query.setLazyLoadForParents(many);
List<Object> idList = getParentIdList(batchSize);
many.addWhereParentIdIn(query, idList);
many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
@@ -14,5 +14,5 @@ public interface LoadSecondaryQuery {
/**
* Execute the secondary query with a given batch size.
*/
void loadSecondaryQuery(OrmQueryRequest<?> parentRequest);
void loadSecondaryQuery(OrmQueryRequest<?> parentRequest, boolean forEach);
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.TransactionCallback;
import com.avaje.ebean.annotation.DocStoreMode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebean.event.changelog.BeanChange;
@@ -59,6 +60,26 @@ public class ScopedTransaction implements SpiTransaction {
}
}
@Override
public DocStoreMode getDocStoreMode() {
return transaction.getDocStoreMode();
}
@Override
public void setDocStoreMode(DocStoreMode mode) {
transaction.setDocStoreMode(mode);
}
@Override
public int getDocStoreBatchSize() {
return transaction.getDocStoreBatchSize();
}
@Override
public void setDocStoreBatchSize(int batchSize) {
transaction.setDocStoreBatchSize(batchSize);
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
@@ -83,6 +83,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
BeanDescriptor<?> getBeanDescriptorById(String className);
/**
* Return BeanDescriptor using it's unique doc store queueId.
*/
BeanDescriptor<?> getBeanDescriptorByQueueId(String queueId);
/**
* Return BeanDescriptors mapped to this table.
*/
@@ -3,6 +3,9 @@ package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
/**
@@ -10,6 +13,11 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public interface SpiExpression extends Expression {
/**
* Write the expression as an elastic search expression.
*/
void writeDocQuery(DocQueryContext context) throws IOException;
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
@@ -1,7 +1,9 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
import java.util.List;
/**
@@ -24,4 +26,8 @@ public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
*/
boolean isEmpty();
/**
* Write the top level where expressions taking into account possible extra idEquals expression.
*/
void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException;
}
@@ -1,6 +1,6 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.plugin.BeanType;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -10,11 +10,11 @@ import java.util.Set;
*/
public class SpiExpressionValidation {
private final SpiBeanType<?> desc;
private final BeanType<?> desc;
private final LinkedHashSet<String> unknown = new LinkedHashSet<String>();
public SpiExpressionValidation(SpiBeanType<?> desc) {
public SpiExpressionValidation(BeanType<?> desc) {
this.desc = desc;
}
@@ -0,0 +1,17 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Junction;
import com.avaje.ebeaninternal.server.expression.DocQueryContext;
import java.io.IOException;
/**
* SPI methods for Junction.
*/
public interface SpiJunction<T> extends Junction<T> {
/**
* Write the Junction taking into account it is implied.
*/
void writeDocQueryJunction(DocQueryContext context) throws IOException;
}
@@ -11,7 +11,7 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.plugin.BeanType;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -19,7 +19,6 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.List;
@@ -129,6 +128,21 @@ public interface SpiQuery<T> extends Query<T> {
}
}
/**
* Return true if AutoTune should be attempted on this query.
*/
boolean isAutoTunable();
/**
* Return the bean descriptor for this query.
*/
BeanDescriptor<T> getBeanDescriptor();
/**
* Return true if this query should be executed against the doc store.
*/
boolean isUseDocStore();
/**
* Return the PersistenceContextScope that this query should use.
* <p>
@@ -277,18 +291,13 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Return the lazy loading 'many' property.
*/
BeanPropertyAssocMany<?> getLazyLoadForParentsProperty();
BeanPropertyAssocMany<?> getLazyLoadMany();
/**
* Set the load mode (+lazy or +query) and the load description.
*/
void setLoadDescription(String loadMode, String loadDescription);
/**
* Set the BeanDescriptor for the root type of this query.
*/
void setBeanDescriptor(BeanDescriptor<?> desc);
/**
* Return the joins required to support predicates on the many properties.
*/
@@ -500,6 +509,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
SpiExpressionList<T> getHavingExpressions();
/**
* Return the text expressions.
*/
SpiExpressionList<T> getTextExpression();
/**
* Return additional having clause. Where raw String expressions are added
* to having clause rather than Expression objects.
@@ -640,9 +654,9 @@ public interface SpiQuery<T> extends Query<T> {
*/
boolean isDisableReadAudit();
/**
* Return true if this is a query executing in the background.
*/
/**
* Return true if this is a query executing in the background.
*/
boolean isFutureFetch();
/**
@@ -679,6 +693,6 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Validate the query returning the set of properties with unknown paths.
*/
Set<String> validate(SpiBeanType<T> desc);
Set<String> validate(BeanType<T> desc);
}
@@ -4,6 +4,7 @@ import java.sql.Connection;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.annotation.DocStoreMode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebean.event.changelog.ChangeSet;
@@ -20,7 +21,7 @@ import com.avaje.ebeaninternal.server.persist.BatchControl;
public interface SpiTransaction extends Transaction {
/**
* Return the string prefix with the transactin id and label used in logging.
* Return the string prefix with the transaction id and label used in logging.
*/
String getLogPrefix();
@@ -102,6 +103,20 @@ public interface SpiTransaction extends Transaction {
*/
Boolean isUpdateAllLoadedProperties();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
DocStoreMode getDocStoreMode();
/**
* Return the batch size to us for ElasticSearch Bulk API calls
* as a result of this transaction.
*/
int getDocStoreBatchSize();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
@@ -157,12 +172,12 @@ public interface SpiTransaction extends Transaction {
boolean isBatchThisRequest(PersistRequest.Type type);
/**
* Return the queue used to batch up persist requests.
* Return the BatchControl used to batch up persist requests.
*/
BatchControl getBatchControl();
/**
* Set the queue used to batch up persist requests. There should only be one
* Set the BatchControl used to batch up persist requests. There should only be one
* PersistQueue set per transaction.
*/
void setBatchControl(BatchControl control);
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.transaction.DeleteByIdMap;
@@ -120,4 +121,16 @@ public class TransactionEvent implements Serializable {
}
}
/**
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
if (persistRequestBeans != null) {
for (int i=0; i< persistRequestBeans.size(); i++) {
persistRequestBeans.get(i).addDocStoreUpdates(docStoreUpdates);
}
}
}
}
@@ -5,7 +5,6 @@ import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebean.event.changelog.ChangeSet;
import com.avaje.ebean.event.changelog.ChangeType;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebean.text.json.JsonScalar;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -97,10 +96,7 @@ public class ChangeJsonBuilder {
if (bean.getType() != ChangeType.DELETE) {
gen.writeFieldName("values");
gen.writeStartObject();
// use JsonScalar as it knows how to encode all the scalar
// property types that Ebean supports (Java8, Joda etc)
JsonScalar scalarWriter = json.getScalar(gen);
writeValuePairs(bean, scalarWriter, gen);
writeValuePairs(bean, gen);
gen.writeEndObject();
}
}
@@ -111,7 +107,7 @@ public class ChangeJsonBuilder {
* We are intentionally keeping the same new/old structure for both inserts and updates.
* </p>
*/
protected void writeValuePairs(BeanChange bean, JsonScalar scalarWriter, JsonGenerator gen) throws IOException {
protected void writeValuePairs(BeanChange bean, JsonGenerator gen) throws IOException {
for (Map.Entry<String, ValuePair> entry : bean.getValues().entrySet()) {
gen.writeFieldName(entry.getKey());
@@ -119,11 +115,13 @@ public class ChangeJsonBuilder {
ValuePair value = entry.getValue();
Object newValue = value.getNewValue();
if (newValue != null) {
scalarWriter.write("new", newValue);
gen.writeFieldName("new");
json.writeScalar(gen, newValue);
}
Object oldValue = value.getOldValue();
if (oldValue != null) {
scalarWriter.write("old", oldValue);
gen.writeFieldName("old");
json.writeScalar(gen, oldValue);
}
gen.writeEndObject();
}
@@ -5,7 +5,7 @@ import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeSet;
import com.avaje.ebean.event.changelog.ChangeType;
import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebean.plugin.SpiServerPlugin;
import com.avaje.ebean.plugin.Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,7 +20,7 @@ import java.util.Properties;
* is fully contained with the transaction information.
* </p>
*/
public class DefaultChangeLogListener implements ChangeLogListener, SpiServerPlugin {
public class DefaultChangeLogListener implements ChangeLogListener, Plugin {
/**
* The usual application specific logger.
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebeaninternal.server.type.ScalarTypeUUIDBinary;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -8,8 +10,6 @@ import java.sql.Types;
import java.util.Calendar;
import java.util.UUID;
import com.avaje.ebeaninternal.server.type.ScalarTypeUUIDBinary;
/**
* Default implementation of TypeConverter.
* <p>
@@ -18,451 +18,452 @@ import com.avaje.ebeaninternal.server.type.ScalarTypeUUIDBinary;
*/
public final class BasicTypeConverter implements Serializable {
private static final long serialVersionUID = 7691463236204070311L;
/**
* Type code for java.util.Calendar.
*/
public static final int UTIL_CALENDAR = -999998986;
private static final long serialVersionUID = 7691463236204070311L;
/**
* Type code for java.util.Date.
*/
public static final int UTIL_DATE = -999998988;
/**
* Type code for java.util.Calendar.
*/
public static final int UTIL_CALENDAR = -999998986;
private BasicTypeConverter() {
}
/**
* Type code for java.util.Date.
*/
public static final int UTIL_DATE = -999998988;
/**
* Convert the Object to the required data type.
*
* @param value
* the Object value
* @param toDataType
* the dataType as per java.sql.Types.
*/
public static Object convert(Object value, int toDataType) {
private BasicTypeConverter() {
}
try {
switch (toDataType) {
case UTIL_DATE: {
return toUtilDate(value);
}
case UTIL_CALENDAR: {
return toCalendar(value);
}
case Types.BIGINT: {
return toLong(value);
}
case Types.INTEGER: {
return toInteger(value);
}
case Types.BIT: {
return toBoolean(value);
}
case Types.TINYINT: {
return toByte(value);
}
case Types.SMALLINT: {
return toShort(value);
}
case Types.NUMERIC: {
return toBigDecimal(value);
}
case Types.DECIMAL: {
return toBigDecimal(value);
}
case Types.REAL: {
return toFloat(value);
}
case Types.DOUBLE: {
return toDouble(value);
}
case Types.FLOAT: {
return toDouble(value);
}
case Types.BOOLEAN: {
return toBoolean(value);
}
case Types.TIMESTAMP: {
return toTimestamp(value);
}
case Types.DATE: {
return toDate(value);
}
case Types.VARCHAR: {
return toString(value);
}
case Types.CHAR: {
return toString(value);
}
case Types.OTHER: {
return value;
}
case Types.JAVA_OBJECT: {
return value;
}
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.BLOB: {
return value;
}
case Types.LONGVARCHAR:
case Types.CLOB: {
return value;
}
default: {
String msg = "Unhandled data type [" + toDataType + "] converting [" + value + "]";
throw new RuntimeException(msg);
}
}
} catch (ClassCastException e) {
String m = "ClassCastException converting to data type [" + toDataType + "] value [" + value + "]";
throw new RuntimeException(m);
}
}
/**
* Convert the Object to the required data type.
*
* @param value the Object value
* @param toDataType the dataType as per java.sql.Types.
*/
public static Object convert(Object value, int toDataType) {
/**
* Convert the value to a String.
*/
public static String toString(Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
return (String) value;
}
if (value instanceof char[]) {
return String.valueOf((char[]) value);
}
return value.toString();
}
/**
* Convert the value to a Boolean with an explicit String true value.
*/
public static Boolean toBoolean(Object value, String dbTrueValue) {
if (value == null) {
return null;
try {
switch (toDataType) {
case UTIL_DATE: {
return toUtilDate(value);
}
if (value instanceof Boolean) {
return (Boolean) value;
case UTIL_CALENDAR: {
return toCalendar(value);
}
String s = value.toString();
return s.equalsIgnoreCase(dbTrueValue);
}
/**
* Convert the value to a Boolean. Can be a Boolean or the string values
* "true" or "false".
*/
public static Boolean toBoolean(Object value) {
case Types.BIGINT: {
return toLong(value);
}
case Types.INTEGER: {
return toInteger(value);
}
case Types.BIT: {
return toBoolean(value);
}
case Types.TINYINT: {
return toByte(value);
}
case Types.SMALLINT: {
return toShort(value);
}
case Types.NUMERIC: {
return toBigDecimal(value);
}
case Types.DECIMAL: {
return toBigDecimal(value);
}
case Types.REAL: {
return toFloat(value);
}
case Types.DOUBLE: {
return toDouble(value);
}
case Types.FLOAT: {
return toDouble(value);
}
case Types.BOOLEAN: {
return toBoolean(value);
}
case Types.TIMESTAMP: {
return toTimestamp(value);
}
case Types.DATE: {
return toDate(value);
}
case Types.VARCHAR: {
return toString(value);
}
case Types.CHAR: {
return toString(value);
}
case Types.OTHER: {
return value;
}
case Types.JAVA_OBJECT: {
return value;
}
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.BLOB: {
return value;
}
case Types.LONGVARCHAR:
case Types.CLOB: {
return value;
}
default: {
String msg = "Unhandled data type [" + toDataType + "] converting [" + value + "]";
throw new RuntimeException(msg);
}
}
} catch (ClassCastException e) {
String m = "ClassCastException converting to data type [" + toDataType + "] value [" + value + "]";
throw new RuntimeException(m);
}
}
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
/**
* Convert the value to a String.
*/
public static String toString(Object value) {
return Boolean.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof String) {
return (String) value;
}
if (value instanceof char[]) {
return String.valueOf((char[]) value);
}
/**
* Convert the value to a UUID.
*/
public static UUID toUUID(Object value) {
return value.toString();
}
if (value == null) {
return null;
}
if (value instanceof String) {
return UUID.fromString((String) value);
}
if (value instanceof byte[]) {
return ScalarTypeUUIDBinary.convertFromBytes((byte[])value);
}
return (UUID) value;
}
/**
* convert the passed in object to a BigDecimal. It should be another
* numeric type.
*/
public static BigDecimal toBigDecimal(Object value) {
/**
* Convert the value to a Boolean with an explicit String true value.
*/
public static Boolean toBoolean(Object value, String dbTrueValue) {
if (value == null) {
return null;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
return new BigDecimal(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
String s = value.toString();
return s.equalsIgnoreCase(dbTrueValue);
}
public static Float toFloat(Object value) {
/**
* Convert the value to a Boolean. Can be a Boolean or the string values
* "true" or "false".
*/
public static Boolean toBoolean(Object value) {
if (value == null) {
return null;
}
if (value instanceof Float) {
return (Float) value;
}
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return Float.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
public static Short toShort(Object value) {
return Boolean.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Short) {
return (Short) value;
}
if (value instanceof Number) {
return ((Number) value).shortValue();
}
return Short.valueOf(value.toString());
}
/**
* Convert the value to a UUID.
*/
public static UUID toUUID(Object value) {
public static Byte toByte(Object value) {
if (value == null) {
return null;
}
if (value instanceof UUID) {
return (UUID) value;
}
if (value instanceof String) {
return UUID.fromString((String) value);
}
if (value instanceof byte[]) {
return ScalarTypeUUIDBinary.convertFromBytes((byte[]) value);
}
return UUID.fromString(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Byte) {
return (Byte) value;
}
return Byte.valueOf(value.toString());
}
/**
* convert the passed in object to a BigDecimal. It should be another
* numeric type.
*/
public static BigDecimal toBigDecimal(Object value) {
/**
* convert the passed in object to a Integer. It should be another numeric
* type.
*/
public static Integer toInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
return new BigDecimal(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
return Integer.valueOf(value.toString());
}
public static Float toFloat(Object value) {
/**
* Convert the object to a Long. It should be another numeric type.
*/
public static Long toLong(Object value) {
if (value == null) {
return null;
}
if (value instanceof Float) {
return (Float) value;
}
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return Float.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Long) {
return (Long) value;
}
if (value instanceof String) {
return Long.valueOf((String) value);
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
if (value instanceof java.util.Date) {
return ((java.util.Date) value).getTime();
}
if (value instanceof Calendar) {
return ((Calendar) value).getTime().getTime();
}
return Long.valueOf(value.toString());
}
public static Short toShort(Object value) {
public static BigInteger toMathBigInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof Short) {
return (Short) value;
}
if (value instanceof Number) {
return ((Number) value).shortValue();
}
return Short.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof BigInteger) {
return (BigInteger) value;
}
return new BigInteger(value.toString());
}
public static Byte toByte(Object value) {
/**
* Convert the object to a Double. It should be another numberic type.
*/
public static Double toDouble(Object value) {
if (value == null) {
return null;
}
if (value instanceof Byte) {
return (Byte) value;
}
return Byte.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Double) {
return (Double) value;
}
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return Double.valueOf(value.toString());
}
/**
* convert the passed in object to a Integer. It should be another numeric
* type.
*/
public static Integer toInteger(Object value) {
/**
* convert the passed in object to a Timestamp. It is expected to be a
* java.sql.Date really.
*/
public static Timestamp toTimestamp(Object value) {
if (value == null) {
return null;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
return Integer.valueOf(value.toString());
}
if (value == null) {
return null;
}
if (value instanceof Timestamp) {
return (Timestamp) value;
/**
* Convert the object to a Long. It should be another numeric type.
*/
public static Long toLong(Object value) {
} else if (value instanceof java.util.Date) {
// no nanos here... so hopefully ok
return new Timestamp(((java.util.Date) value).getTime());
if (value == null) {
return null;
}
if (value instanceof Long) {
return (Long) value;
}
if (value instanceof String) {
return Long.valueOf((String) value);
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
if (value instanceof java.util.Date) {
return ((java.util.Date) value).getTime();
}
if (value instanceof Calendar) {
return ((Calendar) value).getTime().getTime();
}
return Long.valueOf(value.toString());
}
} else if (value instanceof Calendar) {
return new Timestamp(((Calendar) value).getTime().getTime());
public static BigInteger toMathBigInteger(Object value) {
} else if (value instanceof String) {
return Timestamp.valueOf((String) value);
if (value == null) {
return null;
}
if (value instanceof BigInteger) {
return (BigInteger) value;
}
return new BigInteger(value.toString());
}
} else if (value instanceof Number) {
return new Timestamp(((Number) value).longValue());
/**
* Convert the object to a Double. It should be another numberic type.
*/
public static Double toDouble(Object value) {
} else {
String msg = "Unable to convert [" + value.getClass().getName() + "] into a Timestamp.";
throw new RuntimeException(msg);
}
}
if (value == null) {
return null;
}
if (value instanceof Double) {
return (Double) value;
}
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return Double.valueOf(value.toString());
}
public static java.sql.Time toTime(Object value) {
/**
* convert the passed in object to a Timestamp. It is expected to be a
* java.sql.Date really.
*/
public static Timestamp toTimestamp(Object value) {
if (value == null) {
return null;
}
if (value instanceof java.sql.Time) {
return (java.sql.Time) value;
if (value == null) {
return null;
}
if (value instanceof Timestamp) {
return (Timestamp) value;
} else if (value instanceof String) {
return java.sql.Time.valueOf((String) value);
} else if (value instanceof java.util.Date) {
// no nanos here... so hopefully ok
return new Timestamp(((java.util.Date) value).getTime());
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
}
}
} else if (value instanceof Calendar) {
return new Timestamp(((Calendar) value).getTime().getTime());
/**
* convert the passed in object to a java sql Date.
*/
public static java.sql.Date toDate(Object value) {
} else if (value instanceof String) {
return Timestamp.valueOf((String) value);
if (value == null) {
return null;
}
if (value instanceof java.sql.Date) {
return (java.sql.Date) value;
} else if (value instanceof Number) {
return new Timestamp(((Number) value).longValue());
} else if (value instanceof java.util.Date) {
return new java.sql.Date(((java.util.Date) value).getTime());
} else {
String msg = "Unable to convert [" + value.getClass().getName() + "] into a Timestamp.";
throw new RuntimeException(msg);
}
}
} else if (value instanceof Calendar) {
return new java.sql.Date(((Calendar) value).getTime().getTime());
public static java.sql.Time toTime(Object value) {
} else if (value instanceof String) {
return java.sql.Date.valueOf((String) value);
if (value == null) {
return null;
}
if (value instanceof java.sql.Time) {
return (java.sql.Time) value;
} else if (value instanceof Number) {
return new java.sql.Date(((Number) value).longValue());
} else if (value instanceof String) {
return java.sql.Time.valueOf((String) value);
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
}
}
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
}
}
/**
* convert the passed in object to a java sql Date.
*/
public static java.util.Date toUtilDate(Object value) {
/**
* convert the passed in object to a java sql Date.
*/
public static java.sql.Date toDate(Object value) {
if (value == null) {
return null;
}
if (value instanceof java.sql.Timestamp) {
// loss of nanos precision
return new java.util.Date(((java.sql.Timestamp) value).getTime());
}
// DEVNOTE: strictly speaking do I need to convert a java.sql.Date to
// java.util.Date? equals() is symmetrical so perhaps this is not
// really required?
if (value instanceof java.sql.Date) {
return new java.util.Date(((java.sql.Date) value).getTime());
}
if (value instanceof java.util.Date) {
return (java.util.Date) value;
if (value == null) {
return null;
}
if (value instanceof java.sql.Date) {
return (java.sql.Date) value;
} else if (value instanceof Calendar) {
return ((Calendar) value).getTime();
} else if (value instanceof java.util.Date) {
return new java.sql.Date(((java.util.Date) value).getTime());
} else if (value instanceof String) {
return new java.util.Date(Timestamp.valueOf((String) value).getTime());
} else if (value instanceof Calendar) {
return new java.sql.Date(((Calendar) value).getTime().getTime());
} else if (value instanceof Number) {
return new java.util.Date(((Number) value).longValue());
} else if (value instanceof String) {
return java.sql.Date.valueOf((String) value);
} else {
throw new RuntimeException("Unable to convert [" + value.getClass().getName() + "] into a java.util.Date");
}
}
} else if (value instanceof Number) {
return new java.sql.Date(((Number) value).longValue());
/**
* convert the passed in object to a java sql Date.
*/
public static Calendar toCalendar(Object value) {
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
}
}
if (value == null) {
return null;
}
if (value instanceof Calendar) {
return (Calendar) value;
/**
* convert the passed in object to a java sql Date.
*/
public static java.util.Date toUtilDate(Object value) {
} else if (value instanceof java.util.Date) {
java.util.Date date = ((java.util.Date) value);
return toCalendarFromDate(date);
if (value == null) {
return null;
}
if (value instanceof java.sql.Timestamp) {
// loss of nanos precision
return new java.util.Date(((java.sql.Timestamp) value).getTime());
}
// DEVNOTE: strictly speaking do I need to convert a java.sql.Date to
// java.util.Date? equals() is symmetrical so perhaps this is not
// really required?
if (value instanceof java.sql.Date) {
return new java.util.Date(((java.sql.Date) value).getTime());
}
if (value instanceof java.util.Date) {
return (java.util.Date) value;
} else if (value instanceof String) {
java.util.Date date = toUtilDate(value);
return toCalendarFromDate(date);
} else if (value instanceof Calendar) {
return ((Calendar) value).getTime();
} else if (value instanceof Number) {
long timeMillis = ((Number) value).longValue();
java.util.Date date = new java.util.Date(timeMillis);
return toCalendarFromDate(date);
} else if (value instanceof String) {
return new java.util.Date(Timestamp.valueOf((String) value).getTime());
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.util.Date";
throw new RuntimeException(m);
}
}
} else if (value instanceof Number) {
return new java.util.Date(((Number) value).longValue());
private static Calendar toCalendarFromDate(java.util.Date date) {
} else {
throw new RuntimeException("Unable to convert [" + value.getClass().getName() + "] into a java.util.Date");
}
}
Calendar cal = Calendar.getInstance();
cal.setTime(date);
/**
* convert the passed in object to a java sql Date.
*/
public static Calendar toCalendar(Object value) {
return cal;
}
if (value == null) {
return null;
}
if (value instanceof Calendar) {
return (Calendar) value;
} else if (value instanceof java.util.Date) {
java.util.Date date = ((java.util.Date) value);
return toCalendarFromDate(date);
} else if (value instanceof String) {
java.util.Date date = toUtilDate(value);
return toCalendarFromDate(date);
} else if (value instanceof Number) {
long timeMillis = ((Number) value).longValue();
java.util.Date date = new java.util.Date(timeMillis);
return toCalendarFromDate(date);
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.util.Date";
throw new RuntimeException(m);
}
}
private static Calendar toCalendarFromDate(java.util.Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
}
@@ -36,16 +36,20 @@ public abstract class BeanRequest {
* A transaction may have been passed in or active in the thread local. If
* not then create one implicitly to handle the request.
* </p>
*
* @return True if a transaction was set (from current or created).
*/
public void createImplicitTransIfRequired() {
if (transaction == null) {
transaction = ebeanServer.getCurrentServerTransaction();
if (transaction == null || !transaction.isActive()) {
// create an implicit transaction to execute this query
transaction = ebeanServer.createServerTransaction(false, -1);
createdTransaction = true;
}
}
public boolean createImplicitTransIfRequired() {
if (transaction != null) {
return false;
}
transaction = ebeanServer.getCurrentServerTransaction();
if (transaction == null || !transaction.isActive()) {
// create an implicit transaction to execute this query
transaction = ebeanServer.createServerTransaction(false, -1);
createdTransaction = true;
}
return true;
}
/**
@@ -126,7 +126,7 @@ public class DefaultBeanLoader {
if (pc == null) {
pc = new DefaultPersistenceContext();
pc.put(parentId, parentBean);
parentDesc.contextPut(pc, parentId, parentBean);
}
boolean useManyIdCache = beanCollection != null && parentDesc.isManyPropCaching();
@@ -263,7 +263,7 @@ public class DefaultBeanLoader {
if (pc == null) {
// a reference with no existing persistenceContext
pc = new DefaultPersistenceContext();
pc.put(id, bean);
desc.contextPut(pc, id, bean);
ebi.setPersistenceContext(pc);
}
@@ -18,9 +18,9 @@ import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.plugin.BeanType;
import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebean.plugin.SpiServerPlugin;
import com.avaje.ebean.plugin.Plugin;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.LoadBeanRequest;
@@ -66,6 +66,7 @@ import com.avaje.ebeaninternal.server.transaction.TransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.util.ParamTypeHelper;
import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import com.avaje.ebeanservice.docstore.api.DocStoreIntegration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -138,7 +139,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final CQueryEngine cqueryEngine;
private final List<SpiServerPlugin> serverPlugins;
private final List<Plugin> serverPlugins;
private final DdlGenerator ddlGenerator;
@@ -152,6 +153,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final JsonContext jsonContext;
private final DocumentStore documentStore;
private final MetaInfoManager metaInfoManager;
/**
@@ -219,8 +222,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.maxCallStack = serverConfig.getMaxCallStack();
this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked();
this.transactionManager = config.getTransactionManager();
this.transactionScopeManager = config.getTransactionScopeManager();
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
@@ -232,6 +233,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
this.transactionScopeManager = config.createTransactionScopeManager(transactionManager);
this.documentStore = docStoreComponents.documentStore();
this.serverPlugins = config.getPlugins();
this.ddlGenerator = new DdlGenerator(this, serverConfig);
@@ -248,7 +255,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
autoTuneService.startup();
for (SpiServerPlugin plugin : serverPlugins) {
for (Plugin plugin : serverPlugins) {
plugin.configure(this);
}
}
@@ -282,7 +289,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
for (SpiEbeanPlugin plugin : ebeanPlugins) {
plugin.execute(online);
}
for (SpiServerPlugin plugin : serverPlugins) {
for (Plugin plugin : serverPlugins) {
plugin.online(online);
}
}
@@ -408,7 +415,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private void shutdownPlugins() {
for (SpiServerPlugin plugin : serverPlugins) {
for (Plugin plugin : serverPlugins) {
try {
plugin.shutdown();
} catch (Throwable e) {
@@ -573,48 +580,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
BeanDescriptor desc = getBeanDescriptor(type);
// convert the id type if necessary
id = desc.convertId(id);
Object ref = null;
PersistenceContext ctx = null;
PersistenceContext pc = null;
SpiTransaction t = transactionScopeManager.get();
if (t != null) {
// first try the persistence context
ctx = t.getPersistenceContext();
ref = ctx.get(type, id);
}
if (ref == null) {
InheritInfo inheritInfo = desc.getInheritInfo();
if (inheritInfo != null) {
// we actually need to do a query because
// we don't know the type without the
// discriminator value
BeanProperty idProp = desc.getIdProperty();
if (idProp == null) {
throw new PersistenceException("No ID properties for this type? " + desc);
}
// just select the id properties and
// the discriminator column (auto added)
Query<T> query = createQuery(type);
query.select(idProp.getName()).setId(id);
ref = query.findUnique();
} else {
// use the default reference options
ref = desc.createReference(null, id);
}
if (ctx != null && (ref instanceof EntityBean)) {
// Not putting a vanilla reference in the persistence context
ctx.put(id, ref);
pc = t.getPersistenceContext();
Object existing = desc.contextGet(pc, id);
if (existing != null) {
return (T)existing;
}
}
return (T) ref;
InheritInfo inheritInfo = desc.getInheritInfo();
if (inheritInfo == null) {
return (T)desc.contextRef(pc, null, id);
}
BeanProperty idProp = desc.getIdProperty();
if (idProp == null) {
throw new PersistenceException("No ID properties for this type? " + desc);
}
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
return find(type).select(idProp.getName()).setId(id).findUnique();
}
@Override
@@ -902,7 +892,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) throws PersistenceException {
BeanDescriptor<?> desc = getBeanDescriptor(beanType);
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
throw new PersistenceException("Is " + beanType.getName() + " an Entity Bean? BeanDescriptor not found?");
}
@@ -912,7 +902,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// this will parse the query
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, deployQuery);
return new DefaultOrmQuery<T>(desc, this, expressionFactory, deployQuery);
}
@Override
@@ -947,10 +937,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
public <T> Query<T> createQuery(Class<T> beanType, String query) {
BeanDescriptor<?> desc = getBeanDescriptor(beanType);
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
String m = beanType.getName() + " is NOT an Entity Bean registered with this server?";
throw new PersistenceException(m);
throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?");
}
switch (desc.getEntityType()) {
case SQL:
@@ -959,10 +948,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// use the "default" SqlSelect
DeployNamedQuery defaultSqlSelect = desc.getNamedQuery("default");
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, defaultSqlSelect);
return new DefaultOrmQuery<T>(desc, this, expressionFactory, defaultSqlSelect);
default:
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, query);
return new DefaultOrmQuery<T>(desc, this, expressionFactory, query);
}
}
@@ -1042,15 +1031,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(type);
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
spiQuery.setBeanDescriptor(desc);
return createQueryRequest(desc, spiQuery, t);
return createQueryRequest(spiQuery, t);
}
private <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> query, Transaction t) {
private <T> SpiOrmQueryRequest<T> createQueryRequest(SpiQuery<T> query, Transaction t) {
if (desc.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) {
if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
@@ -1063,7 +1049,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
query.setOrigin(createCallStack());
}
OrmQueryRequest<T> request = new OrmQueryRequest<T>(this, queryEngine, query, desc, (SpiTransaction) t);
OrmQueryRequest<T> request = new OrmQueryRequest<T>(this, queryEngine, query, (SpiTransaction) t);
request.prepareQuery();
return request;
@@ -1073,36 +1059,38 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Try to get the object out of the persistence context.
*/
@SuppressWarnings("unchecked")
private <T> T findIdCheckPersistenceContextAndCache(Transaction transaction, BeanDescriptor<T> beanDescriptor, SpiQuery<T> query) {
private <T> T findIdCheckPersistenceContextAndCache(Transaction transaction, SpiQuery<T> query) {
SpiTransaction t = (SpiTransaction) transaction;
if (t == null) {
t = getCurrentServerTransaction();
}
PersistenceContext context = null;
BeanDescriptor<T> desc = query.getBeanDescriptor();
PersistenceContext pc = null;
if (t != null && useTransactionPersistenceContext(query)) {
// first look in the transaction scoped persistence context
context = t.getPersistenceContext();
if (context != null) {
WithOption o = context.getWithOption(beanDescriptor.getBeanType(), query.getId());
pc = t.getPersistenceContext();
if (pc != null) {
WithOption o = desc.contextGetWithOption(pc, query.getId());
if (o != null) {
if (o.isDeleted()) {
// Bean was previously deleted in the same transaction / persistence context
return null;
}
// Return the entity bean instance from the persistence context
return (T) o.getBean();
}
}
}
if (!beanDescriptor.calculateUseCache(query.isUseBeanCache())) {
if (!desc.calculateUseCache(query.isUseBeanCache())) {
// not using bean cache
return null;
}
// Hit the L2 bean cache
return beanDescriptor.cacheBeanGet(query, context);
return desc.cacheBeanGet(query, pc);
}
/**
@@ -1125,20 +1113,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
spiQuery.setBeanDescriptor(desc);
if (SpiQuery.Mode.NORMAL.equals(spiQuery.getMode()) && !spiQuery.isLoadBeanCache()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(t, desc, spiQuery);
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery);
if (bean != null) {
return bean;
}
}
SpiOrmQueryRequest<T> request = createQueryRequest(desc, spiQuery, t);
SpiOrmQueryRequest<T> request = createQueryRequest(spiQuery, t);
if (request.isUseDocStore()) {
return docStore().find(request);
}
try {
request.initTransIfRequired();
return (T) request.findId();
@@ -1157,9 +1144,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return findId(query, t);
}
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(query.getBeanType());
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
BeanDescriptor<T> desc = spiQuery.getBeanDescriptor();
T bean = desc.cacheNaturalKeyLookup((SpiQuery<T>) query, (SpiTransaction) t);
T bean = desc.cacheNaturalKeyLookup(spiQuery, (SpiTransaction) t);
if (bean != null) {
return bean;
}
@@ -1340,6 +1328,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
throw new PersistenceException("maxRows must be specified for findPagedList() query");
}
if (spiQuery.isUseDocStore()) {
return docStore().findPagedList(createQueryRequest(Type.LIST, query, transaction));
}
return new LimitOffsetPagedList<T>(this, spiQuery);
}
@@ -1347,6 +1339,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
if (request.isUseDocStore()) {
docStore().findEach(request, consumer);
return;
}
request.initTransIfRequired();
request.findEach(consumer);
// no try finally - findVisit guarantee's cleanup of the transaction if required
@@ -1355,6 +1352,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public <T> void findEachWhile(Query<T> query, QueryEachWhileConsumer<T> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
if (request.isUseDocStore()) {
docStore().findEachWhile(request, consumer);
return;
}
request.initTransIfRequired();
request.findEachWhile(consumer);
@@ -1392,11 +1393,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public <T> List<T> findList(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
return (List<T>) result;
}
if (request.isUseDocStore()) {
return docStore().findList(request);
}
try {
request.initTransIfRequired();
@@ -2000,22 +2003,32 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Return all the SPI BeanTypes.
*/
public List<? extends SpiBeanType<?>> getBeanTypes() {
public List<? extends BeanType<?>> getBeanTypes() {
return getBeanDescriptors();
}
/**
* Return the SPI bean types mapped to the given table.
*/
public List<? extends SpiBeanType<?>> getBeanTypes(String tableName) {
public List<? extends BeanType<?>> getBeanTypes(String tableName) {
return beanDescriptorManager.getBeanTypes(tableName);
}
@Override
public BeanType<?> getBeanTypeForQueueId(String queueId) {
return getBeanDescriptorByQueueId(queueId);
}
@Override
public BeanDescriptor<?> getBeanDescriptorByQueueId(String queueId) {
return beanDescriptorManager.getBeanDescriptorByQueueId(queueId);
}
/**
* Return the SPI bean types for the given bean class.
*/
@Override
public <T> SpiBeanType<T> getBeanType(Class<T> beanType) {
public <T> BeanType<T> getBeanType(Class<T> beanType) {
return getBeanDescriptor(beanType);
}
@@ -2113,6 +2126,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return callStackFactory.createCallStack(finalTrace);
}
@Override
public DocumentStore docStore() {
return documentStore;
}
@Override
public JsonContext json() {
// immutable thread safe so return shared instance
@@ -11,7 +11,8 @@ import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.plugin.SpiServerPlugin;
import com.avaje.ebean.plugin.Plugin;
import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
@@ -46,6 +47,10 @@ import com.avaje.ebeaninternal.server.transaction.TransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
import com.avaje.ebeaninternal.server.type.TypeManager;
import com.avaje.ebeanservice.docstore.api.DocStoreFactory;
import com.avaje.ebeanservice.docstore.api.DocStoreIntegration;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeanservice.docstore.none.NoneDocStoreFactory;
import com.fasterxml.jackson.core.JsonFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,10 +87,6 @@ public class InternalConfiguration {
private final BeanDescriptorManager beanDescriptorManager;
private final TransactionManager transactionManager;
private final TransactionScopeManager transactionScopeManager;
private final CQueryEngine cQueryEngine;
private final ClusterManager clusterManager;
@@ -100,15 +101,18 @@ public class InternalConfiguration {
private final JsonFactory jsonFactory;
private final DocStoreFactory docStoreFactory;
/**
* List of plugins (that ultimately the DefaultServer configures late in construction).
*/
private final List<SpiServerPlugin> plugins = new ArrayList<SpiServerPlugin>();
private final List<Plugin> plugins = new ArrayList<Plugin>();
public InternalConfiguration(XmlConfig xmlConfig, ClusterManager clusterManager,
ServerCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
this.jsonFactory = serverConfig.getJsonFactory();
this.xmlConfig = xmlConfig;
this.clusterManager = clusterManager;
@@ -130,25 +134,21 @@ public class InternalConfiguration {
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
this.transactionManager = createTransactionManager();
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
this.binder = getBinder(typeManager, databasePlatform);
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
}
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
externalTransactionManager = new JtaTransactionManager();
}
if (externalTransactionManager != null) {
externalTransactionManager.setTransactionManager(transactionManager);
this.transactionScopeManager = new ExternalTransactionScopeManager(transactionManager, externalTransactionManager);
logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]");
} else {
this.transactionScopeManager = new DefaultTransactionScopeManager(transactionManager);
}
private DocStoreFactory initDocStoreFactory(DocStoreFactory service) {
return service == null ? new NoneDocStoreFactory() : service;
}
/**
* Return the doc store factory.
*/
public DocStoreFactory getDocStoreFactory() {
return docStoreFactory;
}
/**
@@ -156,8 +156,8 @@ public class InternalConfiguration {
* later on the DefaultServer for late call to configure().
*/
public <T> T plugin(T maybePlugin) {
if (maybePlugin instanceof SpiServerPlugin) {
plugins.add((SpiServerPlugin) maybePlugin);
if (maybePlugin instanceof Plugin) {
plugins.add((Plugin) maybePlugin);
}
return maybePlugin;
}
@@ -165,10 +165,10 @@ public class InternalConfiguration {
/**
* Return the list of plugins we collected during construction.
*/
public List<SpiServerPlugin> getPlugins() {
public List<Plugin> getPlugins() {
// find additional plugins via ServiceLoader ...
for (SpiServerPlugin plugin : ServiceLoader.load(SpiServerPlugin.class)) {
for (Plugin plugin : ServiceLoader.load(Plugin.class)) {
if (!plugins.contains(plugin)) {
plugins.add(plugin);
}
@@ -244,34 +244,6 @@ public class InternalConfiguration {
return new NotSupportedJsonExpression();
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
private TransactionManager createTransactionManager() {
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
if (isAutoCommitMode()) {
return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
return new TransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
/**
* Return true if autoCommit mode is on.
*/
private boolean isAutoCommitMode() {
if (serverConfig.isAutoCommitMode()) {
// explicitly set
return true;
}
DataSource dataSource = serverConfig.getDataSource();
return dataSource instanceof DataSourcePool && ((DataSourcePool) dataSource).getAutoCommit();
}
public JsonContext createJsonContext(SpiEbeanServer server) {
return new DJsonContext(server, jsonFactory, typeManager);
@@ -317,10 +289,6 @@ public class InternalConfiguration {
return expressionFactory;
}
public TypeManager getTypeManager() {
return typeManager;
}
public Binder getBinder() {
return binder;
}
@@ -345,14 +313,6 @@ public class InternalConfiguration {
return deployUtil;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public TransactionScopeManager getTransactionScopeManager() {
return transactionScopeManager;
}
public CQueryEngine getCQueryEngine() {
return cQueryEngine;
}
@@ -368,4 +328,57 @@ public class InternalConfiguration {
public GeneratedPropertyFactory getGeneratedPropertyFactory() {
return new GeneratedPropertyFactory(serverConfig);
}
/**
* Create the DocStoreIntegration components for the given server.
*/
public DocStoreIntegration createDocStoreIntegration(SpiServer server) {
return plugin(docStoreFactory.create(server));
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
public TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses());
}
if (isAutoCommitMode()) {
return new AutoCommitTransactionManager(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses());
}
return new TransactionManager(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses());
}
/**
* Return true if autoCommit mode is on.
*/
private boolean isAutoCommitMode() {
if (serverConfig.isAutoCommitMode()) {
// explicitly set
return true;
}
DataSource dataSource = serverConfig.getDataSource();
return dataSource instanceof DataSourcePool && ((DataSourcePool) dataSource).getAutoCommit();
}
/**
* Create the TransactionScopeManager taking into account JTA or external transaction manager.
*/
public TransactionScopeManager createTransactionScopeManager(TransactionManager transactionManager) {
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
externalTransactionManager = new JtaTransactionManager();
}
if (externalTransactionManager != null) {
externalTransactionManager.setTransactionManager(transactionManager);
logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]");
return new ExternalTransactionScopeManager(transactionManager, externalTransactionManager);
} else {
return new DefaultTransactionScopeManager(transactionManager);
}
}
}
@@ -12,6 +12,7 @@ import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.text.json.JsonReadOptions;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.HashQuery;
@@ -59,6 +60,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
private PersistenceContext persistenceContext;
private JsonReadOptions jsonRead;
private HashQuery cacheKey;
private CQueryPlanKey queryPlanKey;
@@ -68,11 +71,9 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
/**
* Create the InternalQueryRequest.
*/
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery<T> query, BeanDescriptor<T> desc, SpiTransaction t) {
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery<T> query, SpiTransaction t) {
super(server, t);
this.beanDescriptor = desc;
this.beanDescriptor = query.getBeanDescriptor();
this.rawSql = query.getRawSql();
this.finder = beanDescriptor.getBeanFinder();
this.queryEngine = queryEngine;
@@ -96,8 +97,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return ebeanServer.getDatabasePlatform().getLikeClause();
}
public void executeSecondaryQueries() {
loadContext.executeSecondaryQueries(this);
public void executeSecondaryQueries(boolean forEach) {
// disable lazy loading leaves loadContext null
if (loadContext != null) {
loadContext.executeSecondaryQueries(this, forEach);
}
}
/**
@@ -133,6 +137,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return loadContext;
}
@Override
public boolean isUseDocStore() {
return query.isUseDocStore();
}
/**
* Run BeanQueryAdapter preQuery() if needed.
*/
@@ -149,7 +158,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
public void prepareQuery() {
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
this.queryPlanKey = query.prepare(this);
}
@@ -186,7 +194,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
*/
public void persistenceContextAdd(EntityBean bean) {
Object id = beanDescriptor.getId(bean);
persistenceContext.put(id, bean);
beanDescriptor.contextPut(persistenceContext, id, bean);
}
/**
@@ -210,9 +218,27 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
createdTransaction = true;
}
}
// initialise the persistenceContext and loadContext
this.persistenceContext = getPersistenceContext(query, transaction);
this.loadContext = new DLoadContext(this, secondaryQueries);
persistenceContext = getPersistenceContext(query, transaction);
loadContext = new DLoadContext(this, secondaryQueries);
}
/**
* Return the JsonReadOptions taking into account lazy loading and persistence context.
*/
public JsonReadOptions createJsonReadOptions() {
persistenceContext = getPersistenceContext(query, transaction);
if (query.getPersistenceContext() == null) {
query.setPersistenceContext(persistenceContext);
}
jsonRead = new JsonReadOptions();
jsonRead.setPersistenceContext(persistenceContext);
if (!query.isDisableLazyLoading()) {
loadContext = new DLoadContext(this, secondaryQueries);
jsonRead.setLoadContext(loadContext);
}
return jsonRead;
}
/**
@@ -221,6 +247,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
public void flushPersistenceContextOnIterate() {
persistenceContext = new DefaultPersistenceContext();
loadContext.resetPersistenceContext(persistenceContext);
if (jsonRead != null) {
jsonRead.setPersistenceContext(persistenceContext);
jsonRead.setLoadContext(loadContext);
}
}
/**
@@ -236,7 +266,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
// determine the scope (from the query and then server)
PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query);
return (scope == PersistenceContextScope.QUERY) ? new DefaultPersistenceContext() : t.getPersistenceContext();
return (scope == PersistenceContextScope.QUERY || t == null) ? new DefaultPersistenceContext() : t.getPersistenceContext();
}
/**
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.annotation.DocStoreMode;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.event.BeanPersistController;
@@ -19,9 +20,13 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.persist.PersistExecute;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdate;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -30,7 +35,7 @@ import java.util.Set;
/**
* PersistRequest for insert update or delete of a bean.
*/
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T> {
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, DocStoreUpdate {
private final BeanManager<T> beanManager;
@@ -64,6 +69,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private final boolean publish;
private DocStoreMode docStoreMode;
private ConcurrencyMode concurrencyMode;
/**
@@ -102,6 +109,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private Set<String> updatedProperties;
/**
* Flags indicating the dirty properties on the bean.
*/
private boolean[] dirtyProperties;
/**
* Flag set when request is added to JDBC batch.
*/
@@ -135,7 +147,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.parentBean = parentBean;
this.controller = beanDescriptor.getPersistController();
this.type = type;
this.docStoreMode = calcDocStoreMode(transaction, type);
if (saveRecurse) {
this.persistCascade = t.isPersistCascade();
}
@@ -157,6 +169,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.dirty = intercept.isDirty();
}
/**
* Return the document store event that should be used for this request.
*
* Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we
* don't want to send to the doc store.
*/
private DocStoreMode calcDocStoreMode(SpiTransaction txn, Type type) {
DocStoreMode txnMode = (txn == null) ? null : txn.getDocStoreMode();
return beanDescriptor.getDocStoreMode(type, txnMode);
}
/**
* Return true if the draftDirty property should be set to true for this request.
*/
@@ -178,7 +201,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
createImplicitTransIfRequired();
if (createImplicitTransIfRequired()) {
docStoreMode = calcDocStoreMode(transaction, type);
}
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
@@ -239,6 +265,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.getDirtyPropertyNames();
}
/**
* Return the dirty properties on this request.
*/
public boolean[] getDirtyProperties() {
return dirtyProperties;
}
/**
* Return true if any of the given property names are dirty.
*/
@@ -247,6 +280,19 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.hasDirtyProperty(propertyNames);
}
/**
* Return true if any of the given properties are dirty.
*/
public boolean hasDirtyProperty(int[] propertyPositions) {
for (int i = 0; i < propertyPositions.length; i++) {
if (dirtyProperties[propertyPositions[i]]) {
return true;
}
}
return false;
}
@Override
public Map<String, ValuePair> getUpdatedValues() {
return intercept.getDirtyValues();
@@ -254,7 +300,15 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isNotify() {
this.notifyCache = beanDescriptor.isCacheNotify(publish);
return notifyCache || isNotifyPersistListener();
return notifyCache || isNotifyPersistListener() || isDocStoreNotify();
}
/**
* Return true if this request should updateAdd an ElasticSearch index
* by queuing an event or direct updateAdd (via Bulk API).
*/
private boolean isDocStoreNotify() {
return docStoreMode != DocStoreMode.IGNORE;
}
public boolean isNotifyPersistListener() {
@@ -283,6 +337,45 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Process the persist request updating the document store.
*/
public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
switch (type) {
case INSERT:
beanDescriptor.docStoreInsert(idValue, this, txn);
break;
case UPDATE:
beanDescriptor.docStoreUpdate(idValue, this, txn);
break;
case DELETE:
beanDescriptor.docStoreDeleteById(idValue, txn);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
/**
* Add this event to the queue entries in IndexUpdates.
*/
public void addToQueue(DocStoreUpdates docStoreUpdates) {
switch (type) {
case INSERT:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case UPDATE:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case DELETE:
docStoreUpdates.queueDelete(beanDescriptor.getDocStoreQueueId(), idValue);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
beanPersistMap.add(beanDescriptor, type, idValue);
@@ -469,6 +562,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return false;
}
/**
* Return true if this was a hard/permanent delete request (and should cascade as such).
*/
public boolean isHardDeleteCascade() {
return (type == Type.DELETE && beanDescriptor.isSoftDelete());
}
/**
* Checks for @Draftable entity beans with @Draft property that the bean is a 'draft'.
* Save or Update is not allowed to execute using 'live' beans - must use publish().
@@ -605,7 +705,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private void postUpdate() {
if (statelessUpdate) {
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
beanDescriptor.contextClear(transaction.getPersistenceContext(), idValue);
}
}
@@ -613,9 +713,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Aggressive L1 and L2 cache cleanup for deletes.
*/
private void postDelete() {
// Delete the bean from the PersistenceContent
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
// Delete from cache early even if transaction fails
beanDescriptor.contextClear(transaction.getPersistenceContext(), idValue);
beanDescriptor.cacheHandleDelete(idValue, this);
}
@@ -637,6 +735,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controllerPost();
}
if (type == Type.UPDATE && docStoreMode == DocStoreMode.UPDATE) {
// get the dirty properties for update notification to the doc store
dirtyProperties = intercept.getDirtyProperties();
}
// if bean persisted again then should result in an update
intercept.setLoaded();
if (isInsert()) {
@@ -806,6 +908,29 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return updatedManysOnly;
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
if (type == Type.UPDATE) {
beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates);
}
switch (docStoreMode) {
case UPDATE: {
docStoreUpdates.addPersist(this);
return;
}
case QUEUE: {
if (type == Type.DELETE) {
docStoreUpdates.queueDelete(beanDescriptor.getDocStoreQueueId(), idValue);
} else {
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
}
}
}
}
/**
* Determine if all loaded properties should be used for an update.
@@ -874,4 +999,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return Type.SOFT_DELETE == type;
}
/**
* Set the value of the Version property on the bean.
*/
public void setVersionValue(Object versionValue) {
beanDescriptor.getVersionProperty().setValueIntercept(entityBean, versionValue);
}
}
@@ -7,6 +7,7 @@ import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeanservice.docstore.api.DocQueryRequest;
import java.util.List;
import java.util.Map;
@@ -15,7 +16,7 @@ import java.util.Set;
/**
* Defines the ORM query request api.
*/
public interface SpiOrmQueryRequest<T> {
public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
/**
* Return the query.
@@ -116,4 +117,9 @@ public interface SpiOrmQueryRequest<T> {
* Mark the underlying transaction as not being query only.
*/
void markNotQueryOnly();
/**
* Return true if this query is expected to use the doc store.
*/
boolean isUseDocStore();
}
@@ -0,0 +1,79 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import java.sql.SQLException;
/**
* Local interface to handle Embedded, Reference and Reference Exported
* cases.
*/
abstract class AssocOneHelp {
protected final BeanPropertyAssocOne property;
protected final BeanDescriptor target;
AssocOneHelp(BeanPropertyAssocOne property) {
this.property = property;
this.target = property.targetDescriptor;
}
/**
* Effectively skip reading (the jdbc resultSet as already in the persistence context etc).
*/
void loadIgnore(DbReadContext ctx) {
property.targetIdBinder.loadIgnore(ctx);
}
/**
* Read and return the bean.
*/
Object read(DbReadContext ctx) throws SQLException {
// Support for Inheritance hierarchy on exported OneToOne ?
Object id = property.targetIdBinder.read(ctx);
if (id == null) {
return null;
}
PersistenceContext pc = ctx.getPersistenceContext();
Object existing = target.contextGet(pc, id);
if (existing != null) {
return existing;
}
Object ref = target.contextRef(pc, ctx.isReadOnly(), id);
EntityBeanIntercept ebi = ((EntityBean) ref)._ebean_getIntercept();
ctx.register(property.name, ebi);
return ref;
}
/**
* Read setting values into the bean.
*/
Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
Object val = read(ctx);
if (bean != null) {
property.setValue(bean, val);
ctx.propagateState(val);
}
return val;
}
/**
* Append to the select clause.
*/
abstract void appendSelect(DbSqlContext ctx, boolean subQuery);
/**
* Append to the from clause.
*/
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
// nothing required here
}
}
@@ -0,0 +1,61 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.bean.EntityBean;
import java.sql.SQLException;
/**
* Helper for Embedded BeanPropertyAssocOne.
*/
final class AssocOneHelpEmbedded extends AssocOneHelp {
public AssocOneHelpEmbedded(BeanPropertyAssocOne property) {
super(property);
}
void loadIgnore(DbReadContext ctx) {
for (int i = 0; i < property.embeddedProps.length; i++) {
property.embeddedProps[i].loadIgnore(ctx);
}
}
@Override
Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
Object dbVal = read(ctx);
if (bean != null) {
// set back to the parent bean
property.setValue(bean, dbVal);
ctx.propagateState(dbVal);
return dbVal;
} else {
return null;
}
}
@Override
Object read(DbReadContext ctx) throws SQLException {
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
boolean notNull = false;
for (int i = 0; i < property.embeddedProps.length; i++) {
Object value = property.embeddedProps[i].readSet(ctx, embeddedBean);
if (value != null) {
notNull = true;
}
}
if (notNull) {
ctx.propagateState(embeddedBean);
return embeddedBean;
} else {
return null;
}
}
@Override
void appendSelect(DbSqlContext ctx, boolean subQuery) {
for (int i = 0; i < property.embeddedProps.length; i++) {
property.embeddedProps[i].appendSelect(ctx, subQuery);
}
}
}

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