diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java
index 327d89275..15de32146 100644
--- a/src/main/java/com/avaje/ebean/EbeanServer.java
+++ b/src/main/java/com/avaje/ebean/EbeanServer.java
@@ -1819,4 +1819,29 @@ public interface EbeanServer {
*/
JsonContext json();
+ /**
+ * Publish a single bean given its type and id.
+ *
+ * The values are published from the draft to the live bean.
+ *
+ *
+ * @param beanType the type of the entity bean
+ * @param id the id of the entity bean
+ * @param transaction the transaction the publish process should use
+ * @param the type of the entity bean
+ */
+ void publish(Class beanType, Object id, Transaction transaction);
+
+ /**
+ * Publish the beans that match the query.
+ *
+ * The values are published from the draft beans to the live beans.
+ *
+ *
+ * @param query the query used to select the draft beans to publish
+ * @param transaction the transaction the publish process should use
+ * @param the type of the entity bean
+ */
+ void publish(Query query, Transaction transaction);
+
}
diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java
index 4fcf88a37..fee79aafa 100644
--- a/src/main/java/com/avaje/ebean/Query.java
+++ b/src/main/java/com/avaje/ebean/Query.java
@@ -297,6 +297,11 @@ public interface Query extends Serializable {
*/
Query asOf(Timestamp asOf);
+ /**
+ * Execute the query against the draft set of tables.
+ */
+ Query asDraft();
+
/**
* Cancel the query execution if supported by the underlying database and
* driver.
diff --git a/src/main/java/com/avaje/ebean/annotation/DraftDirty.java b/src/main/java/com/avaje/ebean/annotation/DraftDirty.java
new file mode 100644
index 000000000..5cb9e6b65
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DraftDirty.java
@@ -0,0 +1,16 @@
+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;
+
+/**
+ * Marks a boolean property on a @Draftable bean that only exists on the 'draft' table
+ * and is used to detect when a draft has unpublished changes.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface DraftDirty {
+
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DraftOnly.java b/src/main/java/com/avaje/ebean/annotation/DraftOnly.java
new file mode 100644
index 000000000..57d793e0f
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DraftOnly.java
@@ -0,0 +1,15 @@
+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;
+
+/**
+ * Marks a property on a @Draftable bean that only exists on the 'draft' and not the 'live' table.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface DraftOnly {
+
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/Draftable.java b/src/main/java/com/avaje/ebean/annotation/Draftable.java
new file mode 100644
index 000000000..5110596b8
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/Draftable.java
@@ -0,0 +1,15 @@
+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 an entity bean that has 'draftable' support.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Draftable {
+
+}
diff --git a/src/main/java/com/avaje/ebean/annotation/DraftableElement.java b/src/main/java/com/avaje/ebean/annotation/DraftableElement.java
new file mode 100644
index 000000000..36dce8986
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DraftableElement.java
@@ -0,0 +1,16 @@
+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 an entity bean that has 'draftable' support but it not a 'root level' bean
+ * but instead child related to another @Draftable entity bean.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DraftableElement {
+
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/CurrentModel.java b/src/main/java/com/avaje/ebean/dbmigration/model/CurrentModel.java
index 48bca0e0f..c0016cc7f 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/CurrentModel.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/CurrentModel.java
@@ -77,6 +77,9 @@ public class CurrentModel {
ModelBuildBeanVisitor visitor = new ModelBuildBeanVisitor(context);
VisitAllUsing visit = new VisitAllUsing(visitor, server);
visit.visitAllBeans();
+
+ // adjust the foreign keys on the 'draft' tables
+ context.adjustDraftReferences();
}
return model;
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MColumn.java b/src/main/java/com/avaje/ebean/dbmigration/model/MColumn.java
index 80f428470..25ee8d8fc 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/MColumn.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/MColumn.java
@@ -35,6 +35,8 @@ public class MColumn {
*/
private AlterColumn alterColumn;
+ private boolean draftOnly;
+
public MColumn(Column column) {
this.name = column.getName();
this.type = column.getType();
@@ -63,6 +65,28 @@ public class MColumn {
this.notnull = notnull;
}
+ /**
+ * Return a copy of this column used for creating the associated draft table.
+ */
+ public MColumn copyForDraft() {
+
+ MColumn copy = new MColumn(name, type);
+ copy.draftOnly = draftOnly;
+ copy.checkConstraint = checkConstraint;
+ copy.checkConstraintName = checkConstraintName;
+ copy.defaultValue = defaultValue;
+ copy.references = references;
+ copy.foreignKeyName = foreignKeyName;
+ copy.foreignKeyIndex = foreignKeyIndex;
+ copy.historyExclude = historyExclude;
+ copy.notnull = notnull;
+ copy.primaryKey = primaryKey;
+ copy.identity = identity;
+ copy.unique = unique;
+ copy.uniqueOneToOne = uniqueOneToOne;
+ return copy;
+ }
+
public String getName() {
return name;
}
@@ -174,6 +198,21 @@ public class MColumn {
return uniqueOneToOne;
}
+
+ /**
+ * Set the draftOnly status for this column.
+ */
+ public void setDraftOnly(boolean draftOnly) {
+ this.draftOnly = draftOnly;
+ }
+
+ /**
+ * Return the draftOnly status for this column.
+ */
+ public boolean isDraftOnly() {
+ return draftOnly;
+ }
+
public Column createColumn() {
Column c = new Column();
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MTable.java b/src/main/java/com/avaje/ebean/dbmigration/model/MTable.java
index e85937955..9e901bbd8 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/MTable.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/MTable.java
@@ -43,6 +43,17 @@ public class MTable {
*/
private final String name;
+ /**
+ * The associated draft table.
+ */
+ private MTable draftTable;
+
+ /**
+ * Marked true for draft tables. These need to have their FK references adjusted
+ * after all the draft tables have been identified.
+ */
+ private boolean draft;
+
/**
* Primary key name.
*/
@@ -107,6 +118,27 @@ public class MTable {
*/
private AddColumn addColumn;
+ /**
+ * Create a copy of this table structure as a 'draft' table.
+ *
+ * Note that both tables contain @DraftOnly MColumns and these are filtered out
+ * later when creating the CreateTable object.
+ */
+ public MTable createDraftTable() {
+
+ draftTable = new MTable(name+"_draft");
+ draftTable.draft = true;
+ draftTable.whenCreatedColumn = whenCreatedColumn;
+ // compoundKeys
+ // compoundUniqueConstraints
+ draftTable.identityType = identityType;
+
+ for (MColumn col: columns.values()) {
+ draftTable.addColumn(col.copyForDraft());
+ }
+
+ return draftTable;
+ }
/**
* Construct for migration.
@@ -164,7 +196,10 @@ public class MTable {
}
for (MColumn column : this.columns.values()) {
- createTable.getColumn().add(column.createColumn());
+ // filter out draftOnly columns from the base table
+ if (draft || !column.isDraftOnly()) {
+ createTable.getColumn().add(column.createColumn());
+ }
}
for (MCompoundForeignKey compoundKey : compoundKeys) {
@@ -271,6 +306,13 @@ public class MTable {
return name;
}
+ /**
+ * Return true if this table is a 'Draft' table.
+ */
+ public boolean isDraft() {
+ return draft;
+ }
+
public String getPkName() {
return pkName;
}
@@ -511,4 +553,42 @@ public class MTable {
}
return false;
}
+
+ /**
+ * Adjust the references (FK) if it should relate to a draft table.
+ */
+ public void adjustReferences(ModelContainer modelContainer) {
+
+ Collection cols = columns.values();
+ for (MColumn col : cols) {
+ String references = col.getReferences();
+ if (references != null) {
+ String baseTable = extractBaseTable(references);
+ MTable refBaseTable = modelContainer.getTable(baseTable);
+ if (refBaseTable.draftTable != null) {
+ // change references to another associated 'draft' table
+ String newReferences = deriveReferences(references, refBaseTable.draftTable.getName());
+ col.setReferences(newReferences);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the base table name from references (table.column).
+ */
+ private String extractBaseTable(String references) {
+ int lastDot = references.lastIndexOf('.');
+ return references.substring(0,lastDot);
+ }
+
+ /**
+ * Return the new references using the given draftTableName.
+ * (The referenced column is the same as before).
+ */
+ private String deriveReferences(String references, String draftTableName) {
+ int lastDot = references.lastIndexOf('.');
+ return draftTableName+"."+references.substring(lastDot+1);
+ }
+
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/ModelContainer.java b/src/main/java/com/avaje/ebean/dbmigration/model/ModelContainer.java
index 1a878215f..071ab5fa1 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/ModelContainer.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/ModelContainer.java
@@ -12,6 +12,7 @@ import com.avaje.ebean.dbmigration.migration.DropIndex;
import com.avaje.ebean.dbmigration.migration.DropTable;
import com.avaje.ebean.dbmigration.migration.Migration;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -38,6 +39,18 @@ public class ModelContainer {
}
+ /**
+ * Adjust the FK references on all the draft tables.
+ */
+ public void adjustDraftReferences() {
+ Collection tables = this.tables.values();
+ for (MTable table : tables) {
+ if (table.isDraft()) {
+ table.adjustReferences(this);
+ }
+ }
+ }
+
/**
* Return the map of all the tables.
*/
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java
index 9ba81fa96..155c09de8 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildBeanVisitor.java
@@ -5,7 +5,6 @@ import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.dbmigration.migration.IdentityType;
import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;
-import com.avaje.ebean.dbmigration.model.visitor.BeanPropertyVisitor;
import com.avaje.ebean.dbmigration.model.visitor.BeanVisitor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -29,7 +28,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
* This creates an MTable and adds it to the model.
*
*/
- public BeanPropertyVisitor visitBean(BeanDescriptor> descriptor) {
+ public ModelBuildPropertyVisitor visitBean(BeanDescriptor> descriptor) {
if (!descriptor.isInheritanceRoot()) {
return null;
@@ -58,7 +57,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
table.addColumn(new MColumn(discColumn, discDbType, true));
}
- return new ModelBuildPropertyVisitor(ctx, table, descriptor.getCompoundUniqueConstraints());
+ return new ModelBuildPropertyVisitor(ctx, table, descriptor);
}
/**
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java
index 0cd86310e..2ec9e9b70 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java
@@ -31,6 +31,14 @@ public class ModelBuildContext {
this.maxLength = maxLength;
}
+ /**
+ * Adjust the foreign key references on any draft tables (that reference other draft tables).
+ * This is called as a 'second pass' after all the draft tables have been identified.
+ */
+ public void adjustDraftReferences() {
+ model.adjustDraftReferences();
+ }
+
public String primaryKeyName(String tableName) {
return maxLength(constraintNaming.primaryKeyName(tableName), 0);
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java
index abc67be4a..4f75c4d59 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java
@@ -5,6 +5,7 @@ import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MCompoundForeignKey;
import com.avaje.ebean.dbmigration.model.MTable;
import com.avaje.ebean.dbmigration.model.visitor.BaseTablePropertyVisitor;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
@@ -14,6 +15,7 @@ import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@@ -26,6 +28,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private final MTable table;
+ private final BeanDescriptor> beanDescriptor;
+
private final IndexSet indexSet = new IndexSet();
private MColumn lastColumn;
@@ -36,11 +40,11 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private int countCheck;
- public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, CompoundUniqueConstraint[] constraints) {
+ public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, BeanDescriptor> beanDescriptor) {
this.ctx = ctx;
this.table = table;
-
- addCompoundUniqueConstraint(constraints);
+ this.beanDescriptor = beanDescriptor;
+ addCompoundUniqueConstraint(beanDescriptor.getCompoundUniqueConstraints());
}
/**
@@ -97,6 +101,36 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
compoundKey.setIndexName(null);
}
}
+
+ addDraftTable();
+ }
+
+ /**
+ * Create a 'draft' table that is mostly the same as the base table.
+ * It has @DraftOnly columns and adjusted primary and foreign keys.
+ */
+ private void addDraftTable() {
+ if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) {
+ // create a 'Draft' table which looks very similar (change PK, FK etc)
+ MTable draftTable = table.createDraftTable();
+ draftTable.setPkName(ctx.primaryKeyName(draftTable.getName()));
+
+ int fkCount = 0;
+ int ixCount = 0;
+ Collection cols = draftTable.getColumns().values();
+ for (MColumn col: cols) {
+ if (col.getForeignKeyName() != null) {
+ // Note that we adjust the 'references' table later in a second pass
+ // after we know all the tables that are 'draftable'
+ //col.setReferences(refTable + "." + refColumn);
+ col.setForeignKeyName(ctx.foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount));
+
+ String[] indexCols = {col.getName()};
+ col.setForeignKeyIndex(ctx.foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
+ }
+ }
+ ctx.addTable(draftTable);
+ }
}
@@ -213,6 +247,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
+ col.setDraftOnly(p.isDraftOnly());
if (p.isId()) {
col.setPrimaryKey(true);
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/visitor/BeanVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/visitor/BeanVisitor.java
index d38889317..3483c88d0 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/visitor/BeanVisitor.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/visitor/BeanVisitor.java
@@ -1,5 +1,6 @@
package com.avaje.ebean.dbmigration.model.visitor;
+import com.avaje.ebean.dbmigration.model.build.ModelBuildPropertyVisitor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -12,6 +13,6 @@ public interface BeanVisitor {
* Visit a BeanDescriptor and return a PropertyVisitor to use to visit each
* property on the entity bean (return null to skip visiting this bean).
*/
- BeanPropertyVisitor visitBean(BeanDescriptor> descriptor);
+ ModelBuildPropertyVisitor visitBean(BeanDescriptor> descriptor);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
index 34d3a9e8c..565c0a800 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
@@ -94,6 +94,11 @@ public interface SpiQuery extends Query {
}
enum TemporalMode {
+ /**
+ * Query runs against draft tables.
+ */
+ DRAFT,
+
/**
* Query runs against current data (normal).
*/
@@ -171,6 +176,11 @@ public interface SpiQuery extends Query {
*/
boolean isAsOfQuery();
+ /**
+ * Return true if this is a 'As Draft' query.
+ */
+ boolean isAsDraft();
+
/**
* Return the asOf Timestamp which the query should run as.
*/
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 6ec3524ca..3d56e8e32 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -1625,6 +1625,27 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ public void publish(Query query, Transaction transaction) {
+
+ TransWrapper wrap = initTransIfRequired(transaction);
+ try {
+ SpiTransaction trans = wrap.transaction;
+ persister.publish(query, trans);
+ wrap.commitIfCreated();
+
+ } catch (RuntimeException e) {
+ wrap.rollbackIfCreated();
+ throw e;
+ }
+ }
+
+ @Override
+ public void publish(Class beanType, Object id, Transaction transaction) {
+
+ Query query = find(beanType).setId(id);
+ publish(query, transaction);
+ }
+
private EntityBean checkEntityBean(Object bean) {
if (bean == null) {
throw new IllegalArgumentException(Message.msg("bean.isnull"));
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 9d40af426..cee293377 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
@@ -127,13 +127,14 @@ public class InternalConfiguration {
this.beanDescriptorManager = new BeanDescriptorManager(this);
Map asOfTableMapping = beanDescriptorManager.deploy();
+ Map draftTableMap = beanDescriptorManager.getDraftTableMap();
this.transactionManager = createTransactionManager();
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
this.binder = getBinder(typeManager, databasePlatform);
- this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod());
+ this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
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 e53c735d3..3fc05528c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -119,6 +119,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
*/
private boolean requestUpdateAllLoadedProps;
+ private boolean publish;
+
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
@@ -736,4 +738,51 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
return requestUpdateAllLoadedProps;
}
+
+ /**
+ * This is a persist request for a 'publish' action.
+ */
+ public void setPublish() {
+ publish = true;
+ }
+
+ /**
+ * Return true if this request is a 'publish' action.
+ */
+ public boolean isPublish() {
+ return publish;
+ }
+
+ /**
+ * Return the key for an update persist request.
+ */
+ public int getUpdatePlanHash() {
+
+ int hash;
+ if (determineUpdateAllLoadedProperties()) {
+ hash = intercept.getLoadedPropertyHash();
+ } else {
+ hash = intercept.getDirtyPropertyHash();
+ }
+
+ BeanProperty versionProperty = beanDescriptor.getVersionProperty();
+ if (versionProperty != null) {
+ if (intercept.isLoadedProperty(versionProperty.getPropertyIndex())) {
+ hash = hash * 31 + 7;
+ }
+ }
+
+ if (publish) {
+ hash = hash * 31;
+ }
+
+ return hash;
+ }
+
+ /**
+ * Return the table to update depending if the request is a 'publish' one or normal.
+ */
+ public String getUpdateTable() {
+ return publish ? beanDescriptor.getBaseTable() : beanDescriptor.getDraftTable();
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java
index a666f5d93..b939219d8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java
@@ -3,12 +3,12 @@ package com.avaje.ebeaninternal.server.core;
import java.util.Collection;
import com.avaje.ebean.CallableSql;
+import com.avaje.ebean.Query;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.Update;
import com.avaje.ebean.bean.EntityBean;
-
/**
* API for persisting a bean.
*/
@@ -17,77 +17,79 @@ public interface Persister {
/**
* Update the bean.
*/
- void update(EntityBean entityBean, Transaction t);
+ void update(EntityBean entityBean, Transaction t);
- /**
- * Update the bean specifying deleteMissingChildren.
- */
- void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
+ /**
+ * Update the bean specifying deleteMissingChildren.
+ */
+ void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
- /**
- * Force an Insert using the given bean.
- */
- void insert(EntityBean entityBean, Transaction t);
+ /**
+ * Force an Insert using the given bean.
+ */
+ void insert(EntityBean entityBean, Transaction t);
- /**
- * Insert or update the bean depending on its state.
- */
- void save(EntityBean entityBean, Transaction t);
+ /**
+ * Insert or update the bean depending on its state.
+ */
+ void save(EntityBean entityBean, Transaction t);
- /**
- * Save the associations of a ManyToMany given the owner bean and the
- * propertyName of the ManyToMany collection.
- */
- void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
+ /**
+ * Save the associations of a ManyToMany given the owner bean and the
+ * propertyName of the ManyToMany collection.
+ */
+ void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
- /**
- * Save an association (OneToMany, ManyToOne, OneToOne or ManyToMany).
- *
- * @param parentBean
- * the bean that owns the association.
- * @param propertyName
- * the name of the property to save.
- * @param t
- * the transaction to use.
- */
- void saveAssociation(EntityBean parentBean, String propertyName, Transaction t);
+ /**
+ * Save an association (OneToMany, ManyToOne, OneToOne or ManyToMany).
+ *
+ * @param parentBean the bean that owns the association.
+ * @param propertyName the name of the property to save.
+ * @param t the transaction to use.
+ */
+ void saveAssociation(EntityBean parentBean, String propertyName, Transaction t);
- /**
- * Delete the associations of a ManyToMany given the owner bean and the property name of the ManyToMany.
- */
- int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
+ /**
+ * Delete the associations of a ManyToMany given the owner bean and the property name of the ManyToMany.
+ */
+ int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t);
- /**
- * Delete a bean given it's type and id value.
- *
- * This will also cascade delete one level of children.
- *
- */
- int delete(Class> beanType, Object id, Transaction transaction);
+ /**
+ * Delete a bean given it's type and id value.
+ *
+ * This will also cascade delete one level of children.
+ *
+ */
+ int delete(Class> beanType, Object id, Transaction transaction);
- /**
- * Delete the bean.
- */
- void delete(EntityBean entityBean, Transaction t);
+ /**
+ * Delete the bean.
+ */
+ void delete(EntityBean entityBean, Transaction t);
- /**
- * Delete multiple beans given a collection of Id values.
- */
- void deleteMany(Class> beanType, Collection> ids, Transaction transaction);
+ /**
+ * Delete multiple beans given a collection of Id values.
+ */
+ void deleteMany(Class> beanType, Collection> ids, Transaction transaction);
+
+ /**
+ * Execute the Update.
+ */
+ int executeOrmUpdate(Update> update, Transaction t);
+
+ /**
+ * Execute the UpdateSql.
+ */
+ int executeSqlUpdate(SqlUpdate update, Transaction t);
+
+ /**
+ * Execute the CallableSql.
+ */
+ int executeCallable(CallableSql callable, Transaction t);
+
+ /**
+ * Publish the draft beans matching the given query.
+ */
+ void publish(Query query, Transaction transaction);
- /**
- * Execute the Update.
- */
- int executeOrmUpdate(Update> update, Transaction t);
-
- /**
- * Execute the UpdateSql.
- */
- int executeSqlUpdate(SqlUpdate update, Transaction t);
-
- /**
- * Execute the CallableSql.
- */
- int executeCallable(CallableSql callable, Transaction t);
-
}
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 628bfcfa7..364ceb51f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -147,11 +147,17 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
private final String baseTableVersionsBetween;
private final boolean historySupport;
+ private final String draftTable;
+
/**
* Set to true if read auditing is on for this bean type.
*/
private final boolean readAuditing;
+ private final boolean draftable;
+
+ private final boolean draftableElement;
+
/**
* Map of BeanProperty Linked so as to preserve order.
*/
@@ -319,7 +325,8 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
private final boolean updateChangesOnly;
private final boolean cacheSharableBeans;
-
+
+ private final BeanDescriptorDraftHelp draftHelp;
private final BeanDescriptorCacheHelp cacheHelp;
private final BeanDescriptorJsonHelp jsonHelp;
@@ -372,7 +379,10 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
this.compoundUniqueConstraints = deploy.getCompoundUniqueConstraints();
this.readAuditing = deploy.isReadAuditing();
+ this.draftable = deploy.isDraftable();
+ this.draftableElement = deploy.isDraftableElement();
this.historySupport = deploy.isHistorySupport();
+ this.draftTable = deploy.getDraftTable();
this.baseTable = InternString.intern(deploy.getBaseTable());
this.baseTableAsOf = deploy.getBaseTableAsOf();
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
@@ -413,6 +423,7 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = new BeanDescriptorJsonHelp(this);
+ this.draftHelp = new BeanDescriptorDraftHelp(this);
// Check if there are no cascade save associated beans ( subject to change
// in initialiseOther()). Note that if we are in an inheritance hierarchy
@@ -525,12 +536,15 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
*
* @param withHistoryTables map populated if @History is supported on this entity bean
*/
- public void initialiseId(Map withHistoryTables) {
+ public void initialiseId(Map withHistoryTables, Map draftTables) {
if (logger.isTraceEnabled()) {
logger.trace("BeanDescriptor initialise " + fullName);
}
+ if (draftable) {
+ draftTables.put(baseTable, draftTable);
+ }
if (historySupport) {
// add mapping (used to swap out baseTable for asOf queries)
withHistoryTables.put(baseTable, baseTableAsOf);
@@ -810,6 +824,10 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
return inheritInfo == null || inheritInfo.isRoot();
}
+ public T publish(T draftBean, T liveBean) {
+ return draftHelp.publish(draftBean, liveBean);
+ }
+
/**
* Set the bean caching on or off.
*/
@@ -1856,12 +1874,20 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
*/
public String getBaseTable(SpiQuery.TemporalMode mode) {
switch (mode) {
+ case DRAFT: return draftTable;
case VERSIONS: return baseTableVersionsBetween;
case AS_OF: return baseTableAsOf;
default: return baseTable;
}
}
+ /**
+ * Return the associated draft table.
+ */
+ public String getDraftTable() {
+ return draftTable;
+ }
+
/**
* Return true if read auditing is on this entity bean.
*/
@@ -1869,6 +1895,20 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
return readAuditing;
}
+ /**
+ * Return true if this entity type is draftable.
+ */
+ public boolean isDraftable() {
+ return draftable;
+ }
+
+ /**
+ * Return true if this entity type is a draftable element (child).
+ */
+ public boolean isDraftableElement() {
+ return draftableElement;
+ }
+
/**
* Return true if this entity bean has history support.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java
new file mode 100644
index 000000000..86ade6568
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorDraftHelp.java
@@ -0,0 +1,43 @@
+package com.avaje.ebeaninternal.server.deploy;
+
+import com.avaje.ebean.bean.EntityBean;
+
+/**
+ * Helper for BeanDescriptor that manages draft entity beans.
+ *
+ * @param The entity bean type
+ */
+public final class BeanDescriptorDraftHelp {
+
+ private final BeanDescriptor desc;
+
+ public BeanDescriptorDraftHelp(BeanDescriptor desc) {
+ this.desc = desc;
+ }
+
+ /**
+ *
+ */
+ public void initialise() {
+ }
+
+ public T publish(T draftBean, T liveBean) {
+
+ if (liveBean == null) {
+ liveBean = (T)desc.createEntityBean();
+ }
+
+ EntityBean draft = (EntityBean)draftBean;
+ EntityBean live = (EntityBean)liveBean;
+
+ BeanProperty idProperty = desc.getIdProperty();
+ idProperty.publish(draft, live);
+
+ BeanProperty[] props = desc.propertiesNonMany();
+ for (int i = 0; i < props.length; i++) {
+ props[i].publish(draft, live);
+ }
+
+ return liveBean;
+ }
+}
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 a36703079..72c6b1a8a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -167,6 +167,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private final Map asOfTableMap = new HashMap();
+ /**
+ * Map of base tables to 'draft' tables.
+ */
+ private final Map draftTableMap = new HashMap();
+
/**
* Create for a given database dbConfig.
*/
@@ -269,6 +274,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return idBinderFactory.createIdBinder(idProperty);
}
+ /**
+ * Return the map of base tables to draft tables.
+ */
+ public Map getDraftTableMap() {
+ return draftTableMap;
+ }
+
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
@@ -396,7 +408,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// first (as they are needed to initialise the
// associated properties in the second pass).
for (BeanDescriptor> d : descMap.values()) {
- d.initialiseId(asOfTableMap);
+ d.initialiseId(asOfTableMap, draftTableMap);
}
// PASS 2:
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
index 3ae1978d5..dbec4d4bf 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
@@ -223,6 +223,10 @@ public class BeanProperty implements ElPropertyValue {
final boolean jsonDeserialize;
+ final boolean draftOnly;
+
+ final boolean draftDirty;
+
final boolean indexed;
final String indexName;
@@ -249,6 +253,8 @@ public class BeanProperty implements ElPropertyValue {
this.dbInsertable = deploy.isDbInsertable();
this.dbUpdatable = deploy.isDbUpdateable();
this.excludedFromHistory = deploy.isExcludedFromHistory();
+ this.draftDirty = deploy.isDraftDirty();
+ this.draftOnly = deploy.isDraftOnly();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
@@ -333,6 +339,8 @@ public class BeanProperty implements ElPropertyValue {
this.formula = false;
this.excludedFromHistory = source.excludedFromHistory;
+ this.draftDirty = source.draftDirty;
+ this.draftOnly = source.draftOnly;
this.fetchEager = source.fetchEager;
this.unidirectionalShadow = source.unidirectionalShadow;
this.discriminator = source.discriminator;
@@ -493,7 +501,7 @@ public class BeanProperty implements ElPropertyValue {
if (formula) {
ctx.appendFormulaSelect(sqlFormulaSelect);
- } else if (!isTransient) {
+ } else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
if (secondaryTableJoin != null) {
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
@@ -592,6 +600,18 @@ public class BeanProperty implements ElPropertyValue {
return local;
}
+ /**
+ * Copy/set the property value from the draft bean to the live bean.
+ */
+ public void publish(EntityBean draftBean, EntityBean liveBean) {
+
+ if (!version && !draftOnly) {
+ // set property value from draft to live
+ Object value = getValueIntercept(draftBean);
+ setValueIntercept(liveBean, value);
+ }
+ }
+
/**
* Set the value of the property without interception or
* PropertyChangeSupport.
@@ -600,10 +620,7 @@ public class BeanProperty implements ElPropertyValue {
try {
setter.set(bean, value);
} catch (Exception ex) {
- String beanType = bean == null ? "null" : bean.getClass().getName();
- String msg = "set " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType
- + "] threw error";
- throw new RuntimeException(msg, ex);
+ throw new RuntimeException(setterErrorMsg(bean, value, "set "), ex);
}
}
@@ -614,13 +631,18 @@ public class BeanProperty implements ElPropertyValue {
try {
setter.setIntercept(bean, value);
} catch (Exception ex) {
- String beanType = bean == null ? "null" : bean.getClass().getName();
- String msg = "setIntercept " + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType
- + "] threw error";
- throw new RuntimeException(msg, ex);
+ throw new RuntimeException(setterErrorMsg(bean, value, "setIntercept "), ex);
}
}
+ /**
+ * Return an error message when calling a setter.
+ */
+ private String setterErrorMsg(EntityBean bean, Object value, String prefix) {
+ String beanType = bean == null ? "null" : bean.getClass().getName();
+ return prefix + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + "] threw error";
+ }
+
public Object getCacheDataValue(EntityBean bean) {
return getValue(bean);
}
@@ -900,8 +922,16 @@ public class BeanProperty implements ElPropertyValue {
/**
* Return true if this property is loadable from a resultSet.
*/
- public boolean isLoadProperty() {
- return !isTransient || formula;
+ public boolean isLoadProperty(boolean draftQuery) {
+ return !ignoreDraftOnlyProperty(draftQuery) && (!isTransient || formula);
+ }
+
+ /**
+ * Return true if this is a draftOnly property on a non-asDraft query and as such this
+ * property should not be included in a sql query.
+ */
+ protected boolean ignoreDraftOnlyProperty(boolean draftQuery) {
+ return draftOnly && !draftQuery;
}
/**
@@ -951,7 +981,7 @@ public class BeanProperty implements ElPropertyValue {
return lob;
}
- private boolean isLobType(int type) {
+ public static boolean isLobType(int type) {
switch (type) {
case Types.CLOB:
return true;
@@ -1000,6 +1030,21 @@ public class BeanProperty implements ElPropertyValue {
return excludedFromHistory;
}
+ /**
+ * Return true if this property only exists on the draft table.
+ */
+ public boolean isDraftOnly() {
+ return draftOnly;
+ }
+
+ /**
+ * Return true if this property is a boolean flag only on the draft table
+ * indicating that when the draft is different from the published row.
+ */
+ public boolean isDraftDirty() {
+ return draftDirty;
+ }
+
/**
* Return true if this property should be included in an Insert.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java
index f4085c646..0581f3288 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java
@@ -79,4 +79,9 @@ public interface DbReadContext {
* Return the query mode.
*/
SpiQuery.Mode getQueryMode();
+
+ /**
+ * Return true if the underlying query is a 'asDraft' query.
+ */
+ boolean isDraftQuery();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java
index 2542c7c7b..1f6714c06 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbSqlContext.java
@@ -120,4 +120,9 @@ public interface DbSqlContext {
*/
void appendHistorySysPeriod();
+ /**
+ * Return true if the query is a 'asDraft' query.
+ */
+ boolean isDraftQuery();
+
}
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 2489e75ab..c8c8ec4e7 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
@@ -118,10 +118,16 @@ public class DeployBeanDescriptor {
private String baseTableVersionsBetween;
+ private String draftTable;
+
private boolean historySupport;
private boolean readAuditing;
+ private boolean draftable;
+
+ private boolean draftableElement;
+
private TableName baseTableFull;
private String[] properties;
@@ -201,6 +207,22 @@ public class DeployBeanDescriptor {
return readAuditing;
}
+ public void setDraftable() {
+ draftable = true;
+ }
+
+ public boolean isDraftable() {
+ return draftable;
+ }
+
+ public void setDraftableElement() {
+ draftableElement = true;
+ }
+
+ public boolean isDraftableElement() {
+ return draftableElement;
+ }
+
public boolean isScalaObject() {
Class>[] interfaces = beanType.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
@@ -485,6 +507,10 @@ public class DeployBeanDescriptor {
postLoaders.add(postLoad);
}
+ public String getDraftTable() {
+ return draftTable;
+ }
+
/**
* Return the base table. Only properties mapped to the base table are by
* default persisted.
@@ -522,6 +548,7 @@ public class DeployBeanDescriptor {
this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName();
this.baseTableAsOf = baseTable + asOfSuffix;
this.baseTableVersionsBetween = baseTable + versionsBetweenSuffix;
+ this.draftTable = (draftable) ? baseTable+"_draft" : baseTable;
}
public void sortProperties() {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
index 2224b0565..c87c35246 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
@@ -8,6 +8,7 @@ import com.avaje.ebean.config.ScalarTypeConverter;
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.generatedproperty.GeneratedProperty;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
@@ -182,6 +183,9 @@ public class DeployBeanProperty {
private boolean excludedFromHistory;
+ private boolean draftOnly;
+ private boolean draftDirty;
+
public DeployBeanProperty(DeployBeanDescriptor> desc, Class> propertyType, ScalarType> scalarType, ScalarTypeConverter, ?> typeConverter) {
this.desc = desc;
this.propertyType = propertyType;
@@ -598,7 +602,7 @@ public class DeployBeanProperty {
*/
public void setDbType(int dbType) {
this.dbType = dbType;
- this.lob = isLobType(dbType);
+ this.lob = BeanProperty.isLobType(dbType);
}
/**
@@ -609,22 +613,6 @@ public class DeployBeanProperty {
return lob;
}
- private boolean isLobType(int type) {
- switch (type) {
- case Types.CLOB:
- return true;
- case Types.BLOB:
- return true;
- case Types.LONGVARBINARY:
- return true;
- case Types.LONGVARCHAR:
- return true;
-
- default:
- return false;
- }
- }
-
public boolean isDbNumberType() {
return isNumericType(dbType);
}
@@ -849,4 +837,21 @@ public class DeployBeanProperty {
public void setExcludedFromHistory() {
this.excludedFromHistory = true;
}
+
+ public void setDraftOnly() {
+ this.draftOnly = true;
+ }
+
+ public boolean isDraftOnly() {
+ return draftOnly;
+ }
+
+ public void setDraftDirty() {
+ this.draftOnly = true;
+ this.draftDirty = true;
+ }
+
+ public boolean isDraftDirty() {
+ return draftDirty;
+ }
}
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 c869a4339..5a6e124b5 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
@@ -9,6 +9,8 @@ import javax.persistence.UniqueConstraint;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.annotation.CacheTuning;
+import com.avaje.ebean.annotation.Draftable;
+import com.avaje.ebean.annotation.DraftableElement;
import com.avaje.ebean.annotation.EntityConcurrencyMode;
import com.avaje.ebean.annotation.History;
import com.avaje.ebean.annotation.Index;
@@ -98,6 +100,16 @@ public class AnnotationClass extends AnnotationParser {
}
}
+ Draftable draftable = cls.getAnnotation(Draftable.class);
+ if (draftable != null) {
+ descriptor.setDraftable();
+ }
+
+ DraftableElement draftableElement = cls.getAnnotation(DraftableElement.class);
+ if (draftableElement != null) {
+ descriptor.setDraftableElement();
+ }
+
ReadAudit readAudit = cls.getAnnotation(ReadAudit.class);
if (readAudit != null) {
descriptor.setReadAuditing();
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 5cb6ad912..85683bdb6 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
@@ -149,6 +149,14 @@ public class AnnotationFields extends AnnotationParser {
util.setLobType(prop);
}
+ if (get(prop, DraftOnly.class) != null) {
+ prop.setDraftOnly();
+ }
+
+ if (get(prop, DraftDirty.class) != null) {
+ prop.setDraftDirty();
+ }
+
DbJson dbJson = get(prop, DbJson.class);
if (dbJson != null) {
util.setDbJsonType(prop, dbJson);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
index db3bdb663..92ed1ea97 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
@@ -83,17 +83,7 @@ public final class DefaultPersister implements Persister {
*/
public int executeCallable(CallableSql callSql, Transaction t) {
- PersistRequestCallableSql request = new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute);
- try {
- request.initTransIfRequired();
- int rc = request.executeOrQueue();
- request.commitTransIfRequired();
- return rc;
-
- } catch (RuntimeException e) {
- request.rollbackTransIfRequired();
- throw e;
- }
+ return executeOrQueue(new PersistRequestCallableSql(server, callSql, (SpiTransaction) t, persistExecute));
}
/**
@@ -110,7 +100,10 @@ public final class DefaultPersister implements Persister {
throw new PersistenceException(msg);
}
- PersistRequestOrmUpdate request = new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute);
+ return executeOrQueue(new PersistRequestOrmUpdate(server, mgr, ormUpdate, (SpiTransaction) t, persistExecute));
+ }
+
+ private int executeOrQueue(PersistRequest request) {
try {
request.initTransIfRequired();
int rc = request.executeOrQueue();
@@ -128,17 +121,52 @@ public final class DefaultPersister implements Persister {
*/
public int executeSqlUpdate(SqlUpdate updSql, Transaction t) {
- PersistRequestUpdateSql request = new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute);
- try {
- request.initTransIfRequired();
- int rc = request.executeOrQueue();
- request.commitTransIfRequired();
- return rc;
+ return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute));
+ }
- } catch (RuntimeException e) {
- request.rollbackTransIfRequired();
- throw e;
+ @Override
+ public void publish(Query query, Transaction transaction) {
+
+ query.asDraft();
+
+ Class beanType = query.getBeanType();
+ List draftBeans = server.findList(query, transaction);
+
+ BeanDescriptor desc = server.getBeanDescriptor(beanType);
+
+ // get the list of Id's
+ List