diff --git a/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java b/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java index 95fb94776..88ff882fd 100644 --- a/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java +++ b/src/main/java/com/avaje/ebean/DocStoreQueueEntry.java @@ -39,13 +39,13 @@ public final class DocStoreQueueEntry { } } - final Action type; + private final Action type; - final String queueId; + private final String queueId; - final String path; + private final String path; - final Object beanId; + private final Object beanId; /** * Construct for an INDEX or DELETE action. diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index 374b4083e..e594400d5 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -132,6 +132,11 @@ public class ServerConfig { */ private DocStoreConfig docStoreConfig = new DocStoreConfig(); + /** + * Set to true when the EbeanServer only uses Document store. + */ + private boolean docStoreOnly; + /** * This is used to populate @WhoCreated, @WhoModified and * support other audit features (who executed a query etc). @@ -1210,6 +1215,20 @@ public class ServerConfig { this.namingConvention = namingConvention; } + /** + * Return true if this EbeanServer is a Document store only instance (has no JDBC DB). + */ + public boolean isDocStoreOnly() { + return docStoreOnly; + } + + /** + * Set to true if this EbeanServer is Document store only instance (has no JDBC DB). + */ + public void setDocStoreOnly(boolean docStoreOnly) { + this.docStoreOnly = docStoreOnly; + } + /** * Return the configuration for the ElasticSearch integration. */ @@ -2405,6 +2424,7 @@ public class ServerConfig { dbTypeConfig.setGeometrySRID(srid); } + docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly); disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache); explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode); autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode); diff --git a/src/main/java/com/avaje/ebean/plugin/BeanType.java b/src/main/java/com/avaje/ebean/plugin/BeanType.java index 71fb1936a..fb2afc74f 100644 --- a/src/main/java/com/avaje/ebean/plugin/BeanType.java +++ b/src/main/java/com/avaje/ebean/plugin/BeanType.java @@ -73,6 +73,11 @@ public interface BeanType { */ boolean isValidExpression(String property); + /** + * Return true if the type is document store only. + */ + boolean isDocStoreOnly(); + /** * Return the base table this bean type maps to. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java index 85bc3fd89..b5a4d1fd5 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java @@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.server.core.PersistDeferredRelationship; import com.avaje.ebeaninternal.server.core.PersistRequest; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeanservice.docstore.api.DocStoreTransaction; import javax.persistence.PersistenceException; import javax.persistence.RollbackException; @@ -79,6 +80,11 @@ public class ScopedTransaction implements SpiTransaction { } } + @Override + public DocStoreTransaction getDocStoreTransaction() { + return transaction.getDocStoreTransaction(); + } + @Override public DocStoreMode getDocStoreMode() { return transaction.getDocStoreMode(); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java index e63071432..bab1cb769 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java @@ -9,6 +9,7 @@ import com.avaje.ebeaninternal.server.core.PersistDeferredRelationship; import com.avaje.ebeaninternal.server.core.PersistRequest; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeanservice.docstore.api.DocStoreTransaction; import java.sql.Connection; @@ -265,4 +266,9 @@ public interface SpiTransaction extends Transaction { * Send the change set to be prepared and then logged. */ void sendChangeLog(ChangeSet changeSet); + + /** + * Return a document store transaction. + */ + DocStoreTransaction getDocStoreTransaction(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java index 87f4a610e..98ece7bad 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java @@ -11,6 +11,7 @@ import com.avaje.ebean.config.PropertyMap; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.UnderscoreNamingConvention; import com.avaje.ebean.config.dbplatform.DatabasePlatform; +import com.avaje.ebean.config.dbplatform.H2Platform; import com.avaje.ebean.dbmigration.DbOffline; import com.avaje.ebeaninternal.api.SpiBackgroundExecutor; import com.avaje.ebeaninternal.api.SpiEbeanServer; @@ -94,9 +95,14 @@ public class DefaultContainer implements SpiContainer { BootupClasses bootupClasses = getBootupClasses(serverConfig); - setDataSource(serverConfig); - // check the autoCommit and Transaction Isolation - boolean online = checkDataSource(serverConfig); + boolean online = true; + if (serverConfig.isDocStoreOnly()) { + serverConfig.setDatabasePlatform(new H2Platform()); + } else { + setDataSource(serverConfig); + // check the autoCommit and Transaction Isolation + online = checkDataSource(serverConfig); + } // determine database platform (Oracle etc) setDatabasePlatform(serverConfig); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 5b82a9fcd..59c3b3e99 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -284,7 +284,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { */ public void executePlugins(boolean online) { - ddlGenerator.execute(online); + if (!serverConfig.isDocStoreOnly()) { + ddlGenerator.execute(online); + } for (Plugin plugin : serverPlugins) { plugin.online(online); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index 25665cfd4..83d931b79 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -43,6 +43,7 @@ import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditPrepare; import com.avaje.ebeaninternal.server.text.json.DJsonContext; import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager; import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager; +import com.avaje.ebeaninternal.server.transaction.DocStoreTransactionManager; import com.avaje.ebeaninternal.server.transaction.ExplicitTransactionManager; import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager; import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager; @@ -342,11 +343,12 @@ public class InternalConfiguration { if (serverConfig.isExplicitTransactionBeginMode()) { return new ExplicitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager); } - if (isAutoCommitMode()) { return new AutoCommitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager); } - + if (serverConfig.isDocStoreOnly()) { + return new DocStoreTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager); + } return new TransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index a0d98ec3e..66f9117b4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -155,7 +155,9 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe * Prepare the query and calculate the query plan key. */ public void prepareQuery() { - + if (beanDescriptor.isDocStoreOnly()) { + query.setUseDocStore(true); + } adapterPreQuery(); this.secondaryQueries = query.convertJoins(); this.queryPlanKey = query.prepare(this); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index 23a2e910a..49e78a971 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -138,6 +138,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP private long version; + private long now; + /** * Flag set when request is added to JDBC batch registered as a "getter callback" to automatically flush batch. */ @@ -1096,4 +1098,41 @@ public final class PersistRequestBean extends PersistRequest implements BeanP // delete handled by the BeanController so return 0 return 0; } + + /** + * Persist to the document store now (via buffer, not post commit). + */ + public void docStorePersist() { + idValue = beanDescriptor.getId(entityBean); + switch (type) { + case UPDATE: + dirtyProperties = intercept.getDirtyProperties(); + break; + } + // processing now so set IGNORE (unlike DB + DocStore processing with post-commit) + docStoreMode = DocStoreMode.IGNORE; + try { + docStoreUpdate(transaction.getDocStoreTransaction().obtain()); + postExecute(); + if (type == Type.UPDATE + && beanDescriptor.isDocStoreEmbeddedInvalidation() + && transaction.isPersistCascade()) { + // queue embedded/nested updates for later processing + beanDescriptor.docStoreUpdateEmbedded(this, transaction.getDocStoreTransaction().queue()); + } + } catch (IOException e) { + throw new PersistenceException("Error persisting doc store bean", e); + } + } + + /** + * Use a common 'now' value across both when created and when updated etc. + */ + public long now() { + if (now == 0) { + now = System.currentTimeMillis(); + } + return now; + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java index 6fb8d9602..7aa6b6ff0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.core.bootup; +import com.avaje.ebean.annotation.DocStore; import com.avaje.ebean.config.CompoundType; import com.avaje.ebean.config.IdGenerator; import com.avaje.ebean.config.ScalarTypeConverter; @@ -463,18 +464,14 @@ public class BootupClasses implements ClassFilter { } private boolean isEntity(Class cls) { - - Annotation ann = cls.getAnnotation(Entity.class); - if (ann != null) { - return true; - } - ann = cls.getAnnotation(Table.class); - return ann != null; + return has(cls, Entity.class) || has(cls, Table.class) || has(cls, DocStore.class); } private boolean isEmbeddable(Class cls) { + return has(cls, Embeddable.class); + } - Annotation ann = cls.getAnnotation(Embeddable.class); - return ann != null; + private boolean has(Class cls, Class ann) { + return cls.getAnnotation(ann) != null; } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 8d8c17d7a..0036f68af 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -85,7 +85,6 @@ import javax.persistence.PersistenceException; import java.io.IOException; import java.lang.reflect.Modifier; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; @@ -139,7 +138,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } public enum EntityType { - ORM, EMBEDDED, VIEW, SQL + ORM, EMBEDDED, VIEW, SQL, DOC } /** @@ -397,7 +396,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final BeanDescriptorJsonHelp jsonHelp; private DocStoreBeanAdapter docStoreAdapter; private DocumentMapping docMapping; - + private boolean docStoreEmbeddedInvalidation; private final String defaultSelectClause; @@ -602,6 +601,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return ebeanServer; } + /** + * Return true if this is a "Doc Store only" entity bean. + */ + public boolean isDocStoreOnly() { + return EntityType.DOC == entityType; + } + /** * Return the type of this domain object. */ @@ -712,6 +718,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } } + /** + * Perform last initialisation for the descriptor. + */ + public void initLast() { + docStoreEmbeddedInvalidation = docStoreAdapter.hasEmbeddedInvalidation(); + } + /** * Initialise the document mapping. */ @@ -927,6 +940,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return docStoreAdapter.isMapped(); } + /** + * Return true if this bean type has embedded doc store invalidation. + */ + public boolean isDocStoreEmbeddedInvalidation() { + return docStoreEmbeddedInvalidation; + } + /** * Return the queueId used to uniquely identify this type when queuing an index updateAdd. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index a153569ba..b7655257f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -558,10 +558,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // create BeanManager for each non-embedded entity bean for (BeanDescriptor d : descMap.values()) { + d.initLast(); if (!d.isEmbedded()) { BeanManager m = beanManagerFactory.create(d); beanManagerMap.put(d.getFullName(), m); - checkForValidEmbeddedId(d); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 8d254e445..5376dc79a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -333,6 +333,10 @@ public class DeployBeanDescriptor { return EntityType.ORM.equals(et); } + public boolean isDocStoreOnly() { + return EntityType.DOC.equals(entityType); + } + public EntityType getEntityType() { if (entityType == null) { entityType = EntityType.ORM; diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java index 54211867a..436f4e3a2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java @@ -95,12 +95,12 @@ public class DeployTableJoin { /** * Add a JoinColumn set. */ - public void addJoinColumn(boolean order, Set jcSet, BeanTable beanTable) { - for (JoinColumn jc: jcSet) { - addJoinColumn(order, jc, beanTable); + public void addJoinColumn(boolean order, Set joinColumns, BeanTable beanTable) { + for (JoinColumn joinColumn : joinColumns) { + addJoinColumn(order, joinColumn, beanTable); } } - + /** * Return the join columns. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index 064de0d4d..cb741dd74 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -1,14 +1,5 @@ package com.avaje.ebeaninternal.server.deploy.parse; -import java.util.Set; - -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.MapKey; -import javax.persistence.OneToMany; -import javax.persistence.OrderBy; - import com.avaje.ebean.annotation.HistoryExclude; import com.avaje.ebean.annotation.PrivateOwned; import com.avaje.ebean.annotation.Where; @@ -25,17 +16,25 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; import com.avaje.ebeaninternal.server.lib.util.StringHelper; import com.avaje.ebeaninternal.server.query.SqlJoinType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import java.util.Set; + /** * Read the deployment annotation for Assoc Many beans. */ -public class AnnotationAssocManys extends AnnotationParser { +class AnnotationAssocManys extends AnnotationParser { private final BeanDescriptorManager factory; /** * Create with the DeployInfo. */ - public AnnotationAssocManys(DeployBeanInfo info, boolean javaxValidationAnnotations, BeanDescriptorManager factory) { + AnnotationAssocManys(DeployBeanInfo info, boolean javaxValidationAnnotations, BeanDescriptorManager factory) { super(info, javaxValidationAnnotations); this.factory = factory; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index f95a19901..6f263b946 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -197,6 +197,9 @@ public class AnnotationAssocOnes extends AnnotationParser { private void readEmbedded(DeployBeanPropertyAssocOne prop) { + if (descriptor.isDocStoreOnly() && prop.getDocStoreDoc() == null) { + prop.setDocStoreEmbedded(""); + } prop.setEmbedded(); prop.setDbInsertable(true); prop.setDbUpdateable(true); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java index c116b6ed9..61b23ed74 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java @@ -47,7 +47,7 @@ public abstract class AnnotationBase { } /** - * Return the annotation for the property. + * Return the annotation for the property. *

* Looks first at the field and then at the getter method. *

@@ -55,7 +55,7 @@ public abstract class AnnotationBase { * If a repeatable annotation class is specified and the annotation is platform * specific(see {@link #getPlatformMatchingAnnotation(Set, Class)}), then the platform specific * annotation is returned. Otherwise the first annotation is retured. Note that you must no longer - * handle "java 1.6 repeatable containers" like {@link JoinColumn} / {@link JoinColumns} yourself. + * handle "java 1.6 repeatable containers" like {@link JoinColumn} / {@link JoinColumns} yourself. *

*

*/ @@ -94,8 +94,7 @@ public abstract class AnnotationBase { } return ret; } - - + /** * Return the annotation for the property. *

@@ -170,10 +169,10 @@ public abstract class AnnotationBase { } /** - * Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first + * Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first * found annotation is returned) */ - public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, + public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Class databasePlatform) { if (annotationType == null) { return null; @@ -205,7 +204,7 @@ public abstract class AnnotationBase { } return null; } - + /** * Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}. *

@@ -232,7 +231,7 @@ public abstract class AnnotationBase { Annotation[] anns = annotatedElement.getAnnotations(); for (Annotation ann : anns) { - if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { + if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { if (ann.annotationType() == annotationType) { ret.add((A) ann); } else { @@ -263,18 +262,18 @@ public abstract class AnnotationBase { return null; } } - + private static final ConcurrentMap valueMethods = new ConcurrentHashMap(); private static final Method nullMethod = getNullMethod(); - - + + /** * Returns the value() method for a possible containerAnnotation. - * Method is retuned only, if its signature is array of containingType. + * Method is retuned only, if its signature is array of containingType. */ private static Method getRepeatableValueMethod( Annotation containerAnnotation, Class containingType) { - + Method method = valueMethods.get(containerAnnotation); if (method == null) { try { @@ -295,11 +294,11 @@ public abstract class AnnotationBase { } return null; } - + /** * Finds a suitable annotation from Set anns for this platform. - * To distinguish between platforms, annotation type T must define - * a method withthis signature: + * To distinguish between platforms, annotation type T must define + * a method withthis signature: *

* Class[] platforms() default {}; *

@@ -345,4 +344,4 @@ public abstract class AnnotationBase { return fallback; } -} \ No newline at end of file +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java index 78974b3d2..7c735f703 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -108,8 +108,17 @@ public class AnnotationClass extends AnnotationParser { private void read(Class cls) { + // maybe doc store only so check for this before @Entity + DocStore docStore = AnnotationBase.findAnnotation(cls,DocStore.class); + if (docStore != null) { + descriptor.readDocStore(docStore); + descriptor.setEntityType(EntityType.DOC); + descriptor.setName(cls.getSimpleName()); + } + Entity entity = AnnotationBase.findAnnotation(cls,Entity.class); if (entity != null) { + descriptor.setEntityType(EntityType.ORM); if (entity.name().equals("")) { descriptor.setName(cls.getSimpleName()); } else { @@ -170,11 +179,6 @@ public class AnnotationClass extends AnnotationParser { descriptor.setDbComment(comment.value()); } - DocStore docStore = AnnotationBase.findAnnotation(cls,DocStore.class); - if (docStore != null) { - descriptor.readDocStore(docStore); - } - UpdateMode updateMode = AnnotationBase.findAnnotation(cls,UpdateMode.class); if (updateMode != null) { descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index b8055b1fa..5ca523a0a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -96,6 +96,13 @@ public class AnnotationFields extends AnnotationParser { DocEmbedded docEmbedded = get(prop, DocEmbedded.class); if (docEmbedded != null) { prop.setDocStoreEmbedded(docEmbedded.doc()); + if (descriptor.isDocStoreOnly()) { + if (get(prop, ManyToOne.class) == null) { + prop.setEmbedded(); + prop.setDbInsertable(true); + prop.setDbUpdateable(true); + } + } } if (prop instanceof DeployBeanPropertyAssocOne) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java index d4b9fcebd..cc2f1f03d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java @@ -16,18 +16,19 @@ public class DmlBeanPersisterFactory implements BeanPersisterFactory { this.metaFactory = new MetaFactory(dbPlatform); } - /** * Create a DmlBeanPersister for the given bean type. */ public BeanPersister create(BeanDescriptor desc) { + if (desc.isDocStoreOnly()) { + return new DocStoreBeanPersister(GeneratedProperties.of(desc)); + } + UpdateMeta updMeta = metaFactory.createUpdate(desc); DeleteMeta delMeta = metaFactory.createDelete(desc); InsertMeta insMeta = metaFactory.createInsert(desc); - return new DmlBeanPersister(updMeta, insMeta, delMeta); - } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DocStoreBeanPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DocStoreBeanPersister.java new file mode 100644 index 000000000..0f5e7b4ad --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DocStoreBeanPersister.java @@ -0,0 +1,37 @@ +package com.avaje.ebeaninternal.server.persist.dml; + +import com.avaje.ebeaninternal.server.core.PersistRequestBean; +import com.avaje.ebeaninternal.server.persist.BeanPersister; + +import javax.persistence.PersistenceException; + +/** + * Document store based BeanPersister. + */ +class DocStoreBeanPersister implements BeanPersister { + + private final GeneratedProperties generatedProperties; + + DocStoreBeanPersister(GeneratedProperties generatedProperties) { + this.generatedProperties = generatedProperties; + } + + @Override + public void insert(PersistRequestBean request) throws PersistenceException { + //request.setIdValueForDocStore(); + generatedProperties.preInsert(request.getEntityBean(), request.now()); + request.docStorePersist(); + } + + @Override + public void update(PersistRequestBean request) throws PersistenceException { + generatedProperties.preUpdate(request.getEntityBean(), request.now()); + request.docStorePersist(); + } + + @Override + public int delete(PersistRequestBean request) throws PersistenceException { + request.docStorePersist(); + return 0; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GeneratedProperties.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GeneratedProperties.java new file mode 100644 index 000000000..ee4c496e2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GeneratedProperties.java @@ -0,0 +1,77 @@ +package com.avaje.ebeaninternal.server.persist.dml; + +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; + +import java.util.List; + +/** + * Executes the generated property (like WhenCreated, WhoCreated etc) for doc store persisting. + */ +class GeneratedProperties { + + /** + * Create the GeneratedProperties for the given bean type. + */ + static GeneratedProperties of(BeanDescriptor desc) { + return new GeneratedPropertyCollector(desc).generatedProperties(); + } + + private final SetValue[] onInsert; + private final SetValue[] onUpdate; + + GeneratedProperties(List inserts, List updates) { + this.onInsert = adapt(inserts); + this.onUpdate = adapt(updates); + } + + private SetValue[] adapt(List inserts) { + SetValue[] setters = new SetValue[inserts.size()]; + for (int i = 0; i < inserts.size(); i++) { + setters[i] = new SetValue(inserts.get(i)); + } + return setters; + } + + /** + * Set all the generated on insert values. + */ + public void preInsert(EntityBean bean, long now) { + for (SetValue setter : onInsert) { + setter.preInsert(bean, now); + } + } + + /** + * Set all the generated on update values. + */ + public void preUpdate(EntityBean bean, long now) { + for (SetValue setter : onUpdate) { + setter.preUpdate(bean, now); + } + } + + + private static class SetValue { + + private final BeanProperty property; + private final GeneratedProperty generatedProperty; + + SetValue(BeanProperty property) { + this.property = property; + this.generatedProperty = property.getGeneratedProperty(); + } + + public void preInsert(EntityBean bean, long now) { + Object value = generatedProperty.getInsertValue(property, bean, now); + property.setValue(bean, value); + } + + public void preUpdate(EntityBean bean, long now) { + Object value = generatedProperty.getUpdateValue(property, bean, now); + property.setValue(bean, value); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GeneratedPropertyCollector.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GeneratedPropertyCollector.java new file mode 100644 index 000000000..51abc3382 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/GeneratedPropertyCollector.java @@ -0,0 +1,40 @@ +package com.avaje.ebeaninternal.server.persist.dml; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collects the generated properties for inserts and updates for a given bean type. + */ +class GeneratedPropertyCollector { + + private final List preInsert = new ArrayList<>(); + private final List preUpdate = new ArrayList<>(); + + GeneratedPropertyCollector(BeanDescriptor desc) { + for (BeanProperty beanProperty : desc.propertiesBaseScalar()) { + add(beanProperty); + } + } + + GeneratedProperties generatedProperties() { + return new GeneratedProperties(preInsert, preUpdate); + } + + void add(BeanProperty prop) { + GeneratedProperty gen = prop.getGeneratedProperty(); + if (gen != null) { + if (gen.includeInInsert()) { + preInsert.add(prop); + } + if (gen.includeInUpdate()) { + preUpdate.add(prop); + } + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreOnlyTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreOnlyTransaction.java new file mode 100644 index 000000000..a90647b38 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreOnlyTransaction.java @@ -0,0 +1,54 @@ +package com.avaje.ebeaninternal.server.transaction; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Document store only transaction. + */ +public class DocStoreOnlyTransaction extends JdbcTransaction { + + /** + * Create a new DocStore only Transaction. + */ + public DocStoreOnlyTransaction(String id, boolean explicit, TransactionManager manager) { + super(id, explicit, null, manager); + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public void setReadOnly(boolean readOnly) { + // do nothing + } + + protected void deactivate() { + // do nothing + } + + @Override + public Connection getInternalConnection() { + throw new RuntimeException("not supported on DocStoreTransaction"); + } + + @Override + public Connection getConnection() { + throw new RuntimeException("not supported on DocStoreTransaction"); + } + + @Override + protected void performRollback() throws SQLException { + // do nothing (could perhaps throw not supported exception) + } + + @Override + protected void performCommit() throws SQLException { + if (docStoreTxn != null) { + manager.docStoreUpdateProcessor.commit(docStoreTxn); + } + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java new file mode 100644 index 000000000..98de8bebd --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java @@ -0,0 +1,44 @@ +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.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; + +import java.sql.Connection; + +/** + * Transaction manager used for doc store only EbeanServer instance. + *

+ * There is no underlying JDBC DataSource etc + */ +public class DocStoreTransactionManager extends TransactionManager { + + private final String prefix = ""; + + /** + * Create the TransactionManager + */ + public DocStoreTransactionManager(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, + DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr) { + super(localL2Caching, config, clusterManager, backgroundExecutor, docStoreUpdateProcessor, descMgr); + } + + @Override + public SpiTransaction createTransaction(boolean explicit, int isolationLevel) { + long id = transactionCounter.incrementAndGet(); + return createTransaction(explicit, null, id); + } + + @Override + public SpiTransaction createQueryTransaction() { + return new DocStoreOnlyTransaction("", false, this); + } + + @Override + protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) { + return new DocStoreOnlyTransaction(prefix + id, explicit, this); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java index 9a13bbe8e..a2d217847 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java @@ -15,6 +15,7 @@ import com.avaje.ebeaninternal.server.core.PersistRequest; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.lib.util.Str; import com.avaje.ebeaninternal.server.persist.BatchControl; +import com.avaje.ebeanservice.docstore.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -164,6 +165,8 @@ public class JdbcTransaction implements SpiTransaction { */ protected final boolean skipCacheAfterWrite; + protected DocStoreTransaction docStoreTxn; + /** * Create a new JdbcTransaction. */ @@ -1079,6 +1082,15 @@ public class JdbcTransaction implements SpiTransaction { getEvent().add(tableName, inserts, updates, deletes); } + @Override + public DocStoreTransaction getDocStoreTransaction() { + if (docStoreTxn == null) { + queryOnly = false; + docStoreTxn = manager.createDocStoreTransaction(docStoreBatchSize); + } + return docStoreTxn; + } + @Override public void putUserObject(String name, Object value) { if (userObjects == null) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java index 9afe59a23..89dbb3b16 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java @@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.api.TransactionEventTable; import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; import com.avaje.ebeaninternal.server.cluster.ClusterManager; import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager; +import com.avaje.ebeanservice.docstore.api.DocStoreTransaction; import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; import org.avaje.datasource.DataSourcePool; @@ -144,6 +145,10 @@ public class TransactionManager { return docStoreActive; } + public DocStoreTransaction createDocStoreTransaction(int docStoreBatchSize) { + return docStoreUpdateProcessor.createTransaction(docStoreBatchSize); + } + public boolean isSkipCacheAfterWrite() { return skipCacheAfterWrite; } @@ -433,4 +438,5 @@ public class TransactionManager { beanDescriptorManager.processViewInvalidation(viewInvalidation); } } + } diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreTransaction.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreTransaction.java new file mode 100644 index 000000000..ea2e81a77 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreTransaction.java @@ -0,0 +1,29 @@ +package com.avaje.ebeanservice.docstore.api; + +/** + * A document store transaction. + *

+ * This might just be a buffer to batch persist requests to the document store and may not + * support transactional semantics (like rollback). + */ +public interface DocStoreTransaction { + + /** + * Obtain a context to persist to (like a buffer). + */ + DocStoreUpdateContext obtain(); + + /** + * Add changes that should be queued to the DocStoreUpdates. + *

+ * This mostly means nested/embedded updates that need to be processed after the source + * persist event has propagated. + *

+ */ + DocStoreUpdates queue(); + + /** + * Flush all changes to the document store. + */ + void flush(); +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java index 2de6e8a2a..d8cd16124 100644 --- a/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocStoreUpdateProcessor.java @@ -31,4 +31,15 @@ public interface DocStoreUpdateProcessor { */ void process(DocStoreUpdates docStoreUpdates, int bulkBatchSize); + /** + * Create a document store transaction hinting at the batch size. + *

+ * The batch size can be set via {@link com.avaje.ebean.Transaction#setDocStoreBatchSize(int)} + */ + DocStoreTransaction createTransaction(int batchSize); + + /** + * Perform commit/flush of the changes made via the document store transaction. + */ + void commit(DocStoreTransaction docStoreTransaction); } diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java index 7fa9f4e83..949d98a12 100644 --- a/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/support/DocStoreBeanBaseAdapter.java @@ -174,9 +174,12 @@ public abstract class DocStoreBeanBaseAdapter implements DocStoreBeanAdapter< String path = pathProp.getPath(); if (path != null) { BeanDescriptor targetDesc = desc.getBeanDescriptor(path); - String idName = targetDesc.getIdProperty().getName(); - String fullPath = path + "." + idName; - targetDesc.docStoreAdapter().registerInvalidationPath(desc.getDocStoreQueueId(), fullPath, pathProp.getProperties()); + BeanProperty idProperty = targetDesc.getIdProperty(); + if (idProperty != null) { + // embedded beans don't have id property + String fullPath = path + "." + idProperty.getName(); + targetDesc.docStoreAdapter().registerInvalidationPath(desc.getDocStoreQueueId(), fullPath, pathProp.getProperties()); + } } } registerPaths = true; diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java index f32486697..c5ec2b9b0 100644 --- a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java +++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStoreUpdateProcessor.java @@ -2,6 +2,7 @@ package com.avaje.ebeanservice.docstore.none; import com.avaje.ebean.plugin.BeanType; import com.avaje.ebeanservice.docstore.api.DocStoreQueryUpdate; +import com.avaje.ebeanservice.docstore.api.DocStoreTransaction; import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor; import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; @@ -10,7 +11,7 @@ import java.io.IOException; /** * DocStoreUpdateProcessor that barfs if it is used. */ -public class NoneDocStoreUpdateProcessor implements DocStoreUpdateProcessor { +class NoneDocStoreUpdateProcessor implements DocStoreUpdateProcessor { @Override public DocStoreQueryUpdate createQueryUpdate(BeanType beanType, int bulkBatchSize) throws IOException { @@ -21,4 +22,14 @@ public class NoneDocStoreUpdateProcessor implements DocStoreUpdateProcessor { public void process(DocStoreUpdates docStoreUpdates, int bulkBatchSize) { throw NoneDocStore.implementationNotInClassPath(); } + + @Override + public DocStoreTransaction createTransaction(int batchSize) { + throw NoneDocStore.implementationNotInClassPath(); + } + + @Override + public void commit(DocStoreTransaction docStoreTransaction) { + throw NoneDocStore.implementationNotInClassPath(); + } }