#304 - ElasticSearch integration part 1 / doc store integration

This commit is contained in:
Robin Bygrave
2016-03-01 15:36:44 +13:00
parent cb1fe297d6
commit 8d3b4a0407
209 changed files with 6736 additions and 573 deletions
@@ -0,0 +1,94 @@
package com.avaje.ebean;
/**
* Bean holding the details to update the document store.
*/
public final class DocStoreQueueEntry {
/**
* Action to either update or delete a document from the index.
*/
public enum Action {
/**
* Action is to update a document in the doc store.
*/
INDEX(1),
/**
* Action is to delete a document from the doc store..
*/
DELETE(2),
/**
* An update is required based on a change to a nested/embedded object at a given path.
*/
NESTED(3);
int value;
Action(int value) {
this.value = value;
}
/**
* Return the value associated with this action type.
*/
public int getValue() {
return value;
}
}
final Action type;
final String queueId;
final String path;
final Object beanId;
/**
* Construct for an INDEX or DELETE action.
*/
public DocStoreQueueEntry(Action type, String queueId, Object beanId) {
this(type, queueId, null, beanId);
}
/**
* Construct for an NESTED/embedded path invalidation action.
*/
public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) {
this.type = type;
this.queueId = queueId;
this.path = path;
this.beanId = beanId;
}
/**
* Return the event type.
*/
public Action getType() {
return type;
}
/**
* Return the associate queueId.
*/
public String getQueueId() {
return queueId;
}
/**
* Return the path if this is a nested update.
*/
public String getPath() {
return path;
}
/**
* Return the bean id (which matches the document id).
*/
public Object getBeanId() {
return beanId;
}
}
@@ -0,0 +1,121 @@
package com.avaje.ebean;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
/**
* Document storage operations.
*/
public interface DocumentStore {
/**
* Update the associated document store using the result of the query.
* <p>
* This will execute the query against the database creating a document for each
* bean graph and sending this to the document store.
* </p>
* <p>
* Note that the select and fetch paths of the query is set for you to match the
* document structure needed based on <code>@DocStore</code> and <code>@DocStoreEmbedded</code>
* so what this query requires is the predicates only.
* </p>
* <p>
* This query will be executed using findEach so it is safe to use a query
* that will fetch a lot of beans. The default bulkBatchSize is used.
* </p>
*
* @param query The query used to update the associated document store.
*/
<T> void indexByQuery(Query<T> query);
/**
* Update the associated document store index using the result of the query additionally specifying a
* bulkBatchSize to use for sending the messages to ElasticSearch.
*/
<T> void indexByQuery(Query<T> query, int bulkBatchSize);
/**
* Update the document store for all beans of this type.
* <p>
* This is the same as indexByQuery where the query has no predicates and so fetches all rows.
*/
void indexAll(Class<?> beanType);
/**
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
*/
@Nullable
<T> T getById(Class<T> beanType, Object id);
/**
* Execute the query against the document store returning the list.
*/
<T> List<T> findList(Query<T> query);
/**
* Execute the query against the document store returning the paged list.
* <p>
* The query should have <code>firstRow</code> or <code>maxRows</code> set prior to calling this method.
* </p>
*/
<T> PagedList<T> findPagedList(Query<T> query);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
*/
<T> void findEach(Query<T> query, QueryEachConsumer<T> consumer);
/**
* Process the queue entries sending updates to the document store or queuing them for later processing.
*/
long process(List<DocStoreQueueEntry> queueEntries) throws IOException;
/**
* Drop the index from the document store (similar to DDL drop table).
*/
void dropIndex(String indexName);
/**
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
*
* @param indexName the name of the new index
* @param alias the alias of the index
* @param mappingResource the path of the mapping file as a resource in the classpath
*/
void createIndex(String indexName, String alias, String mappingResource);
/**
* Copy the index to a new index.
* <p>
* This copy process does not use the database but instead will copy from the source index to a destination index.
* </p>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
*
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex);
/**
* Copy entries from an index to a new index but limiting to documents that have been
* modified since the sinceEpochMillis time.
* <p>
* To support this the document needs to have a <code>@WhenModified</code> property.
* </p>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
*
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex, long sinceEpochMillis);
}
@@ -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.
* <p>
@@ -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);
}
}
+15 -1
View File
@@ -274,7 +274,7 @@ import java.util.Set;
* @param <T>
* the type of Entity bean this query will fetch.
*/
public interface Query<T> extends Serializable {
public interface Query<T> {
/**
* Return the RawSql that was set to use for this query.
@@ -1285,6 +1285,14 @@ public interface Query<T> extends Serializable {
*/
Query<T> setUseQueryCache(boolean useQueryCache);
/**
* Set to true if this query should execute against the doc store.
* <p>
* When setting this you may also consider disabling lazy loading.
* </p>
*/
Query<T> setUseDocStore(boolean useDocStore);
/**
* When set to true when you want the returned beans to be read only.
*/
@@ -1364,4 +1372,10 @@ public interface Query<T> extends Serializable {
* </p>
*/
Set<String> validate();
/**
* Return the query in JSON form for ElasticSearch doc store.
*/
String asElasticQuery();
}
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.annotation.DocStoreEvent;
import com.avaje.ebean.config.DocStoreConfig;
import com.avaje.ebean.config.PersistBatch;
import javax.persistence.PersistenceException;
@@ -81,6 +83,29 @@ public interface Transaction extends Closeable {
*/
boolean isActive();
/**
* Set the behavior for document store updates on this transaction.
* <p>
* For example, set the mode to DocStoreEvent.IGNORE for this transaction and
* then any changes via this transaction are not sent to the doc store. This
* would be used when doing large bulk inserts into the database and we want
* to control how that is sent to the document store.
* </p>
*/
void setDocStoreUpdateMode(DocStoreEvent updateMode);
/**
* Set the batch size to use for sending messages to the document store.
* <p>
* You might set this if you know the changes in this transaction result in especially large or
* especially small payloads and want to adjust the batch size to match.
* </p>
* <p>
* Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
* </p>
*/
void setDocStoreUpdateBatchSize(int batchSize);
/**
* Explicitly turn off or on the cascading nature of save() and delete(). This
* gives the developer exact control over what beans are saved and deleted
@@ -0,0 +1,32 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to indicate that a particular string property should be treated as a 'code' and not analysed for text searching.
* <p>
* By default all Id properties and all Enum properties are treated as 'code' and not analysed.
* </p>
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DocCode {
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
}
@@ -0,0 +1,44 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the entity type maps to a document store (like ElasticSearch).
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocMapping {
/**
* The property name the mapping applies to.
*/
String name();
/**
* Set this to true to indicate that this property should be un-analysed.
*/
boolean code() default false;
/**
* Set this to true to get an additional un-analysed 'raw' field to use for sorting etc.
*/
boolean sortable() default false;
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
}
@@ -0,0 +1,39 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the entity type maps to a document store (like ElasticSearch).
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocProperty {
/**
* Set this to true to indicate that this property should be un-analysed.
*/
boolean code() default false;
/**
* Set this to true to get an additional un-analysed 'raw' field to use for sorting etc.
*/
boolean sortable() default false;
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
}
@@ -0,0 +1,34 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to indicate that a particular string property should support sorting. What this typically means is that
* for ElasticSearch an additional 'raw' field is added that stores the un-analysed value. This un-analysed value
* can be used for sorting etc and the original field used for text searching.
* <p>
* For example, customer name and product name are good candidates for marking with @DocSortable.
* </p>
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocSortable {
/**
* Set to true to have the property additionally stored separately from _source.
*/
boolean store() default false;
/**
* Set a boost value specific to this property.
*/
float boost() default 1;
/**
* Set a value to use instead of null.
*/
String nullValue() default "";
}
@@ -0,0 +1,88 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the entity type maps to a document store (like ElasticSearch).
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocStore {
/**
* A unique Id used when queuing reindex events.
*/
String queueId() default "";
/**
* The ElasticSearch index name. If left unspecified the short name of the bean type is used.
*/
String indexName() default "";
/**
* The ElasticSearch index type. If left unspecified the short name of the bean type is used.
*/
String indexType() default "";
/**
* The number of shards this index should use.
*/
int shards() default 0;
/**
* The number of replicas this index should use.
*/
int replicas() default 0;
/**
* Additional mapping that can be defined on the properties.
*/
DocMapping[] mapping() default {};
/**
* Specify the behavior when bean Insert, Update, Delete events occur.
*/
DocStoreEvent persist() default DocStoreEvent.DEFAULT;
/**
* Specify the behavior when bean Insert occurs.
*/
DocStoreEvent insert() default DocStoreEvent.DEFAULT;
/**
* Specify the behavior when bean Update occurs.
*/
DocStoreEvent update() default DocStoreEvent.DEFAULT;
/**
* Specify the behavior when bean Delete occurs.
*/
DocStoreEvent delete() default DocStoreEvent.DEFAULT;
/**
* Specify to include only some properties in the doc store document.
* <p>
* If this is left as default then all scalar properties are included,
* all @ManyToOne properties are included with just the nested id property
* and no @OneToMany properties are included.
* </p>
* <p>
* Note that typically DocStoreEmbedded is used on @ManyToOne and @OneToMany
* properties to indicate what part of the nested document should be included.
* </p>
*
* <h3>Example:</h3>
* <pre>{@code
*
* // only include the customer id and name
* @DocStore(doc = "id,name")
* @Entity @Table(name = "o_order")
* public class Customer {
*
* }</pre>
*/
String doc() default "";
}
@@ -0,0 +1,37 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the property is included in the parent document store index.
*
* <pre>{@code
*
*
* @DocStore
* @Entity @Table(name = "o_order")
* public class Order {
*
* ...
* // include some customer details including
* // nested billingAddress
* @DocStoreEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
* @ManyToOne
* Customer customer;
*
*
* }</pre>
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocStoreEmbedded {
/**
* The properties on the embedded bean to include in the index.
*/
String doc() default "";
}
@@ -0,0 +1,38 @@
package com.avaje.ebean.annotation;
/**
* Defines the behavior options when a Insert, Update or Delete event occurs
* on a bean with an associated ElasticSearch index.
* <p>
* For some indexes or some transactions it can be beneficial to queue the event
* for later processing rather than look to update ElasticSearch at that time.
* </p>
*/
public enum DocStoreEvent {
/**
* Add the event to the queue for processing later (delaying the update to the document store).
*/
QUEUE,
/**
* Update the document store when transaction succeeds.
*/
UPDATE,
/**
* Ignore the event and not update the document store.
* <p>
* This can be used on a index or for a transaction where you want to have more
* manual programmatic control over the updating of the document store. Say you want to
* IGNORE on a particular transaction and instead manually queue a bulk update.
* </p>
*/
IGNORE,
/**
* The actual mode of QUEUE, UPDATE or IGNORE is set from the default configuration.
*/
DEFAULT
}
@@ -219,6 +219,18 @@ public final class EntityBeanIntercept implements Serializable {
this.fullyLoadedBean = fullyLoadedBean;
}
/**
* Check each property to see if the bean is partially loaded.
*/
public boolean isPartial() {
for (int i = 0; i < loadedProps.length; i++) {
if (!loadedProps[i]) {
return true;
}
}
return false;
}
/**
* Return true if this bean has been directly modified (it has oldValues) or
* if any embedded beans are either new or dirty (and hence need saving).
@@ -469,7 +481,7 @@ public final class EntityBeanIntercept implements Serializable {
public void setLoadedProperty(int propertyIndex) {
loadedProps[propertyIndex] = true;
}
/**
* Return true if the property is loaded.
*/
@@ -560,6 +572,23 @@ public final class EntityBeanIntercept implements Serializable {
return props;
}
/**
* Return the array of flags indicating the dirty properties.
*/
public boolean[] getDirtyProperties() {
int len = getPropertyLength();
boolean[] dirties = new boolean[len];
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
dirties[i] = true;
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
dirties[i] = true;
}
}
return dirties;
}
/**
* Return the set of dirty properties.
*/
@@ -10,12 +10,12 @@ package com.avaje.ebean.bean;
public interface PersistenceContext {
/**
* Put the entity bean into the PersistanceContext.
* Put the entity bean into the PersistenceContext.
*/
void put(Object id, Object bean);
/**
* Put the entity bean into the PersistanceContext if one is not already
* Put the entity bean into the PersistenceContext if one is not already
* present (for this id).
* <p>
* Returns an existing entity bean (if one is already there) and otherwise
@@ -90,6 +90,13 @@ public class ClassLoadConfig {
return context.forName(name);
}
/**
* Return the classLoader to use for service loading etc.
*/
public ClassLoader getClassLoader() {
return context.getClassLoader();
}
/**
* Wraps the preferred, caller and context class loaders.
*/
@@ -138,6 +145,9 @@ public class ClassLoadConfig {
return Class.forName(name, true, classLoader);
}
ClassLoader getClassLoader() {
return preferredLoader != null ? preferredLoader : contextLoader;
}
}
}
@@ -0,0 +1,138 @@
package com.avaje.ebean.config;
import com.avaje.ebean.annotation.DocStoreEvent;
/**
* Configuration for the Document store (ElasticSearch) integration.
*/
public class DocStoreConfig {
/**
* True when the Document store integration is active/on.
*/
boolean active;
/**
* When true the Document store should drop and re-create any document mapping (like DDL).
*/
boolean dropCreate;
/**
* The URL of the Document store server. For example: http://localhost:9200.
*/
String url;
/**
* The default mode used by indexes.
*/
DocStoreEvent persist = DocStoreEvent.UPDATE;
/**
* The default batch size to use for the Bulk API calls.
*/
int bulkBatchSize = 1000;
/**
* Return true if the Document store (ElasticSearch) integration is active.
*/
public boolean isActive() {
return active;
}
/**
* Set to true to make the Document store (ElasticSearch) integration active.
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* Return true if the document store should recreate mappings.
*/
public boolean isDropCreate() {
return dropCreate;
}
/**
* Set to true if the document store should recreate mappings.
*/
public void setDropCreate(boolean dropCreate) {
this.dropCreate = dropCreate;
}
/**
* Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
*/
public DocStoreEvent getPersist() {
return persist;
}
/**
* Set the default behavior for when Insert, Update and Delete events occur on beans that have an associated
* Document store.
* <ul>
* <li>DocStoreEvent.UPDATE - build and send message to Bulk API</li>
* <li>DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing</li>
* <li>DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index</li>
* </ul>
* <p>
* You might choose to use QUEUE if that particular index data is updating very frequently or the cost of indexing
* is expensive. Setting it to QUEUE can mean many changes can be batched together potentially coalescing multiple
* updates for an index entry into a single update.
* </p>
* <p>
* You might choose to use IGNORE when you have your own external process for updating the indexes. In this case
* you don't want Ebean to do anything when the data changes.
* </p>
*/
public void setPersist(DocStoreEvent persist) {
this.persist = persist;
}
/**
* Return the URL to the Document store.
*/
public String getUrl() {
return url;
}
/**
* Set the URL to the Document store server.
*
* For a local ElasticSearch server this would be: http://localhost:9200
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Return the default batch size to use for calls to the Bulk API.
*/
public int getBulkBatchSize() {
return bulkBatchSize;
}
/**
* Set the default batch size to use for calls to the Bulk API.
* <p>
* The batch size can be set on a transaction via {@link com.avaje.ebean.Transaction#setDocStoreUpdateBatchSize(int)}.
* </p>
*/
public void setBulkBatchSize(int bulkBatchSize) {
this.bulkBatchSize = bulkBatchSize;
}
/**
* Load settings specified in properties files.
*/
public void loadSettings(PropertiesWrapper properties) {
active = properties.getBoolean("docstore.active", active);
url = properties.get("docstore.url", url);
persist = properties.getEnum(DocStoreEvent.class, "docstore.persist", persist);
bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize);
}
}
@@ -25,8 +25,10 @@ import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.ServiceLoader;
/**
* The configuration used for creating a EbeanServer.
@@ -129,6 +131,11 @@ public class ServerConfig {
*/
private String classPathReaderClassName;
/**
* Configuration for the ElasticSearch integration.
*/
private DocStoreConfig docStoreConfig = new DocStoreConfig();
/**
* This is used to populate @WhoCreated, @WhoModified and
* support other audit features (who executed a query etc).
@@ -283,7 +290,7 @@ public class ServerConfig {
private DbConstraintNaming constraintNaming = new DbConstraintNaming();
/**
* Behaviour of update to include on the change properties.
* Behaviour of update to include on the change properties.
*/
private boolean updateChangesOnly = true;
@@ -1153,6 +1160,20 @@ public class ServerConfig {
this.namingConvention = namingConvention;
}
/**
* Return the configuration for the ElasticSearch integration.
*/
public DocStoreConfig getDocStoreConfig() {
return docStoreConfig;
}
/**
* Set the configuration for the ElasticSearch integration.
*/
public void setDocStoreConfig(DocStoreConfig docStoreConfig) {
this.docStoreConfig = docStoreConfig;
}
/**
* Return the constraint naming convention used in DDL generation.
*/
@@ -2134,6 +2155,23 @@ public class ServerConfig {
this.classLoadConfig = classLoadConfig;
}
/**
* Return the service loader using the classLoader defined in ClassLoadConfig.
*/
public <T> ServiceLoader<T> serviceLoad(Class<T> spiService) {
return ServiceLoader.load(spiService, classLoadConfig.getClassLoader());
}
/**
* Return the first service using the service loader (or null).
*/
public <T> T service(Class<T> spiService) {
ServiceLoader<T> load = serviceLoad(spiService);
Iterator<T> serviceInstances = load.iterator();
return serviceInstances.hasNext() ? serviceInstances.next() : null;
}
/**
* Load settings from ebean.properties.
*/
@@ -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);
@@ -38,7 +38,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
table.setComment(descriptor.getDbComment());
if (descriptor.isHistorySupport()) {
table.setWithHistory(true);
BeanProperty whenCreated = descriptor.findWhenCreatedProperty();
BeanProperty whenCreated = descriptor.getWhenCreatedProperty();
if (whenCreated != null) {
table.setWhenCreatedColumn(whenCreated.getDbColumn());
}
@@ -0,0 +1,72 @@
package com.avaje.ebean.plugin;
import com.avaje.ebean.FetchPath;
import com.avaje.ebean.Query;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
import java.io.IOException;
/**
* Doc store functions for a specific entity bean type.
*
* @param <T> The type of entity bean
*/
public interface BeanDocType<T> {
/**
* Return the doc store index type for this bean type.
*/
String getIndexType();
/**
* Return the doc store index name for this bean type.
*/
String getIndexName();
/**
* Apply the appropriate fetch path to the query such that the query returns beans matching
* the document store structure with the expected embedded properties.
*/
void applyPath(Query<T> spiQuery);
/**
* Return the FetchPath for the embedded document.
*/
FetchPath getEmbedded(String path);
/**
* For embedded 'many' properties we need a FetchPath relative to the root which is used to
* build and replace the embedded list.
*/
FetchPath getEmbeddedManyRoot(String path);
/**
* Return a 'raw' property mapped for the given property.
* If none exists the given property is returned.
*/
String rawProperty(String property);
/**
* Store the bean in the doc store index.
* <p>
* This somewhat assumes the bean is fetched with appropriate path properties
* to match the expected document structure.
*/
void index(Object idValue, T bean, DocStoreUpdateContext txn) throws IOException;
/**
* Add a delete by Id to the doc store.
*/
void deleteById(Object idValue, DocStoreUpdateContext txn) throws IOException;
/**
* Add a embedded document update to the doc store.
*
* @param idValue the Id value of the bean holding the embedded document
* @param embeddedProperty the embedded property
* @param embeddedRawContent the content of the embedded document in JSON form
* @param txn the doc store transaction to add the update to
*/
void updateEmbedded(Object idValue, String embeddedProperty, String embeddedRawContent, DocStoreUpdateContext txn) throws IOException;
}
@@ -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<T> {
/**
* Return the full name of the bean type.
*/
String getFullName();
/**
* Return the class type this BeanDescriptor describes.
*/
Class<T> getBeanType();
/**
* Return the type bean for an OneToMany or ManyToOne or ManyToMany property.
*/
BeanType<?> getBeanTypeAtPath(String propertyName);
/**
* Return all the properties for this bean type.
*/
Collection<? extends Property> allProperties();
/**
* Return the Id property.
*/
Property getIdProperty();
/**
* Return the when modified property if there is one defined.
*/
Property getWhenModifiedProperty();
/**
* Return the when created property if there is one defined.
*/
Property getWhenCreatedProperty();
/**
* Return the SpiProperty for a property to read values from a bean.
*/
Property getProperty(String propertyName);
/**
* Return the SpiExpressionPath for a given property path.
* <p>
* This can return a property or nested property path.
* </p>
*/
ExpressionPath getExpressionPath(String path);
/**
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
/**
* 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<T> {
*/
String getSequenceName();
/**
* Return true if this bean type has doc store backing.
*/
boolean isDocStoreMapped();
/**
* Return the DocumentMapping for this bean type.
* <p>
* This is the document structure and mapping options for how this bean type is mapped
* for the document store.
* </p>
*/
DocumentMapping getDocMapping();
/**
* Return the doc store queueId for this bean type.
*/
String getDocStoreQueueId();
/**
* Return the doc store support for this bean type.\
*/
BeanDocType<T> docStore();
/**
* Read the JSON content returning the bean.
*/
T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException;
}
@@ -0,0 +1,20 @@
package com.avaje.ebean.plugin;
/**
* A dot notation expression path.
*/
public interface ExpressionPath {
/**
* Return true if there is a property on the path that is a many property.
*/
boolean containsMany();
/**
* Set a value to the bean for this expression path.
*
* @param bean the bean to set the value on
* @param value the value to set
*/
void set(Object bean, Object value);
}
@@ -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();
}
@@ -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);
}
@@ -0,0 +1,35 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.bean.PersistenceContext;
import com.fasterxml.jackson.core.JsonParser;
/**
* Provides a JSON reader that can hold a persistence context and load context while reading JSON.
* <p>
* This provides a mechanism such that an object loaded from JSON can have unique instances
* by using a persistence context and also support further lazy loading (via a load context).
* </p>
*/
public interface JsonBeanReader<T> {
/**
* Read the JSON returning a bean.
*/
T read();
/**
* Create a new reader taking the context from the existing one but using a new JsonParser.
*/
JsonBeanReader<T> forJson(JsonParser moreJson);
/**
* Add a bean explicitly to the persistence context.
*/
void persistenceContextPut(Object beanId, T currentBean);
/**
* Return the persistence context if one is being used.
*/
PersistenceContext getPersistenceContext();
}
@@ -56,6 +56,16 @@ public interface JsonContext {
*/
<T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
/**
* Create and return a new bean reading for the bean type given the JSON options and source.
* <p>
* Note that JsonOption provides an option for setting a persistence context and also enabling
* further lazy loading. Further lazy loading requires a persistence context so if that is set
* on then a persistence context is created if there is not one set.
* </p>
*/
<T> JsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
/**
* Convert json string input into a list of beans of a specific type.
*
@@ -1,5 +1,7 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.bean.PersistenceContext;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -17,6 +19,10 @@ public class JsonReadOptions {
protected Object objectMapper;
protected boolean enableLazyLoading;
protected PersistenceContext persistenceContext;
/**
* Default constructor.
*/
@@ -46,6 +52,25 @@ public class JsonReadOptions {
return this;
}
/**
* Return true if lazy loading is enabled after the objects are loaded.
*/
public boolean isEnableLazyLoading() {
return enableLazyLoading;
}
/**
* Set to true to enable lazy loading on partially populated beans.
* <p>
* If this is set to true a persistence context will be created if one has
* not already been supplied.
* </p>
*/
public JsonReadOptions setEnableLazyLoading(boolean enableLazyLoading) {
this.enableLazyLoading = enableLazyLoading;
return this;
}
/**
* Return the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig).
*/
@@ -56,7 +81,23 @@ public class JsonReadOptions {
/**
* Set the Jackson ObjectMapper to use (if not wanted to use the objectMapper set on the ServerConfig).
*/
public void setObjectMapper(Object objectMapper) {
public JsonReadOptions setObjectMapper(Object objectMapper) {
this.objectMapper = objectMapper;
return this;
}
/**
* Set the persistence context to use when building the object graph from the JSON.
*/
public JsonReadOptions setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
return this;
}
/**
* Return the persistence context to use when marshalling JSON.
*/
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
}
@@ -3,9 +3,13 @@ package com.avaje.ebean.text.json;
import java.io.IOException;
/**
* Writes any scalar type known to Ebean the Jackson generator.
* Writes any scalar type known to Ebean to the underlying Jackson generator.
*/
public interface JsonScalar {
/**
* Write the scalar type to JSON where the value can be any type known to Ebean
* including Enums, Java8 time types, Joda types, URL, URI etc.
*/
void write(String name, Object value) throws IOException;
}
@@ -29,4 +29,5 @@ public interface LoadManyBuffer {
void configureQuery(SpiQuery<?> query);
boolean isUseDocStore();
}
@@ -144,7 +144,7 @@ public class LoadManyRequest extends LoadRequest {
query.setLazyLoadForParents(many);
List<Object> idList = getParentIdList(batchSize);
many.addWhereParentIdIn(query, idList);
many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.TransactionCallback;
import com.avaje.ebean.annotation.DocStoreEvent;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebean.event.changelog.BeanChange;
@@ -59,6 +60,26 @@ public class ScopedTransaction implements SpiTransaction {
}
}
@Override
public DocStoreEvent getDocStoreUpdateMode() {
return transaction.getDocStoreUpdateMode();
}
@Override
public void setDocStoreUpdateMode(DocStoreEvent indexUpdateMode) {
transaction.setDocStoreUpdateMode(indexUpdateMode);
}
@Override
public int getDocStoreBulkBatchSize() {
return transaction.getDocStoreBulkBatchSize();
}
@Override
public void setDocStoreUpdateBatchSize(int batchSize) {
transaction.setDocStoreUpdateBatchSize(batchSize);
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
@@ -83,6 +83,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
BeanDescriptor<?> getBeanDescriptorById(String className);
/**
* Return BeanDescriptor using it's unique doc store queueId.
*/
BeanDescriptor<?> getBeanDescriptorByQueueId(String queueId);
/**
* Return BeanDescriptors mapped to this table.
*/
@@ -3,6 +3,9 @@ package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.expression.ElasticExpressionContext;
import java.io.IOException;
/**
@@ -10,6 +13,11 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public interface SpiExpression extends Expression {
/**
* Write the expression as an elastic search expression.
*/
void writeElastic(ElasticExpressionContext context) throws IOException;
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
@@ -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<T> extends Query<T> {
}
}
/**
* Write the query as an elastic search query.
*/
void writeElastic(ElasticExpressionContext context) throws IOException;
/**
* Return true if AutoTune should be attempted on this query.
*/
boolean isAutoTunable();
/**
* Return the bean descriptor for this query.
*/
BeanDescriptor<T> getBeanDescriptor();
/**
* Return true if this query should be executed against the doc store.
*/
boolean isUseDocStore();
/**
* Return the PersistenceContextScope that this query should use.
* <p>
@@ -276,18 +298,13 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Return the lazy loading 'many' property.
*/
BeanPropertyAssocMany<?> getLazyLoadForParentsProperty();
BeanPropertyAssocMany<?> getLazyLoadMany();
/**
* Set the load mode (+lazy or +query) and the load description.
*/
void setLoadDescription(String loadMode, String loadDescription);
/**
* Set the BeanDescriptor for the root type of this query.
*/
void setBeanDescriptor(BeanDescriptor<?> desc);
/**
* Return the joins required to support predicates on the many properties.
*/
@@ -639,9 +656,9 @@ public interface SpiQuery<T> extends Query<T> {
*/
boolean isDisableReadAudit();
/**
* Return true if this is a query executing in the background.
*/
/**
* Return true if this is a query executing in the background.
*/
boolean isFutureFetch();
/**
@@ -4,6 +4,7 @@ import java.sql.Connection;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.annotation.DocStoreEvent;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebean.event.changelog.ChangeSet;
@@ -20,7 +21,7 @@ import com.avaje.ebeaninternal.server.persist.BatchControl;
public interface SpiTransaction extends Transaction {
/**
* Return the string prefix with the transactin id and label used in logging.
* Return the string prefix with the transaction id and label used in logging.
*/
String getLogPrefix();
@@ -102,6 +103,20 @@ public interface SpiTransaction extends Transaction {
*/
Boolean isUpdateAllLoadedProperties();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
DocStoreEvent getDocStoreUpdateMode();
/**
* Return the batch size to us for ElasticSearch Bulk API calls
* as a result of this transaction.
*/
int getDocStoreBulkBatchSize();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
@@ -157,12 +172,12 @@ public interface SpiTransaction extends Transaction {
boolean isBatchThisRequest(PersistRequest.Type type);
/**
* Return the queue used to batch up persist requests.
* Return the BatchControl used to batch up persist requests.
*/
BatchControl getBatchControl();
/**
* Set the queue used to batch up persist requests. There should only be one
* Set the BatchControl used to batch up persist requests. There should only be one
* PersistQueue set per transaction.
*/
void setBatchControl(BatchControl control);
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.transaction.DeleteByIdMap;
@@ -120,4 +121,16 @@ public class TransactionEvent implements Serializable {
}
}
/**
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
if (persistRequestBeans != null) {
for (int i=0; i< persistRequestBeans.size(); i++) {
persistRequestBeans.get(i).addDocStoreUpdates(docStoreUpdates);
}
}
}
}
@@ -36,16 +36,20 @@ public abstract class BeanRequest {
* A transaction may have been passed in or active in the thread local. If
* not then create one implicitly to handle the request.
* </p>
*
* @return True if a transaction was set (from current or created).
*/
public void createImplicitTransIfRequired() {
if (transaction == null) {
transaction = ebeanServer.getCurrentServerTransaction();
if (transaction == null || !transaction.isActive()) {
// create an implicit transaction to execute this query
transaction = ebeanServer.createServerTransaction(false, -1);
createdTransaction = true;
}
}
public boolean createImplicitTransIfRequired() {
if (transaction != null) {
return false;
}
transaction = ebeanServer.getCurrentServerTransaction();
if (transaction == null || !transaction.isActive()) {
// create an implicit transaction to execute this query
transaction = ebeanServer.createServerTransaction(false, -1);
createdTransaction = true;
}
return true;
}
/**
@@ -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 <T> Query<T> createNamedQuery(Class<T> beanType, String namedQuery) throws PersistenceException {
BeanDescriptor<?> desc = getBeanDescriptor(beanType);
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
throw new PersistenceException("Is " + beanType.getName() + " an Entity Bean? BeanDescriptor not found?");
}
@@ -912,7 +919,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// this will parse the query
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, deployQuery);
return new DefaultOrmQuery<T>(desc, this, expressionFactory, deployQuery);
}
@Override
@@ -947,10 +954,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
public <T> Query<T> createQuery(Class<T> beanType, String query) {
BeanDescriptor<?> desc = getBeanDescriptor(beanType);
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
String m = beanType.getName() + " is NOT an Entity Bean registered with this server?";
throw new PersistenceException(m);
throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?");
}
switch (desc.getEntityType()) {
case SQL:
@@ -959,10 +965,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// use the "default" SqlSelect
DeployNamedQuery defaultSqlSelect = desc.getNamedQuery("default");
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, defaultSqlSelect);
return new DefaultOrmQuery<T>(desc, this, expressionFactory, defaultSqlSelect);
default:
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, query);
return new DefaultOrmQuery<T>(desc, this, expressionFactory, query);
}
}
@@ -1042,15 +1048,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(type);
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
spiQuery.setBeanDescriptor(desc);
return createQueryRequest(desc, spiQuery, t);
return createQueryRequest(spiQuery, t);
}
private <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> query, Transaction t) {
private <T> SpiOrmQueryRequest<T> createQueryRequest(SpiQuery<T> query, Transaction t) {
if (desc.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) {
if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
@@ -1063,7 +1066,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
query.setOrigin(createCallStack());
}
OrmQueryRequest<T> request = new OrmQueryRequest<T>(this, queryEngine, query, desc, (SpiTransaction) t);
OrmQueryRequest<T> request = new OrmQueryRequest<T>(this, queryEngine, query, (SpiTransaction) t);
request.prepareQuery();
return request;
@@ -1073,7 +1076,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Try to get the object out of the persistence context.
*/
@SuppressWarnings("unchecked")
private <T> T findIdCheckPersistenceContextAndCache(Transaction transaction, BeanDescriptor<T> beanDescriptor, SpiQuery<T> query) {
private <T> T findIdCheckPersistenceContextAndCache(Transaction transaction, SpiQuery<T> query) {
SpiTransaction t = (SpiTransaction) transaction;
if (t == null) {
@@ -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<T> desc = query.getBeanDescriptor();
if (!desc.calculateUseCache(query.isUseBeanCache())) {
// not using bean cache
return null;
}
// Hit the L2 bean cache
return beanDescriptor.cacheBeanGet(query, context);
return desc.cacheBeanGet(query, context);
}
/**
@@ -1126,19 +1130,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
spiQuery.setBeanDescriptor(desc);
if (SpiQuery.Mode.NORMAL.equals(spiQuery.getMode()) && !spiQuery.isLoadBeanCache()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(t, desc, spiQuery);
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery);
if (bean != null) {
return bean;
}
}
SpiOrmQueryRequest<T> request = createQueryRequest(desc, spiQuery, t);
SpiOrmQueryRequest<T> request = createQueryRequest(spiQuery, t);
try {
request.initTransIfRequired();
return (T) request.findId();
@@ -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<T>(this, spiQuery);
}
@@ -1397,6 +1402,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (result != null) {
return (List<T>) result;
}
if (request.isUseDocStore()) {
return docStore().findList(query);
}
try {
request.initTransIfRequired();
@@ -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
@@ -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<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
this.transactionManager = createTransactionManager();
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
this.binder = getBinder(typeManager, databasePlatform);
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
}
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
externalTransactionManager = new JtaTransactionManager();
}
if (externalTransactionManager != null) {
externalTransactionManager.setTransactionManager(transactionManager);
this.transactionScopeManager = new ExternalTransactionScopeManager(transactionManager, externalTransactionManager);
logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]");
} else {
this.transactionScopeManager = new DefaultTransactionScopeManager(transactionManager);
}
private DocStoreFactory initDocStoreFactory(DocStoreFactory service) {
return service == null ? new NoneDocStoreFactory() : service;
}
/**
* Return the doc store factory.
*/
public DocStoreFactory getDocStoreFactory() {
return docStoreFactory;
}
/**
@@ -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);
}
}
}
@@ -68,11 +68,9 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
/**
* Create the InternalQueryRequest.
*/
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery<T> query, BeanDescriptor<T> desc, SpiTransaction t) {
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery<T> query, SpiTransaction t) {
super(server, t);
this.beanDescriptor = desc;
this.beanDescriptor = query.getBeanDescriptor();
this.rawSql = query.getRawSql();
this.finder = beanDescriptor.getBeanFinder();
this.queryEngine = queryEngine;
@@ -133,6 +131,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return loadContext;
}
@Override
public boolean isUseDocStore() {
return query.isUseDocStore();
}
/**
* Run BeanQueryAdapter preQuery() if needed.
*/
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.annotation.DocStoreEvent;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.event.BeanPersistController;
@@ -19,9 +20,13 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.persist.PersistExecute;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdate;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -30,7 +35,7 @@ import java.util.Set;
/**
* PersistRequest for insert update or delete of a bean.
*/
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T> {
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T>, DocStoreUpdate {
private final BeanManager<T> beanManager;
@@ -64,6 +69,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private final boolean publish;
private DocStoreEvent docStoreEvent;
private ConcurrencyMode concurrencyMode;
/**
@@ -102,6 +109,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private Set<String> updatedProperties;
/**
* Flags indicating the dirty properties on the bean.
*/
private boolean[] dirtyProperties;
/**
* Flag set when request is added to JDBC batch.
*/
@@ -135,7 +147,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.parentBean = parentBean;
this.controller = beanDescriptor.getPersistController();
this.type = type;
this.docStoreEvent = calcDocStoreEvent(transaction, type);
if (saveRecurse) {
this.persistCascade = t.isPersistCascade();
}
@@ -157,6 +169,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.dirty = intercept.isDirty();
}
/**
* Return the document store event that should be used for this request.
*
* Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we
* don't want to send to the doc store.
*/
private DocStoreEvent calcDocStoreEvent(SpiTransaction txn, Type type) {
DocStoreEvent docStoreEvent = (txn == null) ? null : txn.getDocStoreUpdateMode();
return beanDescriptor.getDocStoreEvent(type, docStoreEvent);
}
/**
* Return true if the draftDirty property should be set to true for this request.
*/
@@ -178,7 +201,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
createImplicitTransIfRequired();
if (createImplicitTransIfRequired()) {
docStoreEvent = calcDocStoreEvent(transaction, type);
}
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
@@ -239,6 +265,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.getDirtyPropertyNames();
}
/**
* Return the dirty properties on this request.
*/
public boolean[] getDirtyProperties() {
return dirtyProperties;
}
/**
* Return true if any of the given property names are dirty.
*/
@@ -247,6 +280,19 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.hasDirtyProperty(propertyNames);
}
/**
* Return true if any of the given properties are dirty.
*/
public boolean hasDirtyProperty(int[] propertyPositions) {
for (int i = 0; i < propertyPositions.length; i++) {
if (dirtyProperties[propertyPositions[i]]) {
return true;
}
}
return false;
}
@Override
public Map<String, ValuePair> getUpdatedValues() {
return intercept.getDirtyValues();
@@ -254,7 +300,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isNotify() {
this.notifyCache = beanDescriptor.isCacheNotify(publish);
return notifyCache || isNotifyPersistListener();
return notifyCache || isNotifyPersistListener() || isDocStoreNotify();
}
/**
* Return true if this request should updateAdd an ElasticSearch index
* by queuing an event or direct updateAdd (via Bulk API).
*/
private boolean isDocStoreNotify() {
// Either queue or directly update the document store
return docStoreEvent != DocStoreEvent.IGNORE;
}
public boolean isNotifyPersistListener() {
@@ -283,6 +338,45 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Process the persist request updating the document store.
*/
public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
switch (type) {
case INSERT:
beanDescriptor.docStoreInsert(idValue, this, txn);
break;
case UPDATE:
beanDescriptor.docStoreUpdate(idValue, this, txn);
break;
case DELETE:
beanDescriptor.docStoreDeleteById(idValue, txn);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
/**
* Add this event to the queue entries in IndexUpdates.
*/
public void addToQueue(DocStoreUpdates docStoreUpdates) {
switch (type) {
case INSERT:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case UPDATE:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case DELETE:
docStoreUpdates.queueDelete(beanDescriptor.getDocStoreQueueId(), idValue);
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
beanPersistMap.add(beanDescriptor, type, idValue);
@@ -637,6 +731,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controllerPost();
}
if (type == Type.UPDATE && docStoreEvent == DocStoreEvent.UPDATE) {
// get the dirty properties for update notification to the doc store
dirtyProperties = intercept.getDirtyProperties();
}
// if bean persisted again then should result in an update
intercept.setLoaded();
if (isInsert()) {
@@ -806,6 +904,29 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return updatedManysOnly;
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
if (type == Type.UPDATE) {
beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates);
}
switch (docStoreEvent) {
case UPDATE: {
docStoreUpdates.addPersist(this);
return;
}
case QUEUE: {
if (type == Type.DELETE) {
docStoreUpdates.queueDelete(beanDescriptor.getDocStoreQueueId(), idValue);
} else {
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
}
}
}
}
/**
* Determine if all loaded properties should be used for an update.
@@ -874,4 +995,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return Type.SOFT_DELETE == type;
}
/**
* Set the value of the Version property on the bean.
*/
public void setVersionValue(Object versionValue) {
beanDescriptor.getVersionProperty().setValueIntercept(entityBean, versionValue);
}
}
@@ -116,4 +116,9 @@ public interface SpiOrmQueryRequest<T> {
* Mark the underlying transaction as not being query only.
*/
void markNotQueryOnly();
/**
* Return true if this query is expected to use the doc store.
*/
boolean isUseDocStore();
}
@@ -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<T> implements MetaBeanInfo, BeanType<T> {
private final BeanProperty versionProperty;
private final int versionPropertyIndex;
private final BeanProperty whenModifiedProperty;
private final BeanProperty whenCreatedProperty;
/**
* Properties that are initialised in the constructor need to be 'unloaded' to support partial object queries.
*/
@@ -342,10 +357,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final boolean cacheSharableBeans;
private final String docStoreQueueId;
private DocumentMapping docMapping;
private final BeanDescriptorDraftHelp<T> draftHelp;
private final BeanDescriptorCacheHelp<T> cacheHelp;
private final BeanDescriptorJsonHelp<T> jsonHelp;
private final DocStoreBeanAdapter<T> docStoreAdapter;
private final String defaultSelectClause;
private final LinkedHashSet<String> defaultSelectClauseSet;
@@ -440,12 +460,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.derivedTableJoins = listHelper.getTableJoin();
boolean noRelationships = propertiesOne.length + propertiesMany.length == 0;
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp<T>(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = new BeanDescriptorJsonHelp<T>(this);
this.draftHelp = new BeanDescriptorDraftHelp<T>(this);
this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy);
this.docStoreQueueId = docStoreAdapter.getQueueId();
// Check if there are no cascade save associated beans ( subject to change
// in initialiseOther()). Note that if we are in an inheritance hierarchy
// then we also need to check every BeanDescriptors in the InheritInfo as
@@ -459,6 +482,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
// object used to handle Id values
this.idBinder = owner.createIdBinder(idProperty);
this.whenModifiedProperty = findWhenModifiedProperty();
this.whenCreatedProperty = findWhenCreatedProperty();
// derive the index position of the Id and Version properties
if (Modifier.isAbstract(beanType.getModifiers())) {
@@ -659,6 +684,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
namedUpdate.initialise(parser);
}
}
docStoreAdapter.registerPaths();
}
/**
* Initialise the document mapping.
*/
public void initialiseDocMapping() {
for (int i = 0; i < propertiesMany.length; i++) {
propertiesMany[i].initialisePostTarget();
}
docMapping = docStoreAdapter.createDocMapping();
}
public void initInheritInfo() {
@@ -868,6 +904,84 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return inheritInfo == null || inheritInfo.isRoot();
}
/**
* Return true if this type maps to a root type of a doc store document (not embedded or ignored).
*/
@Override
public boolean isDocStoreMapped() {
return docStoreAdapter.isMapped();
}
/**
* Return the queueId used to uniquely identify this type when queuing an index updateAdd.
*/
@Override
public String getDocStoreQueueId() {
return docStoreQueueId;
}
@Override
public DocumentMapping getDocMapping() {
return docMapping;
}
/**
* Return the doc store helper for this bean type.
*/
@Override
public BeanDocType docStore() {
return docStoreAdapter;
}
/**
* Return doc store adapter for internal use for processing persist requests.
*/
public DocStoreBeanAdapter<T> docStoreAdapter() {
return docStoreAdapter;
}
/**
* Build the Document mapping recursively with the given prefix relative to the root of the document.
*/
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (prefix != null && idProperty != null) {
// id property not included in the
idProperty.docStoreMapping(mapping, prefix);
}
for (BeanProperty prop: propertiesNonTransient) {
prop.docStoreMapping(mapping, prefix);
}
}
/**
* Return the type of DocStoreEvent that should occur for this type of persist request
* given the transactions requested mode.
*/
public DocStoreEvent getDocStoreEvent(PersistRequest.Type persistType, DocStoreEvent txnMode) {
return docStoreAdapter.getEvent(persistType, txnMode);
}
public void docStoreInsert(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
docStoreAdapter.insert(idValue, persistRequest, bulkUpdate);
}
public void docStoreUpdate(Object idValue, PersistRequestBean<T> persistRequest, DocStoreUpdateContext bulkUpdate) throws IOException {
docStoreAdapter.update(idValue, persistRequest, bulkUpdate);
}
/**
* Check if this update invalidates an embedded part of a doc store document.
*/
public void docStoreUpdateEmbedded(PersistRequestBean<T> request, DocStoreUpdates docStoreUpdates) {
docStoreAdapter.updateEmbedded(request, docStoreUpdates);
}
public void docStoreDeleteById(Object idValue, DocStoreUpdateContext txn) throws IOException {
docStoreAdapter.deleteById(idValue, txn);
}
public T publish(T draftBean, T liveBean) {
return draftHelp.publish(draftBean, liveBean);
}
@@ -1219,10 +1333,24 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return deleteRecurseSkippable && !isBeanCaching();
}
/**
* Return the 'when modified' property if there is one defined.
*/
public BeanProperty getWhenModifiedProperty() {
return whenModifiedProperty;
}
/**
* Return the 'when created' property if there is one defined.
*/
public BeanProperty getWhenCreatedProperty() {
return whenCreatedProperty;
}
/**
* Find a property annotated with @WhenCreated or @CreatedTimestamp.
*/
public BeanProperty findWhenCreatedProperty() {
private BeanProperty findWhenCreatedProperty() {
for (int i = 0; i < propertiesBaseScalar.length; i++) {
if (propertiesBaseScalar[i].isGeneratedWhenCreated()) {
@@ -1235,7 +1363,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
/**
* Find a property annotated with @WhenModified or @UpdatedTimestamp.
*/
public BeanProperty findWhenModifiedProperty() {
private BeanProperty findWhenModifiedProperty() {
for (int i = 0; i < propertiesBaseScalar.length; i++) {
if (propertiesBaseScalar[i].isGeneratedWhenModified()) {
@@ -1328,6 +1456,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return namedUpdates.get(name);
}
@Override
public T createBean() {
return (T)createEntityBean();
}
/**
* Creates a new EntityBean.
*/
@@ -1401,6 +1534,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return targetDesc.getBeanPropertyFromPath(split[1]);
}
@Override
public BeanType<?> getBeanTypeAtPath(String path) {
return getBeanDescriptor(path);
}
/**
* Return the BeanDescriptor for a given path of Associated One or Many beans.
*/
@@ -1473,6 +1611,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
/**
* Return the class type this BeanDescriptor describes.
*/
@Override
public Class<T> getBeanType() {
return beanType;
}
@@ -1484,6 +1623,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
* instead.
* </p>
*/
@Override
public String getFullName() {
return fullName;
}
@@ -1512,6 +1652,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return (idProperty == null) ? null : idProperty.getValue(bean);
}
@Override
public Object beanId(Object bean) {
return getId((EntityBean) bean);
}
@Override
public Object getBeanId(T bean) {
return getId((EntityBean) bean);
@@ -1552,6 +1697,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return idBinder.convertId(idValue);
}
/**
* Set the bean id value converting if necessary.
*/
@Override
public void setBeanId(T bean, Object idValue) {
idBinder.convertSetId(idValue, (EntityBean) bean);
}
/**
* Convert and set the id value.
* <p>
@@ -1563,6 +1716,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return idBinder.convertSetId(idValue, bean);
}
@Override
public Property getProperty(String propName) {
return getBeanProperty(propName);
}
/**
* Get a BeanProperty by its name.
*/
@@ -1585,6 +1743,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return c;
}
/**
* Register all the assoc many properties on this bean that are not populated with the load context.
* <p>
* This provides further lazy loading via the load context.
* </p>
*/
public void lazyLoadRegister(String prefix, EntityBeanIntercept ebi, EntityBean bean, LoadContext loadContext) {
// load the List/Set/Map proxy objects (deferred fetching of lists)
BeanPropertyAssocMany<?>[] manys = propertiesMany();
for (int i = 0; i < manys.length; i++) {
if (!ebi.isLoadedProperty(manys[i].getPropertyIndex())) {
BeanCollection<?> ref = manys[i].createReferenceIfNull(bean);
if (ref != null && !ref.isRegisteredWithLoadContext()) {
String path = SplitName.add(prefix, manys[i].getName());
loadContext.register(path, ref);
}
}
}
}
/**
* Return true if the lazy loading property is a Many in which case just
* define a Reference for the collection and not invoke a query.
@@ -1626,16 +1805,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
// create a compound comparator based on the list of properties
ElComparator<T>[] comparators = new ElComparator[sortBy.size()];
List<Property> sortProps = sortBy.getProperties();
List<SortByClause.Property> sortProps = sortBy.getProperties();
for (int i = 0; i < sortProps.size(); i++) {
Property sortProperty = sortProps.get(i);
SortByClause.Property sortProperty = sortProps.get(i);
comparators[i] = createPropertyComparator(sortProperty);
}
return new ElComparatorCompound<T>(comparators);
}
private ElComparator<T> createPropertyComparator(Property sortProp) {
private ElComparator<T> createPropertyComparator(SortByClause.Property sortProp) {
ElPropertyValue elGetValue = getElGetValue(sortProp.getName());
@@ -1670,6 +1849,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return elGetValue;
}
@Override
public ExpressionPath getExpressionPath(String path) {
return getElGetValue(path);
}
/**
* Similar to ElPropertyValue but also uses foreign key shortcuts.
* <p>
@@ -2130,6 +2314,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return derivedTableJoins;
}
@Override
public Collection<? extends Property> allProperties() {
return propertiesAll();
}
/**
* Return a collection of all BeanProperty. This includes transient properties.
*/
@@ -2168,6 +2357,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
}
@Override
public BeanProperty getIdProperty() {
return idProperty;
}
@@ -2442,6 +2632,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return propertiesLocal;
}
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonHelp.jsonWriteDirty(writeJson, bean, dirtyProps);
}
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException {
jsonHelp.jsonWrite(writeJson, bean, null);
}
@@ -2461,4 +2659,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException {
return jsonHelp.jsonReadObject(jsonRead, path);
}
@Override
public T jsonRead(JsonParser parser, JsonReadOptions readOptions, Object objectMapper) throws IOException {
ReadJson jsonRead = new ReadJson(this, parser, readOptions, objectMapper);
return jsonRead(jsonRead, null);
}
}
@@ -16,14 +16,14 @@ import java.util.Map;
public class BeanDescriptorJsonHelp<T> {
private final BeanDescriptor<T> desc;
private final InheritInfo inheritInfo;
public BeanDescriptorJsonHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.inheritInfo = desc.inheritInfo;
}
public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException {
writeJson.writeStartObject(key);
@@ -46,12 +46,36 @@ public class BeanDescriptorJsonHelp<T> {
protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException {
WriteBean writeBean = writeJson.createWriteBean(desc, bean);
writeBean.write(writeJson);
}
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
if (inheritInfo == null) {
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
} else {
InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass());
BeanDescriptor<?> localDescriptor = localInheritInfo.getBeanDescriptor();
localDescriptor.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
}
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
writeJson.writeStartObject(null);
// render the dirty properties
BeanProperty[] props = desc.propertiesNonTransient();
for (int j = 0; j < props.length; j++) {
if (dirtyProps[props[j].getPropertyIndex()]) {
props[j].jsonWrite(writeJson, bean);
}
}
writeJson.writeEndObject();
}
@SuppressWarnings("unchecked")
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
@@ -66,14 +90,14 @@ public class BeanDescriptorJsonHelp<T> {
return null;
}
if (JsonToken.START_OBJECT != token) {
throw new JsonParseException("Unexpected token "+token+" - expecting start_object", parser.getCurrentLocation());
throw new JsonParseException("Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
}
}
if (desc.inheritInfo == null) {
return jsonReadObject(jsonRead, path);
}
}
// check for the discriminator value to determine the correct sub type
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
@@ -81,8 +105,8 @@ public class BeanDescriptorJsonHelp<T> {
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(msg, parser.getCurrentLocation());
}
String propName = parser.getCurrentName();
String propName = parser.getCurrentName();
if (!propName.equalsIgnoreCase(discColumn)) {
// just try to assume this is the correct bean type in the inheritance
BeanProperty property = desc.getBeanProperty(propName);
@@ -91,24 +115,24 @@ public class BeanDescriptorJsonHelp<T> {
property.jsonRead(jsonRead, bean);
return jsonReadProperties(jsonRead, bean, path);
}
String msg = "Error reading inheritance discriminator, expected property ["+discColumn+"] but got [" + propName + "] ?";
String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?";
throw new JsonParseException(msg, parser.getCurrentLocation());
}
String discValue = parser.nextTextValue();
String discValue = parser.nextTextValue();
// determine the sub type for this particular json object
InheritInfo localInheritInfo = inheritInfo.readType(discValue);
BeanDescriptor<?> localDescriptor = localInheritInfo.getBeanDescriptor();
return (T) localDescriptor.jsonReadObject(jsonRead, path);
}
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBean();
return jsonReadProperties(readJson, bean, path);
}
@SuppressWarnings("unchecked")
protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException {
@@ -117,7 +141,7 @@ public class BeanDescriptorJsonHelp<T> {
}
// unmapped properties, send to JsonReadBeanVisitor later
Map<String,Object> unmappedProperties = null;
Map<String, Object> unmappedProperties = null;
do {
JsonParser parser = readJson.getParser();
@@ -141,16 +165,22 @@ public class BeanDescriptorJsonHelp<T> {
} else {
throw new RuntimeException("Unexpected token " + event + " - expecting key or end_object at: " + parser.getCurrentLocation());
}
} while (true);
// visit JsonReadBeanVisitor (if registered for this path)
readJson.beanVisitor(bean, unmappedProperties);
Object contextBean = null;
Object id = desc.beanId(bean);
if (id != null) {
// check if the bean has already been loaded
contextBean = readJson.persistenceContextPutIfAbsent(id, bean, desc);
}
if (contextBean == null) {
readJson.beanVisitor(bean, unmappedProperties);
}
if (path != null) {
readJson.popPath();
}
return (T)bean;
return contextBean == null ? (T) bean : (T) contextBean;
}
}
@@ -52,6 +52,8 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertiesReader;
import com.avaje.ebeaninternal.server.properties.BeanPropertyInfo;
import com.avaje.ebeaninternal.server.properties.BeanPropertyInfoFactory;
import com.avaje.ebeaninternal.server.properties.EnhanceBeanPropertyInfoFactory;
import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
import com.avaje.ebeanservice.docstore.api.DocStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -111,12 +113,16 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final BeanManagerFactory beanManagerFactory;
private final ServerConfig serverConfig;
private final ChangeLogListener changeLogListener;
private final ChangeLogRegister changeLogRegister;
private final ChangeLogPrepare changeLogPrepare;
private final DocStoreFactory docStoreFactory;
private int enhancedClassCount;
private final boolean updateChangesOnly;
@@ -125,14 +131,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final String serverName;
private final ServerConfig serverConfig;
private Map<Class<?>, DeployBeanInfo<?>> deplyInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<Class<?>, BeanTable>();
private final Map<String, BeanDescriptor<?>> descMap = new HashMap<String, BeanDescriptor<?>>();
private final Map<String, BeanDescriptor<?>> descQueueMap = new HashMap<String, BeanDescriptor<?>>();
private final Map<String, BeanManager<?>> beanManagerMap = new HashMap<String, BeanManager<?>>();
private final Map<String, List<BeanDescriptor<?>>> tableToDescMap = new HashMap<String, List<BeanDescriptor<?>>>();
@@ -183,6 +189,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.serverConfig = config.getServerConfig();
this.serverName = InternString.intern(serverConfig.getName());
this.cacheManager = config.getCacheManager();
this.docStoreFactory = config.getDocStoreFactory();
this.xmlConfig = config.getXmlConfig();
this.dbSequenceBatchSize = serverConfig.getDatabaseSequenceBatchSize();
this.backgroundExecutor = config.getBackgroundExecutor();
@@ -246,6 +253,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return serverConfig;
}
@Override
public <T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor<T> deploy) {
return docStoreFactory.createAdapter(descriptor, deploy);
}
public BeanDescriptor<?> getBeanDescriptorByQueueId(String queueId) {
return descQueueMap.get(queueId);
}
@SuppressWarnings("unchecked")
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
return (BeanDescriptor<T>) descMap.get(entityType.getName());
@@ -433,6 +449,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap);
}
// PASS 4:
// now initialise document mapping which needs target descriptors
for (BeanDescriptor<?> d : descMap.values()) {
d.initialiseDocMapping();
}
// create BeanManager for each non-embedded entity bean
for (BeanDescriptor<?> d : descMap.values()) {
if (!d.isEmbedded()) {
@@ -518,6 +540,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private void registerBeanDescriptor(BeanDescriptor<?> desc) {
descMap.put(desc.getBeanType().getName(), desc);
if (desc.isDocStoreMapped()) {
descQueueMap.put(desc.getDocStoreQueueId(), desc);
}
}
/**
@@ -1050,7 +1075,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private <T> DeployBeanInfo<T> createDeployBeanInfo(Class<T> beanClass) {
DeployBeanDescriptor<T> desc = new DeployBeanDescriptor<T>(beanClass);
DeployBeanDescriptor<T> desc = new DeployBeanDescriptor<T>(beanClass, serverConfig);
desc.setUpdateChangesOnly(updateChangesOnly);
@@ -4,6 +4,8 @@ import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
/**
* Provides a method to find a BeanDescriptor.
@@ -38,6 +40,13 @@ public interface BeanDescriptorMap {
*/
EncryptKey getEncryptKey(String tableName, String columnName);
/**
* Create a IdBinder for this bean property.
*/
IdBinder createIdBinder(BeanProperty id);
/**
* Create a doc store specific adapter for this bean type.
*/
<T> DocStoreBeanAdapter<T> createDocStoreBeanAdapter(BeanDescriptor descriptor, DeployBeanDescriptor<T> deploy);
}
@@ -166,6 +166,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
throw new RuntimeException("ElPropertyDeploy only - not implemented");
}
@Override
public void set(Object bean, Object value) {
throw new RuntimeException("ElPropertyDeploy only - not implemented");
}
public void elSetValue(EntityBean bean, Object value, boolean populate) {
throw new RuntimeException("ElPropertyDeploy only - not implemented");
}
@@ -5,6 +5,10 @@ import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeaninternal.server.type.ScalarTypeEnum;
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import com.avaje.ebean.plugin.Property;
import com.avaje.ebean.text.StringParser;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
@@ -24,6 +28,9 @@ import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean;
import com.avaje.ebeaninternal.util.ValueUtil;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebeanservice.docstore.api.support.DocStructure;
import com.fasterxml.jackson.core.JsonToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +49,7 @@ import java.util.Map;
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class BeanProperty implements ElPropertyValue {
public class BeanProperty implements ElPropertyValue, Property {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
@@ -196,6 +203,8 @@ public class BeanProperty implements ElPropertyValue {
@SuppressWarnings("rawtypes")
final ScalarType scalarType;
final DocPropertyOptions docOptions;
/**
* The length or precision for DB column.
*/
@@ -313,6 +322,7 @@ public class BeanProperty implements ElPropertyValue {
this.lob = isLobType(dbType);
this.propertyType = deploy.getPropertyType();
this.field = deploy.getField();
this.docOptions = deploy.getDocPropertyOptions();
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
@@ -414,6 +424,7 @@ public class BeanProperty implements ElPropertyValue {
this.lob = isLobType(dbType);
this.propertyType = source.getPropertyType();
this.field = source.getField();
this.docOptions = source.docOptions;
this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn);
this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn);
@@ -557,6 +568,11 @@ public class BeanProperty implements ElPropertyValue {
}
}
@Override
public boolean isMany() {
return false;
}
public boolean isAssignableFrom(Class<?> type) {
return owningType.isAssignableFrom(type);
}
@@ -670,6 +686,15 @@ public class BeanProperty implements ElPropertyValue {
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the changed value without invoking interception (lazy loading etc).
* Typically used to set generated values on update.
*/
public void setValueChanged(EntityBean bean, Object value) {
setValue(bean, value);
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
}
/**
* Set the value of the property without interception or
* PropertyChangeSupport.
@@ -716,6 +741,10 @@ public class BeanProperty implements ElPropertyValue {
throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar");
}
public Object getVal(Object bean) {
return getValue((EntityBean)bean);
}
/**
* Return the value of the property method.
*/
@@ -746,6 +775,14 @@ public class BeanProperty implements ElPropertyValue {
return convertToLogicalType(value);
}
@Override
public void set(Object bean, Object value) {
// convert for Enums etc
Object logicalVal = convertToLogicalType(value);
elSetValue((EntityBean) bean, logicalVal, true);
}
public void elSetValue(EntityBean bean, Object value, boolean populate) {
if (bean != null) {
// Not using setValueIntercept at this stage
@@ -1187,6 +1224,14 @@ public class BeanProperty implements ElPropertyValue {
return name;
}
/**
* Append this property to the document store based on includeByDefault setting.
*/
public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) {
if (includeByDefault) {
docStructure.addProperty(name);
}
}
@SuppressWarnings(value = "unchecked")
public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException {
@@ -1263,4 +1308,28 @@ public class BeanProperty implements ElPropertyValue {
map.put(propName, new ValuePair(newVal, oldVal));
}
}
/**
* Add to the document mapping if this property is included for this index.
*/
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (mapping.includesProperty(prefix, name)) {
DocPropertyType type = scalarType.getDocType();
DocPropertyOptions options = docOptions.copy();
if (DocPropertyType.STRING == type && isDocCode()) {
options.setCode(true);
}
mapping.add(new DocPropertyMapping(name, type, options));
}
}
/**
* Return true if this should be treated as a 'code' (effectively not analysed).
*/
private boolean isDocCode() {
return id || scalarType instanceof ScalarTypeEnum;
}
}
@@ -4,6 +4,12 @@ import java.util.ArrayList;
import javax.persistence.PersistenceException;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.avaje.ebeanservice.docstore.api.support.DocStructure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,7 +40,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
IdBinder targetIdBinder;
InheritInfo targetInheritInfo;
String targetIdProperty;
/**
@@ -59,6 +65,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
final String mappedBy;
final String docStoreDoc;
final String extraWhere;
boolean saveRecurseSkippable;
@@ -71,7 +79,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
this.extraWhere = InternString.intern(deploy.getExtraWhere());
this.beanTable = deploy.getBeanTable();
this.mappedBy = InternString.intern(deploy.getMappedBy());
this.docStoreDoc = deploy.getDocStoreDoc();
this.tableJoin = new TableJoin(deploy.getTableJoin());
this.targetType = deploy.getTargetType();
@@ -215,6 +223,63 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
return extraWhere;
}
/**
* Return the elastic search doc for this embedded property.
*/
public String getDocStoreDoc() {
return docStoreDoc;
}
/**
* Determine if and how the associated bean is included in the doc store document.
*/
@Override
public void docStoreInclude(boolean includeByDefault, DocStructure docStructure) {
String embeddedDoc = getDocStoreDoc();
if (embeddedDoc == null) {
// not annotated so use include by default
// which is *ToOne included and *ToMany excluded
if (includeByDefault) {
docStoreIncludeByDefault(docStructure.doc());
}
} else {
// explicitly annotated to be included
if (embeddedDoc.isEmpty()) {
embeddedDoc = "*";
}
// add in a nested way
PathProperties embDoc = PathProperties.parse(embeddedDoc);
docStructure.addNested(name, embDoc);
}
}
/**
* Include the property in the document store by default.
*/
protected void docStoreIncludeByDefault(PathProperties pathProps) {
pathProps.addToPath(null, name);
}
@Override
public void docStoreMapping(DocMappingBuilder mapping, String prefix) {
if (mapping.includesPath(prefix, name)) {
String fullName = SplitName.add(prefix, name);
DocPropertyType type = isMany() ? DocPropertyType.LIST : DocPropertyType.OBJECT;
DocPropertyMapping nested = new DocPropertyMapping(name, type);
mapping.push(nested);
targetDescriptor.docStoreMapping(mapping, fullName);
mapping.pop();
if (!nested.getChildren().isEmpty()) {
mapping.add(nested);
}
}
}
/**
* Return true if this association is updateable.
*/
@@ -1,11 +1,16 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.*;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Expression;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollection.ModifyListenMode;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
@@ -91,6 +96,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
protected BeanPropertyAssocOne<?> childMasterProperty;
private String childMasterIdProperty;
private boolean embeddedExportedProperties;
private BeanCollectionHelp<T> help;
@@ -179,6 +186,21 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
}
/**
* Initialise after the target bean descriptors have been all set.
*/
public void initialisePostTarget() {
if (childMasterProperty != null) {
BeanProperty masterId = childMasterProperty.getTargetDescriptor().getIdProperty();
childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName();
}
}
@Override
protected void docStoreIncludeByDefault(PathProperties pathProps) {
// by default not including "Many" properties in document store
}
/**
* Add the bean to the appropriate collection on the parent bean.
*/
@@ -282,10 +304,29 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return server.findIds(q, t);
}
/**
* Add the loaded current bean to its associated parent.
*/
public void lazyLoadMany(EntityBean current) {
EntityBean parentBean = childMasterProperty.getValueAsEntityBean(current);
if (parentBean != null) {
addBeanToCollectionWithCreate(parentBean, current, true);
}
}
public void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds, boolean useDocStore) {
if (useDocStore) {
// assumes the ManyToOne property is included
query.where().in(childMasterIdProperty, parentIds);
} else {
addWhereParentIdIn(query, parentIds);
}
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
public void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
private void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = manyToMany ? "int_." : "t0.";
if (manyToMany) {
@@ -450,6 +491,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return targetDescriptor.getIdBinder().getAssocIdInExpr(prefix);
}
@Override
public boolean isMany() {
return true;
}
@Override
public boolean isAssocId() {
@@ -904,6 +949,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
jsonHelp.jsonRead(readJson, parentBean);
}
@SuppressWarnings("unchecked")
public void publishMany(EntityBean draft, EntityBean live) {
// collections will not be null due to enhancement
@@ -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);
}
}
@@ -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<T> {
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<T> {
private String dbComment;
/**
* One of NONE, INDEX or EMBEDDED.
*/
private boolean docStoreMapped;
private DocStore docStore;
private PathProperties docStorePathProperties;
private String docStoreQueueId;
private String docStoreIndexName;
private String docStoreIndexType;
private DocStoreEvent docStorePersist;
private DocStoreEvent docStoreInsert;
private DocStoreEvent docStoreUpdate;
private DocStoreEvent docStoreDelete;
/**
* Construct the BeanDescriptor.
*/
public DeployBeanDescriptor(Class<T> beanType) {
public DeployBeanDescriptor(Class<T> beanType, ServerConfig serverConfig) {
this.serverConfig = serverConfig;
this.beanType = beanType;
}
@@ -234,6 +260,26 @@ public class DeployBeanDescriptor<T> {
return draftableElement;
}
/**
* Read the top level doc store deployment information.
*/
public void readDocStore(DocStore docStore) {
this.docStore = docStore;
docStoreMapped = true;
docStoreQueueId = docStore.queueId();
docStoreIndexName = docStore.indexName();
docStoreIndexType = docStore.indexType();
docStorePersist = docStore.persist();
docStoreInsert = docStore.insert();
docStoreUpdate = docStore.update();
docStoreDelete = docStore.delete();
String doc = docStore.doc();
if (doc != null && doc.length() > 0) {
docStorePathProperties = PathProperties.parse(doc);
}
}
public boolean isScalaObject() {
Class<?>[] interfaces = beanType.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
@@ -931,4 +977,61 @@ public class DeployBeanDescriptor<T> {
checkInheritance(parent);
}
}
public PathProperties getDocStorePathProperties() {
return docStorePathProperties;
}
/**
* Return true if this type is mapped for a doc store.
*/
public boolean isDocStoreMapped() {
return docStoreMapped;
}
public String getDocStoreQueueId() {
return docStoreQueueId;
}
public String getDocStoreIndexName() {
return docStoreIndexName;
}
public String getDocStoreIndexType() {
return docStoreIndexType;
}
public DocStore getDocStore() {
return docStore;
}
/**
* Return the DocStore index behavior for bean inserts.
*/
public DocStoreEvent getDocStoreInsertEvent() {
return getDocStoreIndexEvent(docStoreInsert);
}
/**
* Return the DocStore index behavior for bean updates.
*/
public DocStoreEvent getDocStoreUpdateEvent() {
return getDocStoreIndexEvent(docStoreUpdate);
}
/**
* Return the DocStore index behavior for bean deletes.
*/
public DocStoreEvent getDocStoreDeleteEvent() {
return getDocStoreIndexEvent(docStoreDelete);
}
private DocStoreEvent getDocStoreIndexEvent(DocStoreEvent mostSpecific) {
if (!docStoreMapped) {
return DocStoreEvent.IGNORE;
}
if (mostSpecific != DocStoreEvent.DEFAULT) return mostSpecific;
if (docStorePersist != DocStoreEvent.DEFAULT) return docStorePersist;
return serverConfig.getDocStoreConfig().getPersist();
}
}
@@ -1,6 +1,9 @@
package com.avaje.ebeaninternal.server.deploy.meta;
import com.avaje.ebean.annotation.CreatedTimestamp;
import com.avaje.ebean.annotation.DocCode;
import com.avaje.ebean.annotation.DocProperty;
import com.avaje.ebean.annotation.DocSortable;
import com.avaje.ebean.annotation.SoftDelete;
import com.avaje.ebean.annotation.UpdatedTimestamp;
import com.avaje.ebean.annotation.WhenCreated;
@@ -12,6 +15,7 @@ import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.DeployDocPropertyOptions;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
@@ -19,6 +23,7 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertySetter;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeEnum;
import com.avaje.ebeaninternal.server.type.ScalarTypeWrapper;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyOptions;
import javax.persistence.EmbeddedId;
import javax.persistence.FetchType;
@@ -159,6 +164,8 @@ public class DeployBeanProperty {
*/
private int dbType;
private DeployDocPropertyOptions docMapping = new DeployDocPropertyOptions();
/**
* The method used to read the property.
*/
@@ -908,4 +915,21 @@ public class DeployBeanProperty {
public String getDbComment() {
return dbComment;
}
public void setDocProperty(DocProperty docProperty) {
docMapping.setDocProperty(docProperty);
}
public void setDocSortable(DocSortable docSortable) {
docMapping.setDocSortable(docSortable);
}
public void setDocCode(DocCode docCode) {
docMapping.setDocCode(docCode);
}
public DocPropertyOptions getDocPropertyOptions() {
return docMapping.create();
}
}
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.meta;
import com.avaje.ebean.annotation.DocStoreEmbedded;
import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo;
import com.avaje.ebeaninternal.server.deploy.BeanTable;
@@ -37,7 +38,9 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
* From the deployment mappedBy attribute.
*/
String mappedBy;
String docStoreDoc;
/**
* Construct the property.
*/
@@ -126,4 +129,16 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
this.mappedBy = mappedBy;
}
}
/**
* Set DocStoreEmbedded deployment information.
*/
public void setDocStoreEmbedded(DocStoreEmbedded embedded) {
docStoreDoc = embedded.doc();
}
public String getDocStoreDoc() {
return docStoreDoc;
}
}
@@ -11,6 +11,7 @@ import javax.persistence.UniqueConstraint;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.annotation.DocStore;
import com.avaje.ebean.annotation.DbComment;
import com.avaje.ebean.annotation.Draftable;
import com.avaje.ebean.annotation.DraftableElement;
@@ -166,6 +167,11 @@ public class AnnotationClass extends AnnotationParser {
descriptor.setDbComment(comment.value());
}
DocStore docStore = cls.getAnnotation(DocStore.class);
if (docStore != null) {
descriptor.readDocStore(docStore);
}
UpdateMode updateMode = cls.getAnnotation(UpdateMode.class);
if (updateMode != null) {
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
@@ -64,7 +64,7 @@ public class AnnotationFields extends AnnotationParser {
for (DeployBeanProperty prop : descriptor.propertiesAll()) {
if (prop instanceof DeployBeanPropertyAssoc<?>) {
readAssocOne(prop);
readAssocOne((DeployBeanPropertyAssoc<?>)prop);
} else {
readField(prop);
}
@@ -74,7 +74,7 @@ public class AnnotationFields extends AnnotationParser {
/**
* Read the Id marker annotations on EmbeddedId properties.
*/
private void readAssocOne(DeployBeanProperty prop) {
private void readAssocOne(DeployBeanPropertyAssoc<?> prop) {
readJsonAnnotations(prop);
@@ -91,6 +91,11 @@ public class AnnotationFields extends AnnotationParser {
prop.setEmbedded();
}
DocStoreEmbedded docStoreEmbedded = get(prop, DocStoreEmbedded.class);
if (docStoreEmbedded != null) {
prop.setDocStoreEmbedded(docStoreEmbedded);
}
if (prop instanceof DeployBeanPropertyAssocOne<?>) {
if (prop.isId() && !prop.isEmbedded()) {
prop.setEmbedded();
@@ -179,6 +184,19 @@ public class AnnotationFields extends AnnotationParser {
}
}
DocProperty docProperty = get(prop, DocProperty.class);
if (docProperty != null) {
prop.setDocProperty(docProperty);
}
DocSortable docSortable = get(prop, DocSortable.class);
if (docSortable != null) {
prop.setDocSortable(docSortable);
}
DocCode docCode = get(prop, DocCode.class);
if (docCode != null) {
prop.setDocCode(docCode);
}
if (get(prop, DbHstore.class) != null) {
util.setDbHstore(prop);
}
@@ -249,6 +249,11 @@ public class ElPropertyChain implements ElPropertyValue {
return chain[last].elGetValue(prevBean);
}
@Override
public void set(Object bean, Object value) {
elSetValue((EntityBean)bean, value, true);
}
public void elSetValue(EntityBean bean, Object value, boolean populate) {
EntityBean prevBean = bean;
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.el;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.plugin.ExpressionPath;
import com.avaje.ebean.text.StringParser;
/**
@@ -9,7 +10,7 @@ import com.avaje.ebean.text.StringParser;
* This can be used for local sorting and filtering.
* </p>
*/
public interface ElPropertyValue extends ElPropertyDeploy {
public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
/**
* Return the Id values for the given bean value.
@@ -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<String, Object> entry : propMap.entrySet()) {
Object value = entry.getValue();
String propName = entry.getKey();
if (value == null) {
context.writeExists(false, propName);
} else {
context.writeTerm(propName, value);
}
}
context.writeBoolEnd();
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
if (propMap != null) {
@@ -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);
@@ -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) {
@@ -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) {
@@ -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);
@@ -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);
}
@@ -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<T> implements SpiExpressionList<T> {
this(null, null, null, new ArrayList<SpiExpression>());
}
@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<T> implements SpiExpressionList<T> {
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<T> implements SpiExpressionList<T> {
@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<T> implements SpiExpressionList<T> {
* Path does not exist - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonNotExists(String propertyName, String path){
public ExpressionList<T> 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<T> jsonEqualTo(String propertyName, String path, Object value){
public ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value) {
add(expr.jsonEqualTo(propertyName, path, value));
return this;
}
@@ -439,7 +467,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Not Equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object val){
public ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object val) {
add(expr.jsonNotEqualTo(propertyName, path, val));
return this;
}
@@ -448,7 +476,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Greater than - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val){
public ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val) {
add(expr.jsonGreaterThan(propertyName, path, val));
return this;
}
@@ -457,7 +485,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Greater than or equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val){
public ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val) {
add(expr.jsonGreaterOrEqual(propertyName, path, val));
return this;
}
@@ -466,7 +494,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Less than - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonLessThan(String propertyName, String path, Object val){
public ExpressionList<T> jsonLessThan(String propertyName, String path, Object val) {
add(expr.jsonLessThan(propertyName, path, val));
return this;
}
@@ -475,7 +503,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Less than or equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val){
public ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val) {
add(expr.jsonLessOrEqualTo(propertyName, path, val));
return this;
}
@@ -484,7 +512,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
* Between - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
add(expr.jsonBetween(propertyName, path, lowerValue, upperValue));
return this;
}
@@ -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<Object> ids = new ArrayList<Object>(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 <T> void writeOrderBy(OrderBy<T> 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();
}
}
}
@@ -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<Object> bindParams) {
ExistsQueryExpression(boolean not, String sql , List<Object> 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;
}
@@ -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.
*/
@@ -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
@@ -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) {
@@ -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) {
@@ -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.
* <p>
@@ -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);
@@ -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<T> implements Junction<T>, SpiExpression, Expr
this.exprList = exprList;
}
@Override
public void writeElastic(ElasticExpressionContext context) throws IOException {
context.writeBoolStart(!disjunction);
List<SpiExpression> 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<T> implements Junction<T>, SpiExpression, Expr
@Override
public Junction<T> add(Expression item) {
SpiExpression i = (SpiExpression) item;
exprList.add(i);
exprList.add(item);
return this;
}
@@ -143,8 +154,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
List<SpiExpression> list = exprList.internalList();
for (int i = 0; i < list.size(); i++) {
SpiExpression item = list.get(i);
item.addBindValues(request);
list.get(i).addBindValues(request);
}
}
@@ -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) {
@@ -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);
@@ -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
@@ -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());
@@ -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) {
@@ -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;
}
}
@@ -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) {
@@ -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;
}
@@ -0,0 +1,7 @@
package com.avaje.ebeaninternal.server.expression;
/**
* Marked interface for expressions unsupported in doc store.
*/
public interface UnsupportedDocStoreExpression {
}
@@ -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) {
@@ -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<OrmQueryProperties> 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);
}
@@ -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<BeanCollection<?>>(batchSize);
}
@Override
public boolean isUseDocStore() {
return context.parent.useDocStore;
}
public int getBatchSize() {
return batchSize;
}
@@ -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<UpdateGenValue> 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 {
* </p>
*/
@Override
public void registerUpdateGenValue(BeanProperty prop, EntityBean bean, Object value) {
if (updateGenValues == null) {
updateGenValues = new ArrayList<UpdateGenValue>();
}
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);
}
}
}
@@ -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());
}
}
@@ -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.
@@ -194,7 +194,7 @@ public class CQuery<T> 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();
@@ -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);
@@ -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
@@ -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<T> implements SpiQuery<T> {
private static final long serialVersionUID = 6838006264714672460L;
private final Class<T> beanType;
private transient final EbeanServer server;
private final BeanDescriptor<T> 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<T> implements SpiQuery<T> {
*/
private CQueryPlanKey queryPlanKey;
private transient PersistenceContext persistenceContext;
private PersistenceContext persistenceContext;
private ManyWhereJoins manyWhereJoins;
private RawSql rawSql;
public DefaultOrmQuery(Class<T> beanType, EbeanServer server, ExpressionFactory expressionFactory, String query) {
this.beanType = beanType;
private boolean useDocStore;
public DefaultOrmQuery(BeanDescriptor<T> 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<T> implements SpiQuery<T> {
/**
* Additional supply a query which is parsed.
*/
public DefaultOrmQuery(Class<T> beanType, EbeanServer server, ExpressionFactory expressionFactory,
public DefaultOrmQuery(BeanDescriptor<T> 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<T> implements SpiQuery<T> {
}
}
public String asElasticQuery() {
StringWriter sw = new StringWriter(200);
JsonContext json = server.json();
JsonGenerator generator = json.createGenerator(sw);
BeanType<T> 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<T> getBeanDescriptor() {
return beanDescriptor;
}
@Override
public boolean isAutoTunable() {
return beanDescriptor.isAutoTunable() && !isSqlSelect();
}
@Override
public Query<T> setUseDocStore(boolean useDocStore) {
this.useDocStore = useDocStore;
return this;
}
@Override
public boolean isUseDocStore() {
return useDocStore;
}
@Override
public Query<T> apply(FetchPath fetchPath) {
fetchPath.apply(this);
@@ -337,13 +427,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
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<T> implements SpiQuery<T> {
public DefaultOrmQuery<T> copy(EbeanServer server) {
DefaultOrmQuery<T> copy = new DefaultOrmQuery<T>(beanType, server, expressionFactory, (String) null);
DefaultOrmQuery<T> copy = new DefaultOrmQuery<T>(beanDescriptor, server, expressionFactory, (String) null);
copy.name = name;
copy.includeTableJoin = includeTableJoin;
copy.profilingListener = profilingListener;
@@ -669,7 +752,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public BeanPropertyAssocMany<?> getLazyLoadForParentsProperty() {
public BeanPropertyAssocMany<?> getLazyLoadMany() {
return lazyLoadForParentsProperty;
}
@@ -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<String> getFetchPaths() {
return fetchPaths.keySet();
}
/**
* Write the Elastic search source include and fields if necessary.
* <p>
* 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.
* </p>
*/
public void writeElastic(ElasticExpressionContext context) throws IOException {
Set<String> includes = new LinkedHashSet<String>();
Set<String> fields = new LinkedHashSet<String>();
for (Map.Entry<String, OrmQueryProperties> 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<String> 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();
}
}
}
@@ -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.
* <p>
* This is used such that a load context and persistence context can be used to span multiple marshalling requests.
* </p>
*/
public class DJsonBeanReader<T> implements JsonBeanReader<T> {
private final BeanDescriptor<T> desc;
private final ReadJson readJson;
public DJsonBeanReader(BeanDescriptor<T> 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<T> forJson(JsonParser moreJson) {
return new DJsonBeanReader(desc, readJson.forJson(moreJson));
}
}
@@ -106,15 +106,23 @@ public class DJsonContext implements JsonContext {
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
ReadJson readJson = new ReadJson(parser, options, determineObjectMapper(options));
BeanDescriptor<T> desc = getDescriptor(cls);
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
try {
BeanDescriptor<T> d = getDescriptor(cls);
return d.jsonRead(readJson, null);
return desc.jsonRead(readJson, null);
} catch (IOException e) {
throw new JsonIOException(e);
}
}
@Override
public <T> DJsonBeanReader createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
ReadJson readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
return new DJsonBeanReader<T>(desc, readJson);
}
public <T> List<T> toList(Class<T> cls, String json) throws JsonIOException {
return toList(cls, new StringReader(json));
}
@@ -138,9 +146,9 @@ public class DJsonContext implements JsonContext {
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
ReadJson readJson = new ReadJson(src, options, determineObjectMapper(options));
BeanDescriptor<T> desc = getDescriptor(cls);
ReadJson readJson = new ReadJson(desc, src, options, determineObjectMapper(options));
try {
BeanDescriptor<T> d = getDescriptor(cls);
List<T> list = new ArrayList<T>();
@@ -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 {
@@ -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<String, JsonReadBeanVisitor<?>> visitorMap;
private final Map<String, JsonReadBeanVisitor<?>> 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 <T> 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);
}
}
@@ -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);
}
/**
@@ -23,7 +23,7 @@ import java.util.Set;
* <p>
* 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.
* </p>
*/
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) {
@@ -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<Serializable> 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)));
}
}
}
}
}
}
}
@@ -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);
}
/**

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