Compare commits

...
Author SHA1 Message Date
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
Robin Bygrave 76033385dc [maven-release-plugin] prepare release avaje-ebeanorm-6.18.1 2016-02-17 21:50:22 +13:00
Robin Bygrave b61cd30e29 Bump pom to 6.18.1-SNAPSHOT 2016-02-17 21:49:07 +13:00
Robin Bygrave 18edaf877b #420 - Review JodaLocalTime (being stored and fetched using UTC) 2016-02-17 21:36:47 +13:00
Robin Bygrave f055a0d0cb #420 - Review JodaLocalTime (being stored and fetched using UTC) 2016-02-17 21:34:52 +13:00
Robin Bygrave a22574b0c9 #552 - wrong DDL generation with complex columnDefinition 2016-02-17 20:34:13 +13:00
Robin Bygrave 3130f751f5 Merge branch 'refactor-query-hash' 2016-02-17 19:45:01 +13:00
Robin Bygrave c83606c1da #566 - Refactor internals - SpiQuerySecondary and DLoadContext 2016-02-17 19:44:30 +13:00
Robin Bygrave 439858b259 #566 - Refactor internals -OrmQueryDetailProperties - remove hasProperties() 2016-02-17 16:53:32 +13:00
Robin Bygrave 3d3948aa8b #566 - Refactor internals - OrmQueryDetail, skip fetch path sort when empty 2016-02-17 16:47:38 +13:00
Robin Bygrave 27045baf44 #566 - Refactor internals - OrmQueryDetailProperties with final FetchConfig 2016-02-17 16:33:38 +13:00
Robin Bygrave 5c6d6608ef #566 - Refactor internals - Initial tidy on OrmQueryDetail and OrmQueryDetailProperties - internals for fetch paths etc 2016-02-17 13:59:48 +13:00
Robin Bygrave 2fcaab44c6 #566 - Refactor internals - Initial tidy on OrmQueryDetail and OrmQueryDetailProperties 2016-02-17 10:52:55 +13:00
Robin Bygrave 69bf1758bc #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up OrmQueryRequest 2016-02-16 17:07:56 +13:00
Robin Bygrave af554410fd #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up OrmQueryDetail 2016-02-16 16:10:39 +13:00
Robin Bygrave ae7827c483 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up createQueryRequest - part 2 2016-02-16 09:21:10 +13:00
Robin Bygrave 4df2d4020e #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - tidy up createQueryRequest 2016-02-16 08:57:12 +13:00
Robin Bygrave 1aeffd31e0 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - no effective change, reorder methods 2016-02-16 01:46:12 +13:00
Robin Bygrave 5c59066ff8 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - rawSql key 2016-02-16 01:06:30 +13:00
Robin Bygrave a2bf8a0e46 [maven-release-plugin] prepare for next development iteration 2016-02-15 23:49:34 +13:00
Robin Bygrave 3ef77ea410 [maven-release-plugin] prepare release avaje-ebeanorm-6.17.3 2016-02-15 23:49:08 +13:00
Robin Bygrave fe2c2e5331 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - sameByPlan() & sameByBind() 2016-02-15 23:46:28 +13:00
Robin Bygrave 3d91a4f927 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - sameByPlan() & sameByBind() 2016-02-15 23:44:56 +13:00
Robin Bygrave 165d4aca92 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - remove unused 2016-02-15 10:21:22 +13:00
Robin Bygrave 076f8a03b9 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - move to protected calculateQueryPlanHash() 2016-02-13 21:22:52 +13:00
Robin Bygrave ca944eb756 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - refactor InQuery and Exists query 2016-02-13 20:52:41 +13:00
Robin Bygrave 769e4bb488 #566 - Refactor internals - SpiExpression, add prepareExpression() to merge queryAutoTuneHash() and queryPlanHash() - initial 2016-02-13 20:13:16 +13:00
Robin Bygrave 0d456b2ad6 Update TestOrderByFetch for running in Postgres ... by adding a 'name' property to the beans so that there is at least one property in the inserts 2016-02-13 13:09:41 +13:00
Robin Bygrave 39e913cbe2 #565 - Query plan hash collision on firstRow/maxRows combinations - change prime 2016-02-13 12:30:31 +13:00
Robin Bygrave ae38672e02 #565 - Query plan hash collision on firstRow/maxRows combinations - tests 2016-02-13 12:30:00 +13:00
Robin Bygrave 8a8b36ec1e #462 - When fetching, collections can get populated with too many entities - Fix 2016-02-13 00:43:47 +13:00
Robin Bygrave 62aca1e77b #462 - When fetching, collections can get populated with too many entities - Fix 2016-02-12 23:33:17 +13:00
Robin Bygrave f0b80068b6 #462 - No effective change, reformat code ... When fetching, collections can get populated with too many entities - 2016-02-12 23:32:43 +13:00
Rob Bygrave 5721fe3807 Merge pull request #560 from nedap/master
Add test for broken fetching issue #462
2016-02-12 21:58:35 +13:00
Robin Bygrave afffb88a29 [maven-release-plugin] prepare for next development iteration 2016-02-12 21:51:59 +13:00
Pieter Bos 42b112c3d7 Add test for broken fetching issue #462 2016-02-11 17:00:39 +01:00
323 changed files with 13479 additions and 2492 deletions
+5 -5
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.17.2</version>
<version>7.1.1</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>
@@ -71,7 +71,7 @@ class DRawSqlParser {
preFrom = trimSelectKeyword(preFrom);
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
return new Sql(sql, preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
}
/**
@@ -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,121 @@
package com.avaje.ebean;
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 used to update the associated 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.
*/
<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.
*/
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.
*/
@Nullable
<T> T getById(Class<T> beanType, Object id);
/**
* Execute the query against the document store returning the list.
*/
<T> List<T> findList(Query<T> query);
/**
* 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>
*/
<T> PagedList<T> findPagedList(Query<T> query);
/**
* 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>
*/
<T> void findEach(Query<T> query, QueryEachConsumer<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).
*/
void dropIndex(String indexName);
/**
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
*
* @param indexName the name of the new index
* @param alias the alias of the index
* @param mappingResource the path of the mapping file as a resource in the classpath
*/
void createIndex(String indexName, String alias, String mappingResource);
/**
* 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>
*
* @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>
*
* @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);
}
@@ -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,6 +1,5 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
@@ -95,7 +94,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
@@ -249,4 +249,22 @@ public class FetchConfig implements Serializable {
return queryAll;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FetchConfig that = (FetchConfig) o;
if (lazyBatchSize != that.lazyBatchSize) return false;
if (queryBatchSize != that.queryBatchSize) return false;
return queryAll == that.queryAll;
}
@Override
public int hashCode() {
int result = lazyBatchSize;
result = 92821 * result + queryBatchSize;
result = 92821 * result + (queryAll ? 1 : 0);
return result;
}
}
@@ -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);
}
+3 -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;
@@ -588,10 +587,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);
}
}
+17 -4
View File
@@ -1,6 +1,5 @@
package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
@@ -275,7 +274,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 +521,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.
@@ -1286,6 +1285,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 +1372,10 @@ public interface Query<T> extends Serializable {
* </p>
*/
Set<String> validate();
/**
* Return the query in JSON form for ElasticSearch doc store.
*/
String asElasticQuery();
}
+80 -47
View File
@@ -206,6 +206,14 @@ public final class RawSql implements Serializable {
return sql;
}
/**
* Return the key;
*/
public Key getKey() {
boolean parsed = sql != null && sql.parsed;
String unParsedSql = (sql == null) ? "" : sql.unparsedSql;
return new Key(parsed, unParsedSql, columnMapping);
}
/**
* Return the resultSet if this is a ResultSet based RawSql.
@@ -221,16 +229,6 @@ public final class RawSql implements Serializable {
return columnMapping;
}
/**
* Return the hash for this query.
*/
public int queryHash() {
if (resultSet != null) {
return 31 * columnMapping.queryHash();
}
return 31 * sql.queryHash() + columnMapping.queryHash();
}
/**
* Represents the sql part of the query. For parsed RawSql the sql is broken
* up so that Ebean can insert extra WHERE and HAVING expressions into the
@@ -260,13 +258,10 @@ public final class RawSql implements Serializable {
private final boolean distinct;
private final int queryHashCode;
/**
* Construct for unparsed SQL.
*/
protected Sql(String unparsedSql) {
this.queryHashCode = unparsedSql.hashCode();
this.parsed = false;
this.unparsedSql = unparsedSql;
this.preFrom = null;
@@ -282,12 +277,11 @@ public final class RawSql implements Serializable {
/**
* Construct for parsed SQL.
*/
protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr,
protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr,
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
this.queryHashCode = queryHashCode;
this.unparsedSql = unparsedSql;
this.parsed = true;
this.unparsedSql = null;
this.preFrom = preFrom;
this.preHaving = preHaving;
this.preWhere = preWhere;
@@ -298,13 +292,6 @@ public final class RawSql implements Serializable {
this.distinct = distinct;
}
/**
* Return a hash for this query.
*/
public int queryHash() {
return queryHashCode;
}
public String toString() {
if (!parsed) {
return "unparsed[" + unparsedSql + "]";
@@ -406,13 +393,10 @@ public final class RawSql implements Serializable {
private final boolean immutable;
private final int queryHashCode;
/**
* Construct from parsed sql where the columns have been identified.
*/
protected ColumnMapping(List<Column> columns) {
this.queryHashCode = 0;
this.immutable = false;
this.parsed = true;
this.propertyMap = null;
@@ -428,7 +412,6 @@ public final class RawSql implements Serializable {
* Construct for unparsed sql.
*/
protected ColumnMapping() {
this.queryHashCode = 0;
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
@@ -443,17 +426,13 @@ public final class RawSql implements Serializable {
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
//this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<String, Column>();
int hc = 31;
int pos = 0;
for (String prop : propertyNames) {
hc = 31 * hc + prop.hashCode();
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
}
propertyColumnMap = dbColumnMap;
this.queryHashCode = hc;
}
/**
@@ -464,20 +443,28 @@ public final class RawSql implements Serializable {
this.parsed = parsed;
this.dbColumnMap = dbColumnMap;
int hc = ColumnMapping.class.getName().hashCode();
HashMap<String, Column> pcMap = new HashMap<String, Column>();
HashMap<String, String> pMap = new HashMap<String, String>();
for (Column c : dbColumnMap.values()) {
pMap.put(c.getPropertyName(), c.getDbColumn());
pcMap.put(c.getPropertyName(), c);
hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode());
hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode());
}
this.propertyMap = Collections.unmodifiableMap(pMap);
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
this.queryHashCode = hc;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ColumnMapping that = (ColumnMapping) o;
return dbColumnMap.equals(that.dbColumnMap);
}
@Override
public int hashCode() {
return dbColumnMap.hashCode();
}
/**
@@ -521,16 +508,6 @@ public final class RawSql implements Serializable {
}
}
/**
* Return the query hash for this column mapping.
*/
public int queryHash() {
if (queryHashCode == 0) {
throw new RuntimeException("Bug: queryHashCode == 0");
}
return queryHashCode;
}
/**
* Returns true if the Columns where supplied by parsing the sql select
* clause.
@@ -648,6 +625,27 @@ public final class RawSql implements Serializable {
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Column that = (Column) o;
if (indexPos != that.indexPos) return false;
if (!dbColumn.equals(that.dbColumn)) return false;
if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false;
return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null;
}
@Override
public int hashCode() {
int result = indexPos;
result = 31 * result + dbColumn.hashCode();
result = 31 * result + (dbAlias != null ? dbAlias.hashCode() : 0);
result = 31 * result + (propertyName != null ? propertyName.hashCode() : 0);
return result;
}
public String toString() {
return dbColumn + "->" + propertyName;
}
@@ -699,4 +697,39 @@ public final class RawSql implements Serializable {
}
}
}
/**
* A key for the RawSql object using for the query plan.
*/
public static final class Key {
private final boolean parsed;
private final ColumnMapping columnMapping;
private final String unParsedSql;
Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) {
this.parsed = parsed;
this.unParsedSql = unParsedSql;
this.columnMapping = columnMapping;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key that = (Key) o;
return parsed == that.parsed
&& columnMapping.equals(that.columnMapping)
&& unParsedSql.equals(that.unParsedSql);
}
@Override
public int hashCode() {
int result = (parsed ? 1 : 0);
result = 31 * result + columnMapping.hashCode();
result = 31 * result + unParsedSql.hashCode();
return result;
}
}
}
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.annotation.DocStoreEvent;
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 setDocStoreUpdateMode(DocStoreEvent updateMode);
/**
* 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 setDocStoreUpdateBatchSize(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,44 @@
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();
/**
* 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 "";
}
@@ -0,0 +1,39 @@
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 "";
}
@@ -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.
*/
DocStoreEvent persist() default DocStoreEvent.DEFAULT;
/**
* Specify the behavior when bean Insert occurs.
*/
DocStoreEvent insert() default DocStoreEvent.DEFAULT;
/**
* Specify the behavior when bean Update occurs.
*/
DocStoreEvent update() default DocStoreEvent.DEFAULT;
/**
* Specify the behavior when bean Delete occurs.
*/
DocStoreEvent delete() default DocStoreEvent.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 DocStoreEvent {
/**
* 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
}
@@ -125,6 +125,11 @@ public interface BeanCollection<E> extends Serializable {
*/
void internalAdd(Object bean);
/**
* Add the bean with a check to see if it is already contained.
*/
void internalAddWithCheck(Object bean);
/**
* Return the number of elements in the List Set or Map.
*/
@@ -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,12 +10,12 @@ 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);
/**
* 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
@@ -1,5 +1,9 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@@ -8,17 +12,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
/**
* List capable of lazy loading.
*/
public final class BeanList<E> extends AbstractBeanCollection<E> implements List<E>, BeanCollectionAdd {
private static final long serialVersionUID = 1L;
/**
* The underlying List implementation.
*/
@@ -74,6 +74,13 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
@Override
public void internalAddWithCheck(Object bean) {
if (list == null || !list.contains(bean)) {
internalAdd(bean);
}
}
public boolean checkEmptyLazyLoad() {
if (list == null) {
list = new ArrayList<E>();
@@ -99,11 +106,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
private void initAsUntouched() {
init(false);
}
private void init() {
init(true);
}
private void init(boolean setTouched) {
synchronized (this) {
if (list == null) {
@@ -134,7 +141,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public Collection<E> getActualDetails() {
return list;
}
@Override
public Collection<?> getActualEntries() {
return list;
@@ -66,7 +66,18 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
map.put((K) key, (E) bean);
}
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@Override
public void internalAddWithCheck(Object bean) {
throw new RuntimeException("Not allowed for map");
}
public void internalAdd(Object bean) {
throw new RuntimeException("Not allowed for map");
}
@@ -1,15 +1,15 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
/**
* Set capable of lazy loading.
*/
@@ -57,6 +57,13 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
set.add((E) bean);
}
@Override
public void internalAddWithCheck(Object bean) {
if (set == null || !set.contains(bean)) {
internalAdd(bean);
}
}
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (set == null) {
@@ -107,11 +114,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
private void initAsUntouched() {
init(false);
}
private void init() {
init(true);
}
private void init(boolean setTouched) {
synchronized (this) {
if (set == null) {
@@ -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,138 @@
package com.avaje.ebean.config;
import com.avaje.ebean.annotation.DocStoreEvent;
/**
* Configuration for the Document store (ElasticSearch) integration.
*/
public class DocStoreConfig {
/**
* True when the Document store integration is active/on.
*/
boolean active;
/**
* When true the Document store should drop and re-create any document mapping (like DDL).
*/
boolean dropCreate;
/**
* The URL of the Document store server. For example: http://localhost:9200.
*/
String url;
/**
* The default mode used by indexes.
*/
DocStoreEvent persist = DocStoreEvent.UPDATE;
/**
* The default batch size to use for the Bulk API calls.
*/
int bulkBatchSize = 1000;
/**
* Return true if the Document store (ElasticSearch) integration is active.
*/
public boolean isActive() {
return active;
}
/**
* Set to true to make the Document store (ElasticSearch) integration active.
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* Return true if the document store should recreate mappings.
*/
public boolean isDropCreate() {
return dropCreate;
}
/**
* Set to true if the document store should recreate mappings.
*/
public void setDropCreate(boolean dropCreate) {
this.dropCreate = dropCreate;
}
/**
* Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
*/
public DocStoreEvent 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(DocStoreEvent persist) {
this.persist = persist;
}
/**
* Return the URL to the Document store.
*/
public String getUrl() {
return url;
}
/**
* Set the URL to the Document store server.
*
* For a local ElasticSearch server this would be: http://localhost:9200
*/
public void setUrl(String url) {
this.url = url;
}
/**
* 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#setDocStoreUpdateBatchSize(int)}.
* </p>
*/
public void setBulkBatchSize(int bulkBatchSize) {
this.bulkBatchSize = bulkBatchSize;
}
/**
* 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(DocStoreEvent.class, "docstore.persist", persist);
bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize);
}
}
@@ -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;
@@ -387,6 +394,8 @@ public class ServerConfig {
*/
private boolean expressionEqualsWithNullAsNoop;
private String jodaLocalTimeMode;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1151,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.
*/
@@ -1640,6 +1663,20 @@ public class ServerConfig {
this.disableClasspathSearch = disableClasspathSearch;
}
/**
* Return the mode to use for Joda LocalTime support 'normal' or 'utc'.
*/
public String getJodaLocalTimeMode() {
return jodaLocalTimeMode;
}
/**
* Set the mode to use for Joda LocalTime support 'normal' or 'utc'.
*/
public void setJodaLocalTimeMode(String jodaLocalTimeMode) {
this.jodaLocalTimeMode = jodaLocalTimeMode;
}
/**
* Programmatically add classes (typically entities) that this server should
* use.
@@ -2118,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.
*/
@@ -2195,6 +2249,13 @@ public class ServerConfig {
dataSourceConfig.loadSettings(p.withPrefix("datasource"));
}
/**
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
*/
protected void loadDocStoreSettings(PropertiesWrapper p) {
docStoreConfig.loadSettings(p);
}
/**
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
*/
@@ -2223,6 +2284,11 @@ public class ServerConfig {
}
loadDataSourceSettings(p);
if (docStoreConfig == null) {
docStoreConfig = new DocStoreConfig();
}
docStoreConfig.loadSettings(p);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
@@ -2286,6 +2352,7 @@ public class ServerConfig {
dbUuid = DbUuid.BINARY;
}
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
@@ -16,8 +16,6 @@ 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);
@@ -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;
@@ -14,7 +14,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
public PostgresHistoryDdl() {
this.currentTimestamp = "statement_timestamp()";
this.currentTimestamp = "current_timestamp";
}
/**
@@ -5,7 +5,7 @@ import com.avaje.ebean.config.dbplatform.DbTypeMap;
/**
* Converts a logical column definition into platform specific one.
*
* <p>
* This translates standard sql types into platform specific ones.
*/
public class PlatformTypeConverter {
@@ -44,20 +44,21 @@ public class PlatformTypeConverter {
return columnDefinition;
}
String type = columnDefinition.substring(0,open);
String suffix = close + 1 < columnDefinition.length() ? columnDefinition.substring(close + 1) : "";
String type = columnDefinition.substring(0, open);
try {
DbType dbType = platformTypes.lookup(type);
int comma = columnDefinition.indexOf(',',open);
int comma = columnDefinition.indexOf(',', open);
if (comma > -1) {
// scale and precision - decimal(10,4)
int scale = Integer.parseInt(columnDefinition.substring(open+1, comma));
int precision = Integer.parseInt(columnDefinition.substring(comma+1, close));
return dbType.renderType(scale,precision);
int scale = Integer.parseInt(columnDefinition.substring(open + 1, comma));
int precision = Integer.parseInt(columnDefinition.substring(comma + 1, close));
return dbType.renderType(scale, precision) + suffix;
} else {
// scale - varchar(10)
int scale = Integer.parseInt(columnDefinition.substring(open+1, close));
return dbType.renderType(scale,0);
int scale = Integer.parseInt(columnDefinition.substring(open + 1, close));
return dbType.renderType(scale, 0) + suffix;
}
} catch (IllegalArgumentException e) {
@@ -73,7 +74,7 @@ public class PlatformTypeConverter {
try {
DbType dbType = platformTypes.lookup(columnDefinition);
return dbType.renderType(0,0);
return dbType.renderType(0, 0);
} catch (IllegalArgumentException e) {
// assume already platform specific, leave as is
@@ -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());
}
@@ -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,157 @@
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.ebean.text.json.JsonReadOptions;
import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping;
import com.fasterxml.jackson.core.JsonParser;
import java.io.IOException;
import java.util.Collection;
/**
* Information and methods on BeanDescriptors made available to plugins.
*/
public interface BeanType<T> {
/**
* 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 SpiProperty for a property to read values from a bean.
*/
Property getProperty(String propertyName);
/**
* Return the SpiExpressionPath 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();
/**
* Read the JSON content returning the bean.
*/
T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException;
}
@@ -0,0 +1,20 @@
package com.avaje.ebean.plugin;
/**
* 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();
/**
* 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 set(Object bean, Object value);
}
@@ -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);
}
@@ -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 Set<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, Set<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;
@@ -160,9 +162,9 @@ public class PathProperties {
private final String parentPath;
private final String path;
private final Set<String> propSet;
private final LinkedHashSet<String> propSet;
private Props(PathProperties owner, String parentPath, String path, Set<String> propSet) {
private Props(PathProperties owner, String parentPath, String path, LinkedHashSet<String> propSet) {
this.owner = owner;
this.path = path;
this.parentPath = parentPath;
@@ -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;
}
@@ -195,7 +190,7 @@ public class PathProperties {
/**
* Return the properties for this property set.
*/
public Set<String> getProperties() {
public LinkedHashSet<String> getProperties() {
return propSet;
}
@@ -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);
/**
* 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,6 +1,6 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.FetchPath;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -56,6 +56,16 @@ 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;
/**
* Convert json string input into a list of beans of a specific type.
*
@@ -144,21 +154,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.
@@ -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,10 @@ public class JsonReadOptions {
protected Object objectMapper;
protected boolean enableLazyLoading;
protected PersistenceContext persistenceContext;
/**
* Default constructor.
*/
@@ -46,6 +52,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 +81,23 @@ 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;
}
}
@@ -3,9 +3,13 @@ package com.avaje.ebean.text.json;
import java.io.IOException;
/**
* Writes any scalar type known to Ebean the Jackson generator.
* Writes any scalar type known to Ebean to the underlying Jackson generator.
*/
public interface JsonScalar {
/**
* Write the scalar type to JSON where the value can be any type known to Ebean
* including Enums, Java8 time types, Joda types, URL, URI etc.
*/
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;
}
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.api;
/**
* Key used for caching query plans for ORM and RawSql queries.
*/
public interface CQueryPlanKey {
/**
* Used by read audit such that we can log read audit entries without the full sql
* (which would make the read audit logs verbose).
*/
String getPartialKey();
}
@@ -5,32 +5,18 @@ package com.avaje.ebeaninternal.api;
*/
public class HashQuery {
private final HashQueryPlan planHash;
private final CQueryPlanKey planHash;
private final int bindHash;
/**
* Create the HashQuery.
*/
public HashQuery(HashQueryPlan planHash, int bindHash) {
public HashQuery(CQueryPlanKey planHash, int bindHash) {
this.planHash = planHash;
this.bindHash = bindHash;
}
/**
* Return the query plan hash.
*/
public HashQueryPlan getPlanHash() {
return planHash;
}
/**
* Return the bind values hash.
*/
public int getBindHash() {
return bindHash;
}
public int hashCode() {
int hc = 31 * planHash.hashCode();
hc = 31 * hc + bindHash;
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.api;
import java.util.Set;
/**
* Used to build HashQueryPlan instances.
*/
@@ -9,21 +11,19 @@ public class HashQueryPlanBuilder {
private int bindCount;
private String rawSql;
public HashQueryPlanBuilder() {
this.planHash = 31;
this.planHash = 92821;
}
public String toString() {
return planHash+":"+bindCount+(rawSql != null ? ":r" : "");
return planHash+":"+bindCount;
}
/**
* Add a class to the hash calculation.
*/
public HashQueryPlanBuilder add(Class<?> cls) {
planHash = planHash * 31 + cls.getName().hashCode();
planHash = planHash * 92821 + cls.getName().hashCode();
return this;
}
@@ -31,7 +31,22 @@ public class HashQueryPlanBuilder {
* Add an object to the hash calculation.
*/
public HashQueryPlanBuilder add(Object object) {
planHash = planHash * 31 + (object == null ? 0 : object.hashCode());
planHash = planHash * 92821 + (object == null ? 0 : object.hashCode());
return this;
}
/**
* Add the set with order being important.
*/
public HashQueryPlanBuilder addOrdered(Set<?> set) {
if (set == null) {
add(false);
} else {
add(true);
for (Object o : set) {
add(o);
}
}
return this;
}
@@ -39,7 +54,7 @@ public class HashQueryPlanBuilder {
* Add an integer to the hash calculation.
*/
public HashQueryPlanBuilder add(int hashValue) {
planHash = planHash * 31 + (hashValue);
planHash = planHash * 92821 + (hashValue);
return this;
}
@@ -47,7 +62,7 @@ public class HashQueryPlanBuilder {
* Add a boolean to the hash calculation.
*/
public HashQueryPlanBuilder add(boolean booleanValue) {
planHash = planHash * 31 + (booleanValue ? 31 : 0);
planHash = planHash * 92821 + (booleanValue ? 92821 : 0);
return this;
}
@@ -58,19 +73,24 @@ public class HashQueryPlanBuilder {
bindCount += extraBindCount;
}
/**
* Add raw sql to the hash.
*/
public void addRawSql(String rawSql) {
this.rawSql = rawSql;
public void bindIfNotNull(Object someValue) {
if (someValue != null) {
bindCount++;
}
}
/**
* Build and return the calculated HashQueryPlan.
*/
public HashQueryPlan build() {
return new HashQueryPlan(rawSql, planHash, bindCount);
public String build() {
return planHash+"_"+bindCount;
}
public int getPlanHash() {
return planHash;
}
public int getBindCount() {
return bindCount;
}
}
@@ -5,6 +5,9 @@ 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.
@@ -22,17 +25,7 @@ public interface LoadContext {
*/
void executeSecondaryQueries(OrmQueryRequest<?> parentRequest);
/**
* Register any secondary queries (+query or +lazy) with their
* appropriate LoadBeanContext or LoadManyContext.
* <p>
* This is so the LoadBeanContext or LoadManyContext use the
* defined query for +query and +lazy execution.
* </p>
*/
void registerSecondaryQueries(SpiQuery<?> query);
/**
/**
* Return the node for a given path which is used by AutoTune profiling.
*/
ObjectGraphNode getObjectGraphNode(String path);
@@ -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());
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.TransactionCallback;
import com.avaje.ebean.annotation.DocStoreEvent;
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 DocStoreEvent getDocStoreUpdateMode() {
return transaction.getDocStoreUpdateMode();
}
@Override
public void setDocStoreUpdateMode(DocStoreEvent indexUpdateMode) {
transaction.setDocStoreUpdateMode(indexUpdateMode);
}
@Override
public int getDocStoreBulkBatchSize() {
return transaction.getDocStoreBulkBatchSize();
}
@Override
public void setDocStoreUpdateBatchSize(int batchSize) {
transaction.setDocStoreUpdateBatchSize(batchSize);
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
@@ -13,11 +13,8 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.util.List;
@@ -86,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.
*/
@@ -130,22 +132,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void remoteTransactionEvent(RemoteTransactionEvent event);
/**
* Create a query request object.
*/
<T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> q,
Transaction t);
/**
* Compile a query.
*/
<T> CQuery<T> compileQuery(Query<T> query, Transaction t);
/**
* Return the queryEngine for this server.
*/
CQueryEngine getQueryEngine();
/**
* Execute the findId's query but without copying the query.
* <p>
@@ -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.ElasticExpressionContext;
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 writeElastic(ElasticExpressionContext context) throws IOException;
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
@@ -17,32 +25,36 @@ public interface SpiExpression extends Expression {
* </p>
*/
void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins);
/**
/**
* Prepare the expression. For example, compile sub-query expressions etc.
*/
void prepareExpression(BeanQueryRequest<?> request);
/**
* Calculate a hash value used to identify a query for AutoTune tuning.
* <p>
* That is, if the hash changes then the query will be considered different
* from an AutoTune perspective and get different tuning.
* </p>
*/
void queryAutoTuneHash(HashQueryPlanBuilder builder);
/**
* Calculate a hash value for the expression.
* This includes the expression type and property but should exclude
* the bind values.
* <p>
* This is used where queries are the same except for the bind values, in which
* case the query execution plan can be reused.
* </p>
*/
void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
void queryPlanHash(HashQueryPlanBuilder builder);
/**
* Return the hash value for the values that will be bound.
*/
int queryBindHash();
/**
* Return true if the expression is the same without taking into account bind values.
*/
boolean isSameByPlan(SpiExpression other);
/**
* Return true if the expression is the same with respect to bind values.
*/
boolean isSameByBind(SpiExpression other);
/**
* Add some sql to the query.
* <p>
@@ -61,7 +73,7 @@ public interface SpiExpression extends Expression {
/**
* Add the parameter values to be set against query. For each ? place holder
* there should be a corresponding value that is added to the bindList.
*
*
* @param request
* the associated request.
*/
@@ -71,4 +83,10 @@ public interface SpiExpression extends Expression {
* Validate all the properties/paths associated with this expression.
*/
void validate(SpiExpressionValidation validation);
/**
* Return a copy of the expression for use in the query plan key.
*/
SpiExpression copyForPlanKey();
}
@@ -1,17 +1,13 @@
package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.List;
/**
* Internal extension of ExpressionList.
*/
public interface SpiExpressionList<T> extends ExpressionList<T> {
public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
/**
* Return the underlying list of expressions.
@@ -23,52 +19,9 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
SpiExpressionList<?> trimPath(int prefixTrim);
/**
* Restore the ExpressionFactory after deserialisation.
*/
void setExpressionFactory(ExpressionFactory expr);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent join clause.
* </p>
*/
void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoins);
/**
* Return true if this list is empty.
*/
boolean isEmpty();
/**
* Concatenate the expression sql into a String.
* <p>
* The list of expressions are evaluated in order building a sql statement
* with bind parameters.
* </p>
*/
String buildSql(SpiExpressionRequest request);
/**
* Combine the expression bind values into a list.
* <p>
* Expressions are evaluated in order and all the resulting bind values are
* returned as a List.
* </p>
*
* @return the list of all the bind values in order.
*/
ArrayList<Object> buildBindValues(SpiExpressionRequest request);
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
/**
* Validate all the properties/paths used in this expression list.
*/
void validate(SpiExpressionValidation validation);
}
@@ -1,11 +1,11 @@
package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.List;
/**
* Request object used for gathering expression sql and bind values.
*/
@@ -54,7 +54,7 @@ public interface SpiExpressionRequest {
/**
* Return the ordered list of bind values for all expressions in this request.
*/
ArrayList<Object> getBindValues();
List<Object> getBindValues();
/**
* Increments the parameter index and returns that value.
@@ -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;
}
@@ -11,16 +11,17 @@ 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;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext;
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.io.IOException;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
@@ -129,6 +130,26 @@ public interface SpiQuery<T> extends Query<T> {
}
}
/**
* Write the query as an elastic search query.
*/
void writeElastic(ElasticExpressionContext context) throws IOException;
/**
* 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,23 +298,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);
/**
* Initialise/determine the joins required to support 'many' where clause predicates.
*/
boolean initManyWhereJoins();
/**
* Return the joins required to support predicates on the many properties.
*/
@@ -319,31 +330,15 @@ public interface SpiQuery<T> extends Query<T> {
*/
void setFilterMany(String prop, ExpressionList<?> filterMany);
/**
* Remove the query joins from query detail.
* <p>
* These are registered with the Load Context.
* </p>
*/
List<OrmQueryProperties> removeQueryJoins();
/**
* Remove the lazy joins from query detail.
* <p>
* These are registered with the Load Context.
* </p>
*/
List<OrmQueryProperties> removeLazyJoins();
/**
* Set the path of the many when +query/+lazy loading query is executed.
*/
void setLazyLoadManyPath(String lazyLoadManyPath);
/**
* Convert any many joins fetch joins to query joins.
* Convert joins as necessary to query joins etc.
*/
void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch);
SpiQuerySecondary convertJoins();
/**
* Return the TransactionContext.
@@ -467,27 +462,13 @@ public interface SpiQuery<T> extends Query<T> {
String getName();
/**
* Calculate a hash used by AutoTune to identify when a query has changed
* (and hence potentially needs a new tuned query plan to be developed).
* Prepare the query which prepares sub-query expressions and calculates
* and returns the query plan key.
* <p>
* Excludes bind values and occurs prior to AutoTune potentially
* tuning/modifying the query.
* The query plan excludes actual bind values (as they don't effect the query plan).
* </p>
*/
HashQueryPlan queryAutoTuneHash(HashQueryPlanBuilder builder);
/**
* Identifies queries that are the same bar the bind variables.
* <p>
* This is used AFTER AutoTune has potentially tuned the query. This is
* used to identify and reused query plans (the final SQL string and
* associated SqlTree object).
* </p>
* <p>
* Excludes the actual bind values (as they don't effect the query plan).
* </p>
*/
HashQueryPlan queryPlanHash(BeanQueryRequest<?> request);
CQueryPlanKey prepare(BeanQueryRequest<?> request);
/**
* Calculate a hash based on the bind values used in the query.
@@ -675,9 +656,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();
/**
@@ -714,6 +695,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);
}
@@ -0,0 +1,21 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.util.List;
/**
* The secondary query paths for 'query joins' and 'lazy loading'.
*/
public interface SpiQuerySecondary {
/**
* Return a list of path/properties that are query join loaded.
*/
List<OrmQueryProperties> getQueryJoins();
/**
* Return the list of path/properties that are lazy loaded.
*/
List<OrmQueryProperties> getLazyJoins();
}
@@ -4,6 +4,7 @@ import java.sql.Connection;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.annotation.DocStoreEvent;
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>
*/
DocStoreEvent getDocStoreUpdateMode();
/**
* Return the batch size to us for ElasticSearch Bulk API calls
* as a result of this transaction.
*/
int getDocStoreBulkBatchSize();
/**
* 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);
}
}
}
}
@@ -110,7 +110,7 @@ public class ProfileOrigin {
Collection<Props> pathProperties = pathProps.getPathProps();
for (Props props : pathProperties) {
if (!props.isEmpty()) {
detail.addFetch(props.getPath(), props.getPropertiesAsString(), null);
detail.fetch(props.getPath(), props.getPropertiesAsString(), null);
}
}
@@ -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.
@@ -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;
}
/**
@@ -15,13 +15,12 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
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;
@@ -32,7 +31,6 @@ import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.api.SpiQuery.Type;
import com.avaje.ebeaninternal.api.SpiSqlQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -68,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;
@@ -140,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;
@@ -154,6 +153,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final JsonContext jsonContext;
private final DocumentStore documentStore;
private final MetaInfoManager metaInfoManager;
/**
@@ -221,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();
@@ -234,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);
@@ -250,7 +255,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
autoTuneService.startup();
for (SpiServerPlugin plugin : serverPlugins) {
for (Plugin plugin : serverPlugins) {
plugin.configure(this);
}
}
@@ -284,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);
}
}
@@ -410,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) {
@@ -463,19 +468,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return cqueryEngine.buildQuery(orm);
}
public CQueryEngine getQueryEngine() {
return cqueryEngine;
}
public ServerCacheManager getServerCacheManager() {
return serverCacheManager;
}
public void refreshMany(Object parentBean, String propertyName, Transaction t) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName, t);
}
public void refreshMany(Object parentBean, String propertyName) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName);
@@ -913,7 +909,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?");
}
@@ -923,7 +919,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
@@ -958,10 +954,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:
@@ -970,10 +965,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);
}
}
@@ -1048,69 +1043,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return findId(query, t);
}
private <T> SpiOrmQueryRequest<T> createQueryRequest(Type type, Query<T> query, Transaction t) {
<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, Query<T> query, Transaction t) {
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);
}
public <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();
}
if (query.selectAllForLazyLoadProperty()) {
// we need to select all properties to ensure the lazy load property
// was included (was not included by default or via autoTune).
if (logger.isDebugEnabled()) {
logger.debug("Using selectAllForLazyLoadProperty");
}
}
query.selectAllForLazyLoadProperty();
// if determine cost and no origin for AutoTune
if (query.getParentNode() == null) {
query.setOrigin(createCallStack());
}
// determine extra joins required to support where clause
// predicates on *ToMany properties
if (query.initManyWhereJoins()) {
// we need a sql distinct now
query.setSqlDistinct(true);
}
boolean allowOneManyFetch = true;
if (Mode.LAZYLOAD_MANY.equals(query.getMode())) {
allowOneManyFetch = false;
} else if (query.hasMaxRowsOrFirstRow() && !query.isRawSql() && !query.isSqlSelect()) {
// convert ALL fetch joins to Many's to be query joins
// so that limit offset type SQL clauses work
allowOneManyFetch = false;
}
query.convertManyFetchJoinsToQueryJoins(allowOneManyFetch, queryBatchSize);
SpiTransaction serverTrans = (SpiTransaction) t;
OrmQueryRequest<T> request = new OrmQueryRequest<T>(this, queryEngine, query, desc, serverTrans);
BeanQueryAdapter queryAdapter = desc.getQueryAdapter();
if (queryAdapter != null) {
// adaption of the query probably based on the
// current user
queryAdapter.preQuery(request);
}
// the query hash after any tuning
request.calculateQueryPlanHash();
OrmQueryRequest<T> request = new OrmQueryRequest<T>(this, queryEngine, query, (SpiTransaction) t);
request.prepareQuery();
return request;
}
@@ -1119,7 +1076,7 @@ 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) {
@@ -1130,7 +1087,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// first look in the transaction scoped persistence context
context = t.getPersistenceContext();
if (context != null) {
WithOption o = context.getWithOption(beanDescriptor.getBeanType(), query.getId());
WithOption o = context.getWithOption(query.getBeanType(), query.getId());
if (o != null) {
if (o.isDeleted()) {
// Bean was previously deleted in the same transaction / persistence context
@@ -1142,13 +1099,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
if (!beanDescriptor.calculateUseCache(query.isUseBeanCache())) {
BeanDescriptor<T> desc = query.getBeanDescriptor();
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, context);
}
/**
@@ -1172,19 +1130,16 @@ 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);
try {
request.initTransIfRequired();
return (T) request.findId();
@@ -1386,6 +1341,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
throw new PersistenceException("maxRows must be specified for findPagedList() query");
}
if (spiQuery.isUseDocStore()) {
return docStore().findPagedList(query);
}
return new LimitOffsetPagedList<T>(this, spiQuery);
}
@@ -1443,6 +1402,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (result != null) {
return (List<T>) result;
}
if (request.isUseDocStore()) {
return docStore().findList(query);
}
try {
request.initTransIfRequired();
@@ -2046,22 +2008,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);
}
@@ -2159,6 +2131,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);
}
}
}
@@ -1,26 +1,25 @@
package com.avaje.ebeaninternal.server.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.PersistenceException;
import com.avaje.ebean.*;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
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.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.HashQuery;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.LoadContext;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Type;
import com.avaje.ebeaninternal.api.SpiQuerySecondary;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -32,6 +31,13 @@ import com.avaje.ebeaninternal.server.query.CQueryPlan;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Wraps the objects involved in executing a Query.
*/
@@ -55,16 +61,16 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
private HashQuery cacheKey;
private HashQueryPlan queryPlanHash;
private CQueryPlanKey queryPlanKey;
private SpiQuerySecondary secondaryQueries;
/**
* 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;
@@ -125,11 +131,30 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return loadContext;
}
@Override
public boolean isUseDocStore() {
return query.isUseDocStore();
}
/**
* Calculate the query plan hash AFTER any potential AutoTune tuning.
* Run BeanQueryAdapter preQuery() if needed.
*/
public void calculateQueryPlanHash() {
this.queryPlanHash = query.queryPlanHash(this);
private void adapterPreQuery() {
BeanQueryAdapter queryAdapter = beanDescriptor.getQueryAdapter();
if (queryAdapter != null) {
queryAdapter.preQuery(this);
}
}
/**
* Prepare the query and calculate the query plan key.
*/
public void prepareQuery() {
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
this.queryPlanKey = query.prepare(this);
}
public boolean isRawSql() {
@@ -190,8 +215,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
}
// initialise the persistenceContext and loadContext
this.persistenceContext = getPersistenceContext(query, transaction);
this.loadContext = new DLoadContext(this);
this.loadContext.registerSecondaryQueries(query);
this.loadContext = new DLoadContext(this, secondaryQueries);
}
/**
@@ -215,7 +239,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) ? new DefaultPersistenceContext() : t.getPersistenceContext();
}
/**
@@ -305,7 +329,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
*/
@SuppressWarnings("unchecked")
public Set<?> findSet() {
return (Set<T>)queryEngine.findMany(this);
return (Set<T>) queryEngine.findMany(this);
}
/**
@@ -324,10 +348,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return (Map<?, ?>) queryEngine.findMany(this);
}
public SpiQuery.Type getQueryType() {
return query.getType();
}
/**
* Return a bean specific finder if one has been set.
*/
@@ -355,7 +375,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* query plan for this query exists.
*/
public CQueryPlan getQueryPlan() {
return beanDescriptor.getQueryPlan(queryPlanHash);
return beanDescriptor.getQueryPlan(queryPlanKey);
}
/**
@@ -366,15 +386,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* with just the bind variables changing.
* </p>
*/
public HashQueryPlan getQueryPlanHash() {
return queryPlanHash;
public CQueryPlanKey getQueryPlanKey() {
return queryPlanKey;
}
/**
* Put the QueryPlan into the cache.
*/
public void putQueryPlan(CQueryPlan queryPlan) {
beanDescriptor.putQueryPlan(queryPlanHash, queryPlan);
beanDescriptor.putQueryPlan(queryPlanKey, queryPlan);
}
public boolean isUseBeanCache() {
@@ -401,7 +421,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
for (T bean : actualDetails) {
ids.add(beanDescriptor.getIdForJson(bean));
}
beanDescriptor.readAuditMany(queryPlanHash.getPartialKey(), "l2-query-cache", ids);
beanDescriptor.readAuditMany(queryPlanKey.getPartialKey(), "l2-query-cache", ids);
}
return cached;
@@ -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.DocStoreEvent;
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 DocStoreEvent docStoreEvent;
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.docStoreEvent = calcDocStoreEvent(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 DocStoreEvent calcDocStoreEvent(SpiTransaction txn, Type type) {
DocStoreEvent docStoreEvent = (txn == null) ? null : txn.getDocStoreUpdateMode();
return beanDescriptor.getDocStoreEvent(type, docStoreEvent);
}
/**
* 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()) {
docStoreEvent = calcDocStoreEvent(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,16 @@ 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() {
// Either queue or directly update the document store
return docStoreEvent != DocStoreEvent.IGNORE;
}
public boolean isNotifyPersistListener() {
@@ -283,6 +338,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);
@@ -637,6 +731,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controllerPost();
}
if (type == Type.UPDATE && docStoreEvent == DocStoreEvent.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 +904,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 (docStoreEvent) {
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 +995,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);
}
}
@@ -116,4 +116,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();
}
@@ -42,7 +42,7 @@ public interface BeanCollectionHelp<T> {
/**
* Add a bean to the List Set or Map.
*/
void add(BeanCollection<?> collection, EntityBean bean);
void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck);
/**
* Create a lazy loading proxy for a List Set or Map.
@@ -7,6 +7,7 @@ import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.annotation.DocStoreEvent;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
@@ -28,8 +29,13 @@ import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebean.plugin.BeanDocType;
import com.avaje.ebean.plugin.BeanType;
import com.avaje.ebean.plugin.ExpressionPath;
import com.avaje.ebean.plugin.Property;
import com.avaje.ebean.text.json.JsonReadOptions;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.LoadContext;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -60,8 +66,13 @@ import com.avaje.ebeaninternal.server.text.json.ReadJson;
import com.avaje.ebeaninternal.server.text.json.WriteJson;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.util.SortByClause;
import com.avaje.ebeaninternal.util.SortByClause.Property;
import com.avaje.ebeaninternal.util.SortByClauseParser;
import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping;
import com.fasterxml.jackson.core.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,21 +84,21 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
private final ConcurrentHashMap<Integer, SpiUpdatePlan> updatePlanCache = new ConcurrentHashMap<Integer, SpiUpdatePlan>();
private final ConcurrentHashMap<HashQueryPlan, CQueryPlan> queryPlanCache = new ConcurrentHashMap<HashQueryPlan, CQueryPlan>();
private final ConcurrentHashMap<CQueryPlanKey, CQueryPlan> queryPlanCache = new ConcurrentHashMap<CQueryPlanKey, CQueryPlan>();
private final ConcurrentHashMap<String, ElPropertyValue> elCache = new ConcurrentHashMap<String, ElPropertyValue>();
@@ -240,7 +251,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final BeanProperty versionProperty;
private final int versionPropertyIndex;
private final BeanProperty whenModifiedProperty;
private final BeanProperty whenCreatedProperty;
/**
* Properties that are initialised in the constructor need to be 'unloaded' to support partial object queries.
*/
@@ -342,12 +357,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final boolean cacheSharableBeans;
private final String docStoreQueueId;
private DocumentMapping docMapping;
private final BeanDescriptorDraftHelp<T> draftHelp;
private final BeanDescriptorCacheHelp<T> cacheHelp;
private final BeanDescriptorJsonHelp<T> jsonHelp;
private final DocStoreBeanAdapter<T> docStoreAdapter;
private final String defaultSelectClause;
private final Set<String> defaultSelectClauseSet;
private final LinkedHashSet<String> defaultSelectClauseSet;
private SpiEbeanServer ebeanServer;
@@ -440,12 +460,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
this.derivedTableJoins = listHelper.getTableJoin();
boolean noRelationships = propertiesOne.length + propertiesMany.length == 0;
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp<T>(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = new BeanDescriptorJsonHelp<T>(this);
this.draftHelp = new BeanDescriptorDraftHelp<T>(this);
this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy);
this.docStoreQueueId = docStoreAdapter.getQueueId();
// Check if there are no cascade save associated beans ( subject to change
// in initialiseOther()). Note that if we are in an inheritance hierarchy
// then we also need to check every BeanDescriptors in the InheritInfo as
@@ -459,6 +482,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
// object used to handle Id values
this.idBinder = owner.createIdBinder(idProperty);
this.whenModifiedProperty = findWhenModifiedProperty();
this.whenCreatedProperty = findWhenCreatedProperty();
// derive the index position of the Id and Version properties
if (Modifier.isAbstract(beanType.getModifiers())) {
@@ -659,6 +684,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
namedUpdate.initialise(parser);
}
}
docStoreAdapter.registerPaths();
}
/**
* Initialise the document mapping.
*/
public void initialiseDocMapping() {
for (int i = 0; i < propertiesMany.length; i++) {
propertiesMany[i].initialisePostTarget();
}
docMapping = docStoreAdapter.createDocMapping();
}
public void initInheritInfo() {
@@ -856,7 +892,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
/**
* Return the default select clause already parsed into an ordered Set.
*/
public Set<String> getDefaultSelectClauseSet() {
public LinkedHashSet<String> getDefaultSelectClauseSet() {
return defaultSelectClauseSet;
}
@@ -868,6 +904,84 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return inheritInfo == null || inheritInfo.isRoot();
}
/**
* Return true if this type maps to a root type of a doc store document (not embedded or ignored).
*/
@Override
public boolean isDocStoreMapped() {
return docStoreAdapter.isMapped();
}
/**
* Return the queueId used to uniquely identify this type when queuing an index updateAdd.
*/
@Override
public String getDocStoreQueueId() {
return docStoreQueueId;
}
@Override
public DocumentMapping getDocMapping() {
return docMapping;
}
/**
* Return the doc store helper for this bean type.
*/
@Override
public BeanDocType docStore() {
return docStoreAdapter;
}
/**
* Return doc store adapter for internal use for processing persist requests.
*/
public DocStoreBeanAdapter<T> docStoreAdapter() {
return docStoreAdapter;
}
/**
* Build the Document mapping recursively with the given prefix relative to the root of the document.
*/
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (prefix != null && idProperty != null) {
// id property not included in the
idProperty.docStoreMapping(mapping, prefix);
}
for (BeanProperty prop: propertiesNonTransient) {
prop.docStoreMapping(mapping, prefix);
}
}
/**
* Return the type of DocStoreEvent that should occur for this type of persist request
* given the transactions requested mode.
*/
public DocStoreEvent getDocStoreEvent(PersistRequest.Type persistType, DocStoreEvent txnMode) {
return docStoreAdapter.getEvent(persistType, txnMode);
}
public void docStoreInsert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
docStoreAdapter.insert(idValue, persistRequest, bulkUpdate);
}
public void docStoreUpdate(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
docStoreAdapter.update(idValue, persistRequest, bulkUpdate);
}
/**
* Check if this update invalidates an embedded part of a doc store document.
*/
public void docStoreUpdateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) {
docStoreAdapter.updateEmbedded(request, docStoreUpdates);
}
public void docStoreDeleteById(Object idValue, DocStoreUpdateContext txn) throws IOException {
docStoreAdapter.deleteById(idValue, txn);
}
public T publish(T draftBean, T liveBean) {
return draftHelp.publish(draftBean, liveBean);
}
@@ -1163,11 +1277,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
}
public CQueryPlan getQueryPlan(HashQueryPlan key) {
public CQueryPlan getQueryPlan(CQueryPlanKey key) {
return queryPlanCache.get(key);
}
public void putQueryPlan(HashQueryPlan key, CQueryPlan plan) {
public void putQueryPlan(CQueryPlanKey key, CQueryPlan plan) {
queryPlanCache.put(key, plan);
}
@@ -1219,10 +1333,24 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return deleteRecurseSkippable && !isBeanCaching();
}
/**
* Return the 'when modified' property if there is one defined.
*/
public BeanProperty getWhenModifiedProperty() {
return whenModifiedProperty;
}
/**
* Return the 'when created' property if there is one defined.
*/
public BeanProperty getWhenCreatedProperty() {
return whenCreatedProperty;
}
/**
* Find a property annotated with @WhenCreated or @CreatedTimestamp.
*/
public BeanProperty findWhenCreatedProperty() {
private BeanProperty findWhenCreatedProperty() {
for (int i = 0; i < propertiesBaseScalar.length; i++) {
if (propertiesBaseScalar[i].isGeneratedWhenCreated()) {
@@ -1235,7 +1363,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
/**
* Find a property annotated with @WhenModified or @UpdatedTimestamp.
*/
public BeanProperty findWhenModifiedProperty() {
private BeanProperty findWhenModifiedProperty() {
for (int i = 0; i < propertiesBaseScalar.length; i++) {
if (propertiesBaseScalar[i].isGeneratedWhenModified()) {
@@ -1252,7 +1380,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
OrmQueryDetail detail = query.getDetail();
for (int i = 0; i < propertiesMany.length; i++) {
if (detail.includes(propertiesMany[i].getName())) {
if (detail.includesPath(propertiesMany[i].getName())) {
return propertiesMany[i];
}
}
@@ -1328,6 +1456,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return namedUpdates.get(name);
}
@Override
public T createBean() {
return (T)createEntityBean();
}
/**
* Creates a new EntityBean.
*/
@@ -1401,6 +1534,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return targetDesc.getBeanPropertyFromPath(split[1]);
}
@Override
public BeanType<?> getBeanTypeAtPath(String path) {
return getBeanDescriptor(path);
}
/**
* Return the BeanDescriptor for a given path of Associated One or Many beans.
*/
@@ -1473,6 +1611,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
/**
* Return the class type this BeanDescriptor describes.
*/
@Override
public Class<T> getBeanType() {
return beanType;
}
@@ -1484,6 +1623,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
* instead.
* </p>
*/
@Override
public String getFullName() {
return fullName;
}
@@ -1512,6 +1652,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return (idProperty == null) ? null : idProperty.getValue(bean);
}
@Override
public Object beanId(Object bean) {
return getId((EntityBean) bean);
}
@Override
public Object getBeanId(T bean) {
return getId((EntityBean) bean);
@@ -1552,6 +1697,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return idBinder.convertId(idValue);
}
/**
* Set the bean id value converting if necessary.
*/
@Override
public void setBeanId(T bean, Object idValue) {
idBinder.convertSetId(idValue, (EntityBean) bean);
}
/**
* Convert and set the id value.
* <p>
@@ -1563,6 +1716,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return idBinder.convertSetId(idValue, bean);
}
@Override
public Property getProperty(String propName) {
return getBeanProperty(propName);
}
/**
* Get a BeanProperty by its name.
*/
@@ -1585,6 +1743,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return c;
}
/**
* Register all the assoc many properties on this bean that are not populated with the load context.
* <p>
* This provides further lazy loading via the load context.
* </p>
*/
public void lazyLoadRegister(String prefix, EntityBeanIntercept ebi, EntityBean bean, LoadContext loadContext) {
// load the List/Set/Map proxy objects (deferred fetching of lists)
BeanPropertyAssocMany<?>[] manys = propertiesMany();
for (int i = 0; i < manys.length; i++) {
if (!ebi.isLoadedProperty(manys[i].getPropertyIndex())) {
BeanCollection<?> ref = manys[i].createReferenceIfNull(bean);
if (ref != null && !ref.isRegisteredWithLoadContext()) {
String path = SplitName.add(prefix, manys[i].getName());
loadContext.register(path, ref);
}
}
}
}
/**
* Return true if the lazy loading property is a Many in which case just
* define a Reference for the collection and not invoke a query.
@@ -1626,16 +1805,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
// create a compound comparator based on the list of properties
ElComparator<T>[] comparators = new ElComparator[sortBy.size()];
List<Property> sortProps = sortBy.getProperties();
List<SortByClause.Property> sortProps = sortBy.getProperties();
for (int i = 0; i < sortProps.size(); i++) {
Property sortProperty = sortProps.get(i);
SortByClause.Property sortProperty = sortProps.get(i);
comparators[i] = createPropertyComparator(sortProperty);
}
return new ElComparatorCompound<T>(comparators);
}
private ElComparator<T> createPropertyComparator(Property sortProp) {
private ElComparator<T> createPropertyComparator(SortByClause.Property sortProp) {
ElPropertyValue elGetValue = getElGetValue(sortProp.getName());
@@ -1670,6 +1849,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return elGetValue;
}
@Override
public ExpressionPath getExpressionPath(String path) {
return getElGetValue(path);
}
/**
* Similar to ElPropertyValue but also uses foreign key shortcuts.
* <p>
@@ -2130,6 +2314,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return derivedTableJoins;
}
@Override
public Collection<? extends Property> allProperties() {
return propertiesAll();
}
/**
* Return a collection of all BeanProperty. This includes transient properties.
*/
@@ -2168,6 +2357,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
}
@Override
public BeanProperty getIdProperty() {
return idProperty;
}
@@ -2442,6 +2632,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return propertiesLocal;
}
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonHelp.jsonWriteDirty(writeJson, bean, dirtyProps);
}
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException {
jsonHelp.jsonWrite(writeJson, bean, null);
}
@@ -2461,4 +2659,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException {
return jsonHelp.jsonReadObject(jsonRead, path);
}
@Override
public T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException {
ReadJson jsonRead = new ReadJson(this, parser, readOptions, objectMapper);
return jsonRead(jsonRead, null);
}
}
@@ -16,14 +16,14 @@ import java.util.Map;
public class BeanDescriptorJsonHelp<T> {
private final BeanDescriptor<T> desc;
private final InheritInfo inheritInfo;
public BeanDescriptorJsonHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.inheritInfo = desc.inheritInfo;
}
public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException {
writeJson.writeStartObject(key);
@@ -46,12 +46,36 @@ public class BeanDescriptorJsonHelp<T> {
protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException {
WriteBean writeBean = writeJson.createWriteBean(desc, bean);
writeBean.write(writeJson);
}
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
if (inheritInfo == null) {
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
} else {
InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass());
BeanDescriptor<?> localDescriptor = localInheritInfo.getBeanDescriptor();
localDescriptor.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
}
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
writeJson.writeStartObject(null);
// render the dirty properties
BeanProperty[] props = desc.propertiesNonTransient();
for (int j = 0; j < props.length; j++) {
if (dirtyProps[props[j].getPropertyIndex()]) {
props[j].jsonWrite(writeJson, bean);
}
}
writeJson.writeEndObject();
}
@SuppressWarnings("unchecked")
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
@@ -66,14 +90,14 @@ public class BeanDescriptorJsonHelp<T> {
return null;
}
if (JsonToken.START_OBJECT != token) {
throw new JsonParseException("Unexpected token "+token+" - expecting start_object", parser.getCurrentLocation());
throw new JsonParseException("Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
}
}
if (desc.inheritInfo == null) {
return jsonReadObject(jsonRead, path);
}
}
// check for the discriminator value to determine the correct sub type
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
@@ -81,8 +105,8 @@ public class BeanDescriptorJsonHelp<T> {
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(msg, parser.getCurrentLocation());
}
String propName = parser.getCurrentName();
String propName = parser.getCurrentName();
if (!propName.equalsIgnoreCase(discColumn)) {
// just try to assume this is the correct bean type in the inheritance
BeanProperty property = desc.getBeanProperty(propName);
@@ -91,24 +115,24 @@ public class BeanDescriptorJsonHelp<T> {
property.jsonRead(jsonRead, bean);
return jsonReadProperties(jsonRead, bean, path);
}
String msg = "Error reading inheritance discriminator, expected property ["+discColumn+"] but got [" + propName + "] ?";
String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?";
throw new JsonParseException(msg, parser.getCurrentLocation());
}
String discValue = parser.nextTextValue();
String discValue = parser.nextTextValue();
// determine the sub type for this particular json object
InheritInfo localInheritInfo = inheritInfo.readType(discValue);
BeanDescriptor<?> localDescriptor = localInheritInfo.getBeanDescriptor();
return (T) localDescriptor.jsonReadObject(jsonRead, path);
}
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBean();
return jsonReadProperties(readJson, bean, path);
}
@SuppressWarnings("unchecked")
protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException {
@@ -117,7 +141,7 @@ public class BeanDescriptorJsonHelp<T> {
}
// unmapped properties, send to JsonReadBeanVisitor later
Map<String,Object> unmappedProperties = null;
Map<String, Object> unmappedProperties = null;
do {
JsonParser parser = readJson.getParser();
@@ -141,16 +165,22 @@ public class BeanDescriptorJsonHelp<T> {
} else {
throw new RuntimeException("Unexpected token " + event + " - expecting key or end_object at: " + parser.getCurrentLocation());
}
} while (true);
// visit JsonReadBeanVisitor (if registered for this path)
readJson.beanVisitor(bean, unmappedProperties);
Object contextBean = null;
Object id = desc.beanId(bean);
if (id != null) {
// check if the bean has already been loaded
contextBean = readJson.persistenceContextPutIfAbsent(id, bean, desc);
}
if (contextBean == null) {
readJson.beanVisitor(bean, unmappedProperties);
}
if (path != null) {
readJson.popPath();
}
return (T)bean;
return contextBean == null ? (T) bean : (T) contextBean;
}
}
@@ -21,7 +21,7 @@ import com.avaje.ebean.event.changelog.ChangeLogFilter;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.plugin.BeanType;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.TransactionEventTable;
import com.avaje.ebeaninternal.server.core.BootupClasses;
@@ -52,6 +52,8 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertiesReader;
import com.avaje.ebeaninternal.server.properties.BeanPropertyInfo;
import com.avaje.ebeaninternal.server.properties.BeanPropertyInfoFactory;
import com.avaje.ebeaninternal.server.properties.EnhanceBeanPropertyInfoFactory;
import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
import com.avaje.ebeanservice.docstore.api.DocStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -111,12 +113,16 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final BeanManagerFactory beanManagerFactory;
private final ServerConfig serverConfig;
private final ChangeLogListener changeLogListener;
private final ChangeLogRegister changeLogRegister;
private final ChangeLogPrepare changeLogPrepare;
private final DocStoreFactory docStoreFactory;
private int enhancedClassCount;
private final boolean updateChangesOnly;
@@ -125,14 +131,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final String serverName;
private final ServerConfig serverConfig;
private Map<Class<?>, DeployBeanInfo<?>> deplyInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<Class<?>, BeanTable>();
private final Map<String, BeanDescriptor<?>> descMap = new HashMap<String, BeanDescriptor<?>>();
private final Map<String, BeanDescriptor<?>> descQueueMap = new HashMap<String, BeanDescriptor<?>>();
private final Map<String, BeanManager<?>> beanManagerMap = new HashMap<String, BeanManager<?>>();
private final Map<String, List<BeanDescriptor<?>>> tableToDescMap = new HashMap<String, List<BeanDescriptor<?>>>();
@@ -183,6 +189,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.serverConfig = config.getServerConfig();
this.serverName = InternString.intern(serverConfig.getName());
this.cacheManager = config.getCacheManager();
this.docStoreFactory = config.getDocStoreFactory();
this.xmlConfig = config.getXmlConfig();
this.dbSequenceBatchSize = serverConfig.getDatabaseSequenceBatchSize();
this.backgroundExecutor = config.getBackgroundExecutor();
@@ -246,6 +253,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return serverConfig;
}
@Override
public <T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor<T> deploy) {
return docStoreFactory.createAdapter(descriptor, deploy);
}
public BeanDescriptor<?> getBeanDescriptorByQueueId(String queueId) {
return descQueueMap.get(queueId);
}
@SuppressWarnings("unchecked")
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
return (BeanDescriptor<T>) descMap.get(entityType.getName());
@@ -359,7 +375,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Return the BeanDescriptors mapped to the table.
*/
public List<? extends SpiBeanType<?>> getBeanTypes(String tableName) {
public List<? extends BeanType<?>> getBeanTypes(String tableName) {
return tableToDescMap.get(tableName.toLowerCase());
}
@@ -433,6 +449,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap);
}
// PASS 4:
// now initialise document mapping which needs target descriptors
for (BeanDescriptor<?> d : descMap.values()) {
d.initialiseDocMapping();
}
// create BeanManager for each non-embedded entity bean
for (BeanDescriptor<?> d : descMap.values()) {
if (!d.isEmbedded()) {
@@ -518,6 +540,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private void registerBeanDescriptor(BeanDescriptor<?> desc) {
descMap.put(desc.getBeanType().getName(), desc);
if (desc.isDocStoreMapped()) {
descQueueMap.put(desc.getDocStoreQueueId(), desc);
}
}
/**
@@ -1050,7 +1075,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private <T> DeployBeanInfo<T> createDeployBeanInfo(Class<T> beanClass) {
DeployBeanDescriptor<T> desc = new DeployBeanDescriptor<T>(beanClass);
DeployBeanDescriptor<T> desc = new DeployBeanDescriptor<T>(beanClass, serverConfig);
desc.setUpdateChangesOnly(updateChangesOnly);
@@ -4,6 +4,8 @@ import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
/**
* Provides a method to find a BeanDescriptor.
@@ -38,6 +40,13 @@ public interface BeanDescriptorMap {
*/
EncryptKey getEncryptKey(String tableName, String columnName);
/**
* Create a IdBinder for this bean property.
*/
IdBinder createIdBinder(BeanProperty id);
/**
* Create a doc store specific adapter for this bean type.
*/
<T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor<T> deploy);
}
@@ -166,6 +166,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
throw new RuntimeException("ElPropertyDeploy only - not implemented");
}
@Override
public void set(Object bean, Object value) {
throw new RuntimeException("ElPropertyDeploy only - not implemented");
}
public void elSetValue(EntityBean bean, Object value, boolean populate) {
throw new RuntimeException("ElPropertyDeploy only - not implemented");
}
@@ -46,8 +46,12 @@ public final class BeanListHelp<T> implements BeanCollectionHelp<T> {
* Internal add bypassing any modify listening.
*/
@Override
public void add(BeanCollection<?> collection, EntityBean bean) {
collection.internalAdd(bean);
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
@@ -108,13 +108,14 @@ public final class BeanMapHelp<T> implements BeanCollectionHelp<T> {
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean) {
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (bean == null) {
((BeanMap<?, ?>) collection).internalPutNull();
} else {
Object keyValue = beanProperty.getValueIntercept(bean);
((BeanMap<?, ?>) collection).internalPut(keyValue, bean);
BeanMap<?, ?> map = ((BeanMap<?, ?>) collection);
map.internalPutWithCheck(keyValue, bean);
}
}
@@ -5,6 +5,10 @@ import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeaninternal.server.type.ScalarTypeEnum;
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import com.avaje.ebean.plugin.Property;
import com.avaje.ebean.text.StringParser;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
@@ -24,6 +28,9 @@ import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean;
import com.avaje.ebeaninternal.util.ValueUtil;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebeanservice.docstore.api.support.DocStructure;
import com.fasterxml.jackson.core.JsonToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +49,7 @@ import java.util.Map;
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class BeanProperty implements ElPropertyValue {
public class BeanProperty implements ElPropertyValue, Property {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
@@ -196,6 +203,8 @@ public class BeanProperty implements ElPropertyValue {
@SuppressWarnings("rawtypes")
final ScalarType scalarType;
final DocPropertyOptions docOptions;
/**
* The length or precision for DB column.
*/
@@ -313,6 +322,7 @@ public class BeanProperty implements ElPropertyValue {
this.lob = isLobType(dbType);
this.propertyType = deploy.getPropertyType();
this.field = deploy.getField();
this.docOptions = deploy.getDocPropertyOptions();
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
@@ -414,6 +424,7 @@ public class BeanProperty implements ElPropertyValue {
this.lob = isLobType(dbType);
this.propertyType = source.getPropertyType();
this.field = source.getField();
this.docOptions = source.docOptions;
this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn);
this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn);
@@ -557,6 +568,11 @@ public class BeanProperty implements ElPropertyValue {
}
}
@Override
public boolean isMany() {
return false;
}
public boolean isAssignableFrom(Class<?> type) {
return owningType.isAssignableFrom(type);
}
@@ -670,6 +686,15 @@ public class BeanProperty implements ElPropertyValue {
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the changed value without invoking interception (lazy loading etc).
* Typically used to set generated values on update.
*/
public void setValueChanged(EntityBean bean, Object value) {
setValue(bean, value);
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the value of the property without interception or
* PropertyChangeSupport.
@@ -716,6 +741,10 @@ public class BeanProperty implements ElPropertyValue {
throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar");
}
public Object getVal(Object bean) {
return getValue((EntityBean)bean);
}
/**
* Return the value of the property method.
*/
@@ -746,6 +775,14 @@ public class BeanProperty implements ElPropertyValue {
return convertToLogicalType(value);
}
@Override
public void set(Object bean, Object value) {
// convert for Enums etc
Object logicalVal = convertToLogicalType(value);
elSetValue((EntityBean) bean, logicalVal, true);
}
public void elSetValue(EntityBean bean, Object value, boolean populate) {
if (bean != null) {
// Not using setValueIntercept at this stage
@@ -1187,6 +1224,14 @@ public class BeanProperty implements ElPropertyValue {
return name;
}
/**
* Append this property to the document store based on includeByDefault setting.
*/
public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) {
if (includeByDefault) {
docStructure.addProperty(name);
}
}
@SuppressWarnings(value = "unchecked")
public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException {
@@ -1263,4 +1308,28 @@ public class BeanProperty implements ElPropertyValue {
map.put(propName, new ValuePair(newVal, oldVal));
}
}
/**
* Add to the document mapping if this property is included for this index.
*/
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (mapping.includesProperty(prefix, name)) {
DocPropertyType type = scalarType.getDocType();
DocPropertyOptions options = docOptions.copy();
if (DocPropertyType.STRING == type && isDocCode()) {
options.setCode(true);
}
mapping.add(new DocPropertyMapping(name, type, options));
}
}
/**
* Return true if this should be treated as a 'code' (effectively not analysed).
*/
private boolean isDocCode() {
return id || scalarType instanceof ScalarTypeEnum;
}
}
@@ -4,6 +4,12 @@ import java.util.ArrayList;
import javax.persistence.PersistenceException;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebeanservice.docstore.api.support.DocStructure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,7 +40,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
IdBinder targetIdBinder;
InheritInfo targetInheritInfo;
String targetIdProperty;
/**
@@ -59,6 +65,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
final String mappedBy;
final String docStoreDoc;
final String extraWhere;
boolean saveRecurseSkippable;
@@ -71,7 +79,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
this.extraWhere = InternString.intern(deploy.getExtraWhere());
this.beanTable = deploy.getBeanTable();
this.mappedBy = InternString.intern(deploy.getMappedBy());
this.docStoreDoc = deploy.getDocStoreDoc();
this.tableJoin = new TableJoin(deploy.getTableJoin());
this.targetType = deploy.getTargetType();
@@ -215,6 +223,63 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
return extraWhere;
}
/**
* Return the elastic search doc for this embedded property.
*/
public String getDocStoreDoc() {
return docStoreDoc;
}
/**
* Determine if and how the associated bean is included in the doc store document.
*/
@Override
public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) {
String embeddedDoc = getDocStoreDoc();
if (embeddedDoc == null) {
// not annotated so use include by default
// which is *ToOne included and *ToMany excluded
if (includeByDefault) {
docStoreIncludeByDefault(docStructure.doc());
}
} else {
// explicitly annotated to be included
if (embeddedDoc.isEmpty()) {
embeddedDoc = "*";
}
// add in a nested way
PathProperties embDoc = PathProperties.parse(embeddedDoc);
docStructure.addNested(name, embDoc);
}
}
/**
* Include the property in the document store by default.
*/
protected void docStoreIncludeByDefault(PathProperties pathProps) {
pathProps.addToPath(null, name);
}
@Override
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (mapping.includesPath(prefix, name)) {
String fullName = SplitName.add(prefix, name);
DocPropertyType type = isMany() ? DocPropertyType.LIST : DocPropertyType.OBJECT;
DocPropertyMapping nested = new DocPropertyMapping(name, type);
mapping.push(nested);
targetDescriptor.docStoreMapping(mapping, fullName);
mapping.pop();
if (!nested.getChildren().isEmpty()) {
mapping.add(nested);
}
}
}
/**
* Return true if this association is updateable.
*/
@@ -1,11 +1,16 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.*;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Expression;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollection.ModifyListenMode;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
@@ -91,6 +96,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
protected BeanPropertyAssocOne<?> childMasterProperty;
private String childMasterIdProperty;
private boolean embeddedExportedProperties;
private BeanCollectionHelp<T> help;
@@ -179,16 +186,31 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
}
/**
* Initialise after the target bean descriptors have been all set.
*/
public void initialisePostTarget() {
if (childMasterProperty != null) {
BeanProperty masterId = childMasterProperty.getTargetDescriptor().getIdProperty();
childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName();
}
}
@Override
protected void docStoreIncludeByDefault(PathProperties pathProps) {
// by default not including "Many" properties in document store
}
/**
* Add the bean to the appropriate collection on the parent bean.
*/
public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean) {
public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean, boolean withCheck) {
BeanCollection<?> bc = (BeanCollection<?>) super.getValue(parentBean);
if (bc == null) {
bc = help.createEmpty(parentBean);
setValue(parentBean, bc);
}
help.add(bc, detailBean);
help.add(bc, detailBean, withCheck);
}
/**
@@ -282,10 +304,29 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return server.findIds(q, t);
}
/**
* Add the loaded current bean to its associated parent.
*/
public void lazyLoadMany(EntityBean current) {
EntityBean parentBean = childMasterProperty.getValueAsEntityBean(current);
if (parentBean != null) {
addBeanToCollectionWithCreate(parentBean, current, true);
}
}
public void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds, boolean useDocStore) {
if (useDocStore) {
// assumes the ManyToOne property is included
query.where().in(childMasterIdProperty, parentIds);
} else {
addWhereParentIdIn(query, parentIds);
}
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
public void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
private void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = manyToMany ? "int_." : "t0.";
if (manyToMany) {
@@ -416,7 +457,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
public void add(BeanCollection<?> collection, EntityBean bean) {
help.add(collection, bean);
help.add(collection, bean, false);
}
/**
@@ -450,6 +491,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix);
}
@Override
public boolean isMany() {
return true;
}
@Override
public boolean isAssocId() {
@@ -904,6 +949,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
jsonHelp.jsonRead(readJson, parentBean);
}
@SuppressWarnings("unchecked")
public void publishMany(EntityBean draft, EntityBean live) {
// collections will not be null due to enhancement
@@ -61,8 +61,12 @@ public final class BeanSetHelp<T> implements BeanCollectionHelp<T> {
}
}
public void add(BeanCollection<?> collection, EntityBean bean) {
collection.internalAdd(bean);
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
@@ -0,0 +1,73 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.annotation.DocCode;
import com.avaje.ebean.annotation.DocProperty;
import com.avaje.ebean.annotation.DocSortable;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions;
/**
* The options for document property collected when reading deployment mapping.
*/
public class DeployDocPropertyOptions {
private Boolean code;
private Boolean sortable;
private Boolean store;
private Float boost;
private String nullValue;
/**
* Read the DocProperty deployment options.
*/
public void setDocProperty(DocProperty doc) {
code = checkDefault(doc.code());
sortable = checkDefault(doc.sortable());
store = checkDefault(doc.store());
boost = checkDefault(doc.boost());
nullValue = checkDefault(doc.nullValue());
}
/**
* Read the DocSortable deployment options.
*/
public void setDocSortable(DocSortable doc) {
sortable = Boolean.TRUE;
store = checkDefault(doc.store());
boost = checkDefault(doc.boost());
nullValue = checkDefault(doc.nullValue());
}
/**
* Read the DocCode deployment options.
*/
public void setDocCode(DocCode doc) {
code = Boolean.TRUE;
store = checkDefault(doc.store());
boost = checkDefault(doc.boost());
nullValue = checkDefault(doc.nullValue());
}
private String checkDefault(String s) {
return "".equals(s) ? null : s;
}
private Float checkDefault(float boost) {
return (boost == 1) ? null : boost;
}
private Boolean checkDefault(boolean store) {
return (store) ? Boolean.TRUE : null;
}
/**
* Return the DocPropertyOptions with the collected options.
*/
public DocPropertyOptions create() {
return new DocPropertyOptions(code, sortable, store, boost, nullValue);
}
}
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.avaje.ebean.EbeanServer;
@@ -9,7 +10,7 @@ import com.avaje.ebean.SqlUpdate;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
import com.avaje.ebeaninternal.server.expression.IdInExpression;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
public class IntersectionRow {
@@ -98,7 +99,7 @@ public class IntersectionRow {
sb.append(er.getSql());
sb.append(" ) ");
ArrayList<Object> bindValues = er.getBindValues();
List<Object> bindValues = er.getBindValues();
for (int i = 0; i < bindValues.size(); i++) {
bindParams.setParameter(++count, bindValues.get(i));
}
@@ -63,6 +63,32 @@ public final class TableJoin {
return hc;
}
@Override
public int hashCode() {
return queryHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableJoin that = (TableJoin) o;
if (!table.equals(that.table)) return false;
if (type != that.type) return false;
if (columns.length != that.columns.length) return false;
for (int i = 0; i < columns.length; i++) {
if (!columns[i].equals(that.columns[i])) {
return false;
}
}
return true;
}
/**
* Return a hash value for adding to a query plan.
*/
@@ -8,76 +8,97 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
*/
public class TableJoinColumn {
/**
* The local database column name.
*/
private final String localDbColumn;
/**
* The local database column name.
*/
private final String localDbColumn;
/**
* The foreign database column name.
*/
private final String foreignDbColumn;
/**
* The foreign database column name.
*/
private final String foreignDbColumn;
private final boolean insertable;
private final boolean updateable;
private final boolean insertable;
/**
* Hash for including in a query plan
*/
private final int queryHash;
private final boolean updateable;
/**
* Create the pair.
*/
public TableJoinColumn(DeployTableJoinColumn deploy) {
this.localDbColumn = InternString.intern(deploy.getLocalDbColumn());
this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn());
this.insertable = deploy.isInsertable();
this.updateable = deploy.isUpdateable();
this.queryHash = hashOf(localDbColumn) * 31 + hashOf(foreignDbColumn);
}
/**
* Hash for including in a query plan
*/
private final int queryHash;
private int hashOf(String value) {
return (value == null) ? 0 : value.hashCode();
}
/**
* Create the pair.
*/
public TableJoinColumn(DeployTableJoinColumn deploy) {
this.localDbColumn = InternString.intern(deploy.getLocalDbColumn());
this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn());
this.insertable = deploy.isInsertable();
this.updateable = deploy.isUpdateable();
this.queryHash = hash();
}
public String toString() {
return localDbColumn+" = "+foreignDbColumn;
}
int hash() {
int result = localDbColumn != null ? localDbColumn.hashCode() : 0;
result = 31 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0);
result = 31 * result + (insertable ? 1 : 0);
result = 31 * result + (updateable ? 1 : 0);
return result;
}
/**
* Return a hash for including in a query plan.
*/
public int queryHash() {
return queryHash;
}
@Override
public int hashCode() {
return queryHash;
}
/**
* Return the foreign database column name.
*/
public String getForeignDbColumn() {
return foreignDbColumn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
/**
* Return the local database column name.
*/
public String getLocalDbColumn() {
return localDbColumn;
}
TableJoinColumn that = (TableJoinColumn) o;
if (insertable != that.insertable) return false;
if (updateable != that.updateable) return false;
if (!localDbColumn.equals(that.localDbColumn)) return false;
return foreignDbColumn.equals(that.foreignDbColumn);
}
/**
* Return true if this column should be insertable.
*/
public boolean isInsertable() {
return insertable;
}
public String toString() {
return localDbColumn + " = " + foreignDbColumn;
}
/**
* Return true if this column should be updateable.
*/
public boolean isUpdateable() {
return updateable;
}
/**
* Return a hash for including in a query plan.
*/
public int queryHash() {
return queryHash;
}
/**
* Return the foreign database column name.
*/
public String getForeignDbColumn() {
return foreignDbColumn;
}
/**
* Return the local database column name.
*/
public String getLocalDbColumn() {
return localDbColumn;
}
/**
* Return true if this column should be insertable.
*/
public boolean isInsertable() {
return insertable;
}
/**
* Return true if this column should be updateable.
*/
public boolean isUpdateable() {
return updateable;
}
}
@@ -1,6 +1,9 @@
package com.avaje.ebeaninternal.server.deploy.meta;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.annotation.DocStore;
import com.avaje.ebean.annotation.DocStoreEvent;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.TableName;
import com.avaje.ebean.config.dbplatform.IdGenerator;
import com.avaje.ebean.config.dbplatform.IdType;
@@ -10,6 +13,7 @@ import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanPostLoad;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.changelog.ChangeLogFilter;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
import com.avaje.ebeaninternal.server.deploy.ChainedBeanPersistController;
@@ -33,7 +37,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Describes Beans including their deployment information.
@@ -54,6 +57,8 @@ public class DeployBeanDescriptor<T> {
private static final String I_SCALAOBJECT = "scala.ScalaObject";
private final ServerConfig serverConfig;
/**
* Map of BeanProperty Linked so as to preserve order.
*/
@@ -167,11 +172,31 @@ public class DeployBeanDescriptor<T> {
private String dbComment;
/**
* One of NONE, INDEX or EMBEDDED.
*/
private boolean docStoreMapped;
private DocStore docStore;
private PathProperties docStorePathProperties;
private String docStoreQueueId;
private String docStoreIndexName;
private String docStoreIndexType;
private DocStoreEvent docStorePersist;
private DocStoreEvent docStoreInsert;
private DocStoreEvent docStoreUpdate;
private DocStoreEvent docStoreDelete;
/**
* Construct the BeanDescriptor.
*/
public DeployBeanDescriptor(Class<T> beanType) {
public DeployBeanDescriptor(Class<T> beanType, ServerConfig serverConfig) {
this.serverConfig = serverConfig;
this.beanType = beanType;
}
@@ -235,6 +260,26 @@ public class DeployBeanDescriptor<T> {
return draftableElement;
}
/**
* Read the top level doc store deployment information.
*/
public void readDocStore(DocStore docStore) {
this.docStore = docStore;
docStoreMapped = true;
docStoreQueueId = docStore.queueId();
docStoreIndexName = docStore.indexName();
docStoreIndexType = docStore.indexType();
docStorePersist = docStore.persist();
docStoreInsert = docStore.insert();
docStoreUpdate = docStore.update();
docStoreDelete = docStore.delete();
String doc = docStore.doc();
if (doc != null && doc.length() > 0) {
docStorePathProperties = PathProperties.parse(doc);
}
}
public boolean isScalaObject() {
Class<?>[] interfaces = beanType.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
@@ -763,7 +808,7 @@ public class DeployBeanDescriptor<T> {
/**
* Parse the include separating by comma or semicolon.
*/
public Set<String> parseDefaultSelectClause(String rawList) {
public LinkedHashSet<String> parseDefaultSelectClause(String rawList) {
if (rawList == null) {
return null;
@@ -780,7 +825,7 @@ public class DeployBeanDescriptor<T> {
set.add(temp);
}
}
return Collections.unmodifiableSet(set);
return set;
}
/**
@@ -932,4 +977,61 @@ public class DeployBeanDescriptor<T> {
checkInheritance(parent);
}
}
public PathProperties getDocStorePathProperties() {
return docStorePathProperties;
}
/**
* Return true if this type is mapped for a doc store.
*/
public boolean isDocStoreMapped() {
return docStoreMapped;
}
public String getDocStoreQueueId() {
return docStoreQueueId;
}
public String getDocStoreIndexName() {
return docStoreIndexName;
}
public String getDocStoreIndexType() {
return docStoreIndexType;
}
public DocStore getDocStore() {
return docStore;
}
/**
* Return the DocStore index behavior for bean inserts.
*/
public DocStoreEvent getDocStoreInsertEvent() {
return getDocStoreIndexEvent(docStoreInsert);
}
/**
* Return the DocStore index behavior for bean updates.
*/
public DocStoreEvent getDocStoreUpdateEvent() {
return getDocStoreIndexEvent(docStoreUpdate);
}
/**
* Return the DocStore index behavior for bean deletes.
*/
public DocStoreEvent getDocStoreDeleteEvent() {
return getDocStoreIndexEvent(docStoreDelete);
}
private DocStoreEvent getDocStoreIndexEvent(DocStoreEvent mostSpecific) {
if (!docStoreMapped) {
return DocStoreEvent.IGNORE;
}
if (mostSpecific != DocStoreEvent.DEFAULT) return mostSpecific;
if (docStorePersist != DocStoreEvent.DEFAULT) return docStorePersist;
return serverConfig.getDocStoreConfig().getPersist();
}
}
@@ -1,6 +1,9 @@
package com.avaje.ebeaninternal.server.deploy.meta;
import com.avaje.ebean.annotation.CreatedTimestamp;
import com.avaje.ebean.annotation.DocCode;
import com.avaje.ebean.annotation.DocProperty;
import com.avaje.ebean.annotation.DocSortable;
import com.avaje.ebean.annotation.SoftDelete;
import com.avaje.ebean.annotation.UpdatedTimestamp;
import com.avaje.ebean.annotation.WhenCreated;
@@ -12,6 +15,7 @@ import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.DeployDocPropertyOptions;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
@@ -19,6 +23,7 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertySetter;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeEnum;
import com.avaje.ebeaninternal.server.type.ScalarTypeWrapper;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions;
import javax.persistence.EmbeddedId;
import javax.persistence.FetchType;
@@ -159,6 +164,8 @@ public class DeployBeanProperty {
*/
private int dbType;
private DeployDocPropertyOptions docMapping = new DeployDocPropertyOptions();
/**
* The method used to read the property.
*/
@@ -908,4 +915,21 @@ public class DeployBeanProperty {
public String getDbComment() {
return dbComment;
}
public void setDocProperty(DocProperty docProperty) {
docMapping.setDocProperty(docProperty);
}
public void setDocSortable(DocSortable docSortable) {
docMapping.setDocSortable(docSortable);
}
public void setDocCode(DocCode docCode) {
docMapping.setDocCode(docCode);
}
public DocPropertyOptions getDocPropertyOptions() {
return docMapping.create();
}
}
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.meta;
import com.avaje.ebean.annotation.DocStoreEmbedded;
import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo;
import com.avaje.ebeaninternal.server.deploy.BeanTable;
@@ -37,7 +38,9 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
* From the deployment mappedBy attribute.
*/
String mappedBy;
String docStoreDoc;
/**
* Construct the property.
*/
@@ -126,4 +129,16 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
this.mappedBy = mappedBy;
}
}
/**
* Set DocStoreEmbedded deployment information.
*/
public void setDocStoreEmbedded(DocStoreEmbedded embedded) {
docStoreDoc = embedded.doc();
}
public String getDocStoreDoc() {
return docStoreDoc;
}
}
@@ -11,6 +11,7 @@ import javax.persistence.UniqueConstraint;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.annotation.DocStore;
import com.avaje.ebean.annotation.DbComment;
import com.avaje.ebean.annotation.Draftable;
import com.avaje.ebean.annotation.DraftableElement;
@@ -166,6 +167,11 @@ public class AnnotationClass extends AnnotationParser {
descriptor.setDbComment(comment.value());
}
DocStore docStore = cls.getAnnotation(DocStore.class);
if (docStore != null) {
descriptor.readDocStore(docStore);
}
UpdateMode updateMode = cls.getAnnotation(UpdateMode.class);
if (updateMode != null) {
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
@@ -64,7 +64,7 @@ public class AnnotationFields extends AnnotationParser {
for (DeployBeanProperty prop : descriptor.propertiesAll()) {
if (prop instanceof DeployBeanPropertyAssoc<?>) {
readAssocOne(prop);
readAssocOne((DeployBeanPropertyAssoc<?>)prop);
} else {
readField(prop);
}
@@ -74,7 +74,7 @@ public class AnnotationFields extends AnnotationParser {
/**
* Read the Id marker annotations on EmbeddedId properties.
*/
private void readAssocOne(DeployBeanProperty prop) {
private void readAssocOne(DeployBeanPropertyAssoc<?> prop) {
readJsonAnnotations(prop);
@@ -91,6 +91,11 @@ public class AnnotationFields extends AnnotationParser {
prop.setEmbedded();
}
DocStoreEmbedded docStoreEmbedded = get(prop, DocStoreEmbedded.class);
if (docStoreEmbedded != null) {
prop.setDocStoreEmbedded(docStoreEmbedded);
}
if (prop instanceof DeployBeanPropertyAssocOne<?>) {
if (prop.isId() && !prop.isEmbedded()) {
prop.setEmbedded();
@@ -179,6 +184,19 @@ public class AnnotationFields extends AnnotationParser {
}
}
DocProperty docProperty = get(prop, DocProperty.class);
if (docProperty != null) {
prop.setDocProperty(docProperty);
}
DocSortable docSortable = get(prop, DocSortable.class);
if (docSortable != null) {
prop.setDocSortable(docSortable);
}
DocCode docCode = get(prop, DocCode.class);
if (docCode != null) {
prop.setDocCode(docCode);
}
if (get(prop, DbHstore.class) != null) {
util.setDbHstore(prop);
}
@@ -249,6 +249,11 @@ public class ElPropertyChain implements ElPropertyValue {
return chain[last].elGetValue(prevBean);
}
@Override
public void set(Object bean, Object value) {
elSetValue((EntityBean)bean, value, true);
}
public void elSetValue(EntityBean bean, Object value, boolean populate) {
EntityBean prevBean = bean;
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.el;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.plugin.ExpressionPath;
import com.avaje.ebean.text.StringParser;
/**
@@ -9,7 +10,7 @@ import com.avaje.ebean.text.StringParser;
* This can be used for local sorting and filtering.
* </p>
*/
public interface ElPropertyValue extends ElPropertyDeploy {
public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
/**
* Return the Id values for the given bean value.
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
@@ -21,19 +22,20 @@ public abstract class AbstractExpression implements SpiExpression {
this.propName = propName;
}
public String getPropertyName() {
return propName;
@Override
public SpiExpression copyForPlanKey() {
return this;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
String propertyName = getPropertyName();
if (propertyName != null) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName);
if (propName != null) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propName);
if (elProp != null) {
if (elProp.containsFormulaWithJoin()) {
// for findRowCount query select clause
manyWhereJoin.addFormulaWithJoin(propertyName);
manyWhereJoin.addFormulaWithJoin(propName);
}
if (elProp.containsMany()) {
// for findRowCount we join to a many property
@@ -44,13 +46,17 @@ public abstract class AbstractExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(getPropertyName());
public void prepareExpression(BeanQueryRequest<?> request) {
// do nothing
}
protected ElPropertyValue getElProp(SpiExpressionRequest request) {
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(propName);
}
String propertyName = getPropertyName();
return request.getBeanDescriptor().getElGetValue(propertyName);
protected final ElPropertyValue getElProp(SpiExpressionRequest request) {
return request.getBeanDescriptor().getElGetValue(propName);
}
}
@@ -1,9 +1,5 @@
package com.avaje.ebeaninternal.server.expression;
import java.util.Map;
import java.util.Map.Entry;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
@@ -12,7 +8,12 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
class AllEqualsExpression implements SpiExpression {
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
class AllEqualsExpression extends NonPrepareExpression {
private static final long serialVersionUID = -8691773558205937025L;
@@ -26,6 +27,23 @@ class AllEqualsExpression implements SpiExpression {
return propName;
}
@Override
public void writeElastic(ElasticExpressionContext context) throws IOException {
context.writeBoolMustStart();
for (Map.Entry<String, Object> entry : propMap.entrySet()) {
Object value = entry.getValue();
String propName = entry.getKey();
if (value == null) {
context.writeExists(false, propName);
} else {
context.writeTerm(propName, value);
}
}
context.writeBoolEnd();
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
if (propMap != null) {
for (String propertyName : propMap.keySet()) {
@@ -44,6 +62,7 @@ class AllEqualsExpression implements SpiExpression {
}
}
@Override
public void addBindValues(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
@@ -57,6 +76,7 @@ class AllEqualsExpression implements SpiExpression {
}
}
@Override
public void addSql(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
@@ -92,7 +112,8 @@ class AllEqualsExpression implements SpiExpression {
* The null check is required due to the "is null" sql being generated.
* </p>
*/
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(AllEqualsExpression.class);
@@ -100,14 +121,11 @@ class AllEqualsExpression implements SpiExpression {
Object value = entry.getValue();
String propName = entry.getKey();
builder.add(propName).add(value == null ? 0 : 1);
builder.bind(value == null ? 0 : 1);
builder.bindIfNotNull(value);
}
}
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoTuneHash(builder);
}
@Override
public int queryBindHash() {
int hc = 31;
@@ -117,4 +135,48 @@ class AllEqualsExpression implements SpiExpression {
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof AllEqualsExpression)) {
return false;
}
AllEqualsExpression that = (AllEqualsExpression) other;
return isSameByValue(that, false);
}
@Override
public boolean isSameByBind(SpiExpression other) {
if (!(other instanceof AllEqualsExpression)) {
return false;
}
AllEqualsExpression that = (AllEqualsExpression) other;
return isSameByValue(that, true);
}
private boolean isSameByValue(AllEqualsExpression that, boolean byValue) {
if (propMap.size() != that.propMap.size()) {
return false;
}
Iterator<Entry<String, Object>> thisIt = propMap.entrySet().iterator();
Iterator<Entry<String, Object>> thatIt = that.propMap.entrySet().iterator();
while (thisIt.hasNext() && thatIt.hasNext()) {
Entry<String, Object> thisNext = thisIt.next();
Entry<String, Object> thatNext = thatIt.next();
if (!thisNext.getKey().equals(thatNext.getKey())) {
return false;
}
if (!Same.sameBy(byValue, thisNext.getValue(), thatNext.getValue())) {
return false;
}
}
return true;
}
}
@@ -1,9 +1,10 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import java.io.IOException;
class BetweenExpression extends AbstractExpression {
@@ -21,28 +22,50 @@ class BetweenExpression extends AbstractExpression {
this.valueHigh = valHigh;
}
@Override
public void writeElastic(ElasticExpressionContext context) throws IOException {
context.writeRange(propName, Op.GT_EQ, valueLow, Op.LT_EQ, valueHigh);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(valueLow);
request.addBindValue(valueHigh);
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(getPropertyName()).append(BETWEEN).append(" ? and ? ");
request.append(propName).append(BETWEEN).append(" ? and ? ");
}
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(BetweenExpression.class).add(propName);
builder.bind(2);
}
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoTuneHash(builder);
}
@Override
public int queryBindHash() {
int hc = valueLow.hashCode();
hc = hc * 31 + valueHigh.hashCode();
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof BetweenExpression)) {
return false;
}
BetweenExpression that = (BetweenExpression) other;
return this.propName.equals(that.propName);
}
@Override
public boolean isSameByBind(SpiExpression other) {
BetweenExpression that = (BetweenExpression) other;
return valueLow.equals(that.valueLow)
&& valueHigh.equals(that.valueHigh);
}
}
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
@@ -9,10 +8,12 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import java.io.IOException;
/**
* Between expression where a value is between two properties.
*/
class BetweenPropertyExpression implements SpiExpression {
class BetweenPropertyExpression extends NonPrepareExpression {
private static final long serialVersionUID = 2078918165221454910L;
@@ -32,6 +33,16 @@ class BetweenPropertyExpression implements SpiExpression {
return propName;
}
@Override
public void writeElastic(ElasticExpressionContext context) throws IOException {
context.writeBoolMustStart();
context.writeSimple(Op.LT_EQ, lowProperty, value);
context.writeSimple(Op.GT_EQ, highProperty, value);
context.writeBoolEnd();
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(lowProperty));
@@ -51,25 +62,42 @@ class BetweenPropertyExpression implements SpiExpression {
validation.validate(highProperty);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(value);
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(" ? ").append(BETWEEN).append(name(lowProperty)).append(" and ").append(name(highProperty));
}
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(BetweenPropertyExpression.class).add(lowProperty).add(highProperty);
builder.bind(1);
}
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoTuneHash(builder);
}
@Override
public int queryBindHash() {
return value.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof BetweenPropertyExpression)) {
return false;
}
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
return lowProperty.equals(that.lowProperty)
&& highProperty.equals(that.highProperty);
}
@Override
public boolean isSameByBind(SpiExpression other) {
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
return value.equals(that.value);
}
}
@@ -1,10 +1,12 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import java.io.IOException;
class CaseInsensitiveEqualExpression extends AbstractExpression {
private static final long serialVersionUID = -6406036750998971064L;
@@ -16,6 +18,22 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
this.value = value.toLowerCase();
}
@Override
public void writeElastic(ElasticExpressionContext context) throws IOException {
String[] values = value.split(" ");
if (values.length == 1) {
context.writeMatch(propName, value);
} else {
context.writeBoolStart(true);
for (String val : values) {
context.writeMatch(propName, val);
}
context.writeBoolEnd();
}
}
@Override
public void addBindValues(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
@@ -28,29 +46,42 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
request.addBindValue(value);
}
@Override
public void addSql(SpiExpressionRequest request) {
String propertyName = getPropertyName();
String pname = propertyName;
String pname = propName;
ElPropertyValue prop = getElProp(request);
if (prop != null && prop.isDbEncrypted()) {
pname = prop.getBeanProperty().getDecryptProperty(propertyName);
pname = prop.getBeanProperty().getDecryptProperty(propName);
}
request.append("lower(").append(pname).append(") =? ");
}
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(CaseInsensitiveEqualExpression.class).add(propName);
builder.bind(1);
}
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
queryAutoTuneHash(builder);
}
@Override
public int queryBindHash() {
return value.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof CaseInsensitiveEqualExpression)) {
return false;
}
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
return this.propName.equals(that.propName);
}
@Override
public boolean isSameByBind(SpiExpression other) {
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
return value.equals(that.value);
}
}

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