query, int bulkBatchSize);
+
+ /**
+ * Update the document store for all beans of this type.
+ *
+ * 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 getById(Class beanType, Object id);
+
+ /**
+ * Execute the query against the document store returning the list.
+ */
+ List findList(Query query);
+
+ /**
+ * Execute the query against the document store returning the paged list.
+ *
+ * The query should have firstRow or maxRows set prior to calling this method.
+ *
+ */
+ PagedList findPagedList(Query 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.
+ *
+ * For example, with the ElasticSearch doc store this uses SCROLL.
+ *
+ */
+ void findEach(Query query, QueryEachConsumer consumer);
+
+ /**
+ * Process the queue entries sending updates to the document store or queuing them for later processing.
+ */
+ long process(List 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.
+ *
+ * This copy process does not use the database but instead will copy from the source index to a destination index.
+ *
+ *
+ * @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.
+ *
+ * To support this the document needs to have a @WhenModified property.
+ *
+ *
+ * @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);
+
+}
diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java
index ae17c78a3..bce142ffb 100644
--- a/src/main/java/com/avaje/ebean/EbeanServer.java
+++ b/src/main/java/com/avaje/ebean/EbeanServer.java
@@ -1891,6 +1891,11 @@ public interface EbeanServer {
*/
JsonContext json();
+ /**
+ * Return the Document store.
+ */
+ DocumentStore docStore();
+
/**
* Publish a single bean given its type and id returning the resulting live bean.
*
diff --git a/src/main/java/com/avaje/ebean/PersistenceIOException.java b/src/main/java/com/avaje/ebean/PersistenceIOException.java
new file mode 100644
index 000000000..08c26556f
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/PersistenceIOException.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java
index 0db48945f..363f2c90e 100644
--- a/src/main/java/com/avaje/ebean/Query.java
+++ b/src/main/java/com/avaje/ebean/Query.java
@@ -274,7 +274,7 @@ import java.util.Set;
* @param
* the type of Entity bean this query will fetch.
*/
-public interface Query extends Serializable {
+public interface Query {
/**
* Return the RawSql that was set to use for this query.
@@ -1285,6 +1285,14 @@ public interface Query extends Serializable {
*/
Query setUseQueryCache(boolean useQueryCache);
+ /**
+ * Set to true if this query should execute against the doc store.
+ *
+ * When setting this you may also consider disabling lazy loading.
+ *
+ */
+ Query setUseDocStore(boolean useDocStore);
+
/**
* When set to true when you want the returned beans to be read only.
*/
@@ -1364,4 +1372,10 @@ public interface Query extends Serializable {
*
*/
Set validate();
+
+ /**
+ * Return the query in JSON form for ElasticSearch doc store.
+ */
+ String asElasticQuery();
+
}
diff --git a/src/main/java/com/avaje/ebean/Transaction.java b/src/main/java/com/avaje/ebean/Transaction.java
index 67a553de4..549271cbe 100644
--- a/src/main/java/com/avaje/ebean/Transaction.java
+++ b/src/main/java/com/avaje/ebean/Transaction.java
@@ -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.
+ *
+ * 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.
+ *
+ */
+ void setDocStoreUpdateMode(DocStoreEvent updateMode);
+
+ /**
+ * Set the batch size to use for sending messages to the document store.
+ *
+ * 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.
+ *
+ *
+ * Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
+ *
+ */
+ 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
diff --git a/src/main/java/com/avaje/ebean/annotation/DocCode.java b/src/main/java/com/avaje/ebean/annotation/DocCode.java
new file mode 100644
index 000000000..582ba71f2
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocCode.java
@@ -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.
+ *
+ * By default all Id properties and all Enum properties are treated as 'code' and not analysed.
+ *
+ */
+@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 "";
+}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebean/annotation/DocMapping.java b/src/main/java/com/avaje/ebean/annotation/DocMapping.java
new file mode 100644
index 000000000..e8bb77b70
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocMapping.java
@@ -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 "";
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DocProperty.java b/src/main/java/com/avaje/ebean/annotation/DocProperty.java
new file mode 100644
index 000000000..f6e5fe471
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocProperty.java
@@ -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 "";
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DocSortable.java b/src/main/java/com/avaje/ebean/annotation/DocSortable.java
new file mode 100644
index 000000000..f0b87b450
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocSortable.java
@@ -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.
+ *
+ * For example, customer name and product name are good candidates for marking with @DocSortable.
+ *
+ */
+@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 "";
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DocStore.java b/src/main/java/com/avaje/ebean/annotation/DocStore.java
new file mode 100644
index 000000000..503abfe67
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocStore.java
@@ -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.
+ *
+ * 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.
+ *
+ *
+ * Note that typically DocStoreEmbedded is used on @ManyToOne and @OneToMany
+ * properties to indicate what part of the nested document should be included.
+ *
+ *
+ * Example:
+ * {@code
+ *
+ * // only include the customer id and name
+ * @DocStore(doc = "id,name")
+ * @Entity @Table(name = "o_order")
+ * public class Customer {
+ *
+ * }
+ */
+ String doc() default "";
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java b/src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java
new file mode 100644
index 000000000..66adc5e78
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocStoreEmbedded.java
@@ -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.
+ *
+ * {@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;
+ *
+ *
+ * }
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DocStoreEmbedded {
+
+ /**
+ * The properties on the embedded bean to include in the index.
+ */
+ String doc() default "";
+
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java b/src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java
new file mode 100644
index 000000000..dcf8a72c0
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DocStoreEvent.java
@@ -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.
+ *
+ * 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.
+ *
+ */
+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.
+ *
+ * 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.
+ *
+ */
+ IGNORE,
+
+ /**
+ * The actual mode of QUEUE, UPDATE or IGNORE is set from the default configuration.
+ */
+ DEFAULT
+
+}
diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
index bf1abcfdf..24462e614 100644
--- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
+++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
@@ -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.
*/
diff --git a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java
index 0c9357ffa..e1d1c87f8 100644
--- a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java
+++ b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java
@@ -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).
*
* Returns an existing entity bean (if one is already there) and otherwise
diff --git a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
index ca0a75c74..658d2d65f 100644
--- a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
@@ -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;
+ }
}
}
diff --git a/src/main/java/com/avaje/ebean/config/DocStoreConfig.java b/src/main/java/com/avaje/ebean/config/DocStoreConfig.java
new file mode 100644
index 000000000..f8169c644
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/config/DocStoreConfig.java
@@ -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.
+ *
+ * DocStoreEvent.UPDATE - build and send message to Bulk API
+ * DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing
+ * DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index
+ *
+ *
+ * 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.
+ *
+ *
+ * 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.
+ *
+ */
+ 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.
+ *
+ * The batch size can be set on a transaction via {@link com.avaje.ebean.Transaction#setDocStoreUpdateBatchSize(int)}.
+ *
+ */
+ 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);
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index d13fe2c97..b50a8d827 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -25,8 +25,10 @@ import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Properties;
+import java.util.ServiceLoader;
/**
* The configuration used for creating a EbeanServer.
@@ -129,6 +131,11 @@ public class ServerConfig {
*/
private String classPathReaderClassName;
+ /**
+ * Configuration for the ElasticSearch integration.
+ */
+ private DocStoreConfig docStoreConfig = new DocStoreConfig();
+
/**
* This is used to populate @WhoCreated, @WhoModified and
* support other audit features (who executed a query etc).
@@ -283,7 +290,7 @@ public class ServerConfig {
private DbConstraintNaming constraintNaming = new DbConstraintNaming();
/**
- * Behaviour of update to include on the change properties.
+ * Behaviour of update to include on the change properties.
*/
private boolean updateChangesOnly = true;
@@ -1153,6 +1160,20 @@ public class ServerConfig {
this.namingConvention = namingConvention;
}
+ /**
+ * Return the configuration for the ElasticSearch integration.
+ */
+ public DocStoreConfig getDocStoreConfig() {
+ return docStoreConfig;
+ }
+
+ /**
+ * Set the configuration for the ElasticSearch integration.
+ */
+ public void setDocStoreConfig(DocStoreConfig docStoreConfig) {
+ this.docStoreConfig = docStoreConfig;
+ }
+
/**
* Return the constraint naming convention used in DDL generation.
*/
@@ -2134,6 +2155,23 @@ public class ServerConfig {
this.classLoadConfig = classLoadConfig;
}
+ /**
+ * Return the service loader using the classLoader defined in ClassLoadConfig.
+ */
+ public ServiceLoader serviceLoad(Class spiService) {
+
+ return ServiceLoader.load(spiService, classLoadConfig.getClassLoader());
+ }
+
+ /**
+ * Return the first service using the service loader (or null).
+ */
+ public T service(Class spiService) {
+ ServiceLoader load = serviceLoad(spiService);
+ Iterator serviceInstances = load.iterator();
+ return serviceInstances.hasNext() ? serviceInstances.next() : null;
+ }
+
/**
* Load settings from ebean.properties.
*/
@@ -2211,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.
*/
@@ -2239,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);
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java
index 3b24b2337..3db639e35 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java
@@ -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());
}
diff --git a/src/main/java/com/avaje/ebean/plugin/BeanDocType.java b/src/main/java/com/avaje/ebean/plugin/BeanDocType.java
new file mode 100644
index 000000000..e679968f6
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/plugin/BeanDocType.java
@@ -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 The type of entity bean
+ */
+public interface BeanDocType {
+
+ /**
+ * 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 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.
+ *
+ * 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;
+
+}
diff --git a/src/main/java/com/avaje/ebean/plugin/BeanType.java b/src/main/java/com/avaje/ebean/plugin/BeanType.java
index ce44337e5..79d6c784d 100644
--- a/src/main/java/com/avaje/ebean/plugin/BeanType.java
+++ b/src/main/java/com/avaje/ebean/plugin/BeanType.java
@@ -5,32 +5,96 @@ 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 {
+ /**
+ * Return the full name of the bean type.
+ */
+ String getFullName();
+
/**
* Return the class type this BeanDescriptor describes.
*/
Class 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.
+ *
+ * This can return a property or nested property path.
+ *
+ */
+ 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.
- */
+ /**
+ * 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.
*/
@@ -61,4 +125,33 @@ public interface BeanType {
*/
String getSequenceName();
+ /**
+ * Return true if this bean type has doc store backing.
+ */
+ boolean isDocStoreMapped();
+
+ /**
+ * Return the DocumentMapping for this bean type.
+ *
+ * This is the document structure and mapping options for how this bean type is mapped
+ * for the document store.
+ *
+ */
+ DocumentMapping getDocMapping();
+
+ /**
+ * Return the doc store queueId for this bean type.
+ */
+ String getDocStoreQueueId();
+
+ /**
+ * Return the doc store support for this bean type.\
+ */
+ BeanDocType docStore();
+
+ /**
+ * Read the JSON content returning the bean.
+ */
+ T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException;
+
}
diff --git a/src/main/java/com/avaje/ebean/plugin/ExpressionPath.java b/src/main/java/com/avaje/ebean/plugin/ExpressionPath.java
new file mode 100644
index 000000000..e1ef6fbcb
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/plugin/ExpressionPath.java
@@ -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);
+}
diff --git a/src/main/java/com/avaje/ebean/plugin/Property.java b/src/main/java/com/avaje/ebean/plugin/Property.java
new file mode 100644
index 000000000..e8b45a3fb
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/plugin/Property.java
@@ -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();
+}
diff --git a/src/main/java/com/avaje/ebean/plugin/SpiServer.java b/src/main/java/com/avaje/ebean/plugin/SpiServer.java
index 854e96817..12a196339 100644
--- a/src/main/java/com/avaje/ebean/plugin/SpiServer.java
+++ b/src/main/java/com/avaje/ebean/plugin/SpiServer.java
@@ -36,4 +36,8 @@ public interface SpiServer extends EbeanServer {
*/
List extends BeanType>> getBeanTypes(String baseTableName);
+ /**
+ * Return the bean type for a given doc store queueId.
+ */
+ BeanType> getBeanTypeForQueueId(String queueId);
}
diff --git a/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java b/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java
new file mode 100644
index 000000000..6c78f089b
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java
@@ -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.
+ *
+ * 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).
+ *
+ */
+public interface JsonBeanReader {
+
+ /**
+ * 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 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();
+
+}
diff --git a/src/main/java/com/avaje/ebean/text/json/JsonContext.java b/src/main/java/com/avaje/ebean/text/json/JsonContext.java
index 28b540016..adcd55ba5 100644
--- a/src/main/java/com/avaje/ebean/text/json/JsonContext.java
+++ b/src/main/java/com/avaje/ebean/text/json/JsonContext.java
@@ -56,6 +56,16 @@ public interface JsonContext {
*/
T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
+ /**
+ * Create and return a new bean reading for the bean type given the JSON options and source.
+ *
+ * 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.
+ *
+ */
+ JsonBeanReader createBeanReader(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
+
/**
* Convert json string input into a list of beans of a specific type.
*
diff --git a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java
index 4e4f04cbb..aee1d9407 100644
--- a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java
+++ b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java
@@ -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.
+ *
+ * If this is set to true a persistence context will be created if one has
+ * not already been supplied.
+ *
+ */
+ 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;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebean/text/json/JsonScalar.java b/src/main/java/com/avaje/ebean/text/json/JsonScalar.java
index f1cd9cc2f..07d006980 100644
--- a/src/main/java/com/avaje/ebean/text/json/JsonScalar.java
+++ b/src/main/java/com/avaje/ebean/text/json/JsonScalar.java
@@ -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;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java
index cd8701768..d067559e9 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java
@@ -29,4 +29,5 @@ public interface LoadManyBuffer {
void configureQuery(SpiQuery> query);
+ boolean isUseDocStore();
}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java
index e6d918adb..9c845714d 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java
@@ -144,7 +144,7 @@ public class LoadManyRequest extends LoadRequest {
query.setLazyLoadForParents(many);
List idList = getParentIdList(batchSize);
- many.addWhereParentIdIn(query, idList);
+ many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
diff --git a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java
index f2a15ebd2..cd97cbaf4 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java
@@ -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();
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
index 24830422f..a01e2e5bd 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
@@ -83,6 +83,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
BeanDescriptor> getBeanDescriptorById(String className);
+ /**
+ * Return BeanDescriptor using it's unique doc store queueId.
+ */
+ BeanDescriptor> getBeanDescriptorByQueueId(String queueId);
+
/**
* Return BeanDescriptors mapped to this table.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java
index bacd508de..59aec8e2b 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpression.java
@@ -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.
*
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
index 11067a139..df2c81301 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
@@ -16,10 +16,12 @@ 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 java.io.IOException;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
@@ -128,6 +130,26 @@ public interface SpiQuery extends Query {
}
}
+ /**
+ * 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 getBeanDescriptor();
+
+ /**
+ * Return true if this query should be executed against the doc store.
+ */
+ boolean isUseDocStore();
+
/**
* Return the PersistenceContextScope that this query should use.
*
@@ -276,18 +298,13 @@ public interface SpiQuery extends Query {
/**
* Return the lazy loading 'many' property.
*/
- BeanPropertyAssocMany> getLazyLoadForParentsProperty();
+ BeanPropertyAssocMany> getLazyLoadMany();
/**
* Set the load mode (+lazy or +query) and the load description.
*/
void setLoadDescription(String loadMode, String loadDescription);
- /**
- * Set the BeanDescriptor for the root type of this query.
- */
- void setBeanDescriptor(BeanDescriptor> desc);
-
/**
* Return the joins required to support predicates on the many properties.
*/
@@ -639,9 +656,9 @@ public interface SpiQuery extends Query {
*/
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();
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
index 291bc6954..5be5af0a1 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
@@ -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.
+ *
+ * Returning 0 implies to use the system wide default batch size.
+ *
+ */
+ 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.
*
@@ -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);
diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
index 14a52af42..209c71a96 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
@@ -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> persistRequestBeans = getPersistRequestBeans();
+ if (persistRequestBeans != null) {
+ for (int i=0; i< persistRequestBeans.size(); i++) {
+ persistRequestBeans.get(i).addDocStoreUpdates(docStoreUpdates);
+ }
+ }
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java
index 23da4ce10..028f521d6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java
@@ -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.
*
+ *
+ * @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;
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index 7b4e9c44c..c4f35fd77 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -66,6 +66,7 @@ import com.avaje.ebeaninternal.server.transaction.TransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.util.ParamTypeHelper;
import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
+import com.avaje.ebeanservice.docstore.api.DocStoreIntegration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -152,6 +153,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final JsonContext jsonContext;
+ private final DocumentStore documentStore;
+
private final MetaInfoManager metaInfoManager;
/**
@@ -219,8 +222,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.maxCallStack = serverConfig.getMaxCallStack();
this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked();
- this.transactionManager = config.getTransactionManager();
- this.transactionScopeManager = config.getTransactionScopeManager();
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
@@ -232,6 +233,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
+
+ DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
+ this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
+ this.transactionScopeManager = config.createTransactionScopeManager(transactionManager);
+ this.documentStore = docStoreComponents.documentStore();
+
this.serverPlugins = config.getPlugins();
this.ddlGenerator = new DdlGenerator(this, serverConfig);
@@ -902,7 +909,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public Query createNamedQuery(Class beanType, String namedQuery) throws PersistenceException {
- BeanDescriptor> desc = getBeanDescriptor(beanType);
+ BeanDescriptor desc = getBeanDescriptor(beanType);
if (desc == null) {
throw new PersistenceException("Is " + beanType.getName() + " an Entity Bean? BeanDescriptor not found?");
}
@@ -912,7 +919,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// this will parse the query
- return new DefaultOrmQuery(beanType, this, expressionFactory, deployQuery);
+ return new DefaultOrmQuery(desc, this, expressionFactory, deployQuery);
}
@Override
@@ -947,10 +954,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
public Query createQuery(Class beanType, String query) {
- BeanDescriptor> desc = getBeanDescriptor(beanType);
+ BeanDescriptor desc = getBeanDescriptor(beanType);
if (desc == null) {
- String m = beanType.getName() + " is NOT an Entity Bean registered with this server?";
- throw new PersistenceException(m);
+ throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?");
}
switch (desc.getEntityType()) {
case SQL:
@@ -959,10 +965,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// use the "default" SqlSelect
DeployNamedQuery defaultSqlSelect = desc.getNamedQuery("default");
- return new DefaultOrmQuery(beanType, this, expressionFactory, defaultSqlSelect);
+ return new DefaultOrmQuery(desc, this, expressionFactory, defaultSqlSelect);
default:
- return new DefaultOrmQuery(beanType, this, expressionFactory, query);
+ return new DefaultOrmQuery(desc, this, expressionFactory, query);
}
}
@@ -1042,15 +1048,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery spiQuery = (SpiQuery) query;
spiQuery.setType(type);
- BeanDescriptor desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
- spiQuery.setBeanDescriptor(desc);
-
- return createQueryRequest(desc, spiQuery, t);
+ return createQueryRequest(spiQuery, t);
}
- private SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) {
+ private SpiOrmQueryRequest createQueryRequest(SpiQuery query, Transaction t) {
- if (desc.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) {
+ if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
@@ -1063,7 +1066,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
query.setOrigin(createCallStack());
}
- OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, desc, (SpiTransaction) t);
+ OrmQueryRequest request = new OrmQueryRequest(this, queryEngine, query, (SpiTransaction) t);
request.prepareQuery();
return request;
@@ -1073,7 +1076,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Try to get the object out of the persistence context.
*/
@SuppressWarnings("unchecked")
- private T findIdCheckPersistenceContextAndCache(Transaction transaction, BeanDescriptor beanDescriptor, SpiQuery query) {
+ private T findIdCheckPersistenceContextAndCache(Transaction transaction, SpiQuery query) {
SpiTransaction t = (SpiTransaction) transaction;
if (t == null) {
@@ -1084,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
@@ -1096,13 +1099,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
- if (!beanDescriptor.calculateUseCache(query.isUseBeanCache())) {
+ BeanDescriptor 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);
}
/**
@@ -1126,19 +1130,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery spiQuery = (SpiQuery) query;
spiQuery.setType(Type.BEAN);
- BeanDescriptor 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 request = createQueryRequest(desc, spiQuery, t);
+ SpiOrmQueryRequest request = createQueryRequest(spiQuery, t);
try {
request.initTransIfRequired();
return (T) request.findId();
@@ -1340,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(this, spiQuery);
}
@@ -1397,6 +1402,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (result != null) {
return (List) result;
}
+ if (request.isUseDocStore()) {
+ return docStore().findList(query);
+ }
try {
request.initTransIfRequired();
@@ -2011,6 +2019,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
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.
*/
@@ -2113,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
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
index 8dab12bd1..e0e56ef3c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
@@ -12,6 +12,7 @@ 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.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,6 +101,8 @@ public class InternalConfiguration {
private final JsonFactory jsonFactory;
+ private final DocStoreFactory docStoreFactory;
+
/**
* List of plugins (that ultimately the DefaultServer configures late in construction).
*/
@@ -109,6 +112,7 @@ public class InternalConfiguration {
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 asOfTableMapping = beanDescriptorManager.deploy();
Map 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;
}
/**
@@ -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);
+ }
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
index 76ea1aaae..2c890081a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
@@ -68,11 +68,9 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
/**
* Create the InternalQueryRequest.
*/
- public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, BeanDescriptor desc, SpiTransaction t) {
-
+ public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, SpiTransaction t) {
super(server, t);
-
- this.beanDescriptor = desc;
+ this.beanDescriptor = query.getBeanDescriptor();
this.rawSql = query.getRawSql();
this.finder = beanDescriptor.getBeanFinder();
this.queryEngine = queryEngine;
@@ -133,6 +131,11 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
return loadContext;
}
+ @Override
+ public boolean isUseDocStore() {
+ return query.isUseDocStore();
+ }
+
/**
* Run BeanQueryAdapter preQuery() if needed.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
index b55dbecc2..f0e3daa07 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -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 extends PersistRequest implements BeanPersistRequest {
+public final class PersistRequestBean extends PersistRequest implements BeanPersistRequest, DocStoreUpdate {
private final BeanManager beanManager;
@@ -64,6 +69,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
private final boolean publish;
+ private DocStoreEvent docStoreEvent;
+
private ConcurrencyMode concurrencyMode;
/**
@@ -102,6 +109,11 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
*/
private Set 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 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 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 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 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 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 getUpdatedValues() {
return intercept.getDirtyValues();
@@ -254,7 +300,16 @@ public final class PersistRequestBean 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 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 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 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 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);
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java
index b2b6d5a55..555dea8a1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java
@@ -116,4 +116,9 @@ public interface SpiOrmQueryRequest {
* 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();
}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
index 27bcc3266..7557f1d80 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -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.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;
@@ -240,7 +251,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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,10 +357,15 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
private final boolean cacheSharableBeans;
+ private final String docStoreQueueId;
+
+ private DocumentMapping docMapping;
+
private final BeanDescriptorDraftHelp draftHelp;
private final BeanDescriptorCacheHelp cacheHelp;
private final BeanDescriptorJsonHelp jsonHelp;
-
+ private final DocStoreBeanAdapter docStoreAdapter;
+
private final String defaultSelectClause;
private final LinkedHashSet defaultSelectClauseSet;
@@ -440,12 +460,15 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
this.derivedTableJoins = listHelper.getTableJoin();
boolean noRelationships = propertiesOne.length + propertiesMany.length == 0;
-
+
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = new BeanDescriptorJsonHelp(this);
this.draftHelp = new BeanDescriptorDraftHelp(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 implements MetaBeanInfo, BeanType {
// 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 implements MetaBeanInfo, BeanType {
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() {
@@ -868,6 +904,84 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 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 persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
+ docStoreAdapter.insert(idValue, persistRequest, bulkUpdate);
+ }
+
+ public void docStoreUpdate(Object idValue, PersistRequestBean 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 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);
}
@@ -1219,10 +1333,24 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 implements MetaBeanInfo, BeanType {
/**
* 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()) {
@@ -1328,6 +1456,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return namedUpdates.get(name);
}
+ @Override
+ public T createBean() {
+ return (T)createEntityBean();
+ }
+
/**
* Creates a new EntityBean.
*/
@@ -1401,6 +1534,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 implements MetaBeanInfo, BeanType {
/**
* Return the class type this BeanDescriptor describes.
*/
+ @Override
public Class getBeanType() {
return beanType;
}
@@ -1484,6 +1623,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
* instead.
*
*/
+ @Override
public String getFullName() {
return fullName;
}
@@ -1512,6 +1652,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 implements MetaBeanInfo, BeanType {
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.
*
@@ -1563,6 +1716,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 implements MetaBeanInfo, BeanType {
return c;
}
+ /**
+ * Register all the assoc many properties on this bean that are not populated with the load context.
+ *
+ * This provides further lazy loading via the load context.
+ *
+ */
+ 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 implements MetaBeanInfo, BeanType {
// create a compound comparator based on the list of properties
ElComparator[] comparators = new ElComparator[sortBy.size()];
- List sortProps = sortBy.getProperties();
+ List 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(comparators);
}
- private ElComparator createPropertyComparator(Property sortProp) {
+ private ElComparator createPropertyComparator(SortByClause.Property sortProp) {
ElPropertyValue elGetValue = getElGetValue(sortProp.getName());
@@ -1670,6 +1849,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return elGetValue;
}
+ @Override
+ public ExpressionPath getExpressionPath(String path) {
+ return getElGetValue(path);
+ }
+
/**
* Similar to ElPropertyValue but also uses foreign key shortcuts.
*
@@ -2130,6 +2314,11 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 implements MetaBeanInfo, BeanType {
}
}
+ @Override
public BeanProperty getIdProperty() {
return idProperty;
}
@@ -2442,6 +2632,14 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
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 implements MetaBeanInfo, BeanType {
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);
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java
index 0e3830a1c..a189241a1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java
@@ -16,14 +16,14 @@ import java.util.Map;
public class BeanDescriptorJsonHelp {
private final BeanDescriptor desc;
-
+
private final InheritInfo inheritInfo;
-
+
public BeanDescriptorJsonHelp(BeanDescriptor 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 {
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 {
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 {
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 {
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 {
}
// unmapped properties, send to JsonReadBeanVisitor later
- Map unmappedProperties = null;
+ Map unmappedProperties = null;
do {
JsonParser parser = readJson.getParser();
@@ -141,16 +165,22 @@ public class BeanDescriptorJsonHelp {
} 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;
}
-
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
index d5722c0ab..fcf8c61bd 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -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, DeployBeanInfo>> deplyInfoMap = new HashMap, DeployBeanInfo>>();
private final Map, BeanTable> beanTableMap = new HashMap, BeanTable>();
private final Map> descMap = new HashMap>();
+ private final Map> descQueueMap = new HashMap>();
+
private final Map> beanManagerMap = new HashMap>();
private final Map>> tableToDescMap = new HashMap>>();
@@ -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 DocStoreBeanAdapter createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor deploy) {
+ return docStoreFactory.createAdapter(descriptor, deploy);
+ }
+
+ public BeanDescriptor> getBeanDescriptorByQueueId(String queueId) {
+ return descQueueMap.get(queueId);
+ }
+
@SuppressWarnings("unchecked")
public BeanDescriptor getBeanDescriptor(Class entityType) {
return (BeanDescriptor) descMap.get(entityType.getName());
@@ -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 DeployBeanInfo createDeployBeanInfo(Class beanClass) {
- DeployBeanDescriptor desc = new DeployBeanDescriptor(beanClass);
+ DeployBeanDescriptor desc = new DeployBeanDescriptor(beanClass, serverConfig);
desc.setUpdateChangesOnly(updateChangesOnly);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java
index 28284cdf3..6db829d0b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java
@@ -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.
+ */
+ DocStoreBeanAdapter createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor deploy);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java
index c29838389..15521d4a4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanFkeyProperty.java
@@ -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");
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
index b97f7b4f5..9d88e6bdd 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
@@ -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;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java
index 21341e33d..a787a55b4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssoc.java
@@ -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 extends BeanProperty {
IdBinder targetIdBinder;
InheritInfo targetInheritInfo;
-
+
String targetIdProperty;
/**
@@ -59,6 +65,8 @@ public abstract class BeanPropertyAssoc extends BeanProperty {
final String mappedBy;
+ final String docStoreDoc;
+
final String extraWhere;
boolean saveRecurseSkippable;
@@ -71,7 +79,7 @@ public abstract class BeanPropertyAssoc 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 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.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java
index 8cf145d66..a7514a363 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java
@@ -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 extends BeanPropertyAssoc {
*/
protected BeanPropertyAssocOne> childMasterProperty;
+ private String childMasterIdProperty;
+
private boolean embeddedExportedProperties;
private BeanCollectionHelp help;
@@ -179,6 +186,21 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc {
}
}
+ /**
+ * 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.
*/
@@ -282,10 +304,29 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc {
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 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 parentIds) {
+ private void addWhereParentIdIn(SpiQuery> query, List parentIds) {
String tableAlias = manyToMany ? "int_." : "t0.";
if (manyToMany) {
@@ -450,6 +491,10 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc {
return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix);
}
+ @Override
+ public boolean isMany() {
+ return true;
+ }
@Override
public boolean isAssocId() {
@@ -904,6 +949,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc {
jsonHelp.jsonRead(readJson, parentBean);
}
+ @SuppressWarnings("unchecked")
public void publishMany(EntityBean draft, EntityBean live) {
// collections will not be null due to enhancement
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java
new file mode 100644
index 000000000..93e2d0055
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployDocPropertyOptions.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
index e8564a6e0..cbc73a4ab 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java
@@ -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;
@@ -53,6 +57,8 @@ public class DeployBeanDescriptor {
private static final String I_SCALAOBJECT = "scala.ScalaObject";
+ private final ServerConfig serverConfig;
+
/**
* Map of BeanProperty Linked so as to preserve order.
*/
@@ -166,11 +172,31 @@ public class DeployBeanDescriptor {
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 beanType) {
+ public DeployBeanDescriptor(Class beanType, ServerConfig serverConfig) {
+ this.serverConfig = serverConfig;
this.beanType = beanType;
}
@@ -234,6 +260,26 @@ public class DeployBeanDescriptor {
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++) {
@@ -931,4 +977,61 @@ public class DeployBeanDescriptor {
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();
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
index 621af0612..088cf8d29 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
@@ -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();
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java
index 123d27026..2a86d6304 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java
@@ -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 extends DeployBeanProperty {
* From the deployment mappedBy attribute.
*/
String mappedBy;
-
+
+ String docStoreDoc;
+
/**
* Construct the property.
*/
@@ -126,4 +129,16 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty {
this.mappedBy = mappedBy;
}
}
+
+ /**
+ * Set DocStoreEmbedded deployment information.
+ */
+ public void setDocStoreEmbedded(DocStoreEmbedded embedded) {
+ docStoreDoc = embedded.doc();
+ }
+
+ public String getDocStoreDoc() {
+ return docStoreDoc;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java
index 0e3e15ed4..f7421a77b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java
@@ -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());
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
index bb2777181..42bfa9f10 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
@@ -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);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java
index 1b23ac731..98fd42a64 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyChain.java
@@ -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;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java
index 8cfd30604..8ba64b744 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/el/ElPropertyValue.java
@@ -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.
*
*/
-public interface ElPropertyValue extends ElPropertyDeploy {
+public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
/**
* Return the Id values for the given bean value.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java
index f7a372b98..57acd03ce 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/AllEqualsExpression.java
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
+import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -26,6 +27,22 @@ class AllEqualsExpression extends NonPrepareExpression {
return propName;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ context.writeBoolMustStart();
+ for (Map.Entry 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) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java
index 349baeace..871271d9b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenExpression.java
@@ -4,6 +4,8 @@ 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 {
private static final long serialVersionUID = 2078918165221454910L;
@@ -20,6 +22,11 @@ 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);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java
index 06fdba136..86a032d26 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/BetweenPropertyExpression.java
@@ -8,6 +8,8 @@ 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.
*/
@@ -31,6 +33,15 @@ class BetweenPropertyExpression extends NonPrepareExpression {
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) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java
index e4f32285a..eca057435 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/CaseInsensitiveEqualExpression.java
@@ -5,6 +5,8 @@ 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,21 @@ 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) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java
index f085a92b2..4ae366686 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.expression;
+import java.io.IOException;
import java.util.ArrayList;
import com.avaje.ebean.ExampleExpression;
@@ -94,6 +95,17 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
}
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ if (!list.isEmpty()) {
+ context.writeBoolMustStart();
+ for (SpiExpression expr : list) {
+ expr.writeElastic(context);
+ }
+ context.writeBoolEnd();
+ }
+ }
+
@Override
public SpiExpression copyForPlanKey() {
return new DefaultExampleExpression(list);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
index 621f2c1cb..0226c93ea 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
@@ -306,7 +306,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public Expression exists(Query> subQuery) {
- return new ExistsExpression((SpiQuery>) subQuery, false);
+ return new ExistsQueryExpression((SpiQuery>) subQuery, false);
}
/**
@@ -314,7 +314,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public Expression notExists(Query> subQuery) {
- return new ExistsExpression((SpiQuery>) subQuery, true);
+ return new ExistsQueryExpression((SpiQuery>) subQuery, true);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
index fd8f841c0..47998d600 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
@@ -59,6 +60,33 @@ public class DefaultExpressionList implements SpiExpressionList {
this(null, null, null, new ArrayList());
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ writeElastic(context, null);
+ }
+
+ public void writeElastic(ElasticExpressionContext context, SpiExpression idEquals) throws IOException {
+
+ int size = list.size();
+ if (size == 1 && idEquals == null) {
+ // only 1 expression - skip bool must
+ list.get(0).writeElastic(context);
+ } else if (size == 0 && idEquals != null) {
+ // only idEquals - skip bool must
+ idEquals.writeElastic(context);
+ } else {
+ // bool must wrap all the children
+ context.writeBoolMustStart();
+ if (idEquals != null) {
+ idEquals.writeElastic(context);
+ }
+ for (int i = 0; i < size; i++) {
+ list.get(i).writeElastic(context);
+ }
+ context.writeBoolEnd();
+ }
+ }
+
@Override
public SpiExpressionList> trimPath(int prefixTrim) {
throw new RuntimeException("Only allowed on FilterExpressionList");
@@ -382,7 +410,7 @@ public class DefaultExpressionList implements SpiExpressionList {
return false;
}
- DefaultExpressionList> that = (DefaultExpressionList>)other;
+ DefaultExpressionList> that = (DefaultExpressionList>) other;
if (list.size() != that.list.size()) {
return false;
}
@@ -396,7 +424,7 @@ public class DefaultExpressionList implements SpiExpressionList {
@Override
public boolean isSameByBind(SpiExpression other) {
- DefaultExpressionList> that = (DefaultExpressionList>)other;
+ DefaultExpressionList> that = (DefaultExpressionList>) other;
if (list.size() != that.list.size()) {
return false;
}
@@ -421,16 +449,16 @@ public class DefaultExpressionList implements SpiExpressionList {
* Path does not exist - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonNotExists(String propertyName, String path){
+ public ExpressionList jsonNotExists(String propertyName, String path) {
add(expr.jsonNotExists(propertyName, path));
return this;
}
-
+
/**
* Equal to expression for the value at the given path in the JSON document.
*/
@Override
- public ExpressionList jsonEqualTo(String propertyName, String path, Object value){
+ public ExpressionList jsonEqualTo(String propertyName, String path, Object value) {
add(expr.jsonEqualTo(propertyName, path, value));
return this;
}
@@ -439,7 +467,7 @@ public class DefaultExpressionList implements SpiExpressionList {
* Not Equal to - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonNotEqualTo(String propertyName, String path, Object val){
+ public ExpressionList jsonNotEqualTo(String propertyName, String path, Object val) {
add(expr.jsonNotEqualTo(propertyName, path, val));
return this;
}
@@ -448,7 +476,7 @@ public class DefaultExpressionList implements SpiExpressionList {
* Greater than - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonGreaterThan(String propertyName, String path, Object val){
+ public ExpressionList jsonGreaterThan(String propertyName, String path, Object val) {
add(expr.jsonGreaterThan(propertyName, path, val));
return this;
}
@@ -457,7 +485,7 @@ public class DefaultExpressionList implements SpiExpressionList {
* Greater than or equal to - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val){
+ public ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val) {
add(expr.jsonGreaterOrEqual(propertyName, path, val));
return this;
}
@@ -466,7 +494,7 @@ public class DefaultExpressionList implements SpiExpressionList {
* Less than - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonLessThan(String propertyName, String path, Object val){
+ public ExpressionList jsonLessThan(String propertyName, String path, Object val) {
add(expr.jsonLessThan(propertyName, path, val));
return this;
}
@@ -475,7 +503,7 @@ public class DefaultExpressionList implements SpiExpressionList {
* Less than or equal to - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val){
+ public ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val) {
add(expr.jsonLessOrEqualTo(propertyName, path, val));
return this;
}
@@ -484,7 +512,7 @@ public class DefaultExpressionList implements SpiExpressionList {
* Between - for the given path in a JSON document.
*/
@Override
- public ExpressionList jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){
+ public ExpressionList jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
add(expr.jsonBetween(propertyName, path, lowerValue, upperValue));
return this;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java
new file mode 100644
index 000000000..0f70fddce
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ElasticExpressionContext.java
@@ -0,0 +1,303 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.OrderBy;
+import com.avaje.ebean.plugin.BeanType;
+import com.avaje.ebean.plugin.ExpressionPath;
+import com.avaje.ebeaninternal.server.query.SplitName;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Context for writing elastic search expressions.
+ */
+public class ElasticExpressionContext {
+
+ public static final String MUST = "must";
+ public static final String SHOULD = "should";
+ public static final String MUST_NOT = "must_not";
+ public static final String BOOL = "bool";
+ public static final String TERM = "term";
+ public static final String RANGE = "range";
+ public static final String TERMS = "terms";
+ public static final String IDS = "ids";
+ public static final String VALUES = "values";
+ public static final String PREFIX = "prefix";
+ public static final String MATCH = "match";
+ public static final String WILDCARD = "wildcard";
+ public static final String EXISTS = "exists";
+ public static final String FIELD = "field";
+
+ private final JsonGenerator json;
+
+ private final BeanType> desc;
+
+ private String currentNestedPath;
+
+ public ElasticExpressionContext(JsonGenerator json, BeanType> desc) {
+ this.json = json;
+ this.desc = desc;
+ }
+
+ /**
+ * Return the JsonGenerator.
+ */
+ public JsonGenerator json() {
+ return json;
+ }
+
+ /**
+ * Flush the JsonGenerator buffer.
+ */
+ public void flush() throws IOException {
+ endNested();
+ json.flush();
+ }
+
+ /**
+ * Return true if the path contains a many.
+ */
+ public boolean containsMany(String path) {
+ ExpressionPath elPath = desc.getExpressionPath(path);
+ return elPath == null || elPath.containsMany();
+ }
+
+ /**
+ * Return an associated 'raw' property given the property name.
+ */
+ private String rawProperty(String propertyName) {
+ return desc.docStore().rawProperty(propertyName);
+ }
+
+ public void writeBoolStart(boolean conjunction) throws IOException {
+ writeBoolStart((conjunction) ? MUST : SHOULD);
+ }
+
+ public void writeBoolMustStart() throws IOException {
+ writeBoolStart(MUST);
+ }
+
+ public void writeBoolMustNotStart() throws IOException {
+ writeBoolStart(MUST_NOT);
+ }
+
+ private void writeBoolStart(String type) throws IOException {
+ endNested();
+ json.writeStartObject();
+ json.writeObjectFieldStart(BOOL);
+ json.writeArrayFieldStart(type);
+ }
+
+ public void writeBoolEnd() throws IOException {
+ json.writeEndArray();
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+ public void writeTerm(String propertyName, Object value) throws IOException {
+
+ writeRawType(TERM, rawProperty(propertyName), value);
+ }
+
+ public void writeRange(String propertyName, String rangeType, Object value) throws IOException {
+
+ prepareNestedPath(propertyName);
+ json.writeStartObject();
+ json.writeObjectFieldStart(RANGE);
+ json.writeObjectFieldStart(rawProperty(propertyName));
+ json.writeFieldName(rangeType);
+ json.writeObject(value);
+ json.writeEndObject();
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+ public void writeRange(String propertyName, Op lowOp, Object valueLow, Op highOp, Object valueHigh) throws IOException {
+
+ prepareNestedPath(propertyName);
+ json.writeStartObject();
+ json.writeObjectFieldStart(RANGE);
+ json.writeObjectFieldStart(rawProperty(propertyName));
+ json.writeFieldName(lowOp.docExp());
+ json.writeObject(valueLow);
+ json.writeFieldName(highOp.docExp());
+ json.writeObject(valueHigh);
+ json.writeEndObject();
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+ public void writeTerms(String propertyName, Object[] values) throws IOException {
+
+ prepareNestedPath(propertyName);
+ json.writeStartObject();
+ json.writeObjectFieldStart(TERMS);
+ json.writeArrayFieldStart(rawProperty(propertyName));
+ for (Object value : values) {
+ json.writeObject(value);
+ }
+ json.writeEndArray();
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+
+ public void writeIds(List> idList) throws IOException {
+
+ endNested();
+ json.writeStartObject();
+ json.writeObjectFieldStart(IDS);
+ json.writeArrayFieldStart(VALUES);
+ for (Object id : idList) {
+ json.writeObject(id);
+ }
+ json.writeEndArray();
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+ public void writeId(Object value) throws IOException {
+
+ List ids = new ArrayList(1);
+ ids.add(value);
+ writeIds(ids);
+ }
+
+ public void writeSuffix(String propertyName, String value) {
+ throw new IllegalArgumentException("Not implemented yet. Could search for a mapped 'reversed' property and do prefix query");
+ }
+
+ public void writePrefix(String propertyName, String value) throws IOException {
+ // use analysed field
+ prepareNestedPath(propertyName);
+ writeRawType(PREFIX, propertyName, value);
+ }
+
+ public void writeMatch(String propertyName, String value) throws IOException {
+ // use analysed field
+ prepareNestedPath(propertyName);
+ writeRawType(MATCH, propertyName, value);
+ }
+
+ public void writeWildcard(String propertyName, String value) throws IOException {
+ prepareNestedPath(propertyName);
+ writeRawType(WILDCARD, propertyName, value);
+ }
+
+ public void writeRaw(String jsonExpression) throws IOException {
+ json.writeRaw(jsonExpression);
+ }
+
+ public void writeExists(boolean notNull, String propertyName) throws IOException {
+
+ prepareNestedPath(propertyName);
+ if (!notNull) {
+ writeBoolMustNotStart();
+ }
+ writeExists(propertyName);
+ if (!notNull) {
+ writeBoolEnd();
+ }
+ }
+
+ private void writeExists(String propertyName) throws IOException {
+ writeRawType(EXISTS, FIELD, propertyName);
+ }
+
+ private void writeRawType(String type, String propertyName, Object value) throws IOException {
+
+ prepareNestedPath(propertyName);
+ json.writeStartObject();
+ json.writeObjectFieldStart(type);
+ json.writeFieldName(propertyName);
+ json.writeObject(value);
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+
+
+ public void writeSimple(Op type, String propertyName, Object value) throws IOException {
+
+ prepareNestedPath(propertyName);
+ switch (type) {
+ case EQ:
+ writeTerm(propertyName, value);
+ break;
+ case NOT_EQ:
+ writeBoolMustNotStart();
+ writeTerm(propertyName, value);
+ writeBoolEnd();
+ break;
+ case EXISTS:
+ writeExists(true, propertyName);
+ break;
+ case NOT_EXISTS:
+ writeExists(false, propertyName);
+ break;
+ case BETWEEN:
+ throw new IllegalStateException("BETWEEN Not expected in SimpleExpression?");
+
+ default:
+ writeRange(propertyName, type.docExp(), value);
+ }
+
+ }
+
+ /**
+ * Write the query sort.
+ */
+ public void writeOrderBy(OrderBy orderBy) throws IOException {
+
+ if (orderBy != null && !orderBy.isEmpty()) {
+ json.writeArrayFieldStart("sort");
+ for (OrderBy.Property property : orderBy.getProperties()) {
+ json.writeStartObject();
+ json.writeObjectFieldStart(rawProperty(property.getProperty()));
+ json.writeStringField("order", property.isAscending() ? "asc" : "desc");
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+ json.writeEndArray();
+ }
+ }
+
+ private void prepareNestedPath(String propName) throws IOException {
+ ExpressionPath exprPath = desc.getExpressionPath(propName);
+ if (exprPath != null && exprPath.containsMany()) {
+ String[] manyPath = SplitName.splitBegin(propName);
+ startNested(manyPath[0]);
+ } else {
+ endNested();
+ }
+ }
+
+ private void startNested(String nestedPath) throws IOException {
+
+ if (currentNestedPath != null) {
+ if (currentNestedPath.equals(nestedPath)) {
+ // just add to currentNestedPath
+ return;
+ } else {
+ endNested();
+ }
+ }
+ currentNestedPath = nestedPath;
+
+ json.writeStartObject();
+ json.writeObjectFieldStart("nested");
+ json.writeStringField("path", nestedPath);
+ json.writeFieldName("filter");
+ }
+
+ private void endNested() throws IOException {
+ if (currentNestedPath != null) {
+ currentNestedPath = null;
+ //json.writeEndObject();
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java
similarity index 82%
rename from src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java
rename to src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java
index dca741608..fdaf30d2e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.expression;
+import java.io.IOException;
import java.util.List;
import com.avaje.ebean.event.BeanQueryRequest;
@@ -13,7 +14,7 @@ import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
-public class ExistsExpression implements SpiExpression {
+class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpression {
private static final long serialVersionUID = 666990277309851644L;
@@ -25,18 +26,23 @@ public class ExistsExpression implements SpiExpression {
protected String sql;
- public ExistsExpression(SpiQuery> subQuery, boolean not) {
+ public ExistsQueryExpression(SpiQuery> subQuery, boolean not) {
this.subQuery = subQuery;
this.not = not;
}
- ExistsExpression(boolean not, String sql , List bindParams) {
+ ExistsQueryExpression(boolean not, String sql , List bindParams) {
this.not = not;
this.sql = sql;
this.bindParams = bindParams;
this.subQuery = null;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ throw new IllegalStateException("Not supported");
+ }
+
@Override
public void prepareExpression(BeanQueryRequest> request) {
@@ -60,7 +66,7 @@ public class ExistsExpression implements SpiExpression {
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
- builder.add(ExistsExpression.class).add(not);
+ builder.add(ExistsQueryExpression.class).add(not);
builder.add(sql).add(bindParams.size());
}
@@ -90,11 +96,11 @@ public class ExistsExpression implements SpiExpression {
@Override
public boolean isSameByPlan(SpiExpression other) {
- if (!(other instanceof ExistsExpression)) {
+ if (!(other instanceof ExistsQueryExpression)) {
return false;
}
- ExistsExpression that = (ExistsExpression) other;
+ ExistsQueryExpression that = (ExistsQueryExpression) other;
return this.sql.equals(that.sql)
&& this.not == that.not
&& this.bindParams.size() == that.bindParams.size();
@@ -102,7 +108,7 @@ public class ExistsExpression implements SpiExpression {
@Override
public boolean isSameByBind(SpiExpression other) {
- ExistsExpression that = (ExistsExpression) other;
+ ExistsQueryExpression that = (ExistsQueryExpression) other;
if (this.bindParams.size() != that.bindParams.size()) {
return false;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java
index 41aaec673..dd9ae9504 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java
@@ -7,6 +7,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
+
/**
* Slightly redundant as Query.setId() ultimately also does the same job.
*/
@@ -20,6 +22,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression {
this.value = value;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ context.writeId(value);
+ }
+
/**
* Always returns false.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java
index cf67e92c3..0fa095620 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
+import java.io.IOException;
import java.util.List;
/**
@@ -27,6 +28,11 @@ public class IdInExpression extends NonPrepareExpression {
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ context.writeIds(idList);
+ }
+
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java
index 52af4e2d3..be11282eb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
+import java.io.IOException;
import java.util.Collection;
class InExpression extends AbstractExpression {
@@ -28,6 +29,11 @@ class InExpression extends AbstractExpression {
this.not = not;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ context.writeTerms(propName, values);
+ }
+
@Override
public void addBindValues(SpiExpressionRequest request) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java
index 5da4daee4..6914e69bf 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java
@@ -8,12 +8,13 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.query.CQuery;
+import java.io.IOException;
import java.util.List;
/**
* In expression using a sub query.
*/
-class InQueryExpression extends AbstractExpression {
+class InQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression {
private static final long serialVersionUID = 666990277309851644L;
@@ -39,6 +40,11 @@ class InQueryExpression extends AbstractExpression {
this.bindParams = bindParams;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ throw new IllegalStateException("Not supported");
+ }
+
@Override
public void prepareExpression(BeanQueryRequest> request) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
index 0f14623b2..7249a1c17 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
@@ -4,6 +4,8 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+import java.io.IOException;
+
/**
* Generally speaking tests the value at a given path in the JSON document.
*
@@ -57,6 +59,17 @@ class JsonPathExpression extends AbstractExpression {
this.upperValue = upperValue;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ String fullName = propName + "." + path;
+ if (operator == Op.BETWEEN) {
+ context.writeRange(fullName, Op.GT_EQ, value, Op.LT_EQ, upperValue);
+ } else {
+ context.writeSimple(operator, fullName, value);
+ }
+ }
+
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(JsonPathExpression.class).add(propName).add(path).add(operator);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
index 812540bf9..f25eefa0e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
@@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
@@ -98,6 +99,17 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr
this.exprList = exprList;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ context.writeBoolStart(!disjunction);
+ List list = exprList.internalList();
+ for (int i = 0; i < list.size(); i++) {
+ list.get(i).writeElastic(context);
+ }
+ context.writeBoolEnd();
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
@@ -126,8 +138,7 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr
@Override
public Junction add(Expression item) {
- SpiExpression i = (SpiExpression) item;
- exprList.add(i);
+ exprList.add(item);
return this;
}
@@ -143,8 +154,7 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr
List list = exprList.internalList();
for (int i = 0; i < list.size(); i++) {
- SpiExpression item = list.get(i);
- item.addBindValues(request);
+ list.get(i).addBindValues(request);
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java
index 56e58092f..907358ad0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java
@@ -6,6 +6,8 @@ import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
+import java.io.IOException;
+
class LikeExpression extends AbstractExpression {
private static final long serialVersionUID = -5398151809111172380L;
@@ -23,6 +25,36 @@ class LikeExpression extends AbstractExpression {
this.val = value;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ String paramVal = (caseInsensitive) ? val.toLowerCase() : val;
+ switch (type) {
+ case RAW:
+ context.writeWildcard(propName, paramVal);
+ break;
+
+ case STARTS_WITH:
+ context.writePrefix(propName, paramVal);
+ break;
+
+ case ENDS_WITH:
+ context.writeSuffix(propName, paramVal);
+ break;
+
+ case CONTAINS:
+ context.writeMatch(propName, paramVal);
+ break;
+
+ case EQUAL_TO:
+ context.writeTerm(propName, paramVal);
+ break;
+
+ default:
+ throw new RuntimeException("LikeType " + type + " missed?");
+ }
+ }
+
@Override
public void addBindValues(SpiExpressionRequest request) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java
index 977b63d5a..6f750f42c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java
@@ -9,6 +9,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
+
/**
* A logical And or Or for joining two expressions.
*/
@@ -60,6 +62,16 @@ abstract class LogicExpression implements SpiExpression {
this.expTwo = (SpiExpression) expTwo;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ boolean conjunction = joinType.equals(AND);
+ context.writeBoolStart(conjunction);
+ expOne.writeElastic(context);
+ expTwo.writeElastic(context);
+ context.writeBoolEnd();
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
expOne.containsMany(desc, manyWhereJoin);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java
index a59ff8fbc..1f833feb6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java
@@ -8,6 +8,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
+
/**
* Effectively an expression that has no effect.
*/
@@ -20,6 +22,10 @@ class NoopExpression implements SpiExpression {
return this;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins whereManyJoins) {
// nothing to do
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java
index b7d8867a7..60c3dfb82 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java
@@ -9,6 +9,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
+
final class NotExpression implements SpiExpression {
private static final long serialVersionUID = 5648926732402355781L;
@@ -22,6 +24,13 @@ final class NotExpression implements SpiExpression {
this.exp = (SpiExpression) exp;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ context.writeBoolMustNotStart();
+ exp.writeElastic(context);
+ context.writeBoolEnd();
+ }
+
@Override
public SpiExpression copyForPlanKey() {
return new NotExpression(exp.copyForPlanKey());
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java
index 6ad5a58ea..06b55ca50 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NullExpression.java
@@ -5,6 +5,8 @@ import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
+import java.io.IOException;
+
/**
* Slightly redundant as Query.setId() ultimately also does the same job.
@@ -20,6 +22,11 @@ class NullExpression extends AbstractExpression {
this.notNull = notNull;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ context.writeExists(notNull, propName);
+ }
+
@Override
public void addBindValues(SpiExpressionRequest request) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java b/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java
index 77d06c557..c8add0bd4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java
@@ -8,54 +8,56 @@ public enum Op {
/**
* Exists (JSON).
*/
- EXISTS(" is not null "),
+ EXISTS(" is not null ", ""),
/**
* Not Exists (JSON).
*/
- NOT_EXISTS(" is null "),
+ NOT_EXISTS(" is null ", ""),
/**
* Between (JSON).
*/
- BETWEEN(" between ? and ? "),
+ BETWEEN(" between ? and ? ", ""),
/**
* Equal to
*/
- EQ(" = ? "),
+ EQ(" = ? ", ""),
/**
* Not equal to.
*/
- NOT_EQ(" <> ? "),
+ NOT_EQ(" <> ? ", ""),
/**
- *
* Less than.
*/
- LT(" < ? "),
+ LT(" < ? ", "lt"),
/**
* Less than or equal to.
*/
- LT_EQ(" <= ? "),
+ LT_EQ(" <= ? ", "lte"),
/**
* Greater than.
*/
- GT(" > ? "),
+ GT(" > ? ", "gt"),
/**
* Greater than or equal to.
*/
- GT_EQ(" >= ? ");
+ GT_EQ(" >= ? ", "gte");
final String exp;
- Op(String exp) {
+ final String docExp;
+
+ Op(String exp, String docExp) {
this.exp = exp;
+ this.docExp = docExp;
}
/**
@@ -64,4 +66,11 @@ public enum Op {
public String bind() {
return exp;
}
+
+ /**
+ * Return the doc store expression.
+ */
+ public String docExp() {
+ return docExp;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java
index 93261f6b2..6d4e0c27a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java
@@ -7,6 +7,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import java.io.IOException;
+
class RawExpression extends NonPrepareExpression {
private static final long serialVersionUID = 7973903141340334606L;
@@ -20,6 +22,11 @@ class RawExpression extends NonPrepareExpression {
this.values = values;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+ context.writeRaw(sql);
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java
index 0b6a4f9a4..1093c4387 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java
@@ -6,6 +6,8 @@ import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
+import java.io.IOException;
+
public class SimpleExpression extends AbstractExpression {
private static final long serialVersionUID = -382881395755603790L;
@@ -20,6 +22,16 @@ public class SimpleExpression extends AbstractExpression {
this.value = value;
}
+ @Override
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ if (type == Op.BETWEEN) {
+ throw new IllegalStateException("BETWEEN Not expected in SimpleExpression?");
+ }
+
+ context.writeSimple(type, propName, value);
+ }
+
public final String getPropName() {
return propName;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java
new file mode 100644
index 000000000..3849e06c7
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/UnsupportedDocStoreExpression.java
@@ -0,0 +1,7 @@
+package com.avaje.ebeaninternal.server.expression;
+
+/**
+ * Marked interface for expressions unsupported in doc store.
+ */
+public interface UnsupportedDocStoreExpression {
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java
index 966773413..59d7f39cb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java
@@ -45,7 +45,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
protected void configureQuery(SpiQuery> query, String lazyLoadProperty) {
- parent.propagateQueryState(query);
+ parent.propagateQueryState(query, desc.isDocStoreMapped());
query.setParentNode(objectGraphNode);
query.setLazyLoadProperty(lazyLoadProperty);
if (queryProps != null) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java
index cb71ec63e..7abcb9dfe 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.loadcontext;
import com.avaje.ebean.bean.BeanCollection;
+import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
@@ -45,6 +46,7 @@ public class DLoadContext implements LoadContext {
private final boolean disableLazyLoading;
private final boolean disableReadAudit;
private final boolean includeSoftDeletes;
+ protected final boolean useDocStore;
/**
* The path relative to the root of the object graph.
@@ -59,6 +61,34 @@ public class DLoadContext implements LoadContext {
private List secQuery;
+ /**
+ * Construct for use with JSON marshalling (doc store).
+ */
+ public DLoadContext(BeanDescriptor> rootDescriptor, PersistenceContext persistenceContext) {
+
+ this.useDocStore = true;
+ this.rootDescriptor = rootDescriptor;
+ this.ebeanServer = rootDescriptor.getEbeanServer();
+ this.persistenceContext = persistenceContext;
+ this.origin = initOrigin();
+ this.defaultBatchSize = 100;
+ this.excludeBeanCache = false;
+ this.asDraft = false;
+ this.asOf = null;
+ this.readOnly = false;
+ this.disableLazyLoading = false;
+ this.disableReadAudit = false;
+ this.includeSoftDeletes = false;
+ this.relativePath = null;
+ this.useProfiling = false;
+ this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
+ }
+
+ private ObjectGraphOrigin initOrigin() {
+ CallStack callStack = ebeanServer.createCallStack();
+ return new ObjectGraphOrigin(0, callStack, rootDescriptor.getFullName());
+ }
+
public DLoadContext(OrmQueryRequest> request, SpiQuerySecondary secondaryQueries) {
this.persistenceContext = request.getPersistenceContext();
@@ -67,6 +97,7 @@ public class DLoadContext implements LoadContext {
this.rootDescriptor = request.getBeanDescriptor();
SpiQuery> query = request.getQuery();
+ this.useDocStore = query.isUseDocStore();
this.asOf = query.getAsOf();
this.asDraft = query.isAsDraft();
this.includeSoftDeletes = query.isIncludeSoftDeletes();
@@ -300,7 +331,10 @@ public class DLoadContext implements LoadContext {
/**
* Propagate the original query settings (draft, asOf etc) to the secondary queries.
*/
- public void propagateQueryState(SpiQuery> query) {
+ public void propagateQueryState(SpiQuery> query, boolean docStoreMapped) {
+ if (useDocStore && docStoreMapped) {
+ query.setUseDocStore(true);
+ }
if (readOnly != null) {
query.setReadOnly(readOnly);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java
index 6ed015ce0..c484651a2 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java
@@ -56,7 +56,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
public void configureQuery(SpiQuery> query) {
- parent.propagateQueryState(query);
+ parent.propagateQueryState(query, desc.isDocStoreMapped());
query.setParentNode(objectGraphNode);
if (queryProps != null) {
queryProps.configureBeanQuery(query);
@@ -129,6 +129,11 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
this.list = new ArrayList>(batchSize);
}
+ @Override
+ public boolean isUseDocStore() {
+ return context.parent.useDocStore;
+ }
+
public int getBatchSize() {
return batchSize;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java
index fc9473ccc..f85168f26 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.persist.dml;
-import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -17,7 +16,6 @@ import javax.persistence.OptimisticLockException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
-import java.util.ArrayList;
/**
* Base class for Handler implementations.
@@ -48,7 +46,10 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
protected String sql;
- protected ArrayList updateGenValues;
+ /**
+ * The generated value for the @Version property. Must be set after where clause is bound.
+ */
+ protected Object versionValue;
protected DmlHandler(PersistRequestBean> persistRequest, boolean emptyStringToNull) {
this.now = System.currentTimeMillis();
@@ -224,11 +225,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
*
*/
@Override
- public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value) {
- if (updateGenValues == null) {
- updateGenValues = new ArrayList();
- }
- updateGenValues.add(new UpdateGenValue(prop, bean, value));
+ public void registerGeneratedVersion(Object versionValue) {
+ this.versionValue = versionValue;
}
/**
@@ -236,11 +234,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
* clause has been bound.
*/
public void setUpdateGenValues() {
- if (updateGenValues != null) {
- for (int i = 0; i < updateGenValues.size(); i++) {
- UpdateGenValue updGenVal = updateGenValues.get(i);
- updGenVal.setValue();
- }
+ if (versionValue != null) {
+ persistRequest.setVersionValue(versionValue);
}
}
@@ -282,30 +277,4 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
return stmt;
}
- /**
- * Hold the values from GeneratedValue that need to be set to the bean
- * property after the where clause has been built.
- */
- private static final class UpdateGenValue {
-
- private final BeanProperty property;
-
- private final EntityBean bean;
-
- private final Object value;
-
- private UpdateGenValue(BeanProperty property, EntityBean bean, Object value) {
- this.property = property;
- this.bean = bean;
- this.value = value;
- }
-
- /**
- * Set the value to the bean property.
- */
- private void setValue() {
- // support PropertyChangeSupport
- property.setValueIntercept(bean, value);
- }
- }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java
index fba5f5768..5d28a68af 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindablePropertyUpdateGenerated.java
@@ -42,13 +42,14 @@ public class BindablePropertyUpdateGenerated extends BindableProperty {
// generated value should be the correct type
request.bind(value, prop);
- // only register the update value if it was included
- // in the bean in the first place
- if (request.getPersistRequest().isLoadedProperty(prop)) {
- //if (request.isIncluded(prop)) {
- // need to set the generated value to the bean later
- // after the where clause has been generated
- request.registerUpdateGenValue(prop, bean, value);
+ if (prop.isVersion()) {
+ if (request.getPersistRequest().isLoadedProperty(prop)) {
+ // set to the bean after the where clause has been generated
+ request.registerGeneratedVersion(value);
+ }
+ } else {
+ // @WhenModified set without invoking interception
+ prop.setValueChanged(bean, value);
}
}
@@ -60,5 +61,4 @@ public class BindablePropertyUpdateGenerated extends BindableProperty {
request.appendColumn(prop.getDbColumn());
}
-
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java
index 004cd5fd7..c034e5c78 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dmlbind/BindableRequest.java
@@ -48,7 +48,7 @@ public interface BindableRequest {
* Register the value from a update GeneratedValue. This can only be set to
* the bean property after the where clause has bean built.
*/
- void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value);
+ void registerGeneratedVersion(Object value);
/**
* Return the original PersistRequest.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
index 346d83ab8..cbd2c2156 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
@@ -194,7 +194,7 @@ public class CQuery implements DbReadContext, CancelableQuery {
this.queryPlan = queryPlan;
this.query = request.getQuery();
this.queryMode = query.getMode();
- this.lazyLoadManyProperty = query.getLazyLoadForParentsProperty();
+ this.lazyLoadManyProperty = query.getLazyLoadMany();
this.readOnly = request.isReadOnly();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java
index 285f412a1..93eaf9087 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java
@@ -259,7 +259,7 @@ public class SqlTreeBuilder {
buildExtraJoins(desc, myList);
// Optional many property for lazy loading query
- BeanPropertyAssocMany> lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty();
+ BeanPropertyAssocMany> lazyLoadMany = (query == null) ? null : query.getLazyLoadMany();
boolean withId = !rawNoId && !subQuery && (query == null || !query.isDistinct());
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query), disableLazyLoad);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java
index c48697455..b64dacb58 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java
@@ -232,7 +232,6 @@ public class SqlTreeNodeBean implements SqlTreeNode {
PersistenceContext persistenceContext = (!readId || temporalVersions) ? null : ctx.getPersistenceContext();
- boolean newBean = false;
if (readId) {
Object id = localIdBinder.readSet(ctx, localBean);
if (id == null) {
@@ -243,7 +242,6 @@ public class SqlTreeNodeBean implements SqlTreeNode {
contextBean = (EntityBean) persistenceContext.putIfAbsent(id, localBean);
if (contextBean == null) {
// bean just added to the persistenceContext
- newBean = true;
contextBean = localBean;
} else {
// bean already exists in persistenceContext
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index 00354d496..3eff78a71 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -10,6 +10,8 @@ import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.BeanType;
+import com.avaje.ebean.FetchPath;
+import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.HashQuery;
@@ -25,11 +27,15 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect;
import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
-import com.avaje.ebeaninternal.server.expression.DefaultExpressionList;
+import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext;
import com.avaje.ebeaninternal.server.expression.SimpleExpression;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
+import com.avaje.ebeaninternal.server.expression.DefaultExpressionList;
+import com.fasterxml.jackson.core.JsonGenerator;
import javax.persistence.PersistenceException;
+import java.io.IOException;
+import java.io.StringWriter;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
@@ -42,29 +48,27 @@ import java.util.Set;
*/
public class DefaultOrmQuery implements SpiQuery {
- private static final long serialVersionUID = 6838006264714672460L;
-
private final Class beanType;
- private transient final EbeanServer server;
+ private final BeanDescriptor beanDescriptor;
- private transient BeanCollectionTouched beanCollectionTouched;
+ private final EbeanServer server;
- private transient final ExpressionFactory expressionFactory;
+ private BeanCollectionTouched beanCollectionTouched;
+
+ private final ExpressionFactory expressionFactory;
/**
* For lazy loading of ManyToMany we need to add a join to the intersection table. This is that
* join to the intersection table.
*/
- private transient TableJoin includeTableJoin;
+ private TableJoin includeTableJoin;
- private transient ProfilingListener profilingListener;
-
- private transient BeanDescriptor> beanDescriptor;
+ private ProfilingListener profilingListener;
private boolean cancelled;
- private transient CancelableQuery cancelableQuery;
+ private CancelableQuery cancelableQuery;
/**
* The name of the query.
@@ -235,14 +239,17 @@ public class DefaultOrmQuery implements SpiQuery {
*/
private CQueryPlanKey queryPlanKey;
- private transient PersistenceContext persistenceContext;
+ private PersistenceContext persistenceContext;
private ManyWhereJoins manyWhereJoins;
private RawSql rawSql;
- public DefaultOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory, String query) {
- this.beanType = beanType;
+ private boolean useDocStore;
+
+ public DefaultOrmQuery(BeanDescriptor desc, EbeanServer server, ExpressionFactory expressionFactory, String query) {
+ this.beanDescriptor = desc;
+ this.beanType = desc.getBeanType();
this.server = server;
this.expressionFactory = expressionFactory;
this.detail = new OrmQueryDetail();
@@ -255,10 +262,11 @@ public class DefaultOrmQuery implements SpiQuery {
/**
* Additional supply a query which is parsed.
*/
- public DefaultOrmQuery(Class beanType, EbeanServer server, ExpressionFactory expressionFactory,
+ public DefaultOrmQuery(BeanDescriptor desc, EbeanServer server, ExpressionFactory expressionFactory,
DeployNamedQuery namedQuery) throws PersistenceException {
- this.beanType = beanType;
+ this.beanDescriptor = desc;
+ this.beanType = desc.getBeanType();
this.server = server;
this.expressionFactory = expressionFactory;
this.detail = new OrmQueryDetail();
@@ -282,6 +290,88 @@ public class DefaultOrmQuery implements SpiQuery {
}
}
+ public String asElasticQuery() {
+
+ StringWriter sw = new StringWriter(200);
+ JsonContext json = server.json();
+
+ JsonGenerator generator = json.createGenerator(sw);
+
+ BeanType beanType = server.getPluginApi().getBeanType(this.beanType);
+ ElasticExpressionContext context = new ElasticExpressionContext(generator, beanType);
+
+ try {
+ writeElastic(context);
+ context.flush();
+ return sw.toString();
+
+ } catch (IOException e) {
+ throw new PersistenceIOException(e);
+ }
+ }
+
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ JsonGenerator json = context.json();
+ json.writeStartObject();
+ if (firstRow > 0) {
+ json.writeNumberField("from", firstRow);
+ }
+ if (maxRows > 0) {
+ json.writeNumberField("size", maxRows);
+ }
+
+ detail.writeElastic(context);
+ context.writeOrderBy(orderBy);
+
+ json.writeFieldName("query");
+ json.writeStartObject();
+
+ SpiExpression idEquals = null;
+ if (id != null) {
+ idEquals = (SpiExpression)expressionFactory.idEq(id);
+ }
+
+ boolean hasWhere = (whereExpressions != null && !whereExpressions.isEmpty());
+ if (idEquals != null || hasWhere) {
+ json.writeFieldName("filtered");
+ json.writeStartObject();
+ json.writeFieldName("filter");
+ if (hasWhere) {
+ whereExpressions.writeElastic(context, idEquals);
+ } else {
+ idEquals.writeElastic(context);
+ }
+ json.writeEndObject();
+ } else {
+ json.writeObjectFieldStart("match_all");
+ json.writeEndObject();
+ }
+ json.writeEndObject();
+ json.writeEndObject();
+ }
+
+ @Override
+ public BeanDescriptor getBeanDescriptor() {
+ return beanDescriptor;
+ }
+
+ @Override
+ public boolean isAutoTunable() {
+ return beanDescriptor.isAutoTunable() && !isSqlSelect();
+ }
+
+ @Override
+ public Query setUseDocStore(boolean useDocStore) {
+ this.useDocStore = useDocStore;
+ return this;
+ }
+
+ @Override
+ public boolean isUseDocStore() {
+ return useDocStore;
+ }
+
@Override
public Query apply(FetchPath fetchPath) {
fetchPath.apply(this);
@@ -337,13 +427,6 @@ public class DefaultOrmQuery implements SpiQuery {
return this;
}
- /**
- * Set the BeanDescriptor for the root type of this query.
- */
- public void setBeanDescriptor(BeanDescriptor> beanDescriptor) {
- this.beanDescriptor = beanDescriptor;
- }
-
public RawSql getRawSql() {
return rawSql;
}
@@ -559,7 +642,7 @@ public class DefaultOrmQuery implements SpiQuery {
public DefaultOrmQuery copy(EbeanServer server) {
- DefaultOrmQuery copy = new DefaultOrmQuery(beanType, server, expressionFactory, (String) null);
+ DefaultOrmQuery copy = new DefaultOrmQuery(beanDescriptor, server, expressionFactory, (String) null);
copy.name = name;
copy.includeTableJoin = includeTableJoin;
copy.profilingListener = profilingListener;
@@ -669,7 +752,7 @@ public class DefaultOrmQuery implements SpiQuery {
}
@Override
- public BeanPropertyAssocMany> getLazyLoadForParentsProperty() {
+ public BeanPropertyAssocMany> getLazyLoadMany() {
return lazyLoadForParentsProperty;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java
index 47fb077c6..36a331012 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryDetail.java
@@ -6,9 +6,12 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
+import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext;
import com.avaje.ebeaninternal.server.query.SplitName;
+import com.fasterxml.jackson.core.JsonGenerator;
import javax.persistence.PersistenceException;
+import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
@@ -498,4 +501,67 @@ public class OrmQueryDetail implements Serializable {
public Set getFetchPaths() {
return fetchPaths.keySet();
}
+
+ /**
+ * Write the Elastic search source include and fields if necessary.
+ *
+ * Fetch all property is put into includes.
+ * Fetch on 'many' path is put into includes.
+ * Fetch on 'one' paths and root path are put into fields.
+ *
+ */
+ public void writeElastic(ElasticExpressionContext context) throws IOException {
+
+ Set includes = new LinkedHashSet();
+ Set fields = new LinkedHashSet();
+
+ for (Map.Entry entry : fetchPaths.entrySet()) {
+
+ String path = entry.getKey();
+ OrmQueryProperties value = entry.getValue();
+ if (value.allProperties()) {
+ includes.add(path + ".*");
+ } else if (context.containsMany(path)) {
+ for (String propName : value.getIncluded()) {
+ includes.add(path + "." + propName);
+ }
+ } else {
+ for (String propName : value.getIncluded()) {
+ fields.add(path + "." + propName);
+ }
+ }
+ }
+
+ if (hasSelectClause()) {
+ Set included = baseProps.getIncluded();
+ if (included != null) {
+ for (String propName : included) {
+ fields.add(propName);
+ }
+ }
+ }
+
+ if (!includes.isEmpty()) {
+ JsonGenerator json = context.json();
+ json.writeFieldName("_source");
+ json.writeStartObject();
+ json.writeFieldName("include");
+ json.writeStartArray();
+ for (String propName : includes) {
+ json.writeString(propName);
+ }
+ json.writeEndArray();
+ json.writeEndObject();
+ }
+
+ if (!fields.isEmpty()) {
+ JsonGenerator json = context.json();
+ json.writeFieldName("fields");
+ json.writeStartArray();
+ for (String propName : fields) {
+ json.writeString(propName);
+ }
+ json.writeEndArray();
+ }
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java
new file mode 100644
index 000000000..8099e9357
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java
@@ -0,0 +1,51 @@
+package com.avaje.ebeaninternal.server.text.json;
+
+import com.avaje.ebean.PersistenceIOException;
+import com.avaje.ebean.bean.PersistenceContext;
+import com.avaje.ebean.text.json.JsonBeanReader;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.fasterxml.jackson.core.JsonParser;
+
+import java.io.IOException;
+
+/**
+ * A 'context' for reading entity beans from JSON.
+ *
+ * This is used such that a load context and persistence context can be used to span multiple marshalling requests.
+ *
+ */
+public class DJsonBeanReader implements JsonBeanReader {
+
+ private final BeanDescriptor desc;
+
+ private final ReadJson readJson;
+
+ public DJsonBeanReader(BeanDescriptor desc, ReadJson readJson) {
+ this.desc = desc;
+ this.readJson = readJson;
+ }
+
+ @Override
+ public void persistenceContextPut(Object beanId, T currentBean) {
+ readJson.persistenceContextPut(beanId, currentBean);
+ }
+
+ @Override
+ public PersistenceContext getPersistenceContext() {
+ return readJson.getPersistenceContext();
+ }
+
+ @Override
+ public T read() {
+ try {
+ return desc.jsonRead(readJson, null);
+ } catch (IOException e) {
+ throw new PersistenceIOException(e);
+ }
+ }
+
+ @Override
+ public JsonBeanReader forJson(JsonParser moreJson) {
+ return new DJsonBeanReader(desc, readJson.forJson(moreJson));
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java
index 3968b3ecf..78b88fd1b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java
@@ -106,15 +106,23 @@ public class DJsonContext implements JsonContext {
public T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
- ReadJson readJson = new ReadJson(parser, options, determineObjectMapper(options));
+ BeanDescriptor desc = getDescriptor(cls);
+ ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
try {
- BeanDescriptor d = getDescriptor(cls);
- return d.jsonRead(readJson, null);
+ return desc.jsonRead(readJson, null);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
+ @Override
+ public DJsonBeanReader createBeanReader(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
+
+ BeanDescriptor desc = getDescriptor(cls);
+ ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
+ return new DJsonBeanReader(desc, readJson);
+ }
+
public List toList(Class cls, String json) throws JsonIOException {
return toList(cls, new StringReader(json));
}
@@ -138,9 +146,9 @@ public class DJsonContext implements JsonContext {
public List toList(Class cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
- ReadJson readJson = new ReadJson(src, options, determineObjectMapper(options));
+ BeanDescriptor desc = getDescriptor(cls);
+ ReadJson readJson = new ReadJson(desc, src, options, determineObjectMapper(options));
try {
- BeanDescriptor d = getDescriptor(cls);
List list = new ArrayList();
@@ -153,7 +161,7 @@ public class DJsonContext implements JsonContext {
}
do {
- T bean = d.jsonRead(readJson, null);
+ T bean = desc.jsonRead(readJson, null);
if (bean == null) {
break;
} else {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java
index c66f209fe..8fbfa1035 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java
@@ -1,7 +1,14 @@
package com.avaje.ebeaninternal.server.text.json;
+import com.avaje.ebean.bean.EntityBean;
+import com.avaje.ebean.bean.EntityBeanIntercept;
+import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.text.json.JsonReadBeanVisitor;
import com.avaje.ebean.text.json.JsonReadOptions;
+import com.avaje.ebeaninternal.api.LoadContext;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.loadcontext.DLoadContext;
+import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -14,34 +21,120 @@ import java.util.Map;
*/
public class ReadJson {
+ private final BeanDescriptor> rootDesc;
+
/**
* Jackson parser.
*/
- final JsonParser parser;
+ private final JsonParser parser;
/**
* Stack of the path - used to find the appropriate JsonReadBeanVisitor.
*/
- final PathStack pathStack;
+ private final PathStack pathStack;
/**
* Map of the JsonReadBeanVisitor keyed by path.
*/
- final Map> visitorMap;
+ private final Map> visitorMap;
- final Object objectMapper;
+ private final Object objectMapper;
+
+ private final PersistenceContext persistenceContext;
+
+ private final LoadContext loadContext;
/**
* Construct with parser and readOptions.
*/
- public ReadJson(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) {
+ public ReadJson(BeanDescriptor> desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper) {
+ this.rootDesc = desc;
this.parser = parser;
this.objectMapper = objectMapper;
+ this.persistenceContext = initPersistenceContext(readOptions);
+ this.loadContext = initLoadContext(desc, readOptions);
// only create visitorMap, pathStack if needed ...
this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap();
- this.pathStack = (visitorMap == null) ? null : new PathStack();
+ this.pathStack = (visitorMap == null && loadContext == null) ? null : new PathStack();
+ }
+
+ /**
+ * Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance.
+ */
+ private ReadJson(JsonParser moreJson, ReadJson source) {
+ this.parser = moreJson;
+ this.rootDesc = source.rootDesc;
+ this.pathStack = source.pathStack;
+ this.visitorMap = source.visitorMap;
+ this.objectMapper = source.objectMapper;
+ this.persistenceContext = source.persistenceContext;
+ this.loadContext = source.loadContext;
+ }
+
+ private LoadContext initLoadContext(BeanDescriptor> desc, JsonReadOptions readOptions) {
+ if (readOptions != null && readOptions.isEnableLazyLoading()) {
+ return new DLoadContext(desc, persistenceContext);
+ }
+ return null;
+ }
+
+ private PersistenceContext initPersistenceContext(JsonReadOptions readOptions) {
+ if (readOptions != null && readOptions.getPersistenceContext() != null) {
+ return readOptions.getPersistenceContext();
+ }
+ return new DefaultPersistenceContext();
+ }
+
+ /**
+ * Return the persistence context being used if any.
+ */
+ public PersistenceContext getPersistenceContext() {
+ return persistenceContext;
+ }
+
+ /**
+ * Return a new instance of ReadJson using the existing context but with a new JsonParser.
+ */
+ public ReadJson forJson(JsonParser moreJson) {
+ return new ReadJson(moreJson, this);
+ }
+
+ /**
+ * Add the bean to the persistence context.
+ */
+ public void persistenceContextPut(Object beanId, T currentBean) {
+
+ persistenceContextPutIfAbsent(beanId, (EntityBean)currentBean, rootDesc);
+ }
+
+ /**
+ * Put the bean into the persistence context. If there is already a matching bean in the
+ * persistence context then return that instance else return null.
+ */
+ public Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor> beanDesc) {
+
+ if (persistenceContext == null) {
+ // no persistenceContext means no lazy loading either
+ return null;
+ }
+
+ Object contextBean = persistenceContext.putIfAbsent(id, bean);
+ if (contextBean == null) {
+ if (loadContext != null) {
+ EntityBeanIntercept ebi = bean._ebean_getIntercept();
+ if (ebi.isPartial()) {
+ // register for further lazy loading
+ String path = pathStack.peekWithNull();
+ loadContext.register(path, ebi);
+ beanDesc.lazyLoadRegister(path, ebi, bean, loadContext);
+ }
+ ebi.setLoaded();
+ }
+ return null;
+ }
+ return contextBean;
}
/**
@@ -110,4 +203,5 @@ public class ReadJson {
public Object readValueUsingObjectMapper(Class> propertyType) throws IOException {
return getObjectMapper().readValue(parser, propertyType);
}
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java
index 11f423800..301a76cc9 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiTransaction;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
@@ -16,10 +17,10 @@ import java.sql.Connection;
*/
public class AutoCommitTransactionManager extends TransactionManager {
- public AutoCommitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
- ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
-
- super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses);
+ public AutoCommitTransactionManager(ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
+ DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
+
+ super(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr, bootupClasses);
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java
index 92ed02e53..68c0a0a39 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java
@@ -23,7 +23,7 @@ import java.util.Set;
*
* Duplicate beans are ones having the same type and unique id value. These are
* considered duplicates and replaced by the bean instance that was already
- * loaded into the PersistanceContext.
+ * loaded into the PersistenceContext.
*
*/
public final class DefaultPersistenceContext implements PersistenceContext {
@@ -36,13 +36,13 @@ public final class DefaultPersistenceContext implements PersistenceContext {
private final Monitor monitor = new Monitor();
/**
- * Create a new PersistanceContext.
+ * Create a new PersistenceContext.
*/
public DefaultPersistenceContext() {
}
/**
- * Set an object into the PersistanceContext.
+ * Set an object into the PersistenceContext.
*/
public void put(Object id, Object bean) {
synchronized (monitor) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java
index 9f56a3bfc..1f5fe4e61 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java
@@ -9,6 +9,10 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import com.avaje.ebean.annotation.DocStoreEvent;
+import com.avaje.ebeanservice.docstore.api.support.DocStoreDeleteEvent;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
+
/**
* Beans deleted by Id used for updating L2 Cache.
*/
@@ -72,5 +76,28 @@ public final class DeleteByIdMap {
return r;
}
-
+ /**
+ * Add the deletes to the DocStoreUpdates.
+ */
+ public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates, DocStoreEvent txnIndexMode) {
+ for (BeanPersistIds deleteIds : beanMap.values()) {
+ BeanDescriptor> desc = deleteIds.getBeanDescriptor();
+ DocStoreEvent docStoreEvent = desc.getDocStoreEvent(PersistRequest.Type.DELETE, txnIndexMode);
+ if (DocStoreEvent.IGNORE != docStoreEvent) {
+ // Add to queue or bulk update entries
+ boolean queue = (DocStoreEvent.QUEUE == docStoreEvent);
+ String queueId = desc.getDocStoreQueueId();
+ List idValues = deleteIds.getDeleteIds();
+ if (idValues != null) {
+ for (int i = 0; i < idValues.size(); i++) {
+ if (queue) {
+ docStoreUpdates.queueDelete(queueId, idValues.get(i));
+ } else {
+ docStoreUpdates.addDelete(new DocStoreDeleteEvent(desc, idValues.get(i)));
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java
index 25b25e221..54005421e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java
@@ -4,6 +4,7 @@ import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.SpiTransaction;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
@@ -16,10 +17,10 @@ import java.sql.Connection;
*/
public class ExplicitTransactionManager extends TransactionManager {
- public ExplicitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
- ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
+ public ExplicitTransactionManager(ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
+ DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
- super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses);
+ super(serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr, bootupClasses);
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
index 1a61d739c..69ddf2d12 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.transaction;
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.config.dbplatform.DatabasePlatform.OnQueryOnly;
@@ -142,6 +143,14 @@ public class JdbcTransaction implements SpiTransaction {
protected TChangeLogHolder changeLogHolder;
+ /**
+ * The mode for updating doc store indexes for this transaction.
+ * Only set when you want to override the default behavior.
+ */
+ protected DocStoreEvent docStoreUpdateMode;
+
+ protected int docStoreBulkBatchSize;
+
/**
* Create a new JdbcTransaction.
*/
@@ -272,6 +281,25 @@ public class JdbcTransaction implements SpiTransaction {
}
}
+ @Override
+ public int getDocStoreBulkBatchSize() {
+ return docStoreBulkBatchSize;
+ }
+
+ @Override
+ public void setDocStoreUpdateBatchSize(int docStoreBulkBatchSize) {
+ this.docStoreBulkBatchSize = docStoreBulkBatchSize;
+ }
+
+ public DocStoreEvent getDocStoreUpdateMode() {
+ return docStoreUpdateMode;
+ }
+
+ @Override
+ public void setDocStoreUpdateMode(DocStoreEvent docStoreUpdateMode) {
+ this.docStoreUpdateMode = docStoreUpdateMode;
+ }
+
@Override
public List getDerivedRelationship(Object bean) {
if (derivedRelMap == null) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java
index b8da478cb..faa96529a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java
@@ -1,8 +1,11 @@
package com.avaje.ebeaninternal.server.transaction;
+import com.avaje.ebean.annotation.DocStoreEvent;
+import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEvent;
import com.avaje.ebeaninternal.api.TransactionEventTable;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
@@ -37,14 +40,20 @@ public final class PostCommitProcessing {
private final DeleteByIdMap deleteByIdMap;
+ private final DocStoreEvent txnDocStoreMode;
+
+ private final int txnDocStoreBatchSize;
+
/**
- * Create for a TransactionManager and event.
+ * Create for an external modification.
*/
public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, TransactionEvent event) {
this.clusterManager = clusterManager;
this.manager = manager;
this.serverName = manager.getServerName();
+ this.txnDocStoreMode = DocStoreEvent.IGNORE;
+ this.txnDocStoreBatchSize = 0;
this.event = event;
this.deleteByIdMap = event.getDeleteByIdMap();
this.persistBeanRequests = event.getPersistRequestBeans();
@@ -52,6 +61,23 @@ public final class PostCommitProcessing {
this.remoteTransactionEvent = createRemoteTransactionEvent();
}
+ /**
+ * Create for a transaction.
+ */
+ public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction) {
+
+ this.clusterManager = clusterManager;
+ this.manager = manager;
+ this.serverName = manager.getServerName();
+ this.txnDocStoreMode = transaction.getDocStoreUpdateMode();
+ this.txnDocStoreBatchSize = transaction.getDocStoreBulkBatchSize();
+ this.event = transaction.getEvent();
+ this.deleteByIdMap = event.getDeleteByIdMap();
+ this.persistBeanRequests = event.getPersistRequestBeans();
+ this.beanPersistIdMap = createBeanPersistIdMap();
+ this.remoteTransactionEvent = createRemoteTransactionEvent();
+ }
+
public void notifyLocalCacheIndex() {
// notify cache with bulk insert/update/delete statements
@@ -76,6 +102,33 @@ public final class PostCommitProcessing {
}
}
+ /**
+ * Process any document store updates.
+ */
+ protected void processDocStoreUpdates() {
+
+ if (isDocStoreUpdate()) {
+ // collect 'bulk update' and 'queue' events
+ DocStoreUpdates docStoreUpdates = new DocStoreUpdates();
+ event.addDocStoreUpdates(docStoreUpdates);
+ if (deleteByIdMap != null) {
+ deleteByIdMap.addDocStoreUpdates(docStoreUpdates, txnDocStoreMode);
+ }
+
+ if (!docStoreUpdates.isEmpty()) {
+ // send to docstore / ElasticSearch and/or queue
+ manager.processDocStoreUpdates(docStoreUpdates, txnDocStoreBatchSize);
+ }
+ }
+ }
+
+ /**
+ * Return true if updates to the document store occur for this transaction.
+ */
+ private boolean isDocStoreUpdate() {
+ return manager.isDocStoreActive() && (txnDocStoreMode == null || txnDocStoreMode != DocStoreEvent.IGNORE);
+ }
+
public void notifyCluster() {
if (remoteTransactionEvent != null && !remoteTransactionEvent.isEmpty()) {
// send the interesting events to the cluster
@@ -91,6 +144,7 @@ public final class PostCommitProcessing {
return new Runnable() {
public void run() {
localPersistListenersNotify();
+ processDocStoreUpdates();
}
};
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
index b7e7e3b50..239aa0ffa 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
@@ -17,6 +17,8 @@ import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -70,6 +72,13 @@ public class TransactionManager {
protected final String serverName;
+ protected final boolean docStoreActive;
+
+ /**
+ * The elastic search index update processor.
+ */
+ protected final DocStoreUpdateProcessor docStoreUpdateProcessor;
+
protected final PersistBatch persistBatch;
protected final PersistBatch persistBatchOnCascade;
@@ -97,7 +106,7 @@ public class TransactionManager {
/**
* Create the TransactionManager
*/
- public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config,
+ public TransactionManager(ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
this.persistBatch = config.getPersistBatch();
@@ -109,11 +118,12 @@ public class TransactionManager {
this.serverName = config.getName();
this.backgroundExecutor = backgroundExecutor;
this.dataSource = config.getDataSource();
+ this.docStoreActive = config.getDocStoreConfig().isActive();
+ this.docStoreUpdateProcessor = docStoreUpdateProcessor;
this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners());
List transactionEventListeners = bootupClasses.getTransactionEventListeners();
- this.transactionEventListeners = transactionEventListeners.toArray(new
- TransactionEventListener[transactionEventListeners.size()]);
+ this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]);
this.prefix = "";
this.externalTransPrefix = "e";
@@ -136,6 +146,10 @@ public class TransactionManager {
}
}
+ public boolean isDocStoreActive() {
+ return docStoreActive;
+ }
+
public BeanDescriptorManager getBeanDescriptorManager() {
return beanDescriptorManager;
}
@@ -391,7 +405,7 @@ public class TransactionManager {
TXN_LOGGER.debug(transaction.getLogPrefix() + "Commit");
}
- PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction.getEvent());
+ PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
postCommit.notifyLocalCacheIndex();
postCommit.notifyCluster();
@@ -454,6 +468,13 @@ public class TransactionManager {
}
}
+ /**
+ * Process the docstore / ElasticSearch updates.
+ */
+ public void processDocStoreUpdates(DocStoreUpdates docStoreUpdates, int bulkBatchSize) {
+ docStoreUpdateProcessor.process(docStoreUpdates, bulkBatchSize);
+ }
+
/**
* Prepare and then send/log the changeSet.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java
index c1f675bf0..ef7255fdc 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/CtCompoundPropertyElAdapter.java
@@ -11,126 +11,131 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue;
* This is used for non-scalar properties of a Compound Value Object. These only
* occur in nested compound types.
*
- *
+ *
* @author rbygrave
*/
public class CtCompoundPropertyElAdapter implements ElPropertyValue {
- private final CtCompoundProperty prop;
+ private final CtCompoundProperty prop;
- private int deployOrder;
-
- public CtCompoundPropertyElAdapter(CtCompoundProperty prop) {
- this.prop = prop;
- }
-
- public void setDeployOrder(int deployOrder) {
- this.deployOrder = deployOrder;
- }
+ private int deployOrder;
- public Object elConvertType(Object value) {
- return value;
- }
+ public CtCompoundPropertyElAdapter(CtCompoundProperty prop) {
+ this.prop = prop;
+ }
- public Object elGetReference(EntityBean bean) {
- return bean;
- }
+ public void setDeployOrder(int deployOrder) {
+ this.deployOrder = deployOrder;
+ }
- public Object elGetValue(EntityBean bean) {
- return prop.getValue(bean);
- }
+ public Object elConvertType(Object value) {
+ return value;
+ }
- public void elSetValue(EntityBean bean, Object value, boolean populate) {
- prop.setValue(bean, value);
- }
+ public Object elGetReference(EntityBean bean) {
+ return bean;
+ }
- public int getDeployOrder() {
- return deployOrder;
- }
+ public Object elGetValue(EntityBean bean) {
+ return prop.getValue(bean);
+ }
- public String getAssocOneIdExpr(String prefix, String operator) {
- throw new RuntimeException("Not Supported or Expected");
- }
+ @Override
+ public void set(Object bean, Object value) {
+ elSetValue((EntityBean) bean, value, true);
+ }
- public Object[] getAssocOneIdValues(EntityBean bean) {
- throw new RuntimeException("Not Supported or Expected");
- }
-
- public String getAssocIdInExpr(String prefix) {
- throw new RuntimeException("Not Supported or Expected");
- }
+ public void elSetValue(EntityBean bean, Object value, boolean populate) {
+ prop.setValue(bean, value);
+ }
- public String getAssocIdInValueExpr(int size) {
- throw new RuntimeException("Not Supported or Expected");
- }
+ public int getDeployOrder() {
+ return deployOrder;
+ }
- public BeanProperty getBeanProperty() {
- return null;
- }
+ public String getAssocOneIdExpr(String prefix, String operator) {
+ throw new RuntimeException("Not Supported or Expected");
+ }
- public StringParser getStringParser() {
- return null;
- }
+ public Object[] getAssocOneIdValues(EntityBean bean) {
+ throw new RuntimeException("Not Supported or Expected");
+ }
- public boolean isDbEncrypted() {
- return false;
- }
+ public String getAssocIdInExpr(String prefix) {
+ throw new RuntimeException("Not Supported or Expected");
+ }
- public boolean isLocalEncrypted() {
- return false;
- }
+ public String getAssocIdInValueExpr(int size) {
+ throw new RuntimeException("Not Supported or Expected");
+ }
- public boolean isAssocId() {
- return false;
- }
-
- public boolean isAssocProperty() {
- return false;
- }
+ public BeanProperty getBeanProperty() {
+ return null;
+ }
- public boolean isDateTimeCapable() {
- return false;
- }
+ public StringParser getStringParser() {
+ return null;
+ }
- public int getJdbcType() {
- return 0;
- }
+ public boolean isDbEncrypted() {
+ return false;
+ }
- public Object parseDateTime(long systemTimeMillis) {
- throw new RuntimeException("Not Supported or Expected");
- }
-
- @Override
- public boolean containsFormulaWithJoin() {
- return false;
- }
+ public boolean isLocalEncrypted() {
+ return false;
+ }
- public boolean containsMany() {
- return false;
- }
+ public boolean isAssocId() {
+ return false;
+ }
- public boolean containsManySince(String sinceProperty) {
- return containsMany();
- }
-
- public String getDbColumn() {
- return null;
- }
+ public boolean isAssocProperty() {
+ return false;
+ }
- public String getElPlaceholder(boolean encrypted) {
- return null;
- }
+ public boolean isDateTimeCapable() {
+ return false;
+ }
- public String getElPrefix() {
- return null;
- }
+ public int getJdbcType() {
+ return 0;
+ }
- public String getName() {
- return prop.getPropertyName();
- }
+ public Object parseDateTime(long systemTimeMillis) {
+ throw new RuntimeException("Not Supported or Expected");
+ }
- public String getElName() {
- return prop.getPropertyName();
- }
+ @Override
+ public boolean containsFormulaWithJoin() {
+ return false;
+ }
+
+ public boolean containsMany() {
+ return false;
+ }
+
+ public boolean containsManySince(String sinceProperty) {
+ return containsMany();
+ }
+
+ public String getDbColumn() {
+ return null;
+ }
+
+ public String getElPlaceholder(boolean encrypted) {
+ return null;
+ }
+
+ public String getElPrefix() {
+ return null;
+ }
+
+ public String getName() {
+ return prop.getPropertyName();
+ }
+
+ public String getElName() {
+ return prop.getPropertyName();
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
index 8d3be52df..ebf05e87a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
@@ -560,7 +560,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
maxValueLen = Math.max(maxValueLen, value.length());
Object enumValue = Enum.valueOf(enumType, name.trim());
- beanDbMap.add(enumValue, value);
+ beanDbMap.add(enumValue, value, name.trim());
}
if (dbColumnLength == 0 && !integerType) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java
index d2c394015..583207b6a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbIntegerMap.java
@@ -15,16 +15,16 @@ public class EnumToDbIntegerMap extends EnumToDbValueMap {
return Types.INTEGER;
}
- public void add(Object beanValue, Integer dbValue) {
- addInternal(beanValue, dbValue);
+ public void add(Object beanValue, Integer dbValue, String name) {
+ addInternal(beanValue, dbValue, name);
}
@Override
- public EnumToDbIntegerMap add(Object beanValue, String stringDbValue) {
+ public EnumToDbIntegerMap add(Object beanValue, String stringDbValue, String name) {
try {
Integer value = Integer.valueOf(stringDbValue);
- addInternal(beanValue, value);
+ addInternal(beanValue, value, name);
return this;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java
index d5f77818b..335b4197c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbStringMap.java
@@ -15,8 +15,8 @@ public class EnumToDbStringMap extends EnumToDbValueMap {
}
@Override
- public EnumToDbStringMap add(Object beanValue, String dbValue) {
- addInternal(beanValue, dbValue);
+ public EnumToDbStringMap add(Object beanValue, String dbValue, String name) {
+ addInternal(beanValue, dbValue, name);
return this;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java
index dfe037a30..742aa864a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/EnumToDbValueMap.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import java.sql.SQLException;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -21,6 +22,8 @@ public abstract class EnumToDbValueMap {
final LinkedHashMap valueMap;
+ final HashMap nameMap;
+
final boolean allowNulls;
final boolean isIntegerType;
@@ -43,8 +46,9 @@ public abstract class EnumToDbValueMap {
public EnumToDbValueMap(boolean allowNulls, boolean isIntegerType) {
this.allowNulls = allowNulls;
this.isIntegerType = isIntegerType;
- keyMap = new LinkedHashMap();
- valueMap = new LinkedHashMap();
+ this.keyMap = new LinkedHashMap();
+ this.valueMap = new LinkedHashMap();
+ this.nameMap = new HashMap();
}
/**
@@ -81,7 +85,7 @@ public abstract class EnumToDbValueMap {
* Add name value pair where the dbValue is the raw string and may need to
* be converted (to an Integer for example).
*/
- public abstract EnumToDbValueMap add(Object beanValue, String dbValue);
+ public abstract EnumToDbValueMap add(Object beanValue, String dbValue, String name);
/**
* Add a bean value and DB value pair.
@@ -89,10 +93,11 @@ public abstract class EnumToDbValueMap {
* The dbValue will be converted to an Integer if isIntegerType is true;
*
*/
- protected void addInternal(Object beanValue, T dbValue) {
+ protected void addInternal(Object beanValue, T dbValue, String name) {
keyMap.put(beanValue, dbValue);
valueMap.put(dbValue, beanValue);
+ nameMap.put(name, beanValue);
}
/**
@@ -118,6 +123,9 @@ public abstract class EnumToDbValueMap {
return null;
}
Object beanValue = valueMap.get(dbValue);
+ if (beanValue == null) {
+ beanValue = nameMap.get(dbValue);
+ }
if (beanValue == null && !allowNulls) {
String msg = "Bean value for " + dbValue + " not found in " + valueMap;
throw new IllegalArgumentException(msg);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java
index c8982fa90..c462e7a03 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.text.StringFormatter;
import com.avaje.ebean.text.StringParser;
import com.avaje.ebean.text.json.JsonWriter;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -162,6 +163,11 @@ public interface ScalarType extends StringParser, StringFormatter, ScalarData
*/
T parse(String value);
+ /**
+ * Return the type this maps to for JSON document stores.
+ */
+ DocPropertyType getDocType();
+
/**
* Return true if the type can accept long systemTimeMillis input.
*
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java
index 739d9e435..37b31c45b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDate.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -82,6 +83,11 @@ public abstract class ScalarTypeBaseDate extends ScalarTypeBase {
writer.writeNumberField(name, millis);
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.DATE;
+ }
+
public T readData(DataInput dataInput) throws IOException {
if (!dataInput.readBoolean()) {
return null;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java
index 934a0f323..c1c667699 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.JsonConfig;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -122,6 +123,11 @@ public abstract class ScalarTypeBaseDateTime extends ScalarTypeBase {
}
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.DATETIME;
+ }
+
public String formatValue(T t) {
Timestamp ts = convertToTimestamp(t);
return ts.toString();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java
index 5b0e932bd..2e592e6e0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseVarchar.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
@@ -131,4 +132,10 @@ public abstract class ScalarTypeBaseVarchar extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, T value) throws IOException {
writer.writeStringField(name, format(value));
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java
index 8426802f2..7836a4592 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBigDecimal.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -87,4 +88,9 @@ public class ScalarTypeBigDecimal extends ScalarTypeBase {
writer.writeNumberField(name, value);
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.DOUBLE;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java
index 154d4a331..e0eb96078 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -338,6 +339,11 @@ public class ScalarTypeBoolean {
public void jsonWrite(JsonWriter writer, String name, Boolean value) throws IOException {
writer.writeBooleanField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.BOOLEAN;
+ }
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java
index 32792670d..0fb36b1ef 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeByte.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -51,6 +52,11 @@ public class ScalarTypeByte extends ScalarTypeBase {
throw new IOException("Not supported");
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.BINARY;
+ }
+
public String formatValue(Byte t) {
return t.toString();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java
index 30a230be6..87dbb48e0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesBase.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
@@ -52,6 +53,11 @@ public abstract class ScalarTypeBytesBase extends ScalarTypeBase {
return out.toByteArray();
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.BINARY;
+ }
+
public String formatValue(byte[] t) {
throw new TextException("Not supported");
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java
index 412712398..378ce7cb1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.text.json.JsonWriter;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -75,6 +76,11 @@ public class ScalarTypeBytesEncrypted implements ScalarType {
return out.toByteArray();
}
+ @Override
+ public DocPropertyType getDocType() {
+ return baseType.getDocType();
+ }
+
public String format(Object v) {
throw new RuntimeException("Not used");
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java
index d1eb890c2..30f5e5a48 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDayOfWeek.java
@@ -14,7 +14,7 @@ public class ScalarTypeDayOfWeek extends ScalarTypeEnumWithMapping {
static {
DayOfWeek[] values = DayOfWeek.values();
for (DayOfWeek value : values) {
- beanDbMap.add(value, value.getValue());
+ beanDbMap.add(value, value.getValue(), value.name());
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java
index c7918a8d4..b3a7179d6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDouble.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -93,4 +94,10 @@ public class ScalarTypeDouble extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Double value) throws IOException {
writer.writeNumberField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.DOUBLE;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java
index 2ed3169ce..f56df9506 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeDuration.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -110,4 +111,10 @@ public class ScalarTypeDuration extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Duration value) throws IOException {
writer.writeStringField(name, value.toString());
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java
index 171dc277d..ce571332c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.text.json.JsonWriter;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -141,4 +142,10 @@ public class ScalarTypeEncryptedWrapper implements ScalarType {
public void jsonWrite(JsonWriter writer, String name, T value) throws IOException {
wrapped.jsonWrite(writer, name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return wrapped.getDocType();
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java
index 6f9004242..3491058f7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumStandard.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
@@ -241,6 +242,11 @@ public class ScalarTypeEnumStandard {
writer.writeStringField(name, formatValue(value));
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.ENUM;
+ }
+
@Override
public Object readData(DataInput dataInput) throws IOException {
if (!dataInput.readBoolean()) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java
index 4e6124169..dd21eaa7d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
+
import java.sql.SQLException;
import java.util.Iterator;
@@ -82,4 +84,9 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i
return beanDbMap.getDbValue(beanValue);
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java
index be87a0542..fe5507eb4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFile.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
@@ -127,6 +128,11 @@ public class ScalarTypeFile extends ScalarTypeBase {
return tempFile;
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.BINARY;
+ }
+
@Override
public String formatValue(File file) {
throw new TextException("Not supported");
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java
index 2951bb638..51b55d111 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeFloat.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -93,4 +94,10 @@ public class ScalarTypeFloat extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Float value) throws IOException {
writer.writeNumberField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.FLOAT;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java
index 9312cc0ae..626f7c439 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -93,4 +94,10 @@ public class ScalarTypeInteger extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Integer value) throws IOException {
writer.writeNumberField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.INTEGER;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java
index a41d4d823..a82a3ebe7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJodaLocalTime.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -86,6 +87,11 @@ public class ScalarTypeJodaLocalTime extends ScalarTypeBase {
}
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
+
@Override
public LocalTime convertFromMillis(long systemTimeMillis) {
return new LocalTime(systemTimeMillis, DateTimeZone.getDefault());
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java
index 8b12a8df8..131a29a3c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.EJson;
import com.avaje.ebean.text.json.JsonWriter;
@@ -205,6 +206,11 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase {
}
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.OBJECT;
+ }
+
@Override
public Map jsonRead(JsonParser parser, JsonToken event) throws IOException {
// at this point the BeanProperty has read the START_OBJECT token
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java
index ebccef7ab..cf06749a1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
@@ -215,6 +216,11 @@ public abstract class ScalarTypeJsonNode extends ScalarTypeBase {
}
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.OBJECT;
+ }
+
@Override
public JsonNode jsonRead(JsonParser parser, JsonToken event) throws IOException {
// at this point the BeanProperty has read the START_OBJECT token
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java
index d80af485d..8bbb56677 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalTime.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -102,4 +103,10 @@ public class ScalarTypeLocalTime extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, LocalTime value) throws IOException {
writer.writeStringField(name, value.toString());
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java
index 8dffa3b56..1a464f1c4 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -93,4 +94,10 @@ public class ScalarTypeLong extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Long value) throws IOException {
writer.writeNumberField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.LONG;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java
index 93bb233db..d6263fcae 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMathBigInteger.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -101,4 +102,9 @@ public class ScalarTypeMathBigInteger extends ScalarTypeBase {
writer.writeNumberField(name, value.longValue());
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.LONG;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java
index faa863c5b..3c4384a2b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonth.java
@@ -14,7 +14,7 @@ public class ScalarTypeMonth extends ScalarTypeEnumWithMapping {
static {
Month[] values = Month.values();
for (Month value : values) {
- beanDbMap.add(value, value.getValue());
+ beanDbMap.add(value, value.getValue(), value.name());
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java
index 9d491c091..d59474c36 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeMonthDay.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -128,5 +129,9 @@ public class ScalarTypeMonthDay extends ScalarTypeBase {
writer.writeStringField(name, format(value));
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java
index 23e807408..8da6f2ee5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.EJson;
import com.avaje.ebean.text.json.JsonWriter;
@@ -130,4 +131,8 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase {
return EJson.parseObject(parser, event);
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.OBJECT;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java
index c6b4a2a97..b11c3525f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeShort.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -94,4 +95,10 @@ public class ScalarTypeShort extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Short value) throws IOException {
writer.writeNumberField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.SHORT;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java
index 03f786240..ff19ba3a0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeString.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -93,4 +94,9 @@ public class ScalarTypeString extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, String value) throws IOException {
writer.writeStringField(name, value);
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java
index 74ed0871f..6a8b8c97b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeTime.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
import com.fasterxml.jackson.core.JsonParser;
@@ -96,4 +97,9 @@ public class ScalarTypeTime extends ScalarTypeBase {
writer.writeStringField(name, format(value));
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBinary.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBinary.java
index fb789c659..1d9a6c35c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBinary.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBinary.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -153,4 +154,8 @@ public class ScalarTypeUUIDBinary extends ScalarTypeBase {
writer.writeStringField(name, value.toString());
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDNative.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDNative.java
index a809f2dfd..4b6dce2dd 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDNative.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDNative.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -117,4 +118,8 @@ public class ScalarTypeUUIDNative extends ScalarTypeBase {
return strValue == null ? null : parse(strValue);
}
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.STRING;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java
index ac3b36b97..db2553133 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.ScalarTypeConverter;
import com.avaje.ebean.text.json.JsonWriter;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
@@ -193,4 +194,9 @@ public class ScalarTypeWrapper implements ScalarType {
scalarType.jsonWrite(writer, name, unwrapValue);
}
+ @Override
+ public DocPropertyType getDocType() {
+ return scalarType.getDocType();
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeYear.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeYear.java
index bb84595fb..2b3f383d9 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeYear.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeYear.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.JsonWriter;
import com.avaje.ebeaninternal.server.core.BasicTypeConverter;
@@ -97,4 +98,10 @@ public class ScalarTypeYear extends ScalarTypeBase {
public void jsonWrite(JsonWriter writer, String name, Year value) throws IOException {
writer.writeNumberField(name, value.getValue());
}
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.INTEGER;
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreBeanAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreBeanAdapter.java
new file mode 100644
index 000000000..df2d3d473
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreBeanAdapter.java
@@ -0,0 +1,116 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import com.avaje.ebean.Query;
+import com.avaje.ebean.annotation.DocStoreEvent;
+import com.avaje.ebean.plugin.BeanDocType;
+import com.avaje.ebeaninternal.server.core.PersistRequest;
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
+import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Doc store specific adapter to process doc store events for a given bean type.
+ */
+public interface DocStoreBeanAdapter extends BeanDocType {
+
+ /**
+ * In deployment phase read the embedded/nested document information.
+ */
+ void registerPaths();
+
+ /**
+ * Register invalidation events for embedded/nested documents the given path and properties.
+ */
+ void registerInvalidationPath(String queueId, String path, Set properties);
+
+ /**
+ * Apply the document structure to the query so that it fetches the required properties to build
+ * the document (typically in JSON form).
+ */
+ void applyPath(Query query);
+
+ /**
+ * Return true if this type is mapped for doc storage.
+ */
+ boolean isMapped();
+
+ /**
+ * Return the unique queueId for this bean type. This is expected to be a relatively short unique
+ * string (rather than a fully qualified class name).
+ */
+ String getQueueId();
+
+ /**
+ * Determine and return how this persist type will be processed given the transaction mode.
+ *
+ * Some transactions (like bulk updates) might specifically turn off indexing for example.
+ */
+ DocStoreEvent getEvent(PersistRequest.Type persistType, DocStoreEvent txnMode);
+
+ /**
+ * Return the index type for this bean type.
+ */
+ String getIndexType();
+
+ /**
+ * Return the index name for this bean type.
+ */
+ String getIndexName();
+
+ /**
+ * Process a delete by id of a given document.
+ */
+ void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException;
+
+ /**
+ * Process an index event which is effectively an insert or update (or put).
+ */
+ void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException;
+
+ /**
+ * Process an insert persist request.
+ */
+ void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException;
+
+ /**
+ * Process an update persist request.
+ */
+ void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException;
+
+ /**
+ * Process the persist request adding any embedded/nested document invalidation to the docStoreUpdates.
+ *
+ * This is expected to check the specific properties to see what other documents they are nested in
+ * and register invalidation events based on that.
+ *
+ * @param request The persist request
+ * @param docStoreUpdates Invalidation events are registered to this docStoreUpdates
+ */
+ void updateEmbedded(PersistRequestBean request, DocStoreUpdates docStoreUpdates);
+
+ /**
+ * Process an update of an embedded document.
+ *
+ * @param idValue the id of the bean effected by an embedded document update
+ * @param embeddedProperty the path of the property
+ * @param embeddedRawContent the embedded content for this property in JSON form
+ * @param txn the doc store transaction to use to process the update
+ */
+ void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException;
+
+ /**
+ * Create the document mapping.
+ */
+ DocumentMapping createDocMapping();
+
+ /**
+ * Return an un-analysed property to use instead of the given property.
+ *
+ * For analysed properties that we want to sort on we will map the property to an additional
+ * 'raw' property that we can use for sorting etc.
+ *
+ */
+ String rawProperty(String property);
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java
new file mode 100644
index 000000000..1579e2e70
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreFactory.java
@@ -0,0 +1,22 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import com.avaje.ebean.plugin.SpiServer;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+
+/**
+ * Creates the integration components for DocStore integration.
+ */
+public interface DocStoreFactory {
+
+ /**
+ * Create and return the DocStore integration components.
+ */
+ DocStoreIntegration create(SpiServer server);
+
+ /**
+ * Create the doc store specific adapter for the given bean type.
+ */
+ DocStoreBeanAdapter createAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy);
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java
new file mode 100644
index 000000000..5a6911788
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreIntegration.java
@@ -0,0 +1,20 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import com.avaje.ebean.DocumentStore;
+
+/**
+ * All the required features for DocStore integration.
+ */
+public interface DocStoreIntegration {
+
+ /**
+ * Return the DocStoreUpdateProcessor to use.
+ */
+ DocStoreUpdateProcessor updateProcessor();
+
+ /**
+ * Return the DocStore.
+ */
+ DocumentStore documentStore();
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java
new file mode 100644
index 000000000..2a0ff8b41
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreQueryUpdate.java
@@ -0,0 +1,23 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import java.io.IOException;
+
+/**
+ * Update the document store using a Ebean ORM query.
+ *
+ * Executes a forEach query and updates the document store with the bean object graph returned by the query.
+ *
+ */
+public interface DocStoreQueryUpdate {
+
+ /**
+ * Process the bean storing in the document store.
+ */
+ void store(Object idValue, T bean) throws IOException;
+
+ /**
+ * Flush the changes to the document store.
+ */
+ void flush() throws IOException;
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java
new file mode 100644
index 000000000..d07774033
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdate.java
@@ -0,0 +1,19 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import java.io.IOException;
+
+/**
+ * For persist events that know how to publish or queue their change to the Document store.
+ */
+public interface DocStoreUpdate {
+
+ /**
+ * Add the event to the doc store bulk update.
+ */
+ void docStoreUpdate(DocStoreUpdateContext txn) throws IOException;
+
+ /**
+ * Add to the queue for deferred processing.
+ */
+ void addToQueue(DocStoreUpdates docStoreUpdates);
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java
new file mode 100644
index 000000000..b6494710b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateContext.java
@@ -0,0 +1,11 @@
+package com.avaje.ebeanservice.docstore.api;
+
+/**
+ * The doc store specific context/transaction used to collect updates to send to the document store.
+ *
+ * Doc store specific implementations gather changes and bulk update the document store.
+ *
+ */
+public interface DocStoreUpdateContext {
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java
new file mode 100644
index 000000000..abf491bc2
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java
@@ -0,0 +1,34 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import com.avaje.ebean.plugin.BeanType;
+
+import java.io.IOException;
+
+/**
+ * Processes index updates.
+ *
+ * This involves sending updates directly to ElasticSearch via it's Bulk API or
+ * queuing events for future processing.
+ *
+ */
+public interface DocStoreUpdateProcessor {
+
+ /**
+ * Create a processor to handle updates per bean via a findEach query.
+ */
+ DocStoreQueryUpdate createQueryUpdate(BeanType beanType, int bulkBatchSize) throws IOException;
+
+ /**
+ * Process all the updates for a transaction.
+ *
+ * Typically this makes calls to the Bulk API of the document store or simply adds entries
+ * to a queue for future processing.
+ *
+ *
+ * @param docStoreUpdates The 'Bulk' and 'Queue' updates to the indexes for the transaction.
+ * @param bulkBatchSize The batch size to use for Bulk API calls specified on the transaction.
+ * If this is 0 then the default batch size is used.
+ */
+ void process(DocStoreUpdates docStoreUpdates, int bulkBatchSize);
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java
new file mode 100644
index 000000000..cf64d524e
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdates.java
@@ -0,0 +1,115 @@
+package com.avaje.ebeanservice.docstore.api;
+
+import com.avaje.ebean.DocStoreQueueEntry;
+import com.avaje.ebean.DocStoreQueueEntry.Action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Collection of document store updates that are either sent to the document store
+ * or queued for future processing
+ */
+public class DocStoreUpdates {
+
+ /**
+ * Persist inserts and updates.
+ */
+ private final List persistEvents = new ArrayList();
+
+ /**
+ * Delete by Id.
+ */
+ private final List deleteEvents = new ArrayList();
+
+ /**
+ * Nested updates.
+ */
+ private final List nestedEvents = new ArrayList();
+
+ /**
+ * Entries sent to the queue for later processing.
+ */
+ private final List queueEntries = new ArrayList();
+
+ public DocStoreUpdates() {
+ }
+
+ /**
+ * Return true if there are no events to process.
+ */
+ public boolean isEmpty() {
+ return persistEvents.isEmpty() && deleteEvents.isEmpty() && nestedEvents.isEmpty() && queueEntries.isEmpty();
+ }
+
+ /**
+ * Add a persist request.
+ */
+ public void addPersist(DocStoreUpdate bulkRequest) {
+ persistEvents.add(bulkRequest);
+ }
+
+ /**
+ * Add a delete request.
+ */
+ public void addDelete(DocStoreUpdate bulkRequest) {
+ deleteEvents.add(bulkRequest);
+ }
+
+ /**
+ * Add a nested update.
+ */
+ public void addNested(String queueId, String path, Object beanId) {
+ nestedEvents.add(new DocStoreQueueEntry(Action.NESTED, queueId, path, beanId));
+ }
+
+ /**
+ * Queue an 'index' request.
+ */
+ public void queueIndex(String queueId, Object beanId) {
+ queueEntries.add(new DocStoreQueueEntry(Action.INDEX, queueId, beanId));
+ }
+
+ /**
+ * Queue a 'delete' request.
+ */
+ public void queueDelete(String queueId, Object beanId) {
+ queueEntries.add(new DocStoreQueueEntry(Action.DELETE, queueId, beanId));
+ }
+
+ /**
+ * Queue an update to a nested/embedded object.
+ */
+ public void queueNested(String queueId, String path, Object beanId) {
+ queueEntries.add(new DocStoreQueueEntry(Action.NESTED, queueId, path, beanId));
+ }
+
+ /**
+ * Return the persist insert and update requests to be sent to the document store.
+ */
+ public List getPersistEvents() {
+ return persistEvents;
+ }
+
+ /**
+ * Return delete events.
+ */
+ public List getDeleteEvents() {
+ return deleteEvents;
+ }
+
+ /**
+ * Return the list of nested update events.
+ */
+ public List getNestedEvents() {
+ return nestedEvents;
+ }
+
+ /**
+ * Return the entries for sending to the queue.
+ */
+ public List getQueueEntries() {
+ return queueEntries;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java
new file mode 100644
index 000000000..59f51e546
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocumentNotFoundException.java
@@ -0,0 +1,15 @@
+package com.avaje.ebeanservice.docstore.api;
+
+/**
+ * Can be thrown when a document is unexpectedly not found in a document store.
+ */
+public class DocumentNotFoundException extends RuntimeException {
+
+ /**
+ * Construct with a message.
+ */
+ public DocumentNotFoundException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java
new file mode 100644
index 000000000..9f3ab7d23
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocMappingBuilder.java
@@ -0,0 +1,143 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+import com.avaje.ebean.annotation.DocMapping;
+import com.avaje.ebean.annotation.DocStore;
+import com.avaje.ebean.text.PathProperties;
+import com.avaje.ebeaninternal.server.query.SplitName;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * Builds the DocumentMapping for a given bean type.
+ */
+public class DocMappingBuilder {
+
+ private final PathProperties paths;
+
+ private final DocStore docStore;
+
+ private final Stack properties = new Stack();
+
+ private final Map map = new LinkedHashMap();
+
+ /**
+ * Create with the document structure paths and docStore deployment annotation.
+ */
+ public DocMappingBuilder(PathProperties paths, DocStore docStore) {
+ this.paths = paths;
+ this.docStore = docStore;
+ this.properties.push(new DocPropertyMapping());
+ }
+
+ /**
+ * Return true if the property is included in the document.
+ */
+ public boolean includesProperty(String prefix, String name) {
+ return paths.includesProperty(prefix, name);
+ }
+
+ /**
+ * Return true if the path is included in the document.
+ */
+ public boolean includesPath(String prefix, String name) {
+ return paths.includesProperty(prefix, name);
+ }
+
+ /**
+ * Add the property mapping.
+ */
+ public void add(DocPropertyMapping docMapping) {
+
+ DocPropertyMapping currentParent = properties.peek();
+ currentParent.addChild(docMapping);
+
+ String parentName = currentParent.getName();
+ String fullName = SplitName.add(parentName, docMapping.getName());
+ map.put(fullName, docMapping);
+ }
+
+ /**
+ * Push the nested object or list onto the properties stack.
+ */
+ public void push(DocPropertyMapping nested) {
+ properties.push(nested);
+ }
+
+ /**
+ * Pop the nested object or list off the properties stack.
+ */
+ public void pop() {
+ properties.pop();
+ }
+
+ /**
+ * Apply any override mappings from the top level docStore annotation.
+ */
+ public void applyMapping() {
+
+ DocMapping[] mapping = docStore.mapping();
+ for (DocMapping docMapping : mapping) {
+ applyFieldMapping(null, docMapping);
+ }
+ }
+
+ private void applyFieldMapping(String prefix, DocMapping docMapping) {
+
+ String name = docMapping.name();
+ String fullName = SplitName.add(prefix, name);
+
+ DocPropertyMapping mapping = map.get(fullName);
+ if (mapping == null) {
+ throw new IllegalStateException("DocMapping for ["+fullName+"] but property not included in document?");
+ }
+ mapping.apply(docMapping);
+ }
+
+ /**
+ * Collect the mapping of properties to 'raw' properties for those marked as sortable.
+ */
+ public Map collectSortable() {
+
+ DocPropertyMapping peek = properties.peek();
+ SortableVisitor visitor = new SortableVisitor();
+ peek.visit(visitor);
+
+ return visitor.getSortableMap();
+ }
+
+ /**
+ * Create the document mapping.
+ */
+ public DocumentMapping create(String queueId, String indexName, String indexType) {
+
+ int shards = docStore.shards();
+ int replicas = docStore.replicas();
+ DocPropertyMapping root = properties.peek();
+ return new DocumentMapping(queueId, indexName, indexType, paths, root, shards, replicas);
+ }
+
+
+ /**
+ * Find sortable properties to build the mapping to 'raw' properties.
+ */
+ private static class SortableVisitor extends DocPropertyAdapter {
+
+ private Map sortableMap = new LinkedHashMap();
+
+ @Override
+ public void visitProperty(DocPropertyMapping property) {
+
+ DocPropertyOptions options = property.getOptions();
+ if (options != null && Boolean.TRUE.equals(options.getSortable())) {
+ String fullPath = pathStack.peekFullPath(property.getName());
+ sortableMap.put(fullPath, fullPath+".raw");
+ }
+ }
+
+ private Map getSortableMap() {
+ return sortableMap;
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java
new file mode 100644
index 000000000..b7942253a
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyAdapter.java
@@ -0,0 +1,47 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+import com.avaje.ebeaninternal.server.text.json.PathStack;
+
+/**
+ * Adapter for DocPropertyVisitor that does not do anything.
+ * Used to extend and implement only the desired methods.
+ */
+public abstract class DocPropertyAdapter implements DocPropertyVisitor {
+
+ protected PathStack pathStack = new PathStack();
+
+ @Override
+ public void visitProperty(DocPropertyMapping property) {
+ // do nothing
+ }
+
+ @Override
+ public void visitBegin() {
+ // do nothing
+ }
+
+ @Override
+ public void visitEnd() {
+ // do nothing
+ }
+
+ @Override
+ public void visitBeginObject(DocPropertyMapping property) {
+ pathStack.push(property.getName());
+ }
+
+ @Override
+ public void visitEndObject(DocPropertyMapping property) {
+ pathStack.pop();
+ }
+
+ @Override
+ public void visitBeginList(DocPropertyMapping property) {
+ pathStack.push(property.getName());
+ }
+
+ @Override
+ public void visitEndList(DocPropertyMapping property) {
+ pathStack.pop();
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java
new file mode 100644
index 000000000..24681d85e
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyMapping.java
@@ -0,0 +1,130 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+import com.avaje.ebean.annotation.DocMapping;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Property mapping in a doc store document structure.
+ */
+public class DocPropertyMapping {
+
+ private String name;
+
+ private DocPropertyType type;
+
+ private DocPropertyOptions options;
+
+ private List children = new ArrayList();
+
+ /**
+ * Construct ROOT.
+ */
+ public DocPropertyMapping() {
+ this.type = DocPropertyType.ROOT;
+ }
+
+ /**
+ * Construct property mapping.
+ */
+ public DocPropertyMapping(String name, DocPropertyType type) {
+ this.type = type;
+ this.name = name;
+ this.options = new DocPropertyOptions();
+ }
+
+ /**
+ * Construct property mapping with options.
+ */
+ public DocPropertyMapping(String name, DocPropertyType type, DocPropertyOptions options) {
+ this.name = name;
+ this.type = type;
+ this.options = options;
+ }
+
+ /**
+ * Visit this property and any nested children.
+ */
+ public void visit(DocPropertyVisitor visitor) {
+ switch (type) {
+ case ROOT:
+ visitor.visitBegin();
+ visitChildren(visitor);
+ visitor.visitEnd();
+ break;
+ case OBJECT:
+ visitor.visitBeginObject(this);
+ visitChildren(visitor);
+ visitor.visitEndObject(this);
+ break;
+ case LIST:
+ visitor.visitBeginList(this);
+ visitChildren(visitor);
+ visitor.visitEndList(this);
+ break;
+ default:
+ visitor.visitProperty(this);
+ }
+ }
+
+ private void visitChildren(DocPropertyVisitor visitor) {
+
+ for (DocPropertyMapping property : children) {
+ property.visit(visitor);
+ }
+ }
+
+ public String toString() {
+ return "name:"+name+" type:"+type+" options("+options+")";
+ }
+
+ /**
+ * Return the type of the property.
+ */
+ public DocPropertyType getType() {
+ return type;
+ }
+
+ /**
+ * Set the type of the property.
+ */
+ public void setType(DocPropertyType type) {
+ this.type = type;
+ }
+
+ /**
+ * Return the property name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the property options.
+ */
+ public DocPropertyOptions getOptions() {
+ return options;
+ }
+
+ /**
+ * Return the child nested properties.
+ */
+ public List getChildren() {
+ return children;
+ }
+
+ /**
+ * Add a child property.
+ */
+ public void addChild(DocPropertyMapping docMapping) {
+ children.add(docMapping);
+ }
+
+ /**
+ * Apply mapping options to this property.
+ */
+ public void apply(DocMapping docMapping) {
+ options.apply(docMapping);
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java
new file mode 100644
index 000000000..2602c8cd7
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyOptions.java
@@ -0,0 +1,138 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+import com.avaje.ebean.annotation.DocMapping;
+
+/**
+ * Options for mapping a property for document storage.
+ */
+public class DocPropertyOptions {
+
+ private Boolean code;
+
+ private Boolean sortable;
+
+ private Boolean store;
+
+ private Float boost;
+
+ private String nullValue;
+
+ /**
+ * Construct with no values set.
+ */
+ public DocPropertyOptions() {
+
+ }
+
+ /**
+ * Construct as a copy of the source options.
+ */
+ protected DocPropertyOptions(DocPropertyOptions source) {
+ this.code = source.code;
+ this.sortable = source.sortable;
+ this.store = source.store;
+ this.boost = source.boost;
+ this.nullValue = source.nullValue;
+ }
+
+ /**
+ * Construct with options set.
+ */
+ public DocPropertyOptions(Boolean code, Boolean sortable, Boolean store, Float boost, String nullValue) {
+ this.code = code;
+ this.sortable = sortable;
+ this.store = store;
+ this.boost = boost;
+ this.nullValue = nullValue;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (code != null) {
+ sb.append("code:").append(code).append(" ");
+ }
+ if (sortable != null) {
+ sb.append("sortable:").append(sortable).append(" ");
+ }
+ if (store != null) {
+ sb.append("store:").append(store).append(" ");
+ }
+ if (boost != null) {
+ sb.append("boost:").append(boost).append(" ");
+ }
+ if (nullValue != null) {
+ sb.append("nullValue:").append(nullValue).append(" ");
+ }
+ return sb.toString();
+ }
+
+ public Boolean getCode() {
+ return code;
+ }
+
+ public void setCode(Boolean code) {
+ this.code = code;
+ }
+
+ public Boolean getSortable() {
+ return sortable;
+ }
+
+ public void setSortable(Boolean sortable) {
+ this.sortable = sortable;
+ }
+
+ public Float getBoost() {
+ return boost;
+ }
+
+ public void setBoost(Float boost) {
+ this.boost = boost;
+ }
+
+ public String getNullValue() {
+ return nullValue;
+ }
+
+ public void setNullValue(String nullValue) {
+ this.nullValue = nullValue;
+ }
+
+ public Boolean getStore() {
+ return store;
+ }
+
+ public void setStore(Boolean store) {
+ this.store = store;
+ }
+
+ /**
+ * Create a copy of this such that it can be overridden on a per index basis.
+ */
+ public DocPropertyOptions copy() {
+ return new DocPropertyOptions(this);
+ }
+
+ /**
+ * Apply override mapping from the document level or embedded property level.
+ */
+ public void apply(DocMapping docMapping) {
+
+ if (docMapping.code()) {
+ code = true;
+ }
+ if (docMapping.sortable()) {
+ sortable = true;
+ }
+ if (docMapping.store()) {
+ store = true;
+ }
+ if (docMapping.boost() != 1) {
+ boost = docMapping.boost();
+ }
+ if (!"".equals(docMapping.nullValue())) {
+ nullValue = docMapping.nullValue();
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java
new file mode 100644
index 000000000..48e826db2
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java
@@ -0,0 +1,78 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+/**
+ * Types as defined for document store property types.
+ */
+public enum DocPropertyType {
+
+ /**
+ * Enum.
+ */
+ ENUM,
+
+ /**
+ * String.
+ */
+ STRING,
+
+ /**
+ * Boolean.
+ */
+ BOOLEAN,
+
+ /**
+ * Short.
+ */
+ SHORT,
+
+ /**
+ * Integer.
+ */
+ INTEGER,
+
+ /**
+ * Long.
+ */
+ LONG,
+
+ /**
+ * Float.
+ */
+ FLOAT,
+
+ /**
+ * Double.
+ */
+ DOUBLE,
+
+ /**
+ * Date without time.
+ */
+ DATE,
+
+ /**
+ * Date with time.
+ */
+ DATETIME,
+
+ /**
+ * Binary type.
+ */
+ BINARY,
+
+ /**
+ * A nested object.
+ */
+ OBJECT,
+
+ /**
+ * A nested list of objects.
+ */
+ LIST,
+
+ /**
+ * Root level type.
+ */
+ ROOT
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java
new file mode 100644
index 000000000..6ce9540a7
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyVisitor.java
@@ -0,0 +1,43 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+/**
+ * Used to visit the properties in a document structure.
+ */
+public interface DocPropertyVisitor {
+
+ /**
+ * Begin visiting the document structure.
+ */
+ void visitBegin();
+
+ /**
+ * Visit a property.
+ */
+ void visitProperty(DocPropertyMapping property);
+
+ /**
+ * Start visiting a nested object.
+ */
+ void visitBeginObject(DocPropertyMapping property);
+
+ /**
+ * End visiting a nested object.
+ */
+ void visitEndObject(DocPropertyMapping property);
+
+ /**
+ * Start visiting a nested list.
+ */
+ void visitBeginList(DocPropertyMapping property);
+
+ /**
+ * End visiting a nested list.
+ */
+ void visitEndList(DocPropertyMapping property);
+
+ /**
+ * Finished visiting the document structure.
+ */
+ void visitEnd();
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java
new file mode 100644
index 000000000..15d32cc72
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocumentMapping.java
@@ -0,0 +1,103 @@
+package com.avaje.ebeanservice.docstore.api.mapping;
+
+import com.avaje.ebean.FetchPath;
+
+/**
+ * Mapping for a document stored in a doc store (like ElasticSearch).
+ */
+public class DocumentMapping {
+
+ protected final String queueId;
+
+ protected final String name;
+
+ protected final String type;
+
+ protected final FetchPath paths;
+
+ protected final DocPropertyMapping properties;
+
+ protected int shards;
+
+ protected int replicas;
+
+ public DocumentMapping(String queueId, String name, String type, FetchPath paths, DocPropertyMapping properties, int shards, int replicas) {
+ this.queueId = queueId;
+ this.name = name;
+ this.type = type;
+ this.paths = paths;
+ this.properties = properties;
+ this.shards = shards;
+ this.replicas = replicas;
+ }
+
+ /**
+ * Visit all the properties in the document structure.
+ */
+ public void visit(DocPropertyVisitor visitor) {
+ properties.visit(visitor);
+ }
+
+ /**
+ * Return the queueId.
+ */
+ public String getQueueId() {
+ return queueId;
+ }
+
+ /**
+ * Return the name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the type.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Return the document structure as PathProperties.
+ */
+ public FetchPath getPaths() {
+ return paths;
+ }
+
+ /**
+ * Return the document structure with mapping details.
+ */
+ public DocPropertyMapping getProperties() {
+ return properties;
+ }
+
+ /**
+ * Return the number of shards.
+ */
+ public int getShards() {
+ return shards;
+ }
+
+ /**
+ * Set the number of shards.
+ */
+ public void setShards(int shards) {
+ this.shards = shards;
+ }
+
+ /**
+ * Return the number of replicas.
+ */
+ public int getReplicas() {
+ return replicas;
+ }
+
+ /**
+ * Set the number of replicas.
+ */
+ public void setReplicas(int replicas) {
+ this.replicas = replicas;
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java
new file mode 100644
index 000000000..076a3fc98
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java
@@ -0,0 +1,315 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+import com.avaje.ebean.FetchPath;
+import com.avaje.ebean.Query;
+import com.avaje.ebean.annotation.DocStore;
+import com.avaje.ebean.annotation.DocStoreEvent;
+import com.avaje.ebean.text.PathProperties;
+import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import com.avaje.ebeaninternal.server.core.PersistRequest;
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.deploy.BeanProperty;
+import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base implementation for much of DocStoreBeanAdapter.
+ */
+public abstract class DocStoreBeanBaseAdapter implements DocStoreBeanAdapter {
+
+ protected final SpiEbeanServer server;
+
+ /**
+ * The associated BeanDescriptor.
+ */
+ protected final BeanDescriptor desc;
+
+ /**
+ * The type of index.
+ */
+ protected final boolean mapped;
+
+ /**
+ * Nested path properties defining the doc structure for indexing.
+ */
+ protected final DocStructure docStructure;
+
+ /**
+ * Identifier used in the queue system to identify the index.
+ */
+ protected final String queueId;
+
+ /**
+ * ElasticSearch index type.
+ */
+ protected final String indexType;
+
+ /**
+ * ElasticSearch index name.
+ */
+ protected final String indexName;
+
+ /**
+ * Doc store deployment annotation.
+ */
+ private final DocStore docStore;
+
+ /**
+ * Behavior on insert.
+ */
+ protected final DocStoreEvent insert;
+
+ /**
+ * Behavior on update.
+ */
+ protected final DocStoreEvent update;
+
+ /**
+ * Behavior on delete.
+ */
+ protected final DocStoreEvent delete;
+
+ /**
+ * List of embedded paths from other documents that include this document type.
+ * As such an update to this doc type means that those embedded documents need to be updated.
+ */
+ protected final List embeddedInvalidation = new ArrayList();
+
+ /**
+ * Map of properties to 'raw' properties.
+ */
+ private Map sortableMap;
+
+
+ public DocStoreBeanBaseAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) {
+
+ this.desc = desc;
+ this.server = desc.getEbeanServer();
+ this.mapped = deploy.isDocStoreMapped();
+ this.docStructure = (!mapped) ? null : derivePathProperties(deploy);
+ this.docStore = deploy.getDocStore();
+ this.queueId = derive(desc, deploy.getDocStoreQueueId());
+ this.indexName = derive(desc, deploy.getDocStoreIndexName());
+ this.indexType = derive(desc, deploy.getDocStoreIndexType());
+ this.insert = deploy.getDocStoreInsertEvent();
+ this.update = deploy.getDocStoreUpdateEvent();
+ this.delete = deploy.getDocStoreDeleteEvent();
+ }
+
+ @Override
+ public DocumentMapping createDocMapping() {
+
+ if (!mapped) return null;
+
+ PathProperties paths = docStructure.doc();
+
+ DocMappingBuilder mappingBuilder = new DocMappingBuilder(paths, docStore);
+ desc.docStoreMapping(mappingBuilder, null);
+
+ mappingBuilder.applyMapping();
+ prepareMapping(mappingBuilder);
+
+ sortableMap = mappingBuilder.collectSortable();
+
+ docStructure.prepareMany(desc);
+
+ return mappingBuilder.create(queueId, indexName, indexType);
+ }
+
+ protected void prepareMapping(DocMappingBuilder mappingBuilder) {
+ // do nothing by default
+ }
+
+ @Override
+ public String getIndexType() {
+ return indexType;
+ }
+
+ @Override
+ public String getIndexName() {
+ return indexName;
+ }
+
+ @Override
+ public void applyPath(Query query) {
+ query.apply(docStructure.doc());
+ }
+
+ @Override
+ public String rawProperty(String property) {
+
+ String rawProperty = sortableMap.get(property);
+ return rawProperty == null ? property : rawProperty;
+ }
+
+ /**
+ * Register invalidation paths for embedded documents.
+ */
+ @Override
+ public void registerPaths() {
+ if (mapped) {
+ Collection pathProps = docStructure.doc().getPathProps();
+ for (PathProperties.Props pathProp : pathProps) {
+ String path = pathProp.getPath();
+ if (path != null) {
+ BeanDescriptor> targetDesc = desc.getBeanDescriptor(path);
+ String idName = targetDesc.getIdProperty().getName();
+ String fullPath = path + "." + idName;
+ targetDesc.docStoreAdapter().registerInvalidationPath(desc.getDocStoreQueueId(), fullPath, pathProp.getProperties());
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a doc store invalidation listener for the given bean type, path and properties.
+ */
+ @Override
+ public void registerInvalidationPath(String queueId, String path, Set properties) {
+
+ embeddedInvalidation.add(getEmbeddedInvalidation(queueId, path, properties));
+ }
+
+ /**
+ * Return the DsInvalidationListener based on the properties, path.
+ */
+ protected DocStoreEmbeddedInvalidation getEmbeddedInvalidation(String queueId, String path, Set properties) {
+
+ if (properties.contains("*")) {
+ return new DocStoreEmbeddedInvalidation(queueId, path);
+ } else {
+ return new DocStoreEmbeddedInvalidationProperties(queueId, path, getPropertyPositions(properties));
+ }
+ }
+
+ /**
+ * Return the property names as property index positions.
+ */
+ protected int[] getPropertyPositions(Set properties) {
+ List posList = new ArrayList();
+ for (String property : properties) {
+ BeanProperty prop = desc.getBeanProperty(property);
+ if (prop != null) {
+ posList.add(prop.getPropertyIndex());
+ }
+ }
+ int[] pos = new int[posList.size()];
+ for (int i = 0; i request, DocStoreUpdates docStoreUpdates) {
+ for (int i = 0; i < embeddedInvalidation.size(); i++) {
+ embeddedInvalidation.get(i).embeddedInvalidate(request, docStoreUpdates);
+ }
+ }
+
+ /**
+ * Return the pathProperties which defines the JSON document to index.
+ * This can add derived/embedded/nested parts to the document.
+ */
+ protected DocStructure derivePathProperties(DeployBeanDescriptor deploy) {
+
+ if (!mapped) {
+ return null;
+ }
+
+ PathProperties pathProps = deploy.getDocStorePathProperties();
+ boolean includeByDefault = (pathProps == null);
+ if (pathProps == null) {
+ pathProps = new PathProperties();
+ }
+
+ return getDocStructure(pathProps, includeByDefault);
+ }
+
+ protected DocStructure getDocStructure(PathProperties pathProps, boolean includeByDefault) {
+
+ DocStructure docStructure = new DocStructure(pathProps);
+ BeanProperty[] properties = desc.propertiesNonTransient();
+ for (int i = 0; i < properties.length; i++) {
+ properties[i].docStoreInclude(includeByDefault, docStructure);
+ }
+ return docStructure;
+ }
+
+ public FetchPath getEmbedded(String path) {
+ return docStructure.getEmbedded(path);
+ }
+
+ public FetchPath getEmbeddedManyRoot(String path) {
+ return docStructure.getEmbeddedManyRoot(path);
+ }
+
+ @Override
+ public boolean isMapped() {
+ return mapped;
+ }
+
+ @Override
+ public String getQueueId() {
+ return queueId;
+ }
+
+ @Override
+ public DocStoreEvent getEvent(PersistRequest.Type persistType, DocStoreEvent txnMode) {
+
+ if (txnMode == null) {
+ return getDocStoreEvent(persistType);
+ } else if (txnMode == DocStoreEvent.IGNORE) {
+ return DocStoreEvent.IGNORE;
+ }
+ return mapped ? txnMode : getDocStoreEvent(persistType);
+ }
+
+ private DocStoreEvent getDocStoreEvent(PersistRequest.Type persistType) {
+ switch (persistType) {
+ case INSERT:
+ return insert;
+ case UPDATE:
+ return update;
+ case DELETE:
+ return delete;
+ default:
+ return DocStoreEvent.IGNORE;
+ }
+ }
+
+ /**
+ * Return the supplied value or default to the bean name lower case.
+ */
+ protected String derive(BeanDescriptor desc, String suppliedValue) {
+ return (suppliedValue != null && suppliedValue.length() > 0) ? suppliedValue : desc.getName().toLowerCase();
+ }
+
+ @Override
+ public abstract void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException;
+
+ @Override
+ public abstract void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException;
+
+ @Override
+ public abstract void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException;
+
+ @Override
+ public abstract void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException;
+
+ @Override
+ public abstract void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException;
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java
new file mode 100644
index 000000000..3bb51b6c9
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreDeleteEvent.java
@@ -0,0 +1,39 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+import com.avaje.ebean.plugin.BeanType;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdate;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
+
+import java.io.IOException;
+
+/**
+ * A 'Delete by Id' request that is send to the document store.
+ */
+public class DocStoreDeleteEvent implements DocStoreUpdate {
+
+ private final BeanType> beanType;
+
+ private final Object idValue;
+
+ public DocStoreDeleteEvent(BeanType> beanType, Object idValue) {
+ this.beanType = beanType;
+ this.idValue = idValue;
+ }
+
+ /**
+ * Add appropriate JSON content for sending to the ElasticSearch Bulk API.
+ */
+ @Override
+ public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
+ beanType.docStore().deleteById(idValue, txn);
+ }
+
+ /**
+ * Add this event to the queue (for queue delayed processing).
+ */
+ @Override
+ public void addToQueue(DocStoreUpdates docStoreUpdates) {
+ docStoreUpdates.queueDelete(beanType.getDocStoreQueueId(), idValue);
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java
new file mode 100644
index 000000000..fdab9dc0b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidation.java
@@ -0,0 +1,23 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
+
+/**
+ * Checks if a persist request means an embedded/nested object in another document needs updating.
+ */
+public class DocStoreEmbeddedInvalidation {
+
+ protected final String queueId;
+
+ protected final String path;
+
+ public DocStoreEmbeddedInvalidation(String queueId, String path) {
+ this.queueId = queueId;
+ this.path = path;
+ }
+
+ public void embeddedInvalidate(PersistRequestBean> request, DocStoreUpdates docStoreUpdates) {
+ docStoreUpdates.addNested(queueId, path, request.getBeanId());
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java
new file mode 100644
index 000000000..c133d73a6
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreEmbeddedInvalidationProperties.java
@@ -0,0 +1,30 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
+
+/**
+ * Checks if a persist request means an embedded/nested object in another document needs updating.
+ *
+ * This has specific properties to check (so not all properties invalidate).
+ */
+public final class DocStoreEmbeddedInvalidationProperties extends DocStoreEmbeddedInvalidation {
+
+ /**
+ * Properties that trigger invalidation.
+ */
+ private final int[] properties;
+
+ public DocStoreEmbeddedInvalidationProperties(String queueId, String path, int[] properties) {
+ super(queueId, path);
+ this.properties = properties;
+ }
+
+ @Override
+ public void embeddedInvalidate(PersistRequestBean> request, DocStoreUpdates docStoreUpdates) {
+ if (request.hasDirtyProperty(properties)) {
+ docStoreUpdates.addNested(queueId, path, request.getBeanId());
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java
new file mode 100644
index 000000000..763fbf8f2
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreIndexEvent.java
@@ -0,0 +1,42 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+import com.avaje.ebean.plugin.BeanType;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdate;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
+
+import java.io.IOException;
+
+/**
+ * A 'Delete by Id' request that is send to the document store.
+ */
+public class DocStoreIndexEvent implements DocStoreUpdate {
+
+ private final BeanType beanType;
+
+ private final Object idValue;
+
+ private final T bean;
+
+ public DocStoreIndexEvent(BeanType beanType, Object idValue, T bean) {
+ this.beanType = beanType;
+ this.idValue = idValue;
+ this.bean = bean;
+ }
+
+ /**
+ * Add appropriate JSON content for sending to the ElasticSearch Bulk API.
+ */
+ @Override
+ public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
+ beanType.docStore().index(idValue, bean, txn);
+ }
+
+ /**
+ * Add this event to the queue (for queue delayed processing).
+ */
+ @Override
+ public void addToQueue(DocStoreUpdates docStoreUpdates) {
+ docStoreUpdates.queueIndex(beanType.getDocStoreQueueId(), idValue);
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java
new file mode 100644
index 000000000..87a07a01b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStructure.java
@@ -0,0 +1,96 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+import com.avaje.ebean.text.PathProperties;
+import com.avaje.ebean.FetchPath;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.deploy.BeanProperty;
+import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Document structure for mapping to document store.
+ */
+public class DocStructure {
+
+ /**
+ * The full document structure.
+ */
+ private final PathProperties doc;
+
+ /**
+ * The embedded document structures by path.
+ */
+ private final Map embedded = new HashMap();
+
+ private final Map manyRoot = new HashMap();
+
+ /**
+ * Create given an initial deployment doc mapping.
+ */
+ public DocStructure(PathProperties pathProps) {
+ this.doc = pathProps;
+ }
+
+ /**
+ * Add a property at the root level.
+ */
+ public void addProperty(String name) {
+ doc.addToPath(null, name);
+ }
+
+ /**
+ * Add an embedded property with it's document structure.
+ */
+ public void addNested(String path, PathProperties embeddedDoc) {
+ doc.addNested(path, embeddedDoc);
+ embedded.put(path, embeddedDoc);
+ }
+
+ /**
+ * Return the document structure.
+ */
+ public PathProperties doc() {
+ return doc;
+ }
+
+ /**
+ * Return the document structure for an embedded path.
+ */
+ public FetchPath getEmbedded(String path) {
+ return embedded.get(path);
+ }
+
+ public FetchPath getEmbeddedManyRoot(String path) {
+ return manyRoot.get(path);
+ }
+
+ /**
+ * For 'many' nested properties we need an additional root based graph to fetch and update.
+ */
+ public void prepareMany(BeanDescriptor desc) {
+ Set strings = embedded.keySet();
+ for (String prop : strings) {
+ BeanPropertyAssoc> embProp = (BeanPropertyAssoc>)desc.getBeanProperty(prop);
+ if (embProp.isMany()) {
+ prepare(prop, embProp);
+ }
+ }
+ }
+
+ /**
+ * Add a PathProperties for an embedded 'many' property (at the root level).
+ */
+ private void prepare(String prop, BeanPropertyAssoc> embProp) {
+
+ BeanDescriptor> targetDesc = embProp.getTargetDescriptor();
+
+ PathProperties manyRootPath = new PathProperties();
+ manyRootPath.addToPath(null, targetDesc.getIdProperty().getName());
+ manyRootPath.addNested(prop, embedded.get(prop));
+
+ manyRoot.put(prop, manyRootPath);
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java
new file mode 100644
index 000000000..e9b3d9285
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java
@@ -0,0 +1,82 @@
+package com.avaje.ebeanservice.docstore.none;
+
+import com.avaje.ebean.DocStoreQueueEntry;
+import com.avaje.ebean.DocumentStore;
+import com.avaje.ebean.PagedList;
+import com.avaje.ebean.Query;
+import com.avaje.ebean.QueryEachConsumer;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * DocumentStore that barfs it is used.
+ */
+public class NoneDocStore implements DocumentStore {
+
+ public static IllegalStateException implementationNotInClassPath() {
+ throw new IllegalStateException("DocStore implementation not included in the classPath. You need to add the maven dependency for avaje-ebeanorm-elastic");
+ }
+
+ @Override
+ public void dropIndex(String newIndex) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public void createIndex(String indexName, String alias, String mappingResource) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public void indexAll(Class> countryClass) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public long copyIndex(Class> beanType, String newIndex) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public long copyIndex(Class> beanType, String newIndex, long epochMillis) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public void indexByQuery(Query query) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public void indexByQuery(Query query, int bulkBatchSize) {
+ throw implementationNotInClassPath();
+ }
+
+ @Nullable
+ @Override
+ public T getById(Class beanType, Object id) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public PagedList findPagedList(Query query) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public List findList(Query query) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public void findEach(Query query, QueryEachConsumer consumer) {
+ throw implementationNotInClassPath();
+ }
+
+ @Override
+ public long process(List queueEntries) throws IOException {
+ throw implementationNotInClassPath();
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java
new file mode 100644
index 000000000..b53816ba3
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreBeanAdapter.java
@@ -0,0 +1,49 @@
+package com.avaje.ebeanservice.docstore.none;
+
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
+import com.avaje.ebeanservice.docstore.api.support.DocStoreBeanBaseAdapter;
+
+import java.io.IOException;
+
+/**
+ * DocStoreBeanBaseAdapter that barfs if it is used.
+ */
+public class NoneDocStoreBeanAdapter extends DocStoreBeanBaseAdapter {
+
+ public NoneDocStoreBeanAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) {
+ super(desc, deploy);
+ }
+
+ @Override
+ public boolean isMapped() {
+ return false;
+ }
+
+ @Override
+ public void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+
+ @Override
+ public void index(Object idValue, T entityBean, DocStoreUpdateContext txn) throws IOException {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+
+ @Override
+ public void insert(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+
+ @Override
+ public void update(Object idValue, PersistRequestBean persistRequest, DocStoreUpdateContext txn) throws IOException {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+
+ @Override
+ public void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java
new file mode 100644
index 000000000..41547ff11
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreFactory.java
@@ -0,0 +1,39 @@
+package com.avaje.ebeanservice.docstore.none;
+
+import com.avaje.ebean.DocumentStore;
+import com.avaje.ebean.plugin.SpiServer;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
+import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+import com.avaje.ebeanservice.docstore.api.DocStoreFactory;
+import com.avaje.ebeanservice.docstore.api.DocStoreIntegration;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
+
+/**
+ * A stub implementation of DocStoreFactory that will barf if the docStore features are used.
+ */
+public class NoneDocStoreFactory implements DocStoreFactory {
+
+ @Override
+ public DocStoreIntegration create(SpiServer server) {
+ return new NoneIntegration();
+ }
+
+ @Override
+ public DocStoreBeanAdapter createAdapter(BeanDescriptor desc, DeployBeanDescriptor deploy) {
+ return new NoneDocStoreBeanAdapter(desc, deploy);
+ }
+
+ static class NoneIntegration implements DocStoreIntegration {
+
+ @Override
+ public DocStoreUpdateProcessor updateProcessor() {
+ return new NoneDocStoreUpdateProcessor();
+ }
+
+ @Override
+ public DocumentStore documentStore() {
+ return new NoneDocStore();
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java
new file mode 100644
index 000000000..f32486697
--- /dev/null
+++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java
@@ -0,0 +1,24 @@
+package com.avaje.ebeanservice.docstore.none;
+
+import com.avaje.ebean.plugin.BeanType;
+import com.avaje.ebeanservice.docstore.api.DocStoreQueryUpdate;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
+
+import java.io.IOException;
+
+/**
+ * DocStoreUpdateProcessor that barfs if it is used.
+ */
+public class NoneDocStoreUpdateProcessor implements DocStoreUpdateProcessor {
+
+ @Override
+ public DocStoreQueryUpdate createQueryUpdate(BeanType beanType, int bulkBatchSize) throws IOException {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+
+ @Override
+ public void process(DocStoreUpdates docStoreUpdates, int bulkBatchSize) {
+ throw NoneDocStore.implementationNotInClassPath();
+ }
+}
diff --git a/src/test/java/com/avaje/ebean/TestFilterWithEnum.java b/src/test/java/com/avaje/ebean/TestFilterWithEnum.java
index 1c6d72f2c..252cfec2b 100644
--- a/src/test/java/com/avaje/ebean/TestFilterWithEnum.java
+++ b/src/test/java/com/avaje/ebean/TestFilterWithEnum.java
@@ -11,17 +11,17 @@ import com.avaje.tests.model.basic.ResetBasicData;
public class TestFilterWithEnum extends BaseTestCase {
@Test
- public void test() {
-
+ public void test() throws InterruptedException {
+
ResetBasicData.reset();
-
+
List allOrders = Ebean.find(Order.class).findList();
-
+
Filter filter = Ebean.filter(Order.class);
List newOrders = filter.eq("status", Order.Status.NEW).filter(allOrders);
-
+
Assert.assertNotNull(newOrders);
-
+
}
-
+
}
diff --git a/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java b/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java
index 4c6b775db..9a813451c 100644
--- a/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java
+++ b/src/test/java/com/avaje/ebean/bean/EntityBeanInterceptTest.java
@@ -3,6 +3,7 @@ package com.avaje.ebean.bean;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.EBasic;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Test;
@@ -11,6 +12,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -41,4 +43,37 @@ public class EntityBeanInterceptTest extends BaseTestCase {
assertTrue(ebi.hasDirtyProperty(propertyNames));
}
+
+ @Test
+ public void isPartial_when_new() {
+
+ EBasic basic = new EBasic();
+ EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept();
+ assertThat(ebi.isPartial()).isTrue();
+ }
+
+ @Test
+ public void isPartial_when_partial() {
+
+ EBasic basic = new EBasic();
+ basic.setId(42);
+ basic.setName("some");
+ EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept();
+ assertThat(ebi.isPartial()).isTrue();
+ }
+
+ @Test
+ public void isPartial_when_full() {
+
+ EBasic basic = new EBasic();
+ basic.setId(42);
+ basic.setName("some");
+ basic.setDescription("asd");
+ basic.setSomeDate(null);
+ basic.setStatus(EBasic.Status.ACTIVE);
+
+ EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept();
+ assertThat(ebi.isPartial()).isFalse();
+ }
+
}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java b/src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java
new file mode 100644
index 000000000..72ff5d1b7
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/config/DocStoreConfigTest.java
@@ -0,0 +1,33 @@
+package com.avaje.ebean.config;
+
+import com.avaje.ebean.annotation.DocStoreEvent;
+import org.junit.Test;
+
+import java.util.Properties;
+
+import static org.junit.Assert.*;
+
+public class DocStoreConfigTest {
+
+ @Test
+ public void testLoadSettings() throws Exception {
+
+ DocStoreConfig config = new DocStoreConfig();
+
+ Properties properties = new Properties();
+ properties.setProperty("ebean.docstore.active", "true");
+ properties.setProperty("ebean.docstore.bulkBatchSize", "99");
+ properties.setProperty("ebean.docstore.url", "http://foo:9800");
+ properties.setProperty("ebean.docstore.persist", "IGNORE");
+
+ PropertiesWrapper wrapper = new PropertiesWrapper("ebean", null, properties);
+
+ config.loadSettings(wrapper);
+
+ assertTrue(config.isActive());
+ assertEquals("http://foo:9800", config.getUrl());
+ assertEquals(DocStoreEvent.IGNORE, config.getPersist());
+ assertEquals(99, config.getBulkBatchSize());
+
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java b/src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java
new file mode 100644
index 000000000..917d9653a
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/plugin/BeanTypeTest.java
@@ -0,0 +1,192 @@
+package com.avaje.ebean.plugin;
+
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.FetchPath;
+import com.avaje.ebean.text.PathProperties;
+import com.avaje.ebeaninternal.api.SpiQuery;
+import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.Order;
+import com.avaje.tests.model.basic.OrderDetail;
+import com.avaje.tests.model.basic.Person;
+import com.avaje.tests.model.basic.Product;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class BeanTypeTest {
+
+ static EbeanServer server = Ebean.getDefaultServer();
+
+ BeanType beanType(Class cls) {
+ return server.getPluginApi().getBeanType(cls);
+ }
+
+ @Test
+ public void getBeanType() throws Exception {
+ assertThat(beanType(Order.class).getBeanType()).isEqualTo(Order.class);
+ }
+
+ @Test
+ public void getTypeAtPath_when_ManyToOne() throws Exception {
+ BeanType orderType = beanType(Order.class);
+ BeanType> customerType = orderType.getBeanTypeAtPath("customer");
+ assertThat(customerType.getBeanType()).isEqualTo(Customer.class);
+ }
+
+ @Test
+ public void getTypeAtPath_when_OneToMany() throws Exception {
+ BeanType orderType = beanType(Order.class);
+ BeanType> detailsType = orderType.getBeanTypeAtPath("details");
+ assertThat(detailsType.getBeanType()).isEqualTo(OrderDetail.class);
+ }
+
+ @Test
+ public void getTypeAtPath_when_nested() throws Exception {
+ BeanType orderType = beanType(Order.class);
+ BeanType> productType = orderType.getBeanTypeAtPath("details.product");
+ assertThat(productType.getBeanType()).isEqualTo(Product.class);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void getTypeAtPath_when_simpleType() throws Exception {
+
+ beanType(Order.class).getBeanTypeAtPath("status");
+ }
+
+ @Test
+ public void createBean() throws Exception {
+
+ assertThat(beanType(Order.class).createBean()).isNotNull();
+ }
+
+ @Test
+ public void property() throws Exception {
+
+ Order order = new Order();
+ order.setStatus(Order.Status.APPROVED);
+ Property statusProperty = beanType(Order.class).getProperty("status");
+
+ assertThat(statusProperty.getVal(order)).isEqualTo(order.getStatus());
+ }
+
+ @Test
+ public void getBaseTable() throws Exception {
+
+ assertThat(beanType(Order.class).getBaseTable()).isEqualTo("o_order");
+ }
+
+ @Test
+ public void beanId_and_getBeanId() throws Exception {
+
+ Order order = new Order();
+ order.setId(42);
+
+ Object id1 = beanType(Order.class).beanId(order);
+ Object id2 = beanType(Order.class).getBeanId(order);
+
+ assertThat(id1).isEqualTo(order.getId());
+ assertThat(id2).isEqualTo(order.getId());
+ }
+
+ @Test
+ public void setBeanId() throws Exception {
+
+ Order order = new Order();
+ beanType(Order.class).setBeanId(order, 42);
+
+ assertThat(42).isEqualTo(order.getId());
+ }
+
+ @Test
+ public void isDocStoreIndex() throws Exception {
+
+ assertThat(beanType(Order.class).isDocStoreMapped()).isFalse();
+ assertThat(beanType(Person.class).isDocStoreMapped()).isFalse();
+
+ assertThat(beanType(Order.class).getDocMapping()).isNotNull();
+ assertThat(beanType(Person.class).getDocMapping()).isNull();
+ }
+
+ @Test
+ public void docStore_getEmbedded() throws Exception {
+
+ BeanDocType orderDocType = beanType(Order.class).docStore();
+ FetchPath customer = orderDocType.getEmbedded("customer");
+ assertThat(customer).isNotNull();
+ assertThat(customer.getProperties(null)).contains("id","name");
+ }
+
+ @Test
+ public void docStore_getEmbeddedManyRoot() throws Exception {
+
+ BeanDocType orderDocType = beanType(Order.class).docStore();
+
+ FetchPath detailsPath = orderDocType.getEmbedded("details");
+ assertThat(detailsPath).isNotNull();
+
+ FetchPath detailsRoot = orderDocType.getEmbeddedManyRoot("details");
+ assertThat(detailsRoot).isNotNull();
+ assertThat(detailsRoot.getProperties(null)).containsExactly("id", "details");
+ assertThat(detailsRoot.hasPath("details")).isTrue();
+ }
+
+ @Test
+ public void getDocStoreQueueId() throws Exception {
+
+ assertThat(beanType(Order.class).getDocStoreQueueId()).isEqualTo("order");
+ assertThat(beanType(Customer.class).getDocStoreQueueId()).isEqualTo("customer");
+ }
+
+ @Test
+ public void getDocStoreIndexType() throws Exception {
+
+ assertThat(beanType(Order.class).docStore().getIndexType()).isEqualTo("order");
+ assertThat(beanType(Customer.class).docStore().getIndexType()).isEqualTo("customer");
+ }
+
+ @Test
+ public void getDocStoreIndexName() throws Exception {
+
+ assertThat(beanType(Order.class).docStore().getIndexType()).isEqualTo("order");
+ assertThat(beanType(Customer.class).docStore().getIndexType()).isEqualTo("customer");
+ }
+
+ @Test
+ public void docStoreNested() throws Exception {
+
+ FetchPath parse = PathProperties.parse("id,name");
+
+ FetchPath nestedCustomer = beanType(Order.class).docStore().getEmbedded("customer");
+ assertThat(nestedCustomer.toString()).isEqualTo(parse.toString());
+ }
+
+ @Test
+ public void docStoreApplyPath() throws Exception {
+
+ SpiQuery orderQuery = (SpiQuery)server.find(Order.class);
+ beanType(Order.class).docStore().applyPath(orderQuery);
+
+ OrmQueryDetail detail = orderQuery.getDetail();
+ assertThat(detail.getChunk("customer", false).getSelectProperties())
+ .containsExactly("id", "name");
+ }
+
+
+ @Test(expected = IllegalStateException.class)
+ public void docStoreIndex() throws Exception {
+ beanType(Order.class).docStore().index(1, new Order(), null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void docStoreDeleteById() throws Exception {
+ beanType(Order.class).docStore().deleteById(1, null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void docStoreUpdateEmbedded() throws Exception {
+ beanType(Order.class).docStore().updateEmbedded(1, "customer", "someJson", null);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java b/src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java
new file mode 100644
index 000000000..54ba6bd46
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/plugin/ExpressionPathTest.java
@@ -0,0 +1,79 @@
+package com.avaje.ebean.plugin;
+
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.tests.model.basic.Order;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class ExpressionPathTest {
+
+ static EbeanServer server = Ebean.getDefaultServer();
+
+ BeanType beanType(Class cls) {
+ return server.getPluginApi().getBeanType(cls);
+ }
+
+ @Test
+ public void containsMany_when_many() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ assertThat(beanType.getExpressionPath("details").containsMany()).isTrue();
+ }
+
+ @Test
+ public void containsMany_when_manyChild() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ assertThat(beanType.getExpressionPath("details.id").containsMany()).isTrue();
+ }
+
+ @Test
+ public void containsMany_when_manyGrandChild() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ assertThat(beanType.getExpressionPath("details.product.sku").containsMany()).isTrue();
+ }
+
+ @Test
+ public void containsMany_when_one() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ assertThat(beanType.getExpressionPath("customer.name").containsMany()).isFalse();
+ }
+
+ @Test
+ public void containsMany_when_oneWithMany() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ assertThat(beanType.getExpressionPath("customer.contacts").containsMany()).isTrue();
+ }
+
+ @Test
+ public void containsMany_when_oneWithManyChild() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ assertThat(beanType.getExpressionPath("customer.contacts.firstName").containsMany()).isTrue();
+ }
+
+ @Test
+ public void set_when_basic() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ Order order = new Order();
+ beanType.getExpressionPath("id").set(order, 42);
+ assertThat(order.getId()).isEqualTo(42);
+ }
+
+ @Test
+ public void set_when_nested() throws Exception {
+
+ BeanType beanType = beanType(Order.class);
+ Order order = new Order();
+ beanType.getExpressionPath("customer.name").set(order, "Rob");
+ assertThat(order.getCustomer().getName()).isEqualTo("Rob");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/plugin/PropertyTest.java b/src/test/java/com/avaje/ebean/plugin/PropertyTest.java
new file mode 100644
index 000000000..80c49713d
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/plugin/PropertyTest.java
@@ -0,0 +1,56 @@
+package com.avaje.ebean.plugin;
+
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.Order;
+import org.junit.Test;
+
+import static org.assertj.core.api.StrictAssertions.assertThat;
+
+/**
+ */
+public class PropertyTest {
+
+ static EbeanServer server = Ebean.getDefaultServer();
+
+ BeanType beanType(Class cls) {
+ return server.getPluginApi().getBeanType(cls);
+ }
+
+ @Test
+ public void getVal() throws Exception {
+
+ Customer customer = new Customer();
+
+ Order order = new Order();
+ order.setCustomer(customer);
+ order.setStatus(Order.Status.APPROVED);
+
+ Property statusProperty = beanType(Order.class).getProperty("status");
+ assertThat(statusProperty.getVal(order)).isEqualTo(order.getStatus());
+
+ Property customerProperty = beanType(Order.class).getProperty("customer");
+ assertThat(customerProperty.getVal(order)).isEqualTo(customer);
+ }
+
+ @Test
+ public void isMany_when_not() {
+
+ assertThat(beanType(Order.class).getProperty("status").isMany()).isFalse();
+ assertThat(beanType(Order.class).getProperty("customer").isMany()).isFalse();
+ }
+
+ @Test
+ public void isMany_when_true() {
+
+ assertThat(beanType(Order.class).getProperty("details").isMany()).isTrue();
+ }
+
+ @Test
+ public void name() {
+ assertThat(beanType(Order.class).getProperty("status").getName()).isEqualTo("status");
+ assertThat(beanType(Order.class).getProperty("customer").getName()).isEqualTo("customer");
+ assertThat(beanType(Order.class).getProperty("details").getName()).isEqualTo("details");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java b/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java
index 750d29e57..7f41b08e3 100644
--- a/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java
+++ b/src/test/java/com/avaje/ebean/plugin/SpiServerTest.java
@@ -7,7 +7,12 @@ import org.junit.Test;
import java.util.List;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
public class SpiServerTest {
diff --git a/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java b/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java
new file mode 100644
index 000000000..ff85cd96c
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java
@@ -0,0 +1,88 @@
+package com.avaje.ebean.text.json;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.bean.PersistenceContext;
+import com.avaje.tests.model.basic.Customer;
+import com.fasterxml.jackson.core.JsonParser;
+import org.junit.Test;
+
+import java.io.StringReader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class JsonBeanReaderTest extends BaseTestCase {
+
+ static JsonContext json = Ebean.json();
+
+ @Test
+ public void read() throws Exception {
+
+ JsonParser parser = getParser();
+ JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, null);
+
+ Customer customer = beanReader.read();
+ assertThat(customer.getId()).isEqualTo(42);
+ assertThat(customer.getName()).isEqualTo("dummy");
+ }
+
+ private JsonParser getParser() {
+
+ Customer customer = new Customer();
+ customer.setId(42);
+ customer.setName("dummy");
+
+ String rawJson = json.toJson(customer);
+ StringReader reader = new StringReader(rawJson);
+
+ return json.createParser(reader);
+ }
+
+ @Test
+ public void forJson() throws Exception {
+
+ JsonParser parser = getParser();
+ JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, null);
+ beanReader.read();
+
+ JsonParser more = getParser();
+ JsonBeanReader moreReader = beanReader.forJson(more);
+
+ Customer customer = moreReader.read();
+ assertThat(customer.getId()).isEqualTo(42);
+ assertThat(customer.getName()).isEqualTo("dummy");
+ }
+
+ @Test
+ public void persistenceContextPut_when_noPC() throws Exception {
+
+ JsonParser parser = getParser();
+ JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, null);
+ beanReader.read();
+
+ Customer other = new Customer();
+ other.setId(54);
+
+ beanReader.persistenceContextPut(54, other);
+ }
+
+ @Test
+ public void persistenceContextPut_when_hasPC() throws Exception {
+
+ JsonReadOptions options = new JsonReadOptions().setEnableLazyLoading(true);
+
+ JsonParser parser = getParser();
+ JsonBeanReader beanReader = json.createBeanReader(Customer.class, parser, options);
+ Customer customer = beanReader.read();
+
+ Customer other = new Customer();
+ other.setId(54);
+
+ beanReader.persistenceContextPut(54, other);
+ PersistenceContext pc = beanReader.getPersistenceContext();
+ assertThat(pc.get(Customer.class, 54)).isSameAs(other);
+ assertThat(pc.get(Customer.class, 42)).isSameAs(customer);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java b/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java
index 70646ab5f..affad9527 100644
--- a/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java
+++ b/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java
@@ -3,15 +3,24 @@ package com.avaje.ebean.text.json;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.text.PathProperties;
+import com.avaje.tests.model.basic.Contact;
import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.Order;
+import com.avaje.tests.model.basic.ResetBasicData;
import com.fasterxml.jackson.core.JsonGenerator;
import org.junit.Test;
import java.io.StringReader;
import java.io.StringWriter;
+import java.util.List;
import java.util.Map;
-import static org.junit.Assert.*;
+import static org.assertj.core.api.StrictAssertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
public class JsonContextTest {
@@ -25,6 +34,56 @@ public class JsonContextTest {
assertFalse(json.isSupportedType(System.class));
}
+ @Test
+ public void test_jsonWithPersistenceContext() {
+
+ ResetBasicData.reset();
+
+ List orders = Ebean.find(Order.class)
+ .fetch("customer", "id, name")
+ .where().eq("customer.id", 1)
+ .findList();
+
+ String json = Ebean.json().toJson(orders);
+
+ List orders1 = Ebean.json().toList(Order.class, json);
+
+ Customer customer = null;
+ for (Order order : orders1) {
+ Customer tempCustomer = order.getCustomer();
+ if (customer == null) {
+ customer = tempCustomer;
+ } else {
+ assertThat(tempCustomer).isSameAs(customer);
+ }
+ }
+ }
+
+ @Test
+ public void test_json_loadContext() {
+
+ ResetBasicData.reset();
+
+ List orders = Ebean.find(Order.class)
+ .select("status")
+ .fetch("customer", "id, name")
+ .findList();
+
+ String json = Ebean.json().toJson(orders);
+
+ JsonReadOptions options = new JsonReadOptions().setEnableLazyLoading(true);
+
+ List orders1 = Ebean.json().toList(Order.class, json, options);
+
+ for (Order order : orders1) {
+ Customer customer = order.getCustomer();
+ customer.getName();
+ customer.getSmallnote();
+ List contacts = customer.getContacts();
+ contacts.size();
+ }
+ }
+
@Test
public void test_toObject() throws Exception {
@@ -91,7 +150,7 @@ public class JsonContextTest {
assertSame(customer, custReadVisitor.bean);
assertEquals("foo", custReadVisitor.unmapped.get("unknownProp"));
assertEquals(2, custReadVisitor.unmapped.size());
- assertEquals("foobie", ((Map)custReadVisitor.unmapped.get("extraProp")).get("name"));
+ assertEquals("foobie", ((Map) custReadVisitor.unmapped.get("extraProp")).get("name"));
assertEquals("bo", ((Map) custReadVisitor.unmapped.get("extraProp")).get("sim"));
}
diff --git a/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java b/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java
index c05498ae6..7ef2f9775 100644
--- a/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java
+++ b/src/test/java/com/avaje/ebean/text/json/JsonWriteOptionsTests.java
@@ -1,7 +1,6 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.FetchPath;
-import org.junit.Assert;
import org.junit.Test;
import java.util.Set;
diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
index 6ed966cff..b6236ffcf 100644
--- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
+++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
@@ -8,17 +8,14 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
-import com.avaje.ebean.dbmigration.DdlGenerator;
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.SpiServer;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
-import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
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 javax.persistence.OptimisticLockException;
@@ -82,6 +79,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
+ @Override
+ public DocumentStore docStore() {
+ return null;
+ }
+
@Override
public ReadAuditLogger getReadAuditLogger() {
return null;
@@ -97,6 +99,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
+ @Override
+ public BeanDescriptor> getBeanDescriptorByQueueId(String queueId) {
+ return null;
+ }
+
@Override
public List> getBeanDescriptors() {
return null;
diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java
index bb9cde51c..29cd54f06 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java
@@ -9,10 +9,11 @@ import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.tests.model.basic.EBasic;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
-/**
- */
public class BeanDescriptorTest {
@Test
diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java
new file mode 100644
index 000000000..b33d422cb
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_documentMappingTest.java
@@ -0,0 +1,85 @@
+package com.avaje.ebeaninternal.server.deploy;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyAdapter;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
+import com.avaje.ebeanservice.docstore.api.mapping.DocumentMapping;
+import com.avaje.tests.model.basic.Order;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BeanDescriptor_documentMappingTest extends BaseTestCase {
+
+
+ @Test
+ public void docMapping() {
+
+ BeanDescriptor desc = getBeanDescriptor(Order.class);
+
+ DocumentMapping documentMapping = desc.getDocMapping();
+
+ DocPropertyMapping properties = documentMapping.getProperties();
+
+ assertThat(properties).isNotNull();
+ }
+
+ @Test
+ public void docMapping_visitor() {
+
+ BeanDescriptor desc = getBeanDescriptor(Order.class);
+
+ DocumentMapping documentMapping = desc.getDocMapping();
+
+ DocPropertyMapping properties = documentMapping.getProperties();
+
+ assertThat(properties).isNotNull();
+
+ TDVisitor tdVisitor = new TDVisitor();
+ documentMapping.visit(tdVisitor);
+
+ assertThat(tdVisitor.sb.toString()).isEqualTo("{status,orderDate,shipDate, object{customer:id,name,}customerName, nested{details: [id,orderQty,shipQty,unitPrice,cretime,updtime,]}cretime,updtime,}");
+ }
+
+ class TDVisitor extends DocPropertyAdapter {
+
+ StringBuilder sb = new StringBuilder();
+
+ @Override
+ public void visitProperty(DocPropertyMapping property) {
+ sb.append(property.getName()+",");
+ }
+
+ @Override
+ public void visitBegin() {
+
+ sb.append("{");
+ }
+
+ @Override
+ public void visitEnd() {
+ sb.append("}");
+ }
+
+ @Override
+ public void visitBeginObject(DocPropertyMapping property) {
+ sb.append(" object{"+property.getName()+":");
+ }
+
+ @Override
+ public void visitEndObject(DocPropertyMapping property) {
+ sb.append("}");
+ }
+
+ @Override
+ public void visitBeginList(DocPropertyMapping property) {
+ sb.append(" nested{"+property.getName()+": [");
+ }
+
+ @Override
+ public void visitEndList(DocPropertyMapping property) {
+ sb.append("]}");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java
new file mode 100644
index 000000000..c56927a3c
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_propertiesTest.java
@@ -0,0 +1,23 @@
+package com.avaje.ebeaninternal.server.deploy;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.plugin.Property;
+import com.avaje.tests.model.basic.Order;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BeanDescriptor_propertiesTest extends BaseTestCase {
+
+ @Test
+ public void allProperties() {
+
+ BeanDescriptor desc = getBeanDescriptor(Order.class);
+ Collection extends Property> props = desc.allProperties();
+
+ assertThat(props).extracting("name").contains("id", "status", "orderDate", "shipDate");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java
index 01ba7417b..f9c3e9195 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_whenCreatedPropertyTest.java
@@ -21,15 +21,15 @@ public class BeanDescriptor_whenCreatedPropertyTest extends BaseTestCase {
BeanDescriptor desc = server.getBeanDescriptor(Customer.class);
- BeanProperty whenCreatedProperty = desc.findWhenCreatedProperty();
+ BeanProperty whenCreatedProperty = desc.getWhenCreatedProperty();
assertEquals("cretime",whenCreatedProperty.getDbColumn());
- BeanProperty whenModifiedProperty = desc.findWhenModifiedProperty();
+ BeanProperty whenModifiedProperty = desc.getWhenModifiedProperty();
assertEquals("updtime",whenModifiedProperty.getDbColumn());
BeanDescriptor eBasicDesc = server.getBeanDescriptor(EBasic.class);
- assertNull(eBasicDesc.findWhenCreatedProperty());
- assertNull(eBasicDesc.findWhenModifiedProperty());
+ assertNull(eBasicDesc.getWhenCreatedProperty());
+ assertNull(eBasicDesc.getWhenModifiedProperty());
}
}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java
new file mode 100644
index 000000000..9997aba3e
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/BaseElasticTest.java
@@ -0,0 +1,24 @@
+package com.avaje.ebeaninternal.server.expression;
+
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.tests.model.basic.Order;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public abstract class BaseElasticTest extends BaseTestCase {
+
+ public static JsonFactory factory = new JsonFactory();
+
+ public ElasticExpressionContext context(StringWriter sb) throws IOException {
+
+ BeanDescriptor desc = getBeanDescriptor(Order.class);
+ JsonGenerator gen = factory.createGenerator(sb);
+ return new ElasticExpressionContext(gen, desc);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java
index 3c0bc757a..0c8a3b989 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpressionTest.java
@@ -54,7 +54,7 @@ public class DefaultExampleExpressionTest extends BaseTestCase {
DefaultExampleExpression prepare(DefaultExampleExpression expr) {
SpiQuery query = (SpiQuery)spiEbeanServer().find(Customer.class);
- BeanQueryRequest> request = create(query, customerBeanDescriptor());
+ BeanQueryRequest> request = create(query);
expr.prepareExpression(request);
return expr;
@@ -92,8 +92,8 @@ public class DefaultExampleExpressionTest extends BaseTestCase {
}
- private OrmQueryRequest create(SpiQuery query, BeanDescriptor desc) {
- return new OrmQueryRequest(null, null, query, desc, null);
+ private OrmQueryRequest create(SpiQuery query) {
+ return new OrmQueryRequest(null, null, query, null);
}
@Test
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpressionTest.java
similarity index 90%
rename from src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java
rename to src/test/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpressionTest.java
index b3ff17e81..78752ba3d 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsExpressionTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpressionTest.java
@@ -8,12 +8,12 @@ import java.util.Arrays;
import static org.assertj.core.api.StrictAssertions.assertThat;
-public class ExistsExpressionTest {
+public class ExistsQueryExpressionTest {
@NotNull
- private ExistsExpression exp(boolean not, String sql, Object... bindValues) {
- return new ExistsExpression(not, sql, Arrays.asList(bindValues));
+ private ExistsQueryExpression exp(boolean not, String sql, Object... bindValues) {
+ return new ExistsQueryExpression(not, sql, Arrays.asList(bindValues));
}
@Test
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java
new file mode 100644
index 000000000..2ad96cc4d
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/SimpleExpressionElasticTest.java
@@ -0,0 +1,27 @@
+package com.avaje.ebeaninternal.server.expression;
+
+
+import org.junit.Test;
+
+import java.io.StringWriter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SimpleExpressionElasticTest extends BaseElasticTest {
+
+
+ @Test
+ public void writeElastic() throws Exception {
+
+ SimpleExpression eqExp = new SimpleExpression("name", Op.EQ, "rob");
+
+ StringWriter sb = new StringWriter();
+ ElasticExpressionContext context = context(sb);
+ eqExp.writeElastic(context);
+ context.json().flush();
+
+ String json = sb.toString();
+
+ assertThat(json).isEqualTo("{\"term\":{\"name\":\"rob\"}}");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java
new file mode 100644
index 000000000..720a4225a
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQueryElasticTest.java
@@ -0,0 +1,88 @@
+package com.avaje.ebeaninternal.server.querydefn;
+
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Query;
+import com.avaje.ebeaninternal.api.SpiExpressionList;
+import com.avaje.ebeaninternal.api.SpiQuery;
+import com.avaje.ebeaninternal.server.expression.BaseElasticTest;
+import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext;
+import com.avaje.tests.model.basic.Order;
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultOrmQueryElasticTest extends BaseElasticTest {
+
+ @Test
+ public void writeElastic_on_SpiExpressionList() throws IOException {
+
+ Query query = Ebean.find(Order.class)
+ .where().eq("customer.name", "Rob")
+ .query();
+
+ SpiQuery spiQuery = (SpiQuery)query;
+
+ SpiExpressionList whereExpressions = spiQuery.getWhereExpressions();
+
+ StringWriter sb = new StringWriter();
+ ElasticExpressionContext context = context(sb);
+ JsonGenerator json = context.json();
+ json.writeStartObject();
+ json.writeFieldName("filter");
+
+ whereExpressions.writeElastic(context);
+
+ json.writeEndObject();
+ context.flush();
+
+ assertThat(sb.toString()).isEqualTo("{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}");
+ }
+
+ @Test
+ public void writeElastic() throws IOException {
+
+ Query query = Ebean.find(Order.class)
+ .select("status, customer.name, details.product.id")
+ .where().eq("customer.name", "Rob")
+ .query();
+
+ SpiQuery spiQuery = (SpiQuery)query;
+
+ StringWriter sb = new StringWriter();
+ ElasticExpressionContext context = context(sb);
+
+ spiQuery.writeElastic(context);
+ context.flush();
+
+ assertThat(sb.toString()).isEqualTo("{\"fields\":[\"status\",\"customer.name\",\"details.product.id\"],\"query\":{\"filtered\":{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}}}");
+ }
+
+ @Test
+ public void asElasticQuery() throws IOException {
+
+ String elasticQuery = Ebean.find(Order.class)
+ .select("status")
+ .where().eq("customer.name", "Rob")
+ .query().asElasticQuery();
+
+
+ assertThat(elasticQuery).isEqualTo("{\"fields\":[\"status\"],\"query\":{\"filtered\":{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}}}");
+ }
+
+ @Test
+ public void asElasticQuery_firstRowsMaxRows() throws IOException {
+
+ String elasticQuery = Ebean.find(Order.class)
+ .select("status")
+ .setFirstRow(3)
+ .setMaxRows(100)
+ .where().eq("customer.name", "Rob")
+ .query().asElasticQuery();
+
+ assertThat(elasticQuery).isEqualTo("{\"from\":3,\"size\":100,\"fields\":[\"status\"],\"query\":{\"filtered\":{\"filter\":{\"term\":{\"customer.name\":\"Rob\"}}}}}");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java
index 36f9abb03..239ef2698 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java
@@ -2,7 +2,8 @@ package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
-import com.avaje.ebean.EbeanServer;
+import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory;
import com.avaje.tests.model.basic.Order;
import org.junit.Assert;
@@ -81,11 +82,13 @@ public class TestQueryLanguage extends BaseTestCase {
private DefaultOrmQuery check(String q) {
- EbeanServer server = Ebean.getServer(null);
+ SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null);
OrmQueryDetailParser p = new OrmQueryDetailParser(q);
p.parse();
- DefaultOrmQuery qry = new DefaultOrmQuery(Order.class, server,
+
+ BeanDescriptor desc = server.getBeanDescriptor(Order.class);
+ DefaultOrmQuery qry = new DefaultOrmQuery(desc, server,
new DefaultExpressionFactory(false), (String) null);
p.assign(qry);
diff --git a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java
new file mode 100644
index 000000000..4b3f64772
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonDirtyTest.java
@@ -0,0 +1,55 @@
+package com.avaje.ebeaninternal.server.text.json;
+
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.bean.EntityBean;
+import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.ResetBasicData;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.sql.Date;
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+
+public class WriteJsonDirtyTest {
+
+ @Test
+ public void test() throws IOException {
+
+ ResetBasicData.reset();
+ List customers = Ebean.find(Customer.class).findList();
+
+ Customer customer = Ebean.find(Customer.class).setId(customers.get(0).getId())
+ .setUseCache(false)
+ .findUnique();
+
+ SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null);
+ BeanDescriptor descriptor = server.getBeanDescriptor(Customer.class);
+
+ customer.setName("dirtyCustName");
+ customer.setAnniversary(new Date(System.currentTimeMillis()));
+
+ EntityBean entityBean = (EntityBean)customer;
+ boolean[] dirtyProperties = entityBean._ebean_getIntercept().getDirtyProperties();
+
+ StringWriter writer = new StringWriter();
+ JsonFactory jsonFactory = new JsonFactory();
+ JsonGenerator generator = jsonFactory.createGenerator(writer);
+
+ WriteJson writeJson = new WriteJson(server, generator, null, null, null, null);
+ descriptor.jsonWriteDirty(writeJson, entityBean, dirtyProperties);
+
+ generator.flush();
+ generator.close();
+
+ String jsonContent = writer.toString();
+ assertTrue(jsonContent.contains("\"name\":"));
+ assertTrue(jsonContent.contains("\"anniversary\":"));
+ }
+}
diff --git a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java
index bc5219455..a200d80df 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/text/json/WriteJsonTest.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.text.json;
import com.avaje.ebean.config.JsonConfig;
import com.avaje.ebean.text.PathProperties;
+import com.avaje.ebean.FetchPath;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import org.junit.Test;
@@ -19,8 +20,8 @@ public class WriteJsonTest {
JsonFactory jsonFactory = new JsonFactory();
JsonGenerator generator = jsonFactory.createGenerator(new StringWriter());
- PathProperties pathProperties = PathProperties.parse("id,status,name,customer(id,name,address(street,city)),orders(qty,product(sku,prodName))");
- WriteJson writeJson = new WriteJson(null, generator, pathProperties, null, null, JsonConfig.Include.ALL);
+ FetchPath fetchPath = PathProperties.parse("id,status,name,customer(id,name,address(street,city)),orders(qty,product(sku,prodName))");
+ WriteJson writeJson = new WriteJson(null, generator, fetchPath, null, null, JsonConfig.Include.ALL);
WriteJson.WriteBean rootLevel = writeJson.createWriteBean(null, null);
assertTrue(rootLevel.currentIncludeProps.contains("id"));
diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java b/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java
index 2c3878fc1..75e86eca3 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/type/TestEnumToBeanType.java
@@ -18,9 +18,9 @@ public class TestEnumToBeanType {
OrdinalEnum ordinalEnum = new ScalarTypeEnumStandard.OrdinalEnum(Order.Status.class);
EnumToDbValueMap> beanDbMap = EnumToDbValueMap.create(false);
- beanDbMap.add(Customer.Status.ACTIVE, "A");
- beanDbMap.add(Customer.Status.NEW, "N");
- beanDbMap.add(Customer.Status.INACTIVE, "I");
+ beanDbMap.add(Customer.Status.ACTIVE, "A", Customer.Status.ACTIVE.name());
+ beanDbMap.add(Customer.Status.NEW, "N", Customer.Status.NEW.name());
+ beanDbMap.add(Customer.Status.INACTIVE, "I", Customer.Status.INACTIVE.name());
ScalarTypeEnumWithMapping withMapping = new ScalarTypeEnumWithMapping(beanDbMap, Customer.Status.class, 1);
diff --git a/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java
new file mode 100644
index 000000000..6c7a61d99
--- /dev/null
+++ b/src/test/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapterTest.java
@@ -0,0 +1,68 @@
+package com.avaje.ebeanservice.docstore.api.support;
+
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Query;
+import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
+import com.avaje.tests.model.basic.Order;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class DocStoreBeanBaseAdapterTest extends BaseTestCase {
+
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void test_basic_construction() throws Exception {
+
+ SpiEbeanServer server = spiEbeanServer();
+ BeanDescriptor orderDesc = server.getBeanDescriptor(Order.class);
+
+ DeployBeanDescriptor deployDesc = (DeployBeanDescriptor