mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7818e7f23 | ||
|
|
8318bbc4ca | ||
|
|
337646a6ce | ||
|
|
fa6ee93e5b | ||
|
|
0fe691112f | ||
|
|
6946ff07e0 | ||
|
|
7c3d052da0 | ||
|
|
aea7a7fd65 | ||
|
|
f39a684c8e | ||
|
|
3cb5854786 | ||
|
|
e8192723cc | ||
|
|
3787748eae | ||
|
|
0ad52cf6a7 | ||
|
|
51bee836d4 | ||
|
|
9193b6b971 | ||
|
|
e48b9fab0f | ||
|
|
a14ca24b33 | ||
|
|
761f43d6a1 | ||
|
|
737f9a893e | ||
|
|
579812d717 | ||
|
|
fae39fe650 | ||
|
|
debd8ad617 | ||
|
|
6450ba219c | ||
|
|
a578eda2b1 | ||
|
|
a2ee0bfdbe | ||
|
|
872f0e21cb | ||
|
|
8620dbf337 | ||
|
|
4f51a3b130 | ||
|
|
5f194f066a | ||
|
|
31f6e56333 | ||
|
|
0efbe061ab | ||
|
|
555e5ba6fd | ||
|
|
5d3da60828 | ||
|
|
2ac0d136e6 | ||
|
|
97462cb21a | ||
|
|
afaacd4eeb | ||
|
|
5e8afbe50d | ||
|
|
db371cbc6a | ||
|
|
fd9a5167b1 | ||
|
|
7b76e13bce | ||
|
|
42e7103aa6 | ||
|
|
29ca678f01 | ||
|
|
cca6143420 | ||
|
|
fe7b110577 | ||
|
|
16df55eafa | ||
|
|
905a0ce9ec | ||
|
|
bfa8837e61 | ||
|
|
8d5845b9a2 | ||
|
|
87a7d4958e | ||
|
|
511ebd8a29 | ||
|
|
11f4c8d7a3 | ||
|
|
d417a92843 | ||
|
|
4bd5e52df3 | ||
|
|
788a200d16 | ||
|
|
e18d2004dc | ||
|
|
b326724139 | ||
|
|
fc0b47342b | ||
|
|
ec1a68a133 | ||
|
|
8ca12521d7 | ||
|
|
2362ef4130 | ||
|
|
7e233edcc3 | ||
|
|
a8b98282a1 | ||
|
|
b4506aa718 | ||
|
|
848a3b8888 | ||
|
|
67c711d75f | ||
|
|
3d62f58930 | ||
|
|
a30948d38d | ||
|
|
a8232d2d8b | ||
|
|
d4b8f23a5a | ||
|
|
bcf2a44187 | ||
|
|
f3102ba02c | ||
|
|
0351eab3a7 | ||
|
|
409e2b75ce | ||
|
|
dc968f16bf | ||
|
|
bbbb6482fb |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.9.1</version>
|
||||
<version>6.13.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
@@ -61,6 +61,14 @@
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for Spring boot loader classpath scanning support -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-loader</artifactId>
|
||||
<version>1.2.7.RELEASE</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@@ -220,7 +228,7 @@
|
||||
<configuration>
|
||||
<classSource>target/test-classes</classSource>
|
||||
<packages>com.avaje.tests.**</packages>
|
||||
<transformArgs>debug=2</transformArgs>
|
||||
<transformArgs>debug=1</transformArgs>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>enhance</goal>
|
||||
|
||||
@@ -697,12 +697,31 @@ 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 is configured with <code>@SoftDelete</code> then this will perform a soft
|
||||
* delete rather than a hard/permanent delete.
|
||||
* </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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the bean in permanent fashion (will not use soft delete).
|
||||
*/
|
||||
public static boolean deletePermanent(Object bean) throws OptimisticLockException {
|
||||
return serverMgr.getDefaultServer().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -726,6 +745,13 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().deleteAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete permanent all the beans in the Collection (will not use soft delete).
|
||||
*/
|
||||
public static int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException {
|
||||
return serverMgr.getDefaultServer().deleteAllPermanent(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the values of a bean.
|
||||
* <p>
|
||||
|
||||
@@ -1246,16 +1246,52 @@ 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 a bean permanently without soft delete.
|
||||
*/
|
||||
boolean deletePermanent(Object bean) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete using an explicit transaction.
|
||||
*/
|
||||
boolean deletePermanent(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection permanently without soft delete.
|
||||
*/
|
||||
int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection permanently without soft delete using an explicit transaction.
|
||||
*/
|
||||
int deleteAllPermanent(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete the bean given its type and id.
|
||||
@@ -1456,7 +1492,7 @@ public interface EbeanServer {
|
||||
void markAsDirty(Object bean);
|
||||
|
||||
/**
|
||||
* Saves the bean using an update. If you know you are updating a bean then it is preferrable to
|
||||
* Saves the bean using an update. If you know you are updating a bean then it is preferable to
|
||||
* use this update() method rather than save().
|
||||
* <p>
|
||||
* <b>Stateless updates:</b> Note that the bean does not have to be previously fetched to call
|
||||
@@ -1508,7 +1544,6 @@ public interface EbeanServer {
|
||||
* @param deleteMissingChildren
|
||||
* specify false if you do not want 'missing children' of a OneToMany
|
||||
* or ManyToMany to be automatically deleted.
|
||||
|
||||
*/
|
||||
void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException;
|
||||
|
||||
@@ -1819,4 +1854,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);
|
||||
}
|
||||
|
||||
@@ -107,6 +107,16 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Execute the query against the draft set of tables.
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Execute the query including soft deleted rows.
|
||||
*/
|
||||
Query<T> includeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -297,6 +297,16 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Execute the query against the draft set of tables.
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Execute the query including soft deleted rows.
|
||||
*/
|
||||
Query<T> includeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -1313,4 +1323,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();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import java.lang.annotation.Target;
|
||||
* For a timestamp property that is set to the datetime when the entity is
|
||||
* created/inserted.
|
||||
* <p>
|
||||
* This is effectively an alias for @WhenCreated which was added as it hints
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
* <p>
|
||||
* An alternative to using this annotation would be to use insertable=false,
|
||||
* updateable=false with @Column and have the DB insert the current time
|
||||
* (default value on the DB column is SYSTIME etc).
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
/**
|
||||
* Specify the DB storage type used to with <code>@DbEnumValue</code>.
|
||||
*/
|
||||
public enum DbEnumType {
|
||||
|
||||
/**
|
||||
* Store values as database INTEGER.
|
||||
*/
|
||||
INTEGER,
|
||||
|
||||
/**
|
||||
* Store values as database VARCHAR.
|
||||
*/
|
||||
VARCHAR
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify a method on an Enum that returns the value that should be stored in the DB.
|
||||
* <p>
|
||||
* This is the preferred option for mapping Enum's to DB values (preferred over the JPA
|
||||
* standard @Enumerated and Ebean's @EnumValue annotations).
|
||||
* </p>
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public enum Status {
|
||||
* NEW("N"),
|
||||
* ACTIVE("A"),
|
||||
* INACTIVE("I");
|
||||
*
|
||||
* String dbValue;
|
||||
* Status(String dbValue) {
|
||||
* this.dbValue = dbValue;
|
||||
* }
|
||||
*
|
||||
* @DbEnumValue
|
||||
* public String getValue() {
|
||||
* return dbValue;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface DbEnumValue {
|
||||
|
||||
/**
|
||||
* Specify the database type used to store the values (VARCHAR or INTEGER).
|
||||
*/
|
||||
DbEnumType storage() default DbEnumType.VARCHAR;
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
|
||||
* }</pre>
|
||||
* <p>
|
||||
* This is an alternative to using the JPA standard approach or Ebean's
|
||||
* {@link EnumMapping} annotation.
|
||||
* {@link DbEnumValue} annotation.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that if all the EnumValue values are parsable as Integers then Ebean
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
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 a property on an entity bean used to control 'soft delete'
|
||||
* (also known as 'logical delete').
|
||||
* <p>
|
||||
* The property should be of type boolean.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @SoftDelete
|
||||
* boolean deleted;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface SoftDelete {
|
||||
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* For a timestamp property that is set to the datetime when the entity was last
|
||||
* updated.
|
||||
* <p>
|
||||
* This is effectively an alias for @WhenModified which was added as it hints
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.lang.annotation.Target;
|
||||
* This is effectively an alias for @UpdatedTimestamp and added to hint
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -507,7 +507,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
private void setChangedProperty(int propertyIndex) {
|
||||
public void setChangedProperty(int propertyIndex) {
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
|
||||
@@ -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,6 +18,14 @@ public final class PropertyMap implements Serializable {
|
||||
|
||||
private final LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Return properties loaded from test-ebean.properties.
|
||||
*/
|
||||
public static Properties testProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
}
|
||||
|
||||
public static Properties defaultProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
|
||||
@@ -14,17 +14,34 @@ final class PropertyMapLoader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
|
||||
|
||||
/**
|
||||
* Load the <code>test-ebean.properties</code>.
|
||||
*/
|
||||
public static PropertyMap loadTestProperties() {
|
||||
return load(null, "test-ebean.properties");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the ebean.properties (and test-ebean.properties if present).
|
||||
*/
|
||||
public static PropertyMap loadGlobalProperties() {
|
||||
|
||||
boolean loadTestProperties = false;
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName == null) {
|
||||
loadTestProperties = true;
|
||||
fileName = "ebean.properties";
|
||||
}
|
||||
}
|
||||
|
||||
return load(null, fileName);
|
||||
PropertyMap map = load(null, fileName);
|
||||
if (loadTestProperties) {
|
||||
// load test properties if present in classpath
|
||||
map = load(map, "test-ebean.properties");
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +56,6 @@ final class PropertyMapLoader {
|
||||
|
||||
InputStream is = findInputStream(fileName);
|
||||
if (is == null) {
|
||||
logger.error(fileName + " not found");
|
||||
return p;
|
||||
} else {
|
||||
return load(p, is);
|
||||
@@ -47,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.
|
||||
|
||||
@@ -281,9 +281,9 @@ public class ServerConfig {
|
||||
private boolean updatesDeleteMissingChildren = true;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40).
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private boolean uuidStoreAsBinary;
|
||||
private DbUuid dbUuid = DbUuid.AUTO;
|
||||
|
||||
|
||||
private List<BeanFindController> findControllers = new ArrayList<BeanFindController>();
|
||||
@@ -1466,19 +1466,18 @@ public class ServerConfig {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if UUID should be stored as binary(16) (as opposed to varchar(40)).
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public boolean isUuidStoreAsBinary() {
|
||||
return uuidStoreAsBinary;
|
||||
public DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if UUID should be stored as binary(16) (as opposed to varchar(40)).
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setUuidStoreAsBinary(boolean uuidStoreAsBinary) {
|
||||
this.uuidStoreAsBinary = uuidStoreAsBinary;
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2041,6 +2040,21 @@ public class ServerConfig {
|
||||
loadSettings(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from test-ebean.properties and do nothing if the properties is not found.
|
||||
* <p>
|
||||
* This is typically used when test-ebean.properties is put into the test class path and used
|
||||
* to configure Ebean for running tests.
|
||||
* </p>
|
||||
*/
|
||||
public void loadTestProperties() {
|
||||
Properties properties = PropertyMap.testProperties();
|
||||
if (!properties.isEmpty()) {
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
|
||||
*/
|
||||
@@ -2049,8 +2063,11 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key) {
|
||||
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = p.get(key, null);
|
||||
return classname == null ? null : (T) ClassUtil.newInstance(classname);
|
||||
}
|
||||
@@ -2098,15 +2115,15 @@ public class ServerConfig {
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider");
|
||||
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
|
||||
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager");
|
||||
encryptor = createInstance(p, Encryptor.class, "encryptor");
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt");
|
||||
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory");
|
||||
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager");
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
|
||||
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
|
||||
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
|
||||
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory", serverCacheFactory);
|
||||
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager);
|
||||
cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay);
|
||||
classPathReaderClassName = p.get("classpathreader");
|
||||
|
||||
@@ -2152,7 +2169,10 @@ public class ServerConfig {
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
|
||||
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", uuidStoreAsBinary);
|
||||
dbUuid = p.getEnum(DbUuid.class, "dbuuid", dbUuid);
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
dbUuid = DbUuid.BINARY;
|
||||
}
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
|
||||
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
|
||||
@@ -2174,7 +2194,7 @@ public class ServerConfig {
|
||||
|
||||
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
|
||||
|
||||
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention");
|
||||
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention", null);
|
||||
return (nc != null) ? nc : namingConvention;
|
||||
}
|
||||
|
||||
@@ -2274,4 +2294,25 @@ public class ServerConfig {
|
||||
public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) {
|
||||
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
*/
|
||||
public enum DbUuid {
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres.
|
||||
*/
|
||||
AUTO,
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR.
|
||||
*/
|
||||
VARCHAR,
|
||||
|
||||
/**
|
||||
* Store using DB BINARY.
|
||||
*/
|
||||
BINARY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Database platform specific settings.
|
||||
*/
|
||||
@@ -54,28 +52,33 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
*/
|
||||
protected String openQuote = "\"";
|
||||
|
||||
/**
|
||||
* The close quote used by quoted identifiers.
|
||||
* The close quote used by quoted identifiers.
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
|
||||
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
* Set to true if the DB has native UUID type support.
|
||||
*/
|
||||
protected boolean nativeUuidType;
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
@@ -84,28 +87,28 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected DbHistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
protected int booleanDbType = Types.BOOLEAN;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
*/
|
||||
protected int blobDbType = Types.BLOB;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
*/
|
||||
protected int clobDbType = Types.CLOB;
|
||||
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
*/
|
||||
protected boolean treatEmptyStringsAsNull;
|
||||
|
||||
/**
|
||||
* The database platform name.
|
||||
/**
|
||||
* The database platform name.
|
||||
*/
|
||||
protected String name = "generic";
|
||||
|
||||
@@ -124,7 +127,7 @@ public class DatabasePlatform {
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
protected boolean idInExpandedForm;
|
||||
@@ -219,16 +222,12 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return a DB Sequence based IdGenerator.
|
||||
*
|
||||
* @param be
|
||||
* the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds
|
||||
* the DataSource
|
||||
* @param seqName
|
||||
* the name of the sequence
|
||||
* @param batchSize
|
||||
* the number of sequences that should be loaded
|
||||
*
|
||||
* @param be the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds the DataSource
|
||||
* @param seqName the name of the sequence
|
||||
* @param batchSize the number of sequences that should be loaded
|
||||
*/
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
return null;
|
||||
@@ -276,9 +275,16 @@ public class DatabasePlatform {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB supports native UUID.
|
||||
*/
|
||||
public boolean isNativeUuidType() {
|
||||
return nativeUuidType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
*
|
||||
* @return the db type map
|
||||
*/
|
||||
public DbTypeMap getDbTypeMap() {
|
||||
@@ -315,7 +321,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
@@ -324,7 +330,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
@@ -333,7 +339,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
@@ -363,7 +369,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return true if empty strings should be treated as null.
|
||||
*
|
||||
*
|
||||
* @return true, if checks if is treat empty strings as null
|
||||
*/
|
||||
public boolean isTreatEmptyStringsAsNull() {
|
||||
@@ -399,7 +405,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
*
|
||||
* @return the db identity
|
||||
*/
|
||||
public DbIdentity getDbIdentity() {
|
||||
@@ -412,7 +418,7 @@ public class DatabasePlatform {
|
||||
* <p>
|
||||
* Basically add the clauses for limit/offset, rownum, row_number().
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return the sql limiter
|
||||
*/
|
||||
public SqlLimiter getSqlLimiter() {
|
||||
@@ -421,15 +427,12 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Convert backticks to the platform specific open quote and close quote
|
||||
*
|
||||
* <p>
|
||||
* Specific plugins may implement this method to cater for platform specific
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName
|
||||
* the db name
|
||||
*
|
||||
*
|
||||
* @param dbName the db name
|
||||
* @return the string
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
|
||||
@@ -8,6 +8,11 @@ package com.avaje.ebean.config.dbplatform;
|
||||
*/
|
||||
public class DbType {
|
||||
|
||||
/**
|
||||
* DB native UUID type (H2 and Postgres).
|
||||
*/
|
||||
public static final int UUID = 5010;
|
||||
|
||||
/**
|
||||
* Type to map Map content to Postgres HSTORE.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,8 @@ public class DbTypeMap {
|
||||
lookup.put("TIME", Types.TIME);
|
||||
lookup.put("TIMESTAMP", Types.TIMESTAMP);
|
||||
|
||||
lookup.put("UUID", DbType.UUID);
|
||||
|
||||
// Not standard java.sql.Types
|
||||
// logical JSON storage types
|
||||
lookup.put("JSON", DbType.JSON);
|
||||
@@ -101,6 +103,9 @@ public class DbTypeMap {
|
||||
put(Types.BLOB, new DbType("blob"));
|
||||
put(Types.CLOB, new DbType("clob"));
|
||||
|
||||
// DB native UUID support (H2 and Postgres)
|
||||
put(DbType.UUID, new DbType("uuid"));
|
||||
|
||||
if (logicalTypes) {
|
||||
// keep it logical for 2 layer DDL generation
|
||||
put(DbType.HSTORE, new DbType("hstore"));
|
||||
|
||||
@@ -16,6 +16,7 @@ public class H2Platform extends DatabasePlatform {
|
||||
this.dbEncrypt = new H2DbEncrypt();
|
||||
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
|
||||
this.historySupport = new H2HistorySupport();
|
||||
this.nativeUuidType = true;
|
||||
|
||||
// only support getGeneratedKeys with non-batch JDBC
|
||||
// so generally use SEQUENCE instead of IDENTITY for H2
|
||||
|
||||
@@ -23,10 +23,10 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
this.nativeUuidType = true;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
this.historySupport = new PostgresHistorySupport();
|
||||
|
||||
+2
-2
@@ -81,12 +81,12 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
|
||||
.append(" return new;").newLine().newLine();
|
||||
.append(" return new;").newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" return old;").newLine().newLine();
|
||||
.append(" return old;").newLine();
|
||||
apply
|
||||
.append(" end if;").newLine()
|
||||
.append("end;").newLine()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.List;
|
||||
*/
|
||||
public class MCompoundForeignKey {
|
||||
|
||||
private final String name;
|
||||
private String name;
|
||||
private final String referenceTable;
|
||||
private final List<String> columns = new ArrayList<String>();
|
||||
private final List<String> referenceColumns = new ArrayList<String>();
|
||||
@@ -47,6 +47,28 @@ public class MCompoundForeignKey {
|
||||
return fk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a counter to the foreign key and index names to avoid duplication.
|
||||
*/
|
||||
public void addNameSuffix(int counter) {
|
||||
this.name = name + "_" + counter;
|
||||
this.indexName = indexName + "_" + counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign key name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name.
|
||||
*/
|
||||
public String getIndexName() {
|
||||
return indexName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns making up the foreign key in order.
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -42,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.
|
||||
*/
|
||||
@@ -106,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.
|
||||
@@ -163,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) {
|
||||
@@ -270,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;
|
||||
}
|
||||
@@ -482,4 +525,70 @@ public class MTable {
|
||||
return (value == 0) ? null : BigInteger.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are duplicate foreign keys.
|
||||
* <p>
|
||||
* This can occur when an ManyToMany relates back to itself.
|
||||
* </p>
|
||||
*/
|
||||
public void checkDuplicateForeignKeys() {
|
||||
|
||||
if (hasDuplicateForeignKeys()) {
|
||||
int counter = 1;
|
||||
for (MCompoundForeignKey fk : compoundKeys) {
|
||||
fk.addNameSuffix(counter++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the foreign key names are not unique.
|
||||
*/
|
||||
private boolean hasDuplicateForeignKeys() {
|
||||
Set<String> fkNames = new HashSet<String>();
|
||||
for (MCompoundForeignKey fk : compoundKeys) {
|
||||
if (!fkNames.add(fk.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
@@ -213,8 +226,8 @@ public class ModelContainer {
|
||||
/**
|
||||
* Add a table (typically from reading EbeanServer meta data).
|
||||
*/
|
||||
public void addTable(MTable table) {
|
||||
tables.put(table.getName(), table);
|
||||
public MTable addTable(MTable table) {
|
||||
return tables.put(table.getName(), table);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -67,8 +78,8 @@ public class ModelBuildContext {
|
||||
return maxLength(constraintNaming.checkConstraintName(tableName, columnName), checkCount);
|
||||
}
|
||||
|
||||
public void addTable(MTable table) {
|
||||
model.addTable(table);
|
||||
public MTable addTable(MTable table) {
|
||||
return model.addTable(table);
|
||||
}
|
||||
|
||||
public void addIndex(String indexName, String tableName, String columnName) {
|
||||
@@ -120,4 +131,38 @@ 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;
|
||||
int uqCount = 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));
|
||||
}
|
||||
// adjust the unique constraint names
|
||||
if (col.getUnique() != null){
|
||||
col.setUnique(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
|
||||
}
|
||||
if (col.getUniqueOneToOne() != null){
|
||||
col.setUniqueOneToOne(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
|
||||
}
|
||||
}
|
||||
|
||||
addTable(draftTable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-1
@@ -35,9 +35,18 @@ public class ModelBuildIntersectionTable {
|
||||
public void build() {
|
||||
|
||||
intersectionTable = createTable();
|
||||
ctx.addTable(intersectionTable);
|
||||
MTable existingTable = ctx.addTable(intersectionTable);
|
||||
if (existingTable != null) {
|
||||
throw new IllegalStateException("Property " + manyProp.getFullBeanName() + " has duplicate ManyToMany intersection table " + intersectionTable.getName()
|
||||
+ ". Please use @JoinTable to define unique table to use");
|
||||
}
|
||||
|
||||
buildFkConstraints();
|
||||
|
||||
if (manyProp.getTargetDescriptor().isDraftable()) {
|
||||
ctx.createDraft(intersectionTable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void buildFkConstraints() {
|
||||
@@ -47,6 +56,8 @@ public class ModelBuildIntersectionTable {
|
||||
|
||||
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
|
||||
buildFkConstraints(targetDesc, tableJoin.columns(), false);
|
||||
|
||||
intersectionTable.checkDuplicateForeignKeys();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+21
-3
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -59,11 +59,6 @@ public class ScopedTransaction implements SpiTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endQueryOnly() {
|
||||
transaction.endQueryOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return transaction.getLogPrefix();
|
||||
|
||||
@@ -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,15 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
}
|
||||
|
||||
enum TemporalMode {
|
||||
/**
|
||||
* Includes soft deletes rows in the result.
|
||||
*/
|
||||
SOFT_DELETED,
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
DRAFT,
|
||||
|
||||
/**
|
||||
* Query runs against current data (normal).
|
||||
*/
|
||||
@@ -171,6 +182,16 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
boolean isAsOfQuery();
|
||||
|
||||
/**
|
||||
* Return true if this is a 'As Draft' query.
|
||||
*/
|
||||
boolean isAsDraft();
|
||||
|
||||
/**
|
||||
* Return true if this query includes soft deleted rows.
|
||||
*/
|
||||
boolean isIncludeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Return the asOf Timestamp which the query should run as.
|
||||
*/
|
||||
@@ -186,6 +207,10 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
List<String> getAsOfTableAlias();
|
||||
|
||||
void addSoftDeletePredicate(String softDeletePredicate);
|
||||
|
||||
List<String> getSoftDeletePredicates();
|
||||
|
||||
/**
|
||||
* Return a listener that wants to be notified when the bean collection is
|
||||
* first used.
|
||||
@@ -684,4 +709,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);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@ import com.avaje.ebeaninternal.server.persist.BatchControl;
|
||||
*/
|
||||
public interface SpiTransaction extends Transaction {
|
||||
|
||||
/**
|
||||
* End the transaction when had query only use.
|
||||
*/
|
||||
void endQueryOnly();
|
||||
|
||||
/**
|
||||
* Return the string prefix with the transactin id and label used in logging.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
@@ -1301,6 +1310,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.DELETE, query, t);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
request.markNotQueryOnly();
|
||||
return request.delete();
|
||||
} finally {
|
||||
request.endTransIfRequired();
|
||||
@@ -1624,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"));
|
||||
@@ -1797,16 +1876,35 @@ 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) {
|
||||
|
||||
persister.delete(checkEntityBean(bean), t);
|
||||
public boolean delete(Object bean, Transaction t) throws OptimisticLockException {
|
||||
return persister.delete(checkEntityBean(bean), t, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePermanent(Object bean) throws OptimisticLockException {
|
||||
return deletePermanent(bean, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deletePermanent(Object bean, Transaction t) throws OptimisticLockException {
|
||||
return persister.delete(checkEntityBean(bean), t, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteAllPermanent(Collection<?> beans) {
|
||||
return deleteAllInternal(beans.iterator(), null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteAllPermanent(Collection<?> beans, Transaction t) {
|
||||
return deleteAllInternal(beans.iterator(), t, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1814,7 +1912,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public int deleteAll(Collection<?> beans) {
|
||||
return deleteAllInternal(beans.iterator(), null);
|
||||
return deleteAllInternal(beans.iterator(), null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1822,13 +1920,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public int deleteAll(Collection<?> beans, Transaction t) {
|
||||
return deleteAllInternal(beans.iterator(), t);
|
||||
return deleteAllInternal(beans.iterator(), t, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the beans in the iterator with an explicit transaction.
|
||||
*/
|
||||
private int deleteAllInternal(Iterator<?> it, Transaction t) {
|
||||
private int deleteAllInternal(Iterator<?> it, Transaction t, boolean permanent) {
|
||||
|
||||
TransWrapper wrap = initTransIfRequired(t);
|
||||
|
||||
@@ -1838,7 +1936,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
int deleteCount = 0;
|
||||
while (it.hasNext()) {
|
||||
EntityBean bean = checkEntityBean(it.next());
|
||||
persister.delete(bean, trans);
|
||||
persister.delete(bean, trans, permanent);
|
||||
deleteCount++;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -72,6 +72,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
this.readOnly = query.isReadOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the transaction as not being query only.
|
||||
*/
|
||||
@Override
|
||||
public void markNotQueryOnly() {
|
||||
transaction.markNotQueryOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database platform like clause.
|
||||
@@ -219,7 +226,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
*/
|
||||
public void endTransIfRequired() {
|
||||
if (createdTransaction) {
|
||||
transaction.endQueryOnly();
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.avaje.ebeaninternal.server.persist.PersistExecute;
|
||||
public abstract class PersistRequest extends BeanRequest implements BatchPostExecute {
|
||||
|
||||
public enum Type {
|
||||
INSERT, UPDATE, DELETE, UPDATESQL, CALLABLESQL
|
||||
INSERT, UPDATE, DELETE, SOFT_DELETE, DELETE_PERMANENT, UPDATESQL, CALLABLESQL
|
||||
}
|
||||
|
||||
protected boolean persistCascade;
|
||||
|
||||
@@ -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,18 @@ 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:
|
||||
case SOFT_DELETE:
|
||||
// Bean deleted from cache early via postDelete()
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid type " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,6 +422,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,27 +468,39 @@ 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 SOFT_DELETE:
|
||||
prepareForSoftDelete();
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + type);
|
||||
case DELETE:
|
||||
return persistExecute.executeDeleteBean(this);
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete is executed as update so we want to set deleted=true property.
|
||||
*/
|
||||
private void prepareForSoftDelete() {
|
||||
|
||||
beanDescriptor.setSoftDeleteValue(entityBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
|
||||
@@ -508,13 +541,18 @@ 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:
|
||||
case SOFT_DELETE:
|
||||
postDelete();
|
||||
break;
|
||||
case UPDATE:
|
||||
postUpdate();
|
||||
break;
|
||||
default: // do nothing
|
||||
}
|
||||
}
|
||||
@@ -571,35 +609,40 @@ 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:
|
||||
case SOFT_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;
|
||||
case SOFT_DELETE:
|
||||
transaction.logSummary("SoftDelete [" + name + "] [" + idValue + "]" + draft);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,6 +710,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 +764,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 +780,51 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a soft delete request.
|
||||
*/
|
||||
public boolean isSoftDelete() {
|
||||
return Type.SOFT_DELETE == type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, boolean permanent);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public final class RelationalQueryRequest {
|
||||
*/
|
||||
public void endTransIfRequired() {
|
||||
if (createdTransaction) {
|
||||
trans.endQueryOnly();
|
||||
trans.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,4 +112,8 @@ public interface SpiOrmQueryRequest<T> {
|
||||
*/
|
||||
String getDBLikeClause();
|
||||
|
||||
/**
|
||||
* Mark the underlying transaction as not being query only.
|
||||
*/
|
||||
void markNotQueryOnly();
|
||||
}
|
||||
@@ -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,22 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
private final String baseTableVersionsBetween;
|
||||
private final boolean historySupport;
|
||||
|
||||
private final BeanProperty softDeleteProperty;
|
||||
private final boolean softDelete;
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -306,8 +319,9 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
private String idBinderIdSql;
|
||||
|
||||
private String deleteByIdSql;
|
||||
|
||||
private String deleteByIdInSql;
|
||||
private String softDeleteByIdSql;
|
||||
private String softDeleteByIdInSql;
|
||||
|
||||
private final String name;
|
||||
|
||||
@@ -319,7 +333,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 +387,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();
|
||||
@@ -381,8 +399,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
// helper object used to derive lists of properties
|
||||
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
|
||||
|
||||
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
|
||||
this.softDelete = (softDeleteProperty != null);
|
||||
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 +434,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 +547,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 +583,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);
|
||||
@@ -597,6 +627,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql;
|
||||
deleteByIdInSql = "delete from " + baseTable + " where " + idBinderInLHSSqlNoAlias + " ";
|
||||
|
||||
if (softDelete) {
|
||||
softDeleteByIdSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idEqualsSql;
|
||||
softDeleteByIdInSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idBinderInLHSSqlNoAlias + " ";
|
||||
} else {
|
||||
softDeleteByIdSql = null;
|
||||
softDeleteByIdInSql = null;
|
||||
}
|
||||
|
||||
if (!isEmbedded()) {
|
||||
// parse every named update up front into sql dml
|
||||
for (DeployNamedUpdate namedUpdate : namedUpdates.values()) {
|
||||
@@ -645,6 +683,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
case INSERT:
|
||||
return changeLogFilter.includeInsert(request) ? insertBeanChange(request): null;
|
||||
case UPDATE:
|
||||
case SOFT_DELETE:
|
||||
return changeLogFilter.includeUpdate(request) ? updateBeanChange(request): null;
|
||||
case DELETE:
|
||||
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) :null;
|
||||
@@ -686,11 +725,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
cacheHelp.initialise();
|
||||
}
|
||||
|
||||
public SqlUpdate deleteById(Object id, List<Object> idList) {
|
||||
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
|
||||
if (id != null) {
|
||||
return deleteById(id);
|
||||
return deleteById(id, softDelete);
|
||||
} else {
|
||||
return deleteByIdList(idList);
|
||||
return deleteByIdList(idList, softDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,9 +744,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
* Return SQL that can be used to delete a list of Id's without any optimistic
|
||||
* concurrency checking.
|
||||
*/
|
||||
private SqlUpdate deleteByIdList(List<Object> idList) {
|
||||
private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(deleteByIdInSql);
|
||||
String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql;
|
||||
StringBuilder sb = new StringBuilder(baseSql);
|
||||
String inClause = idBinder.getIdInValueExprDelete(idList.size());
|
||||
sb.append(inClause);
|
||||
|
||||
@@ -722,9 +762,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
* Return SQL that can be used to delete by Id without any optimistic
|
||||
* concurrency checking.
|
||||
*/
|
||||
private SqlUpdate deleteById(Object id) {
|
||||
private SqlUpdate deleteById(Object id, boolean softDelete) {
|
||||
|
||||
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByIdSql);
|
||||
String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql;
|
||||
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
|
||||
|
||||
Object[] bindValues = idBinder.getBindValues(id);
|
||||
for (int i = 0; i < bindValues.length; i++) {
|
||||
@@ -810,6 +851,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 +890,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();
|
||||
}
|
||||
|
||||
@@ -1129,6 +1192,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return deleteRecurseSkippable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if delete can use a single SQL statement.
|
||||
*
|
||||
* This implies cascade delete does not continue depth wise and that this is no
|
||||
* associated L2 bean caching.
|
||||
*/
|
||||
public boolean isDeleteByStatement() {
|
||||
return deleteRecurseSkippable && !isBeanCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a property annotated with @WhenCreated or @CreatedTimestamp.
|
||||
*/
|
||||
@@ -1556,6 +1629,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 +1938,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 +1959,58 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
public void setSoftDeleteValue(EntityBean bean) {
|
||||
softDeleteProperty.setSoftDeleteValue(bean);
|
||||
}
|
||||
|
||||
public String getSoftDeleteDbSet() {
|
||||
return softDeleteProperty.getSoftDeleteDbSet();
|
||||
}
|
||||
|
||||
public String getSoftDeletePredicate(String tableAlias) {
|
||||
return softDeleteProperty.getSoftDeleteDbPredicate(tableAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +2103,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);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.server.text.json.ReadJson;
|
||||
import com.avaje.ebeaninternal.server.text.json.WriteJson;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean;
|
||||
import com.avaje.ebeaninternal.util.ValueUtil;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import org.slf4j.Logger;
|
||||
@@ -223,6 +224,18 @@ public class BeanProperty implements ElPropertyValue {
|
||||
|
||||
final boolean jsonDeserialize;
|
||||
|
||||
final boolean draftOnly;
|
||||
|
||||
final boolean draftDirty;
|
||||
|
||||
final boolean draftReset;
|
||||
|
||||
final boolean softDelete;
|
||||
|
||||
final String softDeleteDbSet;
|
||||
|
||||
final String softDeleteDbPredicate;
|
||||
|
||||
final boolean indexed;
|
||||
|
||||
final String indexName;
|
||||
@@ -249,7 +262,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) {
|
||||
this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin());
|
||||
@@ -293,6 +308,16 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
|
||||
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
|
||||
|
||||
this.softDelete = deploy.isSoftDelete();
|
||||
if (softDelete) {
|
||||
ScalarTypeBoolean.BooleanBase boolType = (ScalarTypeBoolean.BooleanBase)scalarType;
|
||||
this.softDeleteDbSet = dbColumn+"="+boolType.getDbTrueLiteral();
|
||||
this.softDeleteDbPredicate = dbColumn+"="+boolType.getDbFalseLiteral();
|
||||
} else {
|
||||
this.softDeleteDbSet = null;
|
||||
this.softDeleteDbPredicate = null;
|
||||
}
|
||||
|
||||
this.jsonSerialize = deploy.isJsonSerialize();
|
||||
this.jsonDeserialize = deploy.isJsonDeserialize();
|
||||
}
|
||||
@@ -333,6 +358,12 @@ 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.softDelete = source.softDelete;
|
||||
this.softDeleteDbSet = source.softDeleteDbSet;
|
||||
this.softDeleteDbPredicate = source.softDeleteDbPredicate;
|
||||
this.fetchEager = source.fetchEager;
|
||||
this.unidirectionalShadow = source.unidirectionalShadow;
|
||||
this.discriminator = source.discriminator;
|
||||
@@ -493,7 +524,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 +623,41 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB literal expression to set the deleted state to true.
|
||||
*/
|
||||
public String getSoftDeleteDbSet() {
|
||||
return softDeleteDbSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB literal predicate used to filter out soft deleted rows from a query.
|
||||
*/
|
||||
public String getSoftDeleteDbPredicate(String tableAlias) {
|
||||
return tableAlias+"."+softDeleteDbPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the soft delete property value on the bean without invoking lazy loading.
|
||||
*/
|
||||
public void setSoftDeleteValue(EntityBean bean) {
|
||||
// assumes boolean deleted true being set which is ok limitation for now
|
||||
setValue(bean, true);
|
||||
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the property without interception or
|
||||
* PropertyChangeSupport.
|
||||
@@ -600,10 +666,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 +677,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 +968,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 +1027,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 +1076,35 @@ 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 is the soft delete property.
|
||||
*/
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -790,7 +800,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
|
||||
public IntersectionRow buildManyDeleteChildren(EntityBean parentBean, ArrayList<Object> excludeDetailIds) {
|
||||
|
||||
IntersectionRow row = new IntersectionRow(tableJoin.getTable());
|
||||
IntersectionRow row = new IntersectionRow(tableJoin.getTable(), targetDescriptor);
|
||||
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
|
||||
row.setExcludeIds(excludeDetailIds, getTargetDescriptor());
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -15,13 +15,21 @@ public class IntersectionRow {
|
||||
|
||||
private final String tableName;
|
||||
|
||||
private final BeanDescriptor<?> targetDescriptor;
|
||||
|
||||
private final LinkedHashMap<String, Object> values = new LinkedHashMap<String, Object>();
|
||||
|
||||
private ArrayList<Object> excludeIds;
|
||||
private BeanDescriptor<?> excludeDescriptor;
|
||||
|
||||
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
|
||||
this.tableName = tableName;
|
||||
this.targetDescriptor = targetDescriptor;
|
||||
}
|
||||
|
||||
public IntersectionRow(String tableName) {
|
||||
this.tableName = tableName;
|
||||
this.targetDescriptor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,22 +72,20 @@ public class IntersectionRow {
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
public SqlUpdate createDelete(EbeanServer server) {
|
||||
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
|
||||
|
||||
BindParams bindParams = new BindParams();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("delete from ").append(tableName).append(" where ");
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (count++ > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(entry.getKey());
|
||||
sb.append(" = ?");
|
||||
bindParams.setParameter(count, entry.getValue());
|
||||
if (softDelete) {
|
||||
sb.append("update ").append(tableName).append(" set ");
|
||||
sb.append(targetDescriptor.getSoftDeleteDbSet());
|
||||
} else {
|
||||
sb.append("delete from ").append(tableName);
|
||||
}
|
||||
sb.append(" where ");
|
||||
|
||||
int count = setBindParams(bindParams, sb);
|
||||
|
||||
if (excludeIds != null) {
|
||||
IdInExpression idIn = new IdInExpression(excludeIds);
|
||||
@@ -108,6 +114,13 @@ public class IntersectionRow {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("delete from ").append(tableName).append(" where ");
|
||||
|
||||
setBindParams(bindParams, sb);
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
private int setBindParams(BindParams bindParams, StringBuilder sb) {
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (count++ > 0) {
|
||||
@@ -120,6 +133,6 @@ public class IntersectionRow {
|
||||
bindParams.setParameter(count, entry.getValue());
|
||||
}
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import com.avaje.ebean.annotation.CreatedTimestamp;
|
||||
import com.avaje.ebean.annotation.SoftDelete;
|
||||
import com.avaje.ebean.annotation.UpdatedTimestamp;
|
||||
import com.avaje.ebean.annotation.WhenCreated;
|
||||
import com.avaje.ebean.annotation.WhenModified;
|
||||
import com.avaje.ebean.annotation.WhoCreated;
|
||||
import com.avaje.ebean.annotation.WhoModified;
|
||||
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 +186,12 @@ public class DeployBeanProperty {
|
||||
|
||||
private boolean excludedFromHistory;
|
||||
|
||||
private boolean draftOnly;
|
||||
private boolean draftDirty;
|
||||
private boolean draftReset;
|
||||
|
||||
private boolean softDelete;
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
@@ -210,16 +220,25 @@ public class DeployBeanProperty {
|
||||
return ID_ORDER;
|
||||
} else if (undirectionalShadow) {
|
||||
return UNIDIRECTIONAL_ORDER;
|
||||
} else if (field.getAnnotation(WhenCreated.class) != null || field.getAnnotation(CreatedTimestamp.class) != null) {
|
||||
return AUDITCOLUMN_ORDER;
|
||||
} else if (field.getAnnotation(WhenModified.class) != null || field.getAnnotation(UpdatedTimestamp.class) != null) {
|
||||
} else if (isAuditProperty()) {
|
||||
return AUDITCOLUMN_ORDER;
|
||||
} else if (field.getAnnotation(Version.class) != null) {
|
||||
return VERSIONCOLUMN_ORDER;
|
||||
} else if (field.getAnnotation(SoftDelete.class) != null) {
|
||||
return VERSIONCOLUMN_ORDER;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean isAuditProperty() {
|
||||
return (field.getAnnotation(WhenCreated.class) != null
|
||||
|| field.getAnnotation(WhenModified.class) != null
|
||||
|| field.getAnnotation(WhoModified.class) != null
|
||||
|| field.getAnnotation(WhoCreated.class) != null
|
||||
|| field.getAnnotation(UpdatedTimestamp.class) != null
|
||||
|| field.getAnnotation(CreatedTimestamp.class) != null);
|
||||
}
|
||||
|
||||
public String getFullBeanName() {
|
||||
return desc.getFullName() + "." + name;
|
||||
}
|
||||
@@ -598,7 +617,7 @@ public class DeployBeanProperty {
|
||||
*/
|
||||
public void setDbType(int dbType) {
|
||||
this.dbType = dbType;
|
||||
this.lob = isLobType(dbType);
|
||||
this.lob = BeanProperty.isLobType(dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,22 +628,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 +852,38 @@ 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;
|
||||
}
|
||||
|
||||
public void setSoftDelete() {
|
||||
this.softDelete = true;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-5
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
+47
-14
@@ -28,6 +28,8 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
private BeanProperty versionProperty;
|
||||
|
||||
private BeanProperty draftDirty;
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
|
||||
private final LinkedHashMap<String, BeanProperty> propertyMap;
|
||||
@@ -73,7 +75,31 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
this.propertyMap = new LinkedHashMap<String, BeanProperty>();
|
||||
|
||||
// see if there is a discriminator property we should add
|
||||
String discriminatorColumn = null;
|
||||
BeanProperty discProperty = null;
|
||||
|
||||
InheritInfo inheritInfo = deploy.getInheritInfo();
|
||||
if (inheritInfo != null) {
|
||||
// Create a BeanProperty for the discriminator column to support
|
||||
// using RawSql queries with inheritance
|
||||
discriminatorColumn = inheritInfo.getDiscriminatorColumn();
|
||||
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
|
||||
discDeployProp.setDiscriminator();
|
||||
discDeployProp.setName(discriminatorColumn);
|
||||
discDeployProp.setDbColumn(discriminatorColumn);
|
||||
|
||||
// only register it in the propertyMap. This might not be used if
|
||||
// an explicit property is mapped to the discriminator on the bean
|
||||
discProperty = new BeanProperty(desc, discDeployProp);
|
||||
}
|
||||
|
||||
for (DeployBeanProperty prop : deploy.propertiesAll()) {
|
||||
if (discriminatorColumn != null && discriminatorColumn.equals(prop.getDbColumn())) {
|
||||
// we have an explicit property mapped to the discriminator column
|
||||
prop.setDiscriminator();
|
||||
discProperty = null;
|
||||
}
|
||||
BeanProperty beanProp = createBeanProperty(owner, prop);
|
||||
propertyMap.put(beanProp.getName(), beanProp);
|
||||
}
|
||||
@@ -84,21 +110,12 @@ public class DeployBeanPropertyLists {
|
||||
allocateToList(prop);
|
||||
}
|
||||
|
||||
InheritInfo inheritInfo = deploy.getInheritInfo();
|
||||
if (inheritInfo != null) {
|
||||
// Create a BeanProperty for the discriminator column to support
|
||||
// using RawSql queries with inheritance
|
||||
String discriminatorColumn = inheritInfo.getDiscriminatorColumn();
|
||||
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
|
||||
discDeployProp.setDiscriminator();
|
||||
discDeployProp.setName(discriminatorColumn);
|
||||
discDeployProp.setDbColumn(discriminatorColumn);
|
||||
|
||||
// create the discriminator BeanProperty and only register it in the propertyMap
|
||||
BeanProperty dprop = new BeanProperty(desc, discDeployProp);
|
||||
propertyMap.put(dprop.getName(), dprop);
|
||||
if (discProperty != null) {
|
||||
// put the discriminator property into the property map only
|
||||
// (after the real properties have been organised into their lists)
|
||||
propertyMap.put(discProperty.getName(), discProperty);
|
||||
}
|
||||
|
||||
|
||||
List<DeployTableJoin> deployTableJoins = deploy.getTableJoins();
|
||||
tableJoins = new TableJoin[deployTableJoins.size()];
|
||||
for (int i = 0; i < deployTableJoins.size(); i++) {
|
||||
@@ -160,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);
|
||||
@@ -270,6 +289,20 @@ public class DeployBeanPropertyLists {
|
||||
return getMany2Many();
|
||||
}
|
||||
|
||||
public BeanProperty getDraftDirty() {
|
||||
return draftDirty;
|
||||
}
|
||||
|
||||
public BeanProperty getSoftDeleteProperty() {
|
||||
|
||||
for (BeanProperty prop: nonManys) {
|
||||
if (prop.isSoftDelete()) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@@ -76,6 +76,8 @@ public class AnnotationFields extends AnnotationParser {
|
||||
*/
|
||||
private void readAssocOne(DeployBeanProperty prop) {
|
||||
|
||||
readJsonAnnotations(prop);
|
||||
|
||||
Id id = get(prop, Id.class);
|
||||
if (id != null) {
|
||||
prop.setId();
|
||||
@@ -117,25 +119,7 @@ public class AnnotationFields extends AnnotationParser {
|
||||
readColumn(column, prop);
|
||||
}
|
||||
|
||||
if (jacksonAnnotationsPresent) {
|
||||
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(!jsonIgnore.value());
|
||||
prop.setJsonDeserialize(!jsonIgnore.value());
|
||||
}
|
||||
}
|
||||
|
||||
Expose expose = get(prop, Expose.class);
|
||||
if (expose != null) {
|
||||
prop.setJsonSerialize(expose.serialize());
|
||||
prop.setJsonDeserialize(expose.deserialize());
|
||||
}
|
||||
|
||||
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(jsonIgnore.serialize());
|
||||
prop.setJsonDeserialize(jsonIgnore.deserialize());
|
||||
}
|
||||
readJsonAnnotations(prop);
|
||||
|
||||
if (prop.getDbColumn() == null) {
|
||||
// No @Column annotation or @Column.name() not set
|
||||
@@ -165,6 +149,20 @@ 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();
|
||||
}
|
||||
SoftDelete softDelete = get(prop, SoftDelete.class);
|
||||
if (softDelete != null) {
|
||||
prop.setSoftDelete();
|
||||
}
|
||||
|
||||
DbJson dbJson = get(prop, DbJson.class);
|
||||
if (dbJson != null) {
|
||||
util.setDbJsonType(prop, dbJson);
|
||||
@@ -291,6 +289,28 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void readJsonAnnotations(DeployBeanProperty prop) {
|
||||
if (jacksonAnnotationsPresent) {
|
||||
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(!jsonIgnore.value());
|
||||
prop.setJsonDeserialize(!jsonIgnore.value());
|
||||
}
|
||||
}
|
||||
|
||||
Expose expose = get(prop, Expose.class);
|
||||
if (expose != null) {
|
||||
prop.setJsonSerialize(expose.serialize());
|
||||
prop.setJsonDeserialize(expose.deserialize());
|
||||
}
|
||||
|
||||
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(jsonIgnore.serialize());
|
||||
prop.setJsonDeserialize(jsonIgnore.deserialize());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasRelationshipItem(DeployBeanProperty prop) {
|
||||
return get(prop, OneToMany.class) != null ||
|
||||
get(prop, ManyToOne.class) != null ||
|
||||
|
||||
@@ -111,8 +111,8 @@ public class DeployUtil {
|
||||
}
|
||||
ScalarType<?> scalarType = typeManager.getScalarType(enumType);
|
||||
if (scalarType == null) {
|
||||
// see if it has a Mapping in avaje.properties
|
||||
scalarType = typeManager.createEnumScalarType(enumType);
|
||||
// look for @DbEnumValue or @EnumValue annotations etc
|
||||
scalarType = typeManager.createEnumScalarType((Class<? extends Enum<?>>)enumType);
|
||||
if (scalarType == null) {
|
||||
// use JPA normal Enum type (without mapping)
|
||||
EnumType type = enumerated != null ? enumerated.value() : null;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
@@ -228,6 +234,16 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
return exprList.asOf(asOf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> asDraft() {
|
||||
return exprList.asDraft();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> includeSoftDeletes() {
|
||||
return exprList.includeSoftDeletes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Version<T>> findVersions() {
|
||||
return exprList.findVersions();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -45,25 +45,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
|
||||
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
|
||||
|
||||
// propagate the readOnly state
|
||||
if (parent.isReadOnly() != null) {
|
||||
query.setReadOnly(parent.isReadOnly());
|
||||
}
|
||||
// propagate the asOf and lazy loading mode
|
||||
query.setDisableLazyLoading(parent.isDisableLazyLoading());
|
||||
query.asOf(parent.getAsOf());
|
||||
parent.propagateQueryState(query);
|
||||
query.setParentNode(objectGraphNode);
|
||||
query.setLazyLoadProperty(lazyLoadProperty);
|
||||
if (parent.isDisableReadAudit()) {
|
||||
query.setDisableReadAuditing();
|
||||
}
|
||||
|
||||
if (queryProps != null) {
|
||||
queryProps.configureBeanQuery(query);
|
||||
}
|
||||
if (parent.isUseAutoTune()) {
|
||||
query.setAutoTune(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected void register(EntityBeanIntercept ebi) {
|
||||
|
||||
@@ -36,12 +36,14 @@ 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;
|
||||
private final int defaultBatchSize;
|
||||
private final boolean disableLazyLoading;
|
||||
private final boolean disableReadAudit;
|
||||
private final boolean includeSoftDeletes;
|
||||
|
||||
/**
|
||||
* The path relative to the root of the object graph.
|
||||
@@ -65,6 +67,8 @@ public class DLoadContext implements LoadContext {
|
||||
|
||||
SpiQuery<?> query = request.getQuery();
|
||||
this.asOf = query.getAsOf();
|
||||
this.asDraft = query.isAsDraft();
|
||||
this.includeSoftDeletes = query.isIncludeSoftDeletes();
|
||||
this.readOnly = query.isReadOnly();
|
||||
this.disableReadAudit = query.isDisableReadAudit();
|
||||
this.disableLazyLoading = query.isDisableLazyLoading();
|
||||
@@ -199,10 +203,6 @@ public class DLoadContext implements LoadContext {
|
||||
return new ObjectGraphNode(origin, path);
|
||||
}
|
||||
|
||||
public boolean isUseAutoTune() {
|
||||
return useProfiling;
|
||||
}
|
||||
|
||||
protected String getFullPath(String path) {
|
||||
if (relativePath == null) {
|
||||
return path;
|
||||
@@ -223,27 +223,6 @@ public class DLoadContext implements LoadContext {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'as of' timestamp that should propagate to secondary queries.
|
||||
*/
|
||||
protected Timestamp getAsOf() {
|
||||
return asOf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if disable read auditing should propagate to secondary queries.
|
||||
*/
|
||||
protected boolean isDisableReadAudit() {
|
||||
return disableReadAudit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if disable lazy loading should propagate to secondary queries.
|
||||
*/
|
||||
protected boolean isDisableLazyLoading() {
|
||||
return disableLazyLoading;
|
||||
}
|
||||
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return persistenceContext;
|
||||
}
|
||||
@@ -326,4 +305,26 @@ public class DLoadContext implements LoadContext {
|
||||
return desc.getBeanPropertyFromPath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate the original query settings (draft, asOf etc) to the secondary queries.
|
||||
*/
|
||||
public void propagateQueryState(SpiQuery<?> query) {
|
||||
if (readOnly != null) {
|
||||
query.setReadOnly(readOnly);
|
||||
}
|
||||
query.setDisableLazyLoading(disableLazyLoading);
|
||||
query.asOf(asOf);
|
||||
if (asDraft) {
|
||||
query.asDraft();
|
||||
}
|
||||
if (includeSoftDeletes) {
|
||||
query.includeSoftDeletes();
|
||||
}
|
||||
if (disableReadAudit) {
|
||||
query.setDisableReadAuditing();
|
||||
}
|
||||
if (useProfiling) {
|
||||
query.setAutoTune(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,26 +56,11 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
|
||||
public void configureQuery(SpiQuery<?> query) {
|
||||
|
||||
// propagate the readOnly state
|
||||
if (parent.isReadOnly() != null) {
|
||||
query.setReadOnly(parent.isReadOnly());
|
||||
}
|
||||
|
||||
// propagate the asOf and lazy loading mode
|
||||
query.setDisableLazyLoading(parent.isDisableLazyLoading());
|
||||
query.asOf(parent.getAsOf());
|
||||
parent.propagateQueryState(query);
|
||||
query.setParentNode(objectGraphNode);
|
||||
if (parent.isDisableReadAudit()) {
|
||||
query.setDisableReadAuditing();
|
||||
}
|
||||
|
||||
if (queryProps != null) {
|
||||
queryProps.configureBeanQuery(query);
|
||||
}
|
||||
|
||||
if (parent.isUseAutoTune()) {
|
||||
query.setAutoTune(true);
|
||||
}
|
||||
}
|
||||
|
||||
public BeanPropertyAssocMany<?> getBeanProperty() {
|
||||
|
||||
@@ -138,6 +138,7 @@ public class BatchedBeanHolder {
|
||||
return inserts.size();
|
||||
|
||||
case UPDATE:
|
||||
case SOFT_DELETE:
|
||||
if (updates == null) {
|
||||
updates = new ArrayList<PersistRequest>();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user