Compare commits

...
Author SHA1 Message Date
Robin Bygrave 761f43d6a1 [maven-release-plugin] prepare release avaje-ebeanorm-6.12.1 2015-11-30 23:24:46 +13:00
Robin Bygrave 737f9a893e Bump pom - 6.12.1-SNAPSHOT 2015-11-30 23:23:58 +13:00
Robin Bygrave 579812d717 #455 - Should not generate "FOR UPDATE" for delete query 2015-11-30 16:50:26 +13:00
Robin Bygrave fae39fe650 #291 - ENH: Add ability to validate a query or property expression - Was Please check column exist on entity 2015-11-30 15:10:22 +13:00
Robin Bygrave debd8ad617 #465 - The encrypt key is logged out in sql statement - when included in where predicate 2015-11-30 12:25:25 +13:00
Robin Bygrave 6450ba219c #469 - Embedded bean made dirty after a prior insert does not trigger dirty state on 'outer' bean 2015-11-27 18:19:59 +13:00
Robin Bygrave a578eda2b1 #467 - Delete bean with unloaded version property throws OptimisticLockException - API Change, return boolean 2015-11-26 16:09:25 +13:00
Robin Bygrave a2ee0bfdbe #467 - Delete bean with unloaded version property throws OptimisticLockException 2015-11-26 15:12:59 +13:00
Robin Bygrave 872f0e21cb #466 - Delete of @Draftable bean ... means also delete matching live bean (aka delete has automatic publish behavior) 2015-11-26 00:49:18 +13:00
Robin Bygrave 8620dbf337 #464 - ENH: Add draftRestore(Class<?> beanType, Object id) ... to refresh a draft from the live (@Draftable feature) 2015-11-25 23:36:17 +13:00
Robin Bygrave 4f51a3b130 #463 - ENH: Add @DraftReset support - property that is set to null on the draft been when it is published (@Draftable feature) 2015-11-25 11:06:14 +13:00
Robin Bygrave 5f194f066a [maven-release-plugin] prepare for next development iteration 2015-11-24 01:04:26 +13:00
Robin Bygrave 31f6e56333 [maven-release-plugin] prepare release avaje-ebeanorm-6.11.1 2015-11-24 01:04:04 +13:00
Robin Bygrave 0efbe061ab #461 - ENH: Add @Draftable support - optimise publish query 2015-11-24 01:01:37 +13:00
Robin Bygrave 555e5ba6fd #461 - ENH: Add @Draftable support - API sans transaction 2015-11-24 01:01:14 +13:00
Robin Bygrave 5d3da60828 #461 - ENH: Add @Draftable support - delete 2015-11-24 00:46:45 +13:00
Robin Bygrave 2ac0d136e6 Bump pom to 6.11.1-SNAPSHOT 2015-11-24 00:02:35 +13:00
Robin Bygrave 97462cb21a #461 - ENH: Add @Draftable support - delete 2015-11-24 00:01:28 +13:00
Robin Bygrave afaacd4eeb #461 - ENH: Add @Draftable support - javadoc 2015-11-23 23:26:09 +13:00
Robin Bygrave 5e8afbe50d #461 - ENH: Add @Draftable support - @DraftDirty 2015-11-23 23:11:15 +13:00
Robin Bygrave db371cbc6a #461 - ENH: Add @Draftable support - publish 2015-11-23 21:28:58 +13:00
Robin Bygrave fd9a5167b1 #461 - ENH: Add @Draftable support - load context 2015-11-23 10:07:09 +13:00
Robin Bygrave 7b76e13bce #461 - ENH: Add @Draftable support - phase 1 2015-11-19 20:44:25 +13:00
Robin Bygrave 42e7103aa6 #458 - Support use of test-ebean.properties without an existing ebean.properties 2015-11-12 10:17:30 +13:00
Robin Bygrave 29ca678f01 No effective change - remove unused code 2015-11-12 09:59:36 +13:00
Robin Bygrave cca6143420 No effective change - code format 2015-11-12 09:48:18 +13:00
Robin Bygrave fe7b110577 #457 - When ebean.properties does not exist do not log error message 2015-11-10 09:47:33 +13:00
Robin Bygrave 16df55eafa [maven-release-plugin] prepare for next development iteration 2015-11-09 14:44:45 +13:00
121 changed files with 2591 additions and 679 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.10.4</version>
<version>6.12.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
+10 -2
View File
@@ -697,12 +697,20 @@ public final class Ebean {
/**
* Delete the bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*/
public static void delete(Object bean) throws OptimisticLockException {
serverMgr.getDefaultServer().delete(bean);
public static boolean delete(Object bean) throws OptimisticLockException {
return serverMgr.getDefaultServer().delete(bean);
}
/**
+128 -2
View File
@@ -1246,16 +1246,32 @@ public interface EbeanServer {
/**
* Delete the bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*/
void delete(Object bean) throws OptimisticLockException;
boolean delete(Object bean) throws OptimisticLockException;
/**
* Delete the bean with an explicit transaction.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. However, if JDBC batch mode is used then this always returns true.
* </p>
*/
void delete(Object bean, Transaction transaction) throws OptimisticLockException;
boolean delete(Object bean, Transaction transaction) throws OptimisticLockException;
/**
* Delete the bean given its type and id.
@@ -1819,4 +1835,114 @@ public interface EbeanServer {
*/
JsonContext json();
/**
* Publish a single bean given its type and id returning the resulting live bean.
* <p>
* The values are published from the draft to the live bean.
* </p>
*
* @param <T> the type of the entity 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 (can be null)
*/
<T> T publish(Class<T> beanType, Object id, Transaction transaction);
/**
* Publish a single bean given its type and id returning the resulting live bean.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft to the live bean.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
*/
<T> T publish(Class<T> beanType, Object id);
/**
* Publish the beans that match the query returning the resulting published beans.
* <p>
* The values are published from the draft beans to the live beans.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
* @param transaction the transaction the publish process should use (can be null)
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Publish the beans that match the query returning the resulting published beans.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft beans to the live beans.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
*/
<T> List<T> publish(Query<T> query);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
* @param transaction the transaction the restore process should use (can be null)
*/
<T> T draftRestore(Class<T> beanType, Object id, Transaction transaction);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
*/
<T> T draftRestore(Class<T> beanType, Object id);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
* @param transaction the transaction the restore process should use (can be null)
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
*/
<T> List<T> draftRestore(Query<T> query);
/**
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties/path for the given bean type.
* </p>
*/
<T> Set<String> validateQuery(Query<T> query);
}
+17 -5
View File
@@ -251,12 +251,24 @@ public abstract class Model {
}
/**
* Delete this entity.
* Delete this bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*
* @see EbeanServer#delete(Object)
*/
public void delete() {
db().delete(this);
public boolean delete() {
return db().delete(this);
}
/**
@@ -276,8 +288,8 @@ public abstract class Model {
/**
* Perform a delete using this entity against the specified server.
*/
public void delete(String server) {
db(server).delete(this);
public boolean delete(String server) {
return db(server).delete(this);
}
/**
+14
View File
@@ -297,6 +297,11 @@ public interface Query<T> extends Serializable {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
Query<T> asDraft();
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -1313,4 +1318,13 @@ public interface Query<T> extends Serializable {
* </p>
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Returns the set of properties or paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties or paths for the given bean type.
* </p>
*/
Set<String> validate();
}
@@ -0,0 +1,20 @@
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.
* <p>
* This property will automatically have it's value set to true when a draft is saved and
* automatically have it's value set to false when the bean is published.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftDirty {
}
@@ -0,0 +1,19 @@
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.
* <p>
* Typically this would be used on a property that is used as part of application 'workflow' such as
* a publish workflow status or when publish timestamp.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftOnly {
}
@@ -0,0 +1,19 @@
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 is set to null on the 'draft bean' on publish.
* <p>
* This is expected to be put on properties that get 'reset' or 'cleared' after a publish.
* These properties might represent a publish comment or publish timestamp.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftReset {
}
@@ -0,0 +1,29 @@
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.
* <p>
* This means that a second set of tables is created to hold draft versions of
* the rows and that these can then be published which effectively copies/transfers
* the values from the 'draft' table to the 'live' table.
* </p>
* <p>
* Ebean Query supports 'find as draft' which builds the resulting object graph using
* the draft tables. This object graph is typically edited, approved in some application
* specific manor and then published.
* </p>
* <p>
* EbeanServer has a publish method which transfers/copies the draft object graph to
* the 'live' tables.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Draftable {
}
@@ -0,0 +1,20 @@
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 'top level'
* (or root level) bean but instead child related to another @Draftable entity bean.
* <p>
* Relationships to @DraftableElements (@OneToMany, @ManyToMany etc) are automatically
* deemed to have Cascade.ALL for save and delete (as well as orphan removal mode).
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DraftableElement {
}
@@ -31,6 +31,16 @@ public interface BeanCollection<E> extends Serializable {
ALL
}
/**
* Add a bean to the list/set with modifyListen notification.
*/
void addBean(E bean);
/**
* Remove a bean to the list/set with modifyListen notification.
*/
void removeBean(E bean);
/**
* Reset the collection back to an empty state ready for reloading.
* <p>
@@ -12,5 +12,5 @@ public interface BeanCollectionAdd {
/**
* Add a loaded bean to the collection.
*/
void addBean(EntityBean bean);
void addEntityBean(EntityBean bean);
}
@@ -60,7 +60,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
@SuppressWarnings("unchecked")
public void addBean(EntityBean bean) {
public void addEntityBean(EntityBean bean) {
list.add((E) bean);
}
@@ -206,6 +206,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
list.add(index, element);
}
@Override
public void addBean(E bean) {
add(bean);
}
public boolean add(E o) {
checkReadOnly();
init();
@@ -319,6 +324,13 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
return list.listIterator(index);
}
@Override
public void removeBean(E bean) {
if (list.remove(bean)) {
getModifyHolder().modifyRemoval(bean);
}
}
public E remove(int index) {
checkReadOnly();
init();
@@ -272,6 +272,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
map.putAll(puts);
}
@Override
public void addBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
}
@Override
public void removeBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
}
public E remove(Object key) {
checkReadOnly();
init();
@@ -53,7 +53,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
}
@SuppressWarnings("unchecked")
public void addBean(EntityBean bean) {
public void addEntityBean(EntityBean bean) {
set.add((E) bean);
}
@@ -174,6 +174,18 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
return set.hashCode();
}
@Override
public void addBean(E bean) {
add(bean);
}
@Override
public void removeBean(E bean) {
if (set.remove(bean)) {
getModifyHolder().modifyRemoval(bean);
}
}
// -----------------------------------------------------//
// proxy method for map
// -----------------------------------------------------//
@@ -18,7 +18,7 @@ final class PropertyMapLoader {
* Load the <code>test-ebean.properties</code>.
*/
public static PropertyMap loadTestProperties() {
return load(null, "test-ebean.properties", false);
return load(null, "test-ebean.properties");
}
/**
@@ -36,10 +36,10 @@ final class PropertyMapLoader {
}
}
PropertyMap map = load(null, fileName, true);
PropertyMap map = load(null, fileName);
if (loadTestProperties) {
// load test properties if present in classpath
load(map, "test-ebean.properties", false);
map = load(map, "test-ebean.properties");
}
return map;
}
@@ -52,13 +52,10 @@ final class PropertyMapLoader {
* @param fileName
* the name of the properties file to load.
*/
public static PropertyMap load(PropertyMap p, String fileName, boolean errorOnNull) {
public static PropertyMap load(PropertyMap p, String fileName) {
InputStream is = findInputStream(fileName);
if (is == null) {
if (errorOnNull) {
logger.error(fileName + " not found");
}
return p;
} else {
return load(p, is);
@@ -66,7 +63,7 @@ final class PropertyMapLoader {
}
/**
* Load the inputstream returning the property map.
* Load the InputStream returning the property map.
*
* @param p
* an existing property map to load into.
@@ -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;
}
@@ -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();
@@ -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<MColumn> 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);
}
}
@@ -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<MTable> tables = this.tables.values();
for (MTable table : tables) {
if (table.isDraft()) {
table.adjustReferences(this);
}
}
}
/**
* Return the map of all the tables.
*/
@@ -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.
* </p>
*/
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);
}
/**
@@ -3,11 +3,14 @@ package com.avaje.ebean.dbmigration.model.build;
import com.avaje.ebean.config.DbConstraintNaming;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;
import com.avaje.ebean.dbmigration.model.ModelContainer;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.type.ScalarType;
import java.util.Collection;
/**
* The context used during DDL generation.
*/
@@ -31,6 +34,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);
}
@@ -120,4 +131,30 @@ public class ModelBuildContext {
return dbTypeMap.get(dbType);
}
/**
* Create the draft table for a given table.
*/
public void createDraft(MTable table) {
MTable draftTable = table.createDraftTable();
draftTable.setPkName(primaryKeyName(draftTable.getName()));
int fkCount = 0;
int ixCount = 0;
Collection<MColumn> 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(foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount));
String[] indexCols = {col.getName()};
col.setForeignKeyIndex(foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
}
}
addTable(draftTable);
}
}
@@ -42,6 +42,11 @@ public class ModelBuildIntersectionTable {
}
buildFkConstraints();
if (manyProp.getTargetDescriptor().isDraftable()) {
ctx.createDraft(intersectionTable);
}
}
private void buildFkConstraints() {
@@ -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,19 @@ 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)
ctx.createDraft(table);
}
}
@@ -213,6 +230,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);
@@ -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);
}
@@ -17,8 +17,13 @@ public interface SpiBeanType<T> {
Class<T> getBeanType();
/**
* Return the base table this bean type maps to.
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
String getBaseTable();
/**
@@ -8,140 +8,139 @@ import org.slf4j.LoggerFactory;
* <p>
* Helper for ClassUtil.
* </p>
*
*
* @author rbygrave
*
*/
class ClassLoadContext {
private static final Logger logger = LoggerFactory.getLogger(ClassLoadContext.class);
private final ClassLoader callerLoader;
private static final Logger logger = LoggerFactory.getLogger(ClassLoadContext.class);
private final ClassLoader contextLoader;
private final ClassLoader callerLoader;
private final boolean preferContext;
private boolean ambiguous;
public static ClassLoadContext of(Class<?> caller, boolean preferContext) {
return new ClassLoadContext(caller, preferContext);
private final ClassLoader contextLoader;
private final boolean preferContext;
private boolean ambiguous;
public static ClassLoadContext of(Class<?> caller, boolean preferContext) {
return new ClassLoadContext(caller, preferContext);
}
/**
* This constructor is package-private to restrict instantiation to
*/
ClassLoadContext(final Class<?> caller, boolean preferContext) {
if (caller == null) {
throw new IllegalArgumentException("caller is null");
}
this.callerLoader = caller.getClassLoader();
this.contextLoader = Thread.currentThread().getContextClassLoader();
this.preferContext = preferContext;
}
public Class<?> forName(String name) throws ClassNotFoundException {
ClassLoader defaultLoader = getDefault(preferContext);
try {
return Class.forName(name, true, defaultLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == defaultLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
/**
* Return the expected class loader to use.
* <p>
* Works on the assumption that the child of the caller or context class
* loader is preferred.
* </p>
*/
public ClassLoader getDefault(boolean preferContext) {
if (contextLoader == null) {
if (logger.isDebugEnabled()) {
logger.debug("No Context ClassLoader, using " + callerLoader.getClass().getName());
}
return callerLoader;
}
if (contextLoader == callerLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Context and Caller ClassLoader's same instance of " + contextLoader.getClass().getName());
}
return callerLoader;
}
/**
* This constructor is package-private to restrict instantiation to
*/
ClassLoadContext(final Class<?> caller, boolean preferContext) {
if (caller == null){
throw new IllegalArgumentException("caller is null");
}
this.callerLoader = caller.getClassLoader();
this.contextLoader = Thread.currentThread().getContextClassLoader();
this.preferContext = preferContext;
if (isChild(contextLoader, callerLoader)) {
if (logger.isDebugEnabled()) {
logger.debug("Caller ClassLoader " + callerLoader.getClass().getName()
+ " child of ContextLoader " + contextLoader.getClass().getName());
}
return callerLoader;
} else if (isChild(callerLoader, contextLoader)) {
if (logger.isDebugEnabled()) {
logger.debug("Context ClassLoader " + contextLoader.getClass().getName()
+ " child of Caller ClassLoader " + callerLoader.getClass().getName());
}
return contextLoader;
} else {
// ambiguous case, perhaps both null
logger.debug("Ambiguous ClassLoader choice preferContext:" + preferContext
+ " Context:" + contextLoader.getClass().getName() + " Caller:" + callerLoader.getClass().getName());
ambiguous = true;
return preferContext ? contextLoader : callerLoader;
}
}
/**
* Return true if the 'default' class loader is ambiguous.
*/
public boolean isAmbiguous() {
return ambiguous;
}
/**
* Return the ClassLoader of the caller.
*/
public ClassLoader getCallerLoader() {
return callerLoader;
}
/**
* Return the Thread Context ClassLoader.
*/
public ClassLoader getContextLoader() {
return contextLoader;
}
/**
* Return the ClassLoader for this class.
*/
public ClassLoader getThisLoader() {
return this.getClass().getClassLoader();
}
/**
* Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
* 'loader1'=='loader2'].
*/
private boolean isChild(final ClassLoader loader1, ClassLoader loader2) {
for (; loader2 != null; loader2 = loader2.getParent()) {
if (loader2 == loader1) {
return true;
}
}
public Class<?> forName(String name) throws ClassNotFoundException {
ClassLoader defaultLoader = getDefault(preferContext);
try {
return Class.forName(name, true, defaultLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == defaultLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
/**
* Return the expected class loader to use.
* <p>
* Works on the assumption that the child of the caller or context class
* loader is preferred.
* </p>
*/
public ClassLoader getDefault(boolean preferContext) {
if (contextLoader == null){
if (logger.isDebugEnabled()){
logger.debug("No Context ClassLoader, using "+callerLoader.getClass().getName());
}
return callerLoader;
}
if (contextLoader == callerLoader){
if (logger.isDebugEnabled()){
logger.debug("Context and Caller ClassLoader's same instance of "+contextLoader.getClass().getName());
}
return callerLoader;
}
if (isChild(contextLoader, callerLoader)) {
if (logger.isDebugEnabled()){
logger.debug("Caller ClassLoader "+callerLoader.getClass().getName()
+" child of ContextLoader "+contextLoader.getClass().getName());
}
return callerLoader;
} else if (isChild(callerLoader, contextLoader)) {
if (logger.isDebugEnabled()){
logger.debug("Context ClassLoader "+contextLoader.getClass().getName()
+" child of Caller ClassLoader "+callerLoader.getClass().getName());
}
return contextLoader;
} else {
// ambiguous case, perhaps both null
logger.debug("Ambiguous ClassLoader choice preferContext:"+preferContext
+" Context:"+contextLoader.getClass().getName()+" Caller:"+callerLoader.getClass().getName());
ambiguous = true;
return preferContext ? contextLoader : callerLoader;
}
}
/**
* Return true if the 'default' class loader is ambiguous.
*/
public boolean isAmbiguous() {
return ambiguous;
}
/**
* Return the ClassLoader of the caller.
*/
public ClassLoader getCallerLoader() {
return callerLoader;
}
/**
* Return the Thread Context ClassLoader.
*/
public ClassLoader getContextLoader() {
return contextLoader;
}
/**
* Return the ClassLoader for this class.
*/
public ClassLoader getThisLoader() {
return this.getClass().getClassLoader();
}
/**
* Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
* 'loader1'=='loader2'].
*/
private boolean isChild(final ClassLoader loader1, ClassLoader loader2) {
for (; loader2 != null; loader2 = loader2.getParent()) {
if (loader2 == loader1) {
return true;
}
}
return false;
}
return false;
}
}
@@ -1,23 +1,11 @@
package com.avaje.ebeaninternal.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper to find classes taking into account the context class loader.
*/
public class ClassUtil {
private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class);
/**
* Load a class taking into account a context class loader (if present).
*/
public static Class<?> forName(String name) throws ClassNotFoundException {
return forName(name, null);
}
/**
* Load a class taking into account a context class loader (if present).
*/
@@ -27,24 +15,9 @@ public class ClassUtil {
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, true);
return ctx.forName(name);
}
public static ClassLoader getClassLoader(Class<?> caller, boolean preferContext) {
if (caller == null) {
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, preferContext);
ClassLoader classLoader = ctx.getDefault(preferContext);
if (ctx.isAmbiguous()) {
logger.info("Ambigous ClassLoader (Context vs Caller) chosen " + classLoader);
}
return classLoader;
}
/**
* Return true if javax validation annotations like Size and NotNull are present.
*/
@@ -66,4 +66,9 @@ public interface SpiExpression extends Expression {
* the associated request.
*/
void addBindValues(SpiExpressionRequest request);
/**
* Validate all the properties/paths associated with this expression.
*/
void validate(SpiExpressionValidation validation);
}
@@ -67,4 +67,8 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
/**
* Validate all the properties/paths used in this expression list.
*/
void validate(SpiExpressionValidation validation);
}
@@ -14,7 +14,7 @@ public interface SpiExpressionRequest {
/**
* Return the DB specific JSON expression handler.
*/
JsonExpressionHandler getJsonHander();
JsonExpressionHandler getJsonHandler();
/**
* Parse the logical property name to the deployment name.
@@ -35,7 +35,12 @@ public interface SpiExpressionRequest {
* Append to the expression sql.
*/
SpiExpressionRequest append(String sql);
/**
* Add an encryption key to bind to this request.
*/
void addBindEncryptKey(Object encryptKey);
/**
* Add a bind value to this request.
*/
@@ -0,0 +1,37 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.plugin.SpiBeanType;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Property expression validation request for a given root bean type.
*/
public class SpiExpressionValidation {
private final SpiBeanType<?> desc;
private final LinkedHashSet<String> unknown = new LinkedHashSet<String>();
public SpiExpressionValidation(SpiBeanType<?> desc) {
this.desc = desc;
}
/**
* Validate that the property expression (path) is valid.
*/
public void validate(String propertyName) {
if (!desc.isValidExpression(propertyName)) {
unknown.add(propertyName);
}
}
/**
* Return the set of properties considered as having unknown paths.
*/
public Set<String> getUnknownProperties() {
return unknown;
}
}
@@ -11,6 +11,7 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -22,6 +23,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
@@ -94,6 +96,11 @@ public interface SpiQuery<T> extends Query<T> {
}
enum TemporalMode {
/**
* Query runs against draft tables.
*/
DRAFT,
/**
* Query runs against current data (normal).
*/
@@ -171,6 +178,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
boolean isAsOfQuery();
/**
* Return true if this is a 'As Draft' query.
*/
boolean isAsDraft();
/**
* Return the asOf Timestamp which the query should run as.
*/
@@ -684,4 +696,10 @@ public interface SpiQuery<T> extends Query<T> {
* Return root table alias set by {@link #alias(String)} command.
*/
String getAlias();
/**
* Validate the query returning the set of properties with unknown paths.
*/
Set<String> validate(SpiBeanType<T> desc);
}
@@ -908,8 +908,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
Class<T> beanType = (Class<T>) list.get(0).getClass();
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(beanType);
if (beanDescriptor == null) {
String m = "BeanDescriptor not found, is [" + beanType + "] an entity bean?";
throw new PersistenceException(m);
throw new PersistenceException("BeanDescriptor not found, is [" + beanType + "] an entity bean?");
}
beanDescriptor.sort(list, sortByClause);
}
@@ -933,6 +932,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, deployQuery);
}
@Override
public <T> Set<String> validateQuery(Query<T> query) {
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(query.getBeanType());
if (beanDescriptor == null) {
throw new PersistenceException("BeanDescriptor not found, is [" + query.getBeanType() + "] an entity bean?");
}
return ((SpiQuery<T>)query).validate(beanDescriptor);
}
public <T> Filter<T> filter(Class<T> beanType) {
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
@@ -1625,6 +1634,75 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public <T> List<T> publish(Query<T> query, Transaction transaction) {
TransWrapper wrap = initTransIfRequired(transaction);
try {
SpiTransaction trans = wrap.transaction;
List<T> liveBeans = persister.publish(query, trans);
wrap.commitIfCreated();
return liveBeans;
} catch (RuntimeException e) {
wrap.rollbackIfCreated();
throw e;
}
}
@Override
public <T> T publish(Class<T> beanType, Object id) {
return publish(beanType, id, null);
}
@Override
public <T> List<T> publish(Query<T> query) {
return publish(query, null);
}
@Override
public <T> T publish(Class<T> beanType, Object id, Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> liveBeans = publish(query, transaction);
return (liveBeans.size() == 1) ? liveBeans.get(0) : null;
}
@Override
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
TransWrapper wrap = initTransIfRequired(transaction);
try {
SpiTransaction trans = wrap.transaction;
List<T> beans = persister.draftRestore(query, trans);
wrap.commitIfCreated();
return beans;
} catch (RuntimeException e) {
wrap.rollbackIfCreated();
throw e;
}
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id, Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> beans = draftRestore(query, transaction);
return (beans.size() == 1) ? beans.get(0) : null;
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id) {
return draftRestore(beanType, id, null);
}
@Override
public <T> List<T> draftRestore(Query<T> query) {
return draftRestore(query, null);
}
private EntityBean checkEntityBean(Object bean) {
if (bean == null) {
throw new IllegalArgumentException(Message.msg("bean.isnull"));
@@ -1798,16 +1876,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Delete the bean.
*/
public void delete(Object bean) {
delete(bean, null);
public boolean delete(Object bean) {
return delete(bean, null);
}
/**
* Delete the bean with the explicit transaction.
*/
public void delete(Object bean, Transaction t) {
public boolean delete(Object bean, Transaction t) {
persister.delete(checkEntityBean(bean), t);
return persister.delete(checkEntityBean(bean), t);
}
/**
@@ -127,13 +127,14 @@ public class InternalConfiguration {
this.beanDescriptorManager = new BeanDescriptorManager(this);
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());
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
@@ -61,6 +61,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private final boolean dirty;
private final boolean publish;
private ConcurrencyMode concurrencyMode;
/**
@@ -120,7 +122,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private boolean requestUpdateAllLoadedProps;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
super(server, t, persistExecute);
this.entityBean = (EntityBean) bean;
@@ -132,7 +134,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.parentBean = parentBean;
this.controller = beanDescriptor.getPersistController();
this.type = type;
if (saveRecurse) {
this.persistCascade = t.isPersistCascade();
}
@@ -147,6 +149,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.checkMutableProperties(intercept);
}
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
this.publish = publish;
if (!publish && beanDescriptor.isDraftable()) {
beanDescriptor.setDraftDirty(entityBean, true);
}
this.dirty = intercept.isDirty();
}
@@ -229,7 +235,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
public boolean isNotify() {
this.notifyCache = beanDescriptor.isCacheNotify();
this.notifyCache = beanDescriptor.isCacheNotify(publish);
return notifyCache || isNotifyPersistListener();
}
@@ -243,17 +249,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void notifyCache() {
if (notifyCache) {
switch (type) {
case INSERT:
beanDescriptor.cacheHandleInsert(this);
break;
case UPDATE:
beanDescriptor.cacheHandleUpdate(idValue, this);
break;
case DELETE:
// Bean deleted from cache early via postDelete()
break;
default:
throw new IllegalStateException("Invalid type " + type);
case INSERT:
beanDescriptor.cacheHandleInsert(this);
break;
case UPDATE:
beanDescriptor.cacheHandleUpdate(idValue, this);
break;
case DELETE:
// Bean deleted from cache early via postDelete()
break;
default:
throw new IllegalStateException("Invalid type " + type);
}
}
}
@@ -415,6 +421,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return beanDescriptor.getId(entityBean);
}
/**
* Create and return a new reference bean matching this beans Id value.
*/
public T createReference() {
return beanDescriptor.createReference(Boolean.FALSE, getBeanId());
}
/**
* Return true if the bean type is a Draftable.
*/
public boolean isDraftable() {
return beanDescriptor.isDraftable();
}
/**
* Return the parent bean for cascading save with unidirectional relationship.
*/
@@ -447,24 +467,23 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
@Override
public int executeNow() {
switch (type) {
case INSERT:
persistExecute.executeInsertBean(this);
return -1;
case INSERT:
persistExecute.executeInsertBean(this);
return -1;
case UPDATE:
if (beanPersistListener != null) {
// store the updated properties for sending later
updatedProperties = getUpdatedProperties();
}
persistExecute.executeUpdateBean(this);
return -1;
case UPDATE:
if (beanPersistListener != null) {
// store the updated properties for sending later
updatedProperties = getUpdatedProperties();
}
persistExecute.executeUpdateBean(this);
return -1;
case DELETE:
persistExecute.executeDeleteBean(this);
return -1;
case DELETE:
return persistExecute.executeDeleteBean(this);
default:
throw new RuntimeException("Invalid type " + type);
default:
throw new RuntimeException("Invalid type " + type);
}
}
@@ -508,13 +527,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Check for optimistic concurrency exception.
*/
public final void checkRowCount(int rowCount) {
if (rowCount != 1) {
if (ConcurrencyMode.VERSION == concurrencyMode && rowCount != 1) {
String m = Message.msg("persist.conc2", "" + rowCount);
throw new OptimisticLockException(m, null, bean);
}
switch (type) {
case DELETE: postDelete(); break;
case UPDATE: postUpdate(); break;
case DELETE:
postDelete();
break;
case UPDATE:
postUpdate();
break;
default: // do nothing
}
}
@@ -571,35 +594,36 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void controllerPost() {
switch (type) {
case INSERT:
controller.postInsert(this);
break;
case UPDATE:
controller.postUpdate(this);
break;
case DELETE:
controller.postDelete(this);
break;
default:
break;
case INSERT:
controller.postInsert(this);
break;
case UPDATE:
controller.postUpdate(this);
break;
case DELETE:
controller.postDelete(this);
break;
default:
break;
}
}
private void logSummary() {
String draft = (beanDescriptor.isDraftable() && !publish) ? " draft[true]" : "";
String name = beanDescriptor.getName();
switch (type) {
case INSERT:
transaction.logSummary("Inserted [" + name + "] [" + idValue + "]");
break;
case UPDATE:
transaction.logSummary("Updated [" + name + "] [" + idValue + "]");
break;
case DELETE:
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]");
break;
default:
break;
case INSERT:
transaction.logSummary("Inserted [" + name + "] [" + idValue + "]" + draft);
break;
case UPDATE:
transaction.logSummary("Updated [" + name + "] [" + idValue + "]" + draft);
break;
case DELETE:
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]" + draft);
break;
default:
break;
}
}
@@ -667,6 +691,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
for (int i = 0; i < len; i++) {
intercept.setLoadedProperty(i);
}
beanDescriptor.setEmbeddedOwner(entityBean);
}
public boolean isReference() {
@@ -720,7 +745,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Determine if all loaded properties should be used for an update.
* <p>
* Takes into account transaction setting and JDBC batch.
* Takes into account transaction setting and JDBC batch.
* </p>
*/
public boolean determineUpdateAllLoadedProperties() {
@@ -736,4 +761,44 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return requestUpdateAllLoadedProps;
}
/**
* 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();
}
}
@@ -1,14 +1,15 @@
package com.avaje.ebeaninternal.server.core;
import java.util.Collection;
import java.util.List;
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 +18,84 @@ 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.
* <p>
* This will also cascade delete one level of children.
* </p>
*/
int delete(Class<?> beanType, Object id, Transaction transaction);
/**
* Delete a bean given it's type and id value.
* <p>
* This will also cascade delete one level of children.
* </p>
*/
int delete(Class<?> beanType, Object id, Transaction transaction);
/**
* Delete the bean.
*/
void delete(EntityBean entityBean, Transaction t);
/**
* Delete the bean.
*/
boolean 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.
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Restore the draft beans back to the matching live beans.
*/
<T> List<T> draftRestore(Query<T> 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);
}
@@ -51,8 +51,8 @@ public class BeanCascadeInfo {
/**
* Set to true if delete should cascade.
*/
public void setDelete(boolean isDelete) {
this.delete = isDelete;
public void setDelete(boolean delete) {
this.delete = delete;
}
/**
* Return true if save should cascade.
@@ -60,5 +60,13 @@ public class BeanCascadeInfo {
public boolean isSave() {
return save;
}
/**
* Set cascade save and delete settings.
*/
public void setSaveDelete(boolean save, boolean delete) {
this.save = save;
this.delete = delete;
}
}
@@ -1,6 +1,8 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.ValuePair;
@@ -147,11 +149,19 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
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;
private final BeanProperty draftDirty;
/**
* Map of BeanProperty Linked so as to preserve order.
*/
@@ -319,7 +329,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
private final boolean updateChangesOnly;
private final boolean cacheSharableBeans;
private final BeanDescriptorDraftHelp<T> draftHelp;
private final BeanDescriptorCacheHelp<T> cacheHelp;
private final BeanDescriptorJsonHelp<T> jsonHelp;
@@ -372,7 +383,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
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();
@@ -383,6 +397,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
this.idProperty = listHelper.getId();
this.versionProperty = listHelper.getVersionProperty();
this.draftDirty = listHelper.getDraftDirty();
this.propMap = listHelper.getPropertyMap();
this.propertiesTransient = listHelper.getTransients();
this.propertiesNonTransient = listHelper.getNonTransients();
@@ -413,6 +428,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
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);
// 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 +541,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
* </p>
* @param withHistoryTables map populated if @History is supported on this entity bean
*/
public void initialiseId(Map<String, String> withHistoryTables) {
public void initialiseId(Map<String, String> withHistoryTables, Map<String,String> 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);
@@ -558,16 +577,21 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
*
* @param asOfTableMap the map of base tables to associated 'with history' tables
* @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name
* @param draftTableMap the map of base tables to associated 'draft' tables.
*/
public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix) {
public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix, Map<String, String> draftTableMap) {
for (int i = 0; i < propertiesManyToMany.length; i++) {
// register associated draft table for M2M intersection
propertiesManyToMany[i].registerDraftIntersectionTable(draftTableMap);
}
if (historySupport) {
// history support on this bean so check all associated intersection tables
// and if they are not excluded register the associated 'with history' table
for (int i = 0; i < propertiesManyToMany.length; i++) {
// register associated history table for M2M intersection
if (!propertiesManyToMany[i].isExcludedFromHistory()) {
// this intersection table has history support so also register
// it into the asOfTableMap
TableJoin intersectionTableJoin = propertiesManyToMany[i].getIntersectionTableJoin();
String intersectionTableName = intersectionTableJoin.getTable();
asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
@@ -810,6 +834,24 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return inheritInfo == null || inheritInfo.isRoot();
}
public T publish(T draftBean, T liveBean) {
return draftHelp.publish(draftBean, liveBean);
}
/**
* Reset properties on the draft bean based on @DraftDirty and @DraftReset.
*/
public boolean draftReset(T draftBean) {
return draftHelp.draftReset(draftBean);
}
/**
* Return the draft dirty boolean property or null if there is not one assigned to this bean type.
*/
public BeanProperty getDraftDirty() {
return draftDirty;
}
/**
* Set the bean caching on or off.
*/
@@ -831,7 +873,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
/**
* Return true if the persist request needs to notify the cache.
*/
public boolean isCacheNotify() {
public boolean isCacheNotify(boolean publish) {
if (draftable && !publish) {
// no caching when editing draft beans
return false;
}
return cacheHelp.isCacheNotify();
}
@@ -1556,6 +1602,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return new ElComparatorProperty<T>(elGetValue, sortProp.isAscending(), nullsHigh);
}
@Override
public boolean isValidExpression(String propertyName) {
try {
return (getElGetValue(propertyName) != null);
} catch (PersistenceException e) {
return false;
}
}
/**
* Get an Expression language Value object.
*/
@@ -1856,12 +1911,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
*/
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 +1932,42 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
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;
}
/**
* If there is a @DraftDirty property set it's value on the bean.
*/
public void setDraftDirty(EntityBean entityBean, boolean value) {
if (draftDirty != null) {
// check to see if the dirty property has already
// been set and if so do not set the value
if (!entityBean._ebean_getIntercept().isChangedProperty(draftDirty.getPropertyIndex())) {
draftDirty.setValueIntercept(entityBean, value);
}
}
}
/**
* Optimise the draft query fetching any draftable element relationships.
*/
public void draftQueryOptimise(Query<T> query) {
// use per query PersistenceContext to ensure fresh beans loaded
query.setPersistenceContextScope(PersistenceContextScope.QUERY);
draftHelp.draftQueryOptimise(query);
}
/**
* Return true if this entity bean has history support.
*/
@@ -1961,6 +2060,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return propertiesEmbedded;
}
/**
* Set the embedded owner on any embedded bean properties.
*/
public void setEmbeddedOwner(EntityBean bean) {
for (int i = 0; i < propertiesEmbedded.length; i++) {
propertiesEmbedded[i].setEmbeddedOwner(bean);
}
}
public BeanProperty getIdProperty() {
return idProperty;
}
@@ -0,0 +1,121 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.Query;
import com.avaje.ebean.bean.EntityBean;
import java.util.ArrayList;
import java.util.List;
/**
* Helper for BeanDescriptor that manages draft entity beans.
*
* @param <T> The entity bean type
*/
public final class BeanDescriptorDraftHelp<T> {
private final BeanDescriptor<T> desc;
private final BeanProperty draftDirty;
private final BeanProperty[] resetProperties;
public BeanDescriptorDraftHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.draftDirty = desc.getDraftDirty();
this.resetProperties = resetProperties();
}
/**
* Return the properties that are reset on draft beans after publish.
*/
private BeanProperty[] resetProperties() {
List<BeanProperty> list = new ArrayList<BeanProperty>();
BeanProperty[] props = desc.propertiesNonMany();
for (BeanProperty prop : props) {
if (prop.isDraftReset()) {
list.add(prop);
}
}
return list.toArray(new BeanProperty[list.size()]);
}
/**
* Set the value of all the 'reset properties' to null on the draft bean.
*/
public boolean draftReset(T draftBean) {
EntityBean draftEntityBean = (EntityBean)draftBean;
if (draftDirty != null) {
// set @DraftDirty property to false
draftDirty.setValueIntercept(draftEntityBean, false);
}
// set to null on all @DraftReset properties
for (BeanProperty resetProperty : resetProperties) {
resetProperty.setValueIntercept(draftEntityBean, null);
}
// return true if the bean is dirty (and should be persisted)
return draftEntityBean._ebean_getIntercept().isDirty();
}
/**
* Transfer the values from the draftBean to the liveBean.
* <p>
* This will recursive transfer values to all @DraftableElement properties.
* </p>
*/
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();
if (idProperty != null) {
idProperty.publish(draft, live);
}
BeanProperty[] props = desc.propertiesNonMany();
for (int i = 0; i < props.length; i++) {
props[i].publish(draft, live);
}
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
for (int i = 0; i < many.length; i++) {
if (many[i].getTargetDescriptor().isDraftable()) {
many[i].publishMany(draft, live);
}
}
return liveBean;
}
/**
* Fetch draftable element relationships.
*/
public void draftQueryOptimise(Query<T> query) {
BeanPropertyAssocOne<?>[] one = desc.propertiesOne();
for (int i = 0; i < one.length; i++) {
if (one[i].getTargetDescriptor().isDraftableElement()) {
query.fetch(one[i].getName());
}
}
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
for (int i = 0; i < many.length; i++) {
if (many[i].getTargetDescriptor().isDraftableElement()) {
query.fetch(many[i].getName());
}
}
}
}
@@ -5,6 +5,7 @@ import com.avaje.ebean.Model;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.RawSqlBuilder;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
@@ -167,6 +168,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private final Map<String,String> asOfTableMap = new HashMap<String, String>();
/**
* Map of base tables to 'draft' tables.
*/
private final Map<String,String> draftTableMap = new HashMap<String, String>();
/**
* Create for a given database dbConfig.
*/
@@ -269,6 +275,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return idBinderFactory.createIdBinder(idProperty);
}
/**
* Return the map of base tables to draft tables.
*/
public Map<String,String> getDraftTableMap() {
return draftTableMap;
}
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
@@ -396,7 +409,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:
@@ -411,7 +424,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// also look for intersection tables with
// associated history support and register them
// into the asOfTableMap
d.initialiseOther(asOfTableMap, asOfViewSuffix);
d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap);
}
// create BeanManager for each non-embedded entity bean
@@ -898,7 +911,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private void checkMappedByOneToMany(DeployBeanInfo<?> info, DeployBeanPropertyAssocMany<?> prop) {
// get the bean descriptor that holds the mappedBy property
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(prop);
if (targetDesc.isDraftableElement()) {
// automatically turning on orphan removal and CascadeType.ALL
prop.setModifyListenMode(BeanCollection.ModifyListenMode.REMOVALS);
prop.getCascadeInfo().setSaveDelete(true, true);
}
if (prop.getMappedBy() == null) {
if (!findMappedBy(prop)) {
@@ -912,7 +931,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
String mappedBy = prop.getMappedBy();
// get the mappedBy property
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(prop);
DeployBeanProperty mappedProp = targetDesc.getBeanProperty(mappedBy);
if (mappedProp == null) {
@@ -948,6 +966,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// get the bean descriptor that holds the mappedBy property
String mappedBy = prop.getMappedBy();
if (mappedBy == null) {
if (getTargetDescriptor(prop).isDraftable()) {
prop.setIntersectionDraftTable();
}
return;
}
@@ -995,6 +1016,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DeployTableJoin inverseJoin = new DeployTableJoin();
mappedIntJoin.copyTo(inverseJoin, false, intTableName);
prop.setInverseJoin(inverseJoin);
if (targetDesc.isDraftable()) {
prop.setIntersectionDraftTable();
}
}
private <T> void setBeanControllerFinderListener(DeployBeanDescriptor<T> descriptor) {
@@ -84,7 +84,7 @@ public final class BeanMapHelp<T> implements BeanCollectionHelp<T> {
this.map = map;
}
public void addBean(EntityBean bean) {
public void addEntityBean(EntityBean bean) {
Object keyValue = beanProperty.getValue(bean);
map.put(keyValue, bean);
}
@@ -223,6 +223,12 @@ public class BeanProperty implements ElPropertyValue {
final boolean jsonDeserialize;
final boolean draftOnly;
final boolean draftDirty;
final boolean draftReset;
final boolean indexed;
final String indexName;
@@ -249,6 +255,9 @@ 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.draftReset = deploy.isDraftReset();
this.secondaryTable = deploy.isSecondaryTable();
if (secondaryTable) {
@@ -333,6 +342,9 @@ public class BeanProperty implements ElPropertyValue {
this.formula = false;
this.excludedFromHistory = source.excludedFromHistory;
this.draftDirty = source.draftDirty;
this.draftOnly = source.draftOnly;
this.draftReset = source.draftReset;
this.fetchEager = source.fetchEager;
this.unidirectionalShadow = source.unidirectionalShadow;
this.discriminator = source.discriminator;
@@ -493,7 +505,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 +604,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 +624,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 +635,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 +926,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 +985,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 +1034,28 @@ 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 is reset/cleared on publish (on the draft bean).
*/
public boolean isDraftReset() {
return draftReset;
}
/**
* Return true if this property should be included in an Insert.
*/
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -39,6 +40,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
* Join for manyToMany intersection table.
*/
private final TableJoin intersectionJoin;
private final String intersectionPublishTable;
private final String intersectionDraftTable;
/**
* For ManyToMany this is the Inverse join used to build reference queries.
@@ -109,6 +112,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
this.mapKey = deploy.getMapKey();
this.fetchOrderBy = deploy.getFetchOrderBy();
this.intersectionJoin = deploy.createIntersectionTableJoin();
if (intersectionJoin != null) {
this.intersectionPublishTable = intersectionJoin.getTable();
this.intersectionDraftTable = deploy.getIntersectionDraftTable();
} else {
this.intersectionPublishTable = null;
this.intersectionDraftTable = null;
}
this.inverseJoin = deploy.createInverseTableJoin();
this.modifyListenMode = deploy.getModifyListenMode();
this.jsonHelp = new BeanPropertyAssocManyJsonHelp(this);
@@ -798,22 +808,39 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return row;
}
public IntersectionRow buildManyToManyDeleteChildren(EntityBean parentBean) {
public IntersectionRow buildManyToManyDeleteChildren(EntityBean parentBean, boolean publish) {
IntersectionRow row = new IntersectionRow(intersectionJoin.getTable());
String tableName = publish ? intersectionPublishTable : intersectionDraftTable;
IntersectionRow row = new IntersectionRow(tableName);
buildExport(row, parentBean);
return row;
}
public IntersectionRow buildManyToManyMapBean(EntityBean parent, EntityBean other) {
IntersectionRow row = new IntersectionRow(intersectionJoin.getTable());
public IntersectionRow buildManyToManyMapBean(EntityBean parent, EntityBean other, boolean publish) {
String tableName = publish ? intersectionPublishTable : intersectionDraftTable;
IntersectionRow row = new IntersectionRow(tableName);
buildExport(row, parent);
buildImport(row, other);
return row;
}
/**
* Register the mapping of intersection table to associated draft table.
*/
public void registerDraftIntersectionTable(Map<String, String> draftTableMap) {
if (hasDraftIntersection()) {
draftTableMap.put(intersectionPublishTable, intersectionDraftTable);
}
}
/**
* Return true if the relationship is a ManyToMany with the intersection having an associated draft table.
*/
private boolean hasDraftIntersection() {
return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable);
}
private void buildExport(IntersectionRow row, EntityBean parentBean) {
if (embeddedExportedProperties) {
@@ -876,4 +903,57 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
jsonHelp.jsonRead(readJson, parentBean);
}
public void publishMany(EntityBean draft, EntityBean live) {
// collections will not be null due to enhancement
BeanCollection<T> draftVal = (BeanCollection<T>)getValueIntercept(draft);
BeanCollection<T> liveVal = (BeanCollection<T>)getValueIntercept(live);
// Organise the existing live beans into map keyed by id
Map<Object, T> liveBeansAsMap = liveBeansAsMap(liveVal);
// publish from each draft to live bean creating new live beans as required
draftVal.size();
Collection<T> actualDetails = draftVal.getActualDetails();
for (T bean : actualDetails) {
Object id = targetDescriptor.getId((EntityBean) bean);
T liveBean = liveBeansAsMap.remove(id);
if (isManyToMany()) {
if (liveBean == null) {
// add new relationship (Map not allowed here)
liveVal.addBean(targetDescriptor.createReference(Boolean.FALSE, id));
}
} else {
// recursively publish the OneToMany child bean
T newLive = targetDescriptor.publish(bean, liveBean);
if (liveBean == null) {
// Map not allowed here
liveVal.addBean(newLive);
}
}
}
// anything remaining should be deleted (so remove from modify aware collection)
Collection<T> values = liveBeansAsMap.values();
for (T value : values) {
liveVal.removeBean(value);
}
}
private Map<Object,T> liveBeansAsMap(BeanCollection<?> liveVal) {
liveVal.size();
Collection<?> liveBeans = liveVal.getActualDetails();
Map<Object,T> liveMap = new LinkedHashMap<Object, T>();
for (Object liveBean : liveBeans) {
Object id = targetDescriptor.getId((EntityBean) liveBean);
liveMap.put(id, (T)liveBean);
}
return liveMap;
}
}
@@ -65,7 +65,7 @@ public class BeanPropertyAssocManyJsonHelp {
// read the entire array
break;
}
add.addBean(detailBean);
add.addEntityBean(detailBean);
if (parentBean != null && many.childMasterProperty != null) {
// bind detail bean back to master via mappedBy property
@@ -578,17 +578,30 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
public void setValue(EntityBean bean, Object value) {
super.setValue(bean, value);
if (embedded && value instanceof EntityBean) {
EntityBean embedded = (EntityBean) value;
embedded._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
setEmbeddedOwner(bean, value);
}
}
/**
* Set the owner on the embedded bean property.
*/
public void setEmbeddedOwner(EntityBean owner) {
Object emb = getValue(owner);
if (emb != null) {
setEmbeddedOwner(owner, emb);
}
}
private void setEmbeddedOwner(EntityBean bean, Object value) {
((EntityBean)value)._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
super.setValueIntercept(bean, value);
if (embedded && value instanceof EntityBean) {
EntityBean embedded = (EntityBean) value;
embedded._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
setEmbeddedOwner(bean, value);
}
}
@@ -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();
}
@@ -120,4 +120,9 @@ public interface DbSqlContext {
*/
void appendHistorySysPeriod();
/**
* Return true if the query is a 'asDraft' query.
*/
boolean isDraftQuery();
}
@@ -118,10 +118,16 @@ public class DeployBeanDescriptor<T> {
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,23 @@ public class DeployBeanDescriptor<T> {
return readAuditing;
}
public void setDraftable() {
draftable = true;
}
public boolean isDraftable() {
return draftable;
}
public void setDraftableElement() {
draftable = true;
draftableElement = true;
}
public boolean isDraftableElement() {
return draftableElement;
}
public boolean isScalaObject() {
Class<?>[] interfaces = beanType.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
@@ -485,6 +508,10 @@ public class DeployBeanDescriptor<T> {
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 +549,7 @@ public class DeployBeanDescriptor<T> {
this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName();
this.baseTableAsOf = baseTable + asOfSuffix;
this.baseTableVersionsBetween = baseTable + versionsBetweenSuffix;
this.draftTable = (draftable) ? baseTable+"_draft" : baseTable;
}
public void sortProperties() {
@@ -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,10 @@ public class DeployBeanProperty {
private boolean excludedFromHistory;
private boolean draftOnly;
private boolean draftDirty;
private boolean draftReset;
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
this.desc = desc;
this.propertyType = propertyType;
@@ -598,7 +603,7 @@ public class DeployBeanProperty {
*/
public void setDbType(int dbType) {
this.dbType = dbType;
this.lob = isLobType(dbType);
this.lob = BeanProperty.isLobType(dbType);
}
/**
@@ -609,22 +614,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 +838,29 @@ 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;
}
public void setDraftReset() {
this.draftReset = true;
}
public boolean isDraftReset() {
return draftReset;
}
}
@@ -9,6 +9,11 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
*/
public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
/**
* The type of the many, set, list or map.
*/
final ManyType manyType;
ModifyListenMode modifyListenMode = ModifyListenMode.NONE;
/**
@@ -35,12 +40,9 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
String mapKey;
/**
* The type of the many, set, list or map.
*/
final ManyType manyType;
String intersectionDraftTable;
/**
/**
* Create this property.
*/
public DeployBeanPropertyAssocMany(DeployBeanDescriptor<?> desc, Class<T> targetType, ManyType manyType) {
@@ -191,4 +193,17 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
}
}
/**
* Return a draft table for intersection between 2 @Draftable entities.
*/
public String getIntersectionDraftTable() {
return (intersectionDraftTable != null) ? intersectionDraftTable : intersectionJoin.getTable();
}
/**
* ManyToMany between 2 @Draftable entities to also need draft intersection table.
*/
public void setIntersectionDraftTable() {
this.intersectionDraftTable = intersectionJoin.getTable()+"_draft";
}
}
@@ -28,6 +28,8 @@ public class DeployBeanPropertyLists {
private BeanProperty versionProperty;
private BeanProperty draftDirty;
private final BeanDescriptor<?> desc;
private final LinkedHashMap<String, BeanProperty> propertyMap;
@@ -175,6 +177,8 @@ public class DeployBeanPropertyLists {
logger.warn("Multiple @Version properties - property " + prop.getFullBeanName()
+ " not treated as a version property");
}
} else if (prop.isDraftDirty()) {
draftDirty = prop;
}
if (prop instanceof BeanPropertyCompound) {
baseCompound.add((BeanPropertyCompound) prop);
@@ -285,6 +289,10 @@ public class DeployBeanPropertyLists {
return getMany2Many();
}
public BeanProperty getDraftDirty() {
return draftDirty;
}
/**
* Mode used to determine which BeanPropertyAssoc to include.
*/
@@ -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();
@@ -149,6 +149,16 @@ public class AnnotationFields extends AnnotationParser {
util.setLobType(prop);
}
if (get(prop, DraftOnly.class) != null) {
prop.setDraftOnly();
}
if (get(prop, DraftDirty.class) != null) {
prop.setDraftDirty();
}
if (get(prop, DraftReset.class) != null) {
prop.setDraftReset();
}
DbJson dbJson = get(prop, DbJson.class);
if (dbJson != null) {
util.setDbJsonType(prop, dbJson);
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -42,6 +43,11 @@ public abstract class AbstractExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(getPropertyName());
}
protected ElPropertyValue getElProp(SpiExpressionRequest request) {
String propertyName = getPropertyName();
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -36,6 +37,13 @@ class AllEqualsExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (String propName: propMap.keySet()) {
validation.validate(propName);
}
}
public void addBindValues(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -44,6 +45,12 @@ class BetweenPropertyExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(lowProperty);
validation.validate(highProperty);
}
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(value);
}
@@ -22,7 +22,7 @@ class CaseInsensitiveEqualExpression extends AbstractExpression {
if (prop != null && prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindValue(encryptKey);
request.addBindEncryptKey(encryptKey);
}
request.addBindValue(value);
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -121,6 +122,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
return this;
}
@Override
public void validate(SpiExpressionValidation validation) {
for (int i = 0; i < list.size(); i++) {
list.get(i).validate(validation);
}
}
/**
* Adds bind values to the request.
*/
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
@@ -85,4 +86,9 @@ public class ExistsExpression implements SpiExpression {
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// Nothing to do for exists expression
}
@Override
public void validate(SpiExpressionValidation validation) {
// Nothing to do for exists expression
}
}
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
@@ -28,6 +29,11 @@ class IdExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
public void addBindValues(SpiExpressionRequest request) {
// 'flatten' EmbeddedId and multiple Id cases
@@ -7,6 +7,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
@@ -27,6 +28,11 @@ public class IdInExpression implements SpiExpression {
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
public void addBindValues(SpiExpressionRequest request) {
// Bind the Id values including EmbeddedId and multiple Id
@@ -78,7 +78,7 @@ class JsonPathExpression extends AbstractExpression {
public void addSql(SpiExpressionRequest request) {
// Use DB specific expression handling (Postgres and Oracle supported)
request.getJsonHander().addSql(request, propName, path, operator, value);
request.getJsonHandler().addSql(request, propName, path, operator, value);
}
@Override
@@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionList;
@@ -81,6 +82,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public void validate(SpiExpressionValidation validation) {
exprList.validate(validation);
}
@Override
public Junction<T> add(Expression item) {
SpiExpression i = (SpiExpression) item;
@@ -29,7 +29,7 @@ class LikeExpression extends AbstractExpression {
if (prop != null && prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindValue(encryptKey);
request.addBindEncryptKey(encryptKey);
}
String bindValue = getValue(val, caseInsensitive, type);
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -53,6 +54,12 @@ abstract class LogicExpression implements SpiExpression {
expTwo.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
expOne.validate(validation);
expTwo.validate(validation);
}
public void addBindValues(SpiExpressionRequest request) {
expOne.addBindValues(request);
expTwo.addBindValues(request);
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -19,6 +20,11 @@ class NoopExpression implements SpiExpression {
// nothing to do
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
@Override
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
builder.add(NoopExpression.class);
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
final class NotExpression implements SpiExpression {
@@ -24,6 +25,11 @@ final class NotExpression implements SpiExpression {
exp.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
exp.validate(validation);
}
public void addBindValues(SpiExpressionRequest request) {
exp.addBindValues(request);
}
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
class RawExpression implements SpiExpression {
@@ -24,6 +25,11 @@ class RawExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always ignored
}
public void addBindValues(SpiExpressionRequest request) {
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -40,7 +40,7 @@ public class SimpleExpression extends AbstractExpression {
if (prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindValue(encryptKey);
request.addBindEncryptKey(encryptKey);
}
//else if (prop.isLocalEncrypted()) {
// not supporting this for equals (but probably could)
@@ -54,10 +54,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
query.asOf(parent.getAsOf());
query.setParentNode(objectGraphNode);
query.setLazyLoadProperty(lazyLoadProperty);
if (parent.isAsDraft()) {
query.asDraft();
}
if (parent.isDisableReadAudit()) {
query.setDisableReadAuditing();
}
if (queryProps != null) {
queryProps.configureBeanQuery(query);
}
@@ -36,6 +36,7 @@ public class DLoadContext implements LoadContext {
private final DLoadBeanContext rootBeanContext;
private final boolean asDraft;
private final Timestamp asOf;
private final Boolean readOnly;
private final boolean excludeBeanCache;
@@ -65,6 +66,7 @@ public class DLoadContext implements LoadContext {
SpiQuery<?> query = request.getQuery();
this.asOf = query.getAsOf();
this.asDraft = query.isAsDraft();
this.readOnly = query.isReadOnly();
this.disableReadAudit = query.isDisableReadAudit();
this.disableLazyLoading = query.isDisableLazyLoading();
@@ -230,6 +232,13 @@ public class DLoadContext implements LoadContext {
return asOf;
}
/**
* Return true if the root query is a 'asDraft' query that should propagate to secondary queries.
*/
protected boolean isAsDraft() {
return asDraft;
}
/**
* Return true if disable read auditing should propagate to secondary queries.
*/
@@ -65,14 +65,15 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
query.setDisableLazyLoading(parent.isDisableLazyLoading());
query.asOf(parent.getAsOf());
query.setParentNode(objectGraphNode);
if (parent.isAsDraft()) {
query.asDraft();
}
if (parent.isDisableReadAudit()) {
query.setDisableReadAuditing();
}
if (queryProps != null) {
queryProps.configureBeanQuery(query);
}
if (parent.isUseAutoTune()) {
query.setAutoTune(true);
}
@@ -22,6 +22,6 @@ public interface BeanPersister {
/**
* execute the delete bean request.
*/
void delete(PersistRequestBean<?> request) throws PersistenceException;
int delete(PersistRequestBean<?> request) throws PersistenceException;
}
@@ -73,15 +73,17 @@ public final class DefaultPersistExecute implements PersistExecute {
/**
* execute the bean delete request.
*/
public <T> void executeDeleteBean(PersistRequestBean<T> request) {
public <T> int executeDeleteBean(PersistRequestBean<T> request) {
BeanManager<T> mgr = request.getBeanManager();
BeanPersister persister = mgr.getBeanPersister();
BeanPersistController controller = request.getBeanController();
if (controller == null || controller.preDelete(request)) {
persister.delete(request);
return persister.delete(request);
}
// delete handled by the BeanController so return 0
return 0;
}
/**
@@ -33,9 +33,11 @@ import com.avaje.ebeaninternal.server.deploy.ManyType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -58,6 +60,10 @@ import java.util.Set;
*/
public final class DefaultPersister implements Persister {
private static final Logger SUM = LoggerFactory.getLogger("org.avaje.ebean.SUM");
private static final Logger PUB = LoggerFactory.getLogger("org.avaje.ebean.PUB");
private static final Logger logger = LoggerFactory.getLogger(DefaultPersister.class);
/**
@@ -83,17 +89,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 +106,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,16 +127,210 @@ 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;
/**
* Restore draft beans to match live beans given the query.
*/
@Override
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
Class<T> beanType = query.getBeanType();
BeanDescriptor<T> desc = server.getBeanDescriptor(beanType);
DraftHandler<T> draftHandler = new DraftHandler<T>(desc, transaction);
List<T> liveBeans = draftHandler.fetchSourceBeans(query, false);
PUB.debug("draftRestore [{}] count[{}]", desc.getName(), liveBeans.size());
if (liveBeans.isEmpty()) {
return Collections.emptyList();
}
draftHandler.fetchDestinationBeans(liveBeans, true);
BeanManager<T> mgr = beanDescriptorManager.getBeanManager(beanType);
for (T liveBean: liveBeans) {
T draftBean = draftHandler.publishToDestinationBean(liveBean);
// reset @DraftDirty and @DraftReset properties
draftHandler.resetDraft(draftBean);
PUB.trace("draftRestore bean [{}] id[{}]", desc.getName(), draftHandler.getId());
update(createRequest(draftBean, transaction, null, mgr, Type.UPDATE, true, false));
}
PUB.debug("draftRestore - complete for [{}]", desc.getName());
return draftHandler.getDrafts();
}
/**
* Helper method to return the list of Id values for the list of beans.
*/
private <T> List<Object> getBeanIds(BeanDescriptor<T> desc, List<T> beans) {
List<Object> idList = new ArrayList<Object>();
for (T liveBean: beans) {
idList.add(desc.getBeanId(liveBean));
}
return idList;
}
/**
* Publish from draft to live given the query.
*/
@Override
public <T> List<T> publish(Query<T> query, Transaction transaction) {
Class<T> beanType = query.getBeanType();
BeanDescriptor<T> desc = server.getBeanDescriptor(beanType);
DraftHandler<T> draftHandler = new DraftHandler<T>(desc, transaction);
List<T> draftBeans = draftHandler.fetchSourceBeans(query, true);
PUB.debug("publish [{}] count[{}]", desc.getName(), draftBeans.size());
if (draftBeans.isEmpty()) {
return Collections.emptyList();
}
draftHandler.fetchDestinationBeans(draftBeans, false);
BeanManager<T> mgr = beanDescriptorManager.getBeanManager(beanType);
List<T> livePublish = new ArrayList<T>(draftBeans.size());
for (T draftBean: draftBeans) {
T liveBean = draftHandler.publishToDestinationBean(draftBean);
livePublish.add(liveBean);
// reset @DraftDirty and @DraftReset properties
draftHandler.resetDraft(draftBean);
Type persistType = draftHandler.isInsert() ? Type.INSERT : Type.UPDATE;
PUB.trace("publish bean [{}] id[{}] type[{}]", desc.getName(), draftHandler.getId(), persistType);
PersistRequestBean<T> request = createRequest(liveBean, transaction, null, mgr, persistType, true, true);
if (persistType == Type.INSERT) {
insert(request);
} else {
update(request);
}
}
draftHandler.updateDrafts(transaction, mgr);
PUB.debug("publish - complete for [{}]", desc.getName());
return livePublish;
}
/**
* Helper to handle draft beans (properties reset etc).
*/
class DraftHandler<T> {
final BeanDescriptor<T> desc;
final Transaction transaction;
final BeanProperty draftDirty;
final List<T> draftUpdates = new ArrayList<T>();
/**
* Id value of the last published bean.
*/
Object id;
/**
* True if the last published bean is new/insert.
*/
boolean insert;
/**
* The destination beans to publish/restore to mapped by id.
*/
Map<?, T> destBeans;
DraftHandler(BeanDescriptor<T> desc, Transaction transaction) {
this.desc = desc;
this.transaction = transaction;
this.draftDirty = desc.getDraftDirty();
}
/**
* Return the list of draft beans with changes (to be persisted).
*/
List<T> getDrafts() {
return draftUpdates;
}
/**
* Set the draft dirty state to false and reset any dirtyReset properties.
*/
void resetDraft(T draftBean) {
if (desc.draftReset(draftBean)) {
// draft bean is dirty so collect it for persisting later
draftUpdates.add(draftBean);
}
}
/**
* Save all the draft beans (with various properties reset etc).
*/
void updateDrafts(Transaction transaction, BeanManager<T> mgr) {
if (!draftUpdates.isEmpty()) {
// update the dirty status on the drafts that have been published
PUB.debug("publish - update dirty status on [{}] drafts", draftUpdates.size());
for (T draftUpdate : draftUpdates) {
update(createRequest(draftUpdate, transaction, null, mgr, Type.UPDATE, false, false));
}
}
}
/**
* Fetch the source beans based on the query.
*/
List<T> fetchSourceBeans(Query<T> query, boolean asDraft) {
desc.draftQueryOptimise(query);
if (asDraft) {
query.asDraft();
}
return server.findList(query, transaction);
}
/**
* Fetch the destination beans that will be published to.
*/
void fetchDestinationBeans(List<T> sourceBeans, boolean asDraft) {
List<Object> ids = getBeanIds(desc, sourceBeans);
Query<T> destQuery = server.find(desc.getBeanType()).where().idIn(ids).query();
if (asDraft) {
destQuery.asDraft();
}
desc.draftQueryOptimise(destQuery);
this.destBeans = server.findMap(destQuery, transaction);
}
/**
* Publish/restore the values from the sourceBean to the matching destination bean.
*/
T publishToDestinationBean(T sourceBean) {
id = desc.getBeanId(sourceBean);
T destBean = destBeans.get(id);
insert = (destBean == null);
// apply changes from liveBean to draftBean
return desc.publish(sourceBean, destBean);
}
/**
* Return true if the last publish resulted in an new bean to insert.
*/
boolean isInsert() {
return insert;
}
/**
* Return the Id value of the last published/restored bean.
*/
Object getId() {
return id;
}
}
@@ -214,10 +407,10 @@ public final class DefaultPersister implements Persister {
}
}
private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode) {
private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) {
// determine insert or update taking into account stateless updates
PersistRequestBean<?> request = createRequest(bean, t, parentBean, insertMode);
PersistRequestBean<?> request = createRequestRecurse(bean, t, parentBean, insertMode, publish);
if (request.isReference()) {
// its a reference...
@@ -305,25 +498,43 @@ public final class DefaultPersister implements Persister {
/**
* Delete the bean with the explicit transaction.
* Return false if the delete is executed without OCC and 0 rows were deleted.
*/
public void delete(EntityBean bean, Transaction t) {
public boolean delete(EntityBean bean, Transaction t) {
PersistRequestBean<EntityBean> request = createRequest(bean, t, Type.DELETE);
boolean deleted = deleteRequest(request);
if (request.isDraftable()) {
// we have just deleting a draft bean so now we need to delete the
// associated 'live' bean. This is effectively an 'automatic publish'.
deleteRequest(createRequest(request.createReference(), t, Type.DELETE, true));
}
return deleted;
}
/**
* Execute the delete request returning true if a delete occurred.
*/
private boolean deleteRequest(PersistRequestBean<?> req) {
PersistRequestBean<?> req = createRequest(bean, t, PersistRequest.Type.DELETE);
if (req.isRegisteredForDeleteBean()) {
// skip deleting bean. Used where cascade is on
// both sides of a relationship
if (logger.isDebugEnabled()) {
logger.debug("skipping delete on alreadyRegistered " + bean);
logger.debug("skipping delete on alreadyRegistered " + req.getBean());
}
return;
return false;
}
try {
req.initTransIfRequiredWithBatchCascade();
delete(req);
boolean deleted = delete(req);
req.commitTransIfRequired();
req.flushBatchOnCascade();
return deleted;
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
throw ex;
@@ -502,7 +713,7 @@ public final class DefaultPersister implements Persister {
* Note that preDelete fires before the deletion of children.
* </p>
*/
private void delete(PersistRequestBean<?> request) {
private boolean delete(PersistRequestBean<?> request) {
DeleteUnloadedForeignKeys unloadedForeignKeys = null;
@@ -521,7 +732,7 @@ public final class DefaultPersister implements Persister {
}
}
request.executeOrQueue();
int count = request.executeOrQueue();
if (request.isPersistCascade()) {
deleteAssocOne(request);
@@ -531,6 +742,8 @@ public final class DefaultPersister implements Persister {
}
}
// return true if using JDBC batch (as we can't tell until the batch is flushed)
return count != 0;
}
/**
@@ -558,7 +771,7 @@ public final class DefaultPersister implements Persister {
if (!prop.isSaveRecurseSkippable(detailBean)) {
t.depth(+1);
prop.setParentBeanToChild(parentBean, detailBean);
saveRecurse(detailBean, t, parentBean, insertMode);
saveRecurse(detailBean, t, parentBean, insertMode, request.isPublish());
t.depth(-1);
}
}
@@ -589,6 +802,7 @@ public final class DefaultPersister implements Persister {
private final SpiTransaction transaction;
private final boolean cascade;
private final boolean deleteMissingChildren;
private final boolean publish;
private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
this.insertedParent = insertedParent;
@@ -597,6 +811,7 @@ public final class DefaultPersister implements Persister {
this.parentBean = parentBean;
this.transaction = request.getTransaction();
this.deleteMissingChildren = request.isDeleteMissingChildren();
this.publish = request.isPublish();
}
private SaveManyPropRequest(BeanPropertyAssocMany<?> many, EntityBean parentBean, SpiTransaction t) {
@@ -606,6 +821,7 @@ public final class DefaultPersister implements Persister {
this.transaction = t;
this.cascade = true;
this.deleteMissingChildren = false;
this.publish = false;
}
public boolean isSaveIntersection() {
@@ -652,6 +868,10 @@ public final class DefaultPersister implements Persister {
}
c.modifyReset();
}
public boolean isPublish() {
return publish;
}
}
private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) {
@@ -703,9 +923,8 @@ public final class DefaultPersister implements Persister {
if (removedBean instanceof EntityBean) {
EntityBean eb = (EntityBean) removedBean;
if (eb._ebean_getIntercept().isLoaded()) {
// only delete if the bean was loaded meaning that
// it is know to exist in the DB
deleteRecurse(removedBean, t);
// only delete if the bean was loaded meaning that it is known to exist in the DB
deleteRequest(createRequest(removedBean, t, PersistRequest.Type.DELETE, saveMany.isPublish()));
}
}
}
@@ -789,7 +1008,7 @@ public final class DefaultPersister implements Persister {
}
if (!skipSavingThisBean) {
saveRecurse(detail, t, parentBean, insertMode);
saveRecurse(detail, t, parentBean, insertMode, saveMany.isPublish());
}
}
}
@@ -822,7 +1041,7 @@ public final class DefaultPersister implements Persister {
BeanDescriptor<?> descriptor = beanDescriptorManager.getBeanDescriptor(ownerBean.getClass());
BeanPropertyAssocMany<?> prop = (BeanPropertyAssocMany<?>) descriptor.getBeanProperty(propertyName);
return deleteAssocManyIntersection(ownerBean, prop, t);
return deleteAssocManyIntersection(ownerBean, prop, t, false);
}
public void saveManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) {
@@ -856,7 +1075,7 @@ public final class DefaultPersister implements Persister {
int revertDepth = -1 * depth;
trans.depth(depth);
saveRecurse(assocBean, t, parentBean, true);
saveRecurse(assocBean, t, parentBean, true, false);
trans.depth(revertDepth);
} else {
@@ -887,7 +1106,7 @@ public final class DefaultPersister implements Persister {
if (vanillaCollection || deleteMissingChildren) {
// delete all intersection rows and then treat all
// beans in the collection as additions
deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t);
deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t, saveManyPropRequest.isPublish());
}
Collection<?> deletions = null;
@@ -939,7 +1158,7 @@ public final class DefaultPersister implements Persister {
} else {
// build a intersection row for 'insert'
IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean);
IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherBean, saveManyPropRequest.isPublish());
SqlUpdate sqlInsert = intRow.createInsert(server);
executeSqlUpdate(sqlInsert, t);
}
@@ -955,7 +1174,7 @@ public final class DefaultPersister implements Persister {
EntityBean otherDelete = (EntityBean) other;
// the object from the 'other' side of the ManyToMany
// build a intersection row for 'delete'
IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete);
IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete, saveManyPropRequest.isPublish());
SqlUpdate sqlDelete = intRow.createDelete(server);
executeSqlUpdate(sqlDelete, t);
}
@@ -965,10 +1184,10 @@ public final class DefaultPersister implements Persister {
t.depth(-1);
}
private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany<?> many, Transaction t) {
private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany<?> many, Transaction t, boolean publish) {
// delete all intersection rows for this bean
IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean);
IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean, publish);
SqlUpdate sqlDelete = intRow.createDeleteChildren(server);
return executeSqlUpdate(sqlDelete, t);
@@ -1017,7 +1236,7 @@ public final class DefaultPersister implements Persister {
for (int i = 0; i < manys.length; i++) {
if (manys[i].isManyToMany()) {
// delete associated rows from intersection table
deleteAssocManyIntersection(parentBean, manys[i], t);
deleteAssocManyIntersection(parentBean, manys[i], t, request.isPublish());
} else {
@@ -1121,7 +1340,7 @@ public final class DefaultPersister implements Persister {
&& !request.isParent(detailBean)) {
SpiTransaction t = request.getTransaction();
t.depth(-1);
saveRecurse(detailBean, t, null, insertMode);
saveRecurse(detailBean, t, null, insertMode, request.isPublish());
t.depth(+1);
}
}
@@ -1207,11 +1426,18 @@ public final class DefaultPersister implements Persister {
* perform an insert, update or delete.
*/
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type) {
return createRequest(bean, t, type, false);
}
/**
* Create the Persist Request Object additionally specifying the publish status.
*/
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
BeanManager<T> mgr = getBeanManager(bean);
if (mgr == null) {
throw new PersistenceException(errNotRegistered(bean.getClass()));
}
return createRequest(bean, t, null, mgr, type, false);
return createRequest(bean, t, null, mgr, type, false, publish);
}
/**
@@ -1219,16 +1445,22 @@ public final class DefaultPersister implements Persister {
* <p>
* This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type).
*/
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, boolean insertMode) {
private <T> PersistRequestBean<T> createRequestRecurse(T bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) {
BeanManager<T> mgr = getBeanManager(bean);
if (mgr == null) {
throw new PersistenceException(errNotRegistered(bean.getClass()));
}
BeanDescriptor<T> desc = mgr.getBeanDescriptor();
EntityBean entityBean = (EntityBean) bean;
// determine Insert or Update based on bean state and insert flag
PersistRequest.Type type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE;
return createRequest(bean, t, parentBean, mgr, type, true);
PersistRequest.Type type;
if (publish) {
// insert if it is a new bean (as publish created it)
type = entityBean._ebean_getIntercept().isNew() ? Type.INSERT : Type.UPDATE;
} else {
// determine Insert or Update based on bean state and insert flag
type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE;
}
return createRequest(bean, t, parentBean, mgr, type, true, publish);
}
/**
@@ -1236,9 +1468,10 @@ public final class DefaultPersister implements Persister {
* perform an insert, update or delete.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr, PersistRequest.Type type, boolean saveRecurse) {
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr,
PersistRequest.Type type, boolean saveRecurse, boolean publish) {
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse);
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse, publish);
}
private String errNotRegistered(Class<?> beanClass) {
@@ -33,7 +33,7 @@ public interface PersistExecute {
/**
* Execute a Bean (or MapBean) delete.
*/
<T> void executeDeleteBean(PersistRequestBean<T> request);
<T> int executeDeleteBean(PersistRequestBean<T> request);
/**
* Execute a Update.
@@ -46,9 +46,10 @@ public class DeleteHandler extends DmlHandler {
* Execute the delete non-batch.
*/
@Override
public void execute() throws SQLException, OptimisticLockException {
public int execute() throws SQLException, OptimisticLockException {
int rowCount = dataBind.executeUpdate();
checkRowCount(rowCount);
return rowCount;
}
@Override
@@ -16,8 +16,9 @@ import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId;
public final class DeleteMeta {
private final String sqlVersion;
private final String sqlNone;
private final String sqlDraftVersion;
private final String sqlDraftNone;
private final BindableId id;
@@ -32,8 +33,19 @@ public final class DeleteMeta {
this.tableName = desc.getBaseTable();
this.id = id;
this.version = version;
this.sqlNone = genSql(ConcurrencyMode.NONE);
this.sqlVersion = genSql(ConcurrencyMode.VERSION);
String tableName = desc.getBaseTable();
this.sqlNone = genSql(ConcurrencyMode.NONE, tableName);
this.sqlVersion = genSql(ConcurrencyMode.VERSION, tableName);
if (desc.isDraftable()) {
String draftTableName = desc.getDraftTable();
this.sqlDraftNone = genSql(ConcurrencyMode.NONE, draftTableName);
this.sqlDraftVersion = genSql(ConcurrencyMode.VERSION, draftTableName);
} else {
this.sqlDraftNone = sqlNone;
this.sqlDraftVersion = sqlVersion;
}
}
public boolean isEmptyStringAsNull() {
@@ -75,25 +87,26 @@ public final class DeleteMeta {
throw new IllegalStateException("Can not deleteById on " + request.getFullName() + " as no @Id property");
}
boolean publish = request.isPublish();
switch (request.determineConcurrencyMode()) {
case NONE:
return sqlNone;
return publish ? sqlNone : sqlDraftNone;
case VERSION:
return sqlVersion;
return publish ? sqlVersion : sqlDraftVersion;
default:
throw new RuntimeException("Invalid mode " + request.determineConcurrencyMode());
}
}
private String genSql(ConcurrencyMode conMode) {
private String genSql(ConcurrencyMode conMode, String table) {
// delete ... where bcol=? and bc1=? and bc2 is null and ...
GenerateDmlRequest request = new GenerateDmlRequest();
request.append("delete from ").append(tableName);
request.append("delete from ").append(table);
request.append(" where ");
request.setWhereIdMode();
@@ -35,10 +35,10 @@ public final class DmlBeanPersister implements BeanPersister {
/**
* execute the bean delete request.
*/
public void delete(PersistRequestBean<?> request) {
public int delete(PersistRequestBean<?> request) {
DeleteHandler delete = new DeleteHandler(request, deleteMeta);
execute(request, delete);
return execute(request, delete);
}
/**
@@ -62,15 +62,17 @@ public final class DmlBeanPersister implements BeanPersister {
/**
* execute request taking batching into account.
*/
private void execute(PersistRequestBean<?> request, PersistHandler handler) {
private int execute(PersistRequestBean<?> request, PersistHandler handler) {
boolean batched = request.isBatched();
try {
handler.bind();
if (batched) {
handler.addBatch();
return -1;
} else {
handler.execute();
return handler.execute();
}
} catch (SQLException e) {
@@ -75,7 +75,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
* Execute now for non-batch execution.
*/
@Override
public abstract void execute() throws SQLException;
public abstract int execute() throws SQLException;
/**
* Check the rowCount.
@@ -55,7 +55,7 @@ public class InsertHandler extends DmlHandler {
public InsertHandler(PersistRequestBean<?> persist, InsertMeta meta) {
super(persist, meta.isEmptyStringToNull());
this.meta = meta;
this.concatinatedKey = meta.isConcatinatedKey();
this.concatinatedKey = meta.isConcatenatedKey();
}
/**
@@ -90,7 +90,7 @@ public class InsertHandler extends DmlHandler {
SpiTransaction t = persistRequest.getTransaction();
// get the appropriate sql
sql = meta.getSql(withId);
sql = meta.getSql(withId, persistRequest.isPublish());
PreparedStatement pstmt;
if (persistRequest.isBatched()) {
@@ -101,7 +101,7 @@ public class InsertHandler extends DmlHandler {
dataBind = new DataBind(pstmt);
// bind the bean property values
meta.bind(this, bean, withId);
meta.bind(this, bean, withId, persistRequest.isPublish());
logSql(sql);
}
@@ -125,8 +125,8 @@ public class InsertHandler extends DmlHandler {
* getGeneratedKeys if required.
*/
@Override
public void execute() throws SQLException, OptimisticLockException {
int rc = dataBind.executeUpdate();
public int execute() throws SQLException, OptimisticLockException {
int rowCount = dataBind.executeUpdate();
if (useGeneratedKeys) {
// get the auto-increment value back and set into the bean
getGeneratedKeys();
@@ -136,8 +136,9 @@ public class InsertHandler extends DmlHandler {
fetchGeneratedKeyUsingSelect();
}
checkRowCount(rc);
checkRowCount(rowCount);
executeDerivedRelationships();
return rowCount;
}
protected void executeDerivedRelationships() {
@@ -167,15 +168,7 @@ public class InsertHandler extends DmlHandler {
ResultSet rset = dataBind.getPstmt().getGeneratedKeys();
try {
if (rset.next()) {
Object idValue = rset.getObject(1);
if (idValue != null) {
persistRequest.setGeneratedKey(idValue);
}
} else {
throw new PersistenceException(Message.msg("persist.autoinc.norows"));
}
setGeneratedKey(rset);
} finally {
try {
rset.close();
@@ -186,6 +179,18 @@ public class InsertHandler extends DmlHandler {
}
}
private void setGeneratedKey(ResultSet rset) throws SQLException {
if (rset.next()) {
Object idValue = rset.getObject(1);
if (idValue != null) {
persistRequest.setGeneratedKey(idValue);
}
} else {
throw new PersistenceException(Message.msg("persist.autoinc.norows"));
}
}
/**
* For non batch insert with DBs that do not support getGeneratedKeys. Use a
* SQL select to fetch back the Id value.
@@ -199,30 +204,21 @@ public class InsertHandler extends DmlHandler {
try {
stmt = conn.prepareStatement(selectLastInsertedId);
rset = stmt.executeQuery();
if (rset.next()) {
Object idValue = rset.getObject(1);
if (idValue != null) {
persistRequest.setGeneratedKey(idValue);
}
} else {
throw new PersistenceException(Message.msg("persist.autoinc.norows"));
}
setGeneratedKey(rset);
} finally {
try {
if (rset != null) {
rset.close();
}
} catch (SQLException ex) {
String msg = "Error closing rset for fetchGeneratedKeyUsingSelect?";
logger.warn(msg, ex);
logger.warn("Error closing ResultSet for fetchGeneratedKeyUsingSelect?", ex);
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException ex) {
String msg = "Error closing stmt for fetchGeneratedKeyUsingSelect?";
logger.warn(msg, ex);
logger.warn("Error closing Statement for fetchGeneratedKeyUsingSelect?", ex);
}
}
}
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableDiscriminator;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList;
import java.sql.SQLException;
@@ -18,21 +19,22 @@ import java.sql.SQLException;
public final class InsertMeta {
private final String sqlNullId;
private final String sqlWithId;
private final String sqlDraftNullId;
private final String sqlDraftWithId;
private final BindableId id;
private final Bindable discriminator;
private final Bindable all;
private final BindableList all;
private final BindableList allExcludeDraftOnly;
private final boolean supportsGetGeneratedKeys;
private final boolean concatinatedKey;
private final String tableName;
/**
* Used for DB that do not support getGeneratedKeys.
*/
@@ -44,16 +46,20 @@ public final class InsertMeta {
private final boolean emptyStringToNull;
public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, Bindable all) {
public InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, BindableList all) {
this.emptyStringToNull = dbPlatform.isTreatEmptyStringsAsNull();
this.tableName = desc.getBaseTable();
this.discriminator = getDiscriminator(desc);
this.id = id;
this.all = all;
this.allExcludeDraftOnly = all.excludeDraftOnly();
this.shadowFKey = shadowFKey;
this.sqlWithId = genSql(false);
String tableName = desc.getBaseTable();
String draftTableName = desc.getDraftTable();
this.sqlWithId = genSql(false, tableName, false);
this.sqlDraftWithId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlWithId;
// only available for single Id property
if (id.isConcatenated()) {
@@ -61,6 +67,7 @@ public final class InsertMeta {
this.concatinatedKey = true;
this.identityDbColumns = null;
this.sqlNullId = null;
this.sqlDraftNullId = null;
this.supportsGetGeneratedKeys = false;
this.selectLastInsertedId = null;
@@ -76,7 +83,8 @@ public final class InsertMeta {
this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys();
this.selectLastInsertedId = desc.getSelectLastInsertedId();
}
this.sqlNullId = genSql(true);
this.sqlNullId = genSql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlNullId;
}
}
@@ -99,7 +107,7 @@ public final class InsertMeta {
/**
* Return true if this is a concatenated key.
*/
public boolean isConcatinatedKey() {
public boolean isConcatenatedKey() {
return concatinatedKey;
}
@@ -137,7 +145,7 @@ public final class InsertMeta {
/**
* Bind the request based on whether the id value(s) are null.
*/
public void bind(DmlHandler request, EntityBean bean, boolean withId) throws SQLException {
public void bind(DmlHandler request, EntityBean bean, boolean withId, boolean publish) throws SQLException {
if (withId) {
id.dmlBind(request, bean);
@@ -148,27 +156,31 @@ public final class InsertMeta {
if (discriminator != null) {
discriminator.dmlBind(request, bean);
}
all.dmlBind(request, bean);
if (publish) {
allExcludeDraftOnly.dmlBind(request, bean);
} else {
all.dmlBind(request, bean);
}
}
/**
* get the sql based whether the id value(s) are null.
*/
public String getSql(boolean withId) {
public String getSql(boolean withId, boolean publish) {
if (withId) {
return sqlWithId;
return publish ? sqlWithId : sqlDraftWithId;
} else {
return sqlNullId;
return publish ? sqlNullId : sqlDraftNullId;
}
}
private String genSql(boolean nullId) {
private String genSql(boolean nullId, String table, boolean draftTable) {
GenerateDmlRequest request = new GenerateDmlRequest();
request.setInsertSetMode();
request.append("insert into ").append(tableName);
request.append("insert into ").append(table);
request.append(" (");
if (!nullId) {
@@ -183,7 +195,11 @@ public final class InsertMeta {
discriminator.dmlAppend(request);
}
all.dmlAppend(request);
if (draftTable) {
all.dmlAppend(request);
} else {
allExcludeDraftOnly.dmlAppend(request);
}
request.append(") values (");
request.append(request.getInsertBindBuffer());
@@ -96,7 +96,7 @@ public class MetaFactory {
embeddedFact.create(allList, desc, DmlMode.INSERT, includeLobs);
assocOneFact.create(allList, desc, DmlMode.INSERT);
Bindable allBindable = new BindableList(allList);
BindableList allBindable = new BindableList(allList);
BeanPropertyAssocOne<?> unidirectional = desc.getUnidirectional();
@@ -25,7 +25,7 @@ public interface PersistHandler {
/**
* Execute now for non-batch execution.
*/
void execute() throws SQLException;
int execute() throws SQLException;
/**
* Close resources including underlying preparedStatement.
@@ -67,11 +67,13 @@ public class UpdateHandler extends DmlHandler {
* Execute the update in non-batch.
*/
@Override
public void execute() throws SQLException, OptimisticLockException {
public int execute() throws SQLException, OptimisticLockException {
if (!emptySetClause) {
int rowCount = dataBind.executeUpdate();
checkRowCount(rowCount);
return rowCount;
}
return 0;
}
@Override
@@ -2,11 +2,9 @@ package com.avaje.ebeaninternal.server.persist.dml;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableId;
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableList;
@@ -39,8 +37,8 @@ public final class UpdateMeta {
this.id = id;
this.version = version;
String sqlNone = genSql(ConcurrencyMode.NONE, set);
String sqlVersion = genSql(ConcurrencyMode.VERSION, set);
String sqlNone = genSql(ConcurrencyMode.NONE, set, desc.getBaseTable());
String sqlVersion = genSql(ConcurrencyMode.VERSION, set, desc.getBaseTable());
this.modeNoneUpdatePlan = new UpdatePlan(ConcurrencyMode.NONE, sqlNone, set);
this.modeVersionUpdatePlan = new UpdatePlan(ConcurrencyMode.VERSION, sqlVersion, set);
@@ -106,27 +104,10 @@ public final class UpdateMeta {
private SpiUpdatePlan getDynamicUpdatePlan(PersistRequestBean<?> persistRequest) {
EntityBeanIntercept ebi = persistRequest.getEntityBeanIntercept();
int hash;
if (persistRequest.determineUpdateAllLoadedProperties()) {
hash = ebi.getLoadedPropertyHash();
} else {
hash = ebi.getDirtyPropertyHash();
}
BeanDescriptor<?> beanDescriptor = persistRequest.getBeanDescriptor();
BeanProperty versionProperty = beanDescriptor.getVersionProperty();
if (versionProperty != null) {
if (ebi.isLoadedProperty(versionProperty.getPropertyIndex())) {
hash = hash * 31 + 7;
}
}
Integer key = hash;
int key = persistRequest.getUpdatePlanHash();
// check if we can use a cached UpdatePlan
BeanDescriptor<?> beanDescriptor = persistRequest.getBeanDescriptor();
SpiUpdatePlan updatePlan = beanDescriptor.getUpdatePlan(key);
if (updatePlan != null) {
return updatePlan;
@@ -142,7 +123,7 @@ public final class UpdateMeta {
ConcurrencyMode mode = persistRequest.determineConcurrencyMode();
// build the SQL for this update statement
String sql = genSql(mode, bindableList);
String sql = genSql(mode, bindableList, persistRequest.getUpdateTable());
updatePlan = new UpdatePlan(key, mode, sql, bindableList);
@@ -152,7 +133,7 @@ public final class UpdateMeta {
return updatePlan;
}
private String genSql(ConcurrencyMode conMode, BindableList bindableList) {
private String genSql(ConcurrencyMode conMode, BindableList bindableList, String tableName) {
// update set col0=?, col1=?, col2=? where bcol=? and bc1=? and bc2=?
@@ -39,4 +39,8 @@ public interface Bindable {
*/
void dmlBind(BindableRequest request, EntityBean bean) throws SQLException;
/**
* Return true if the underlying property is 'draft only'.
*/
boolean isDraftOnly();
}
@@ -28,6 +28,11 @@ public class BindableAssocOne implements Bindable {
return "BindableAssocOne " + assocOne;
}
@Override
public boolean isDraftOnly() {
return assocOne.isDraftOnly();
}
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
if (request.isAddToUpdate(assocOne)) {
list.add(this);
@@ -27,6 +27,11 @@ public class BindableCompound implements Bindable {
return "BindableCompound " + compound + " items:" + Arrays.toString(items);
}
@Override
public boolean isDraftOnly() {
return false;
}
public void dmlAppend(GenerateDmlRequest request) {
for (int i = 0; i < items.length; i++) {
@@ -29,6 +29,11 @@ public class BindableDiscriminator implements Bindable {
return columnName + " = " + discValue;
}
@Override
public boolean isDraftOnly() {
return false;
}
@Override
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
throw new PersistenceException("Never called (only for inserts)");
@@ -27,6 +27,11 @@ public class BindableEmbedded implements Bindable {
return "BindableEmbedded " + embProp + " items:" + Arrays.toString(items);
}
@Override
public boolean isDraftOnly() {
return embProp.isDraftOnly();
}
public void dmlAppend(GenerateDmlRequest request) {
for (int i = 0; i < items.length; i++) {
@@ -27,6 +27,11 @@ public class BindableEncryptedProperty implements Bindable {
return prop.toString();
}
@Override
public boolean isDraftOnly() {
return prop.isDraftOnly();
}
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
if (request.isAddToUpdate(prop)) {
list.add(this);
@@ -30,6 +30,11 @@ public final class BindableIdEmbedded implements BindableId {
matches = MatchedImportedProperty.build(props, desc);
}
@Override
public boolean isDraftOnly() {
return false;
}
public boolean isEmpty() {
return false;
}
@@ -13,6 +13,11 @@ public class BindableIdEmpty implements BindableId {
return true;
}
@Override
public boolean isDraftOnly() {
return false;
}
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
// nothing
}
@@ -38,6 +38,11 @@ public final class BindableIdScalar implements BindableId {
return uidProp.toString();
}
@Override
public boolean isDraftOnly() {
return false;
}
/**
* Does nothing for BindableId.
*/
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.persist.dmlbind;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebean.bean.EntityBean;
@@ -18,6 +19,24 @@ public class BindableList implements Bindable {
items = list.toArray(new Bindable[list.size()]);
}
/**
* Return a bindable list that excludes @DraftOnly properties.
*/
public BindableList excludeDraftOnly() {
List<Bindable> copy = new ArrayList<Bindable>(items.length);
for (Bindable b : items) {
if (!b.isDraftOnly()) {
copy.add(b);
}
}
return new BindableList(copy);
}
@Override
public boolean isDraftOnly() {
return false;
}
public void addAll(List<Bindable> list) {
for (int i = 0; i < items.length; i++) {
list.add(items[i]);
@@ -23,6 +23,11 @@ public class BindableProperty implements Bindable {
return prop.toString();
}
@Override
public boolean isDraftOnly() {
return prop.isDraftOnly();
}
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
if (request.isAddToUpdate(prop)) {
list.add(this);
@@ -38,6 +38,11 @@ public class BindableUnidirectional implements Bindable {
return "BindableShadowFKey " + unidirectional;
}
@Override
public boolean isDraftOnly() {
return false;
}
public void addToUpdate(PersistRequestBean<?> request, List<Bindable> list) {
throw new PersistenceException("Never called (for insert only)");
}
@@ -234,6 +234,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
}
}
@Override
public boolean isDraftQuery() {
return query.isAsDraft();
}
public Boolean isReadOnly() {
return readOnly;
}

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