diff --git a/src/main/java/com/avaje/ebean/annotation/CacheStrategy.java b/src/main/java/com/avaje/ebean/annotation/CacheStrategy.java index 240fcb411..e2a00b2a4 100644 --- a/src/main/java/com/avaje/ebean/annotation/CacheStrategy.java +++ b/src/main/java/com/avaje/ebean/annotation/CacheStrategy.java @@ -1,58 +1,58 @@ -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; - -import com.avaje.ebean.Query; - -/** - * Specify the default cache use specific entity type. - */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface CacheStrategy { - - /** - * When set to true the bean cache will be used unless explicitly stated not - * to in a query via {@link Query#setUseCache(boolean)}. - */ - boolean useBeanCache() default true; - - /** - * A single property that is a natural unique identifier for the bean. - *

- * When a findUnique query is used with this property as the sole expression - * then there will be a lookup into the L2 natural key cache. - *

- */ - String naturalKey() default ""; - - /** - * When set to true the beans returned from a query will default to be - * readOnly. - *

- * If the bean is readOnly and has no relationships then it may be sharable. - *

- *

- * If you try to modify a readOnly bean it will throw an - * IllegalStateException. - *

- */ - boolean readOnly() default false; - - /** - * Specify a query that can be used to warm the cache. - *

- * All the beans fetched by this query will be loaded into the bean cache and - * the query itself will be loaded into the query cache. - *

- *

- * The warming query will typically be executed at startup time after a short - * delay (defaults to a 30 seconds delay). - *

- */ - String warmingQuery() default ""; - -}; +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; + +import com.avaje.ebean.Query; + +/** + * Specify the default cache use specific entity type. + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface CacheStrategy { + + /** + * When set to true the bean cache will be used unless explicitly stated not + * to in a query via {@link Query#setUseCache(boolean)}. + */ + boolean useBeanCache() default true; + + /** + * A single property that is a natural unique identifier for the bean. + *

+ * When a findUnique query is used with this property as the sole expression + * then there will be a lookup into the L2 natural key cache. + *

+ */ + String naturalKey() default ""; + + /** + * When set to true the beans returned from a query will default to be + * readOnly. + *

+ * If the bean is readOnly and has no relationships then it may be sharable. + *

+ *

+ * If you try to modify a readOnly bean it will throw an + * IllegalStateException. + *

+ */ + boolean readOnly() default false; + + /** + * Specify a query that can be used to warm the cache. + *

+ * All the beans fetched by this query will be loaded into the bean cache and + * the query itself will be loaded into the query cache. + *

+ *

+ * The warming query will typically be executed at startup time after a short + * delay (defaults to a 30 seconds delay). + *

+ */ + String warmingQuery() default ""; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/CacheTuning.java b/src/main/java/com/avaje/ebean/annotation/CacheTuning.java index b03d11050..2e5f5065d 100644 --- a/src/main/java/com/avaje/ebean/annotation/CacheTuning.java +++ b/src/main/java/com/avaje/ebean/annotation/CacheTuning.java @@ -1,47 +1,47 @@ -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 cache tuning for a specific entity type. - *

- * If this is not specified then the system default settings are used. - *

- */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface CacheTuning { - - /** - * The maximum size for the cache. - *

- * This defaults to 0 which means unlimited. - *

- */ - int maxSize() default 0; - - /** - * The maximum time (in seconds) that a cache entry is allowed to stay in the - * cache when it has not been accessed. - *

- * This defaults to 0 which means unlimited. - *

- */ - int maxIdleSecs() default 0; - - /** - * The maximum time (in seconds) a cache entry is allowed to stay in the - * cache. - *

- * This is not generally required as the cache entries are automatically - * evicted when related data changes are committed. - *

- *

- * This defaults to 0 which means unlimited. - *

- */ - int maxSecsToLive() default 0; -}; +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 cache tuning for a specific entity type. + *

+ * If this is not specified then the system default settings are used. + *

+ */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface CacheTuning { + + /** + * The maximum size for the cache. + *

+ * This defaults to 0 which means unlimited. + *

+ */ + int maxSize() default 0; + + /** + * The maximum time (in seconds) that a cache entry is allowed to stay in the + * cache when it has not been accessed. + *

+ * This defaults to 0 which means unlimited. + *

+ */ + int maxIdleSecs() default 0; + + /** + * The maximum time (in seconds) a cache entry is allowed to stay in the + * cache. + *

+ * This is not generally required as the cache entries are automatically + * evicted when related data changes are committed. + *

+ *

+ * This defaults to 0 which means unlimited. + *

+ */ + int maxSecsToLive() default 0; +}; diff --git a/src/main/java/com/avaje/ebean/annotation/CreatedTimestamp.java b/src/main/java/com/avaje/ebean/annotation/CreatedTimestamp.java index 0d688cd93..abb89788b 100644 --- a/src/main/java/com/avaje/ebean/annotation/CreatedTimestamp.java +++ b/src/main/java/com/avaje/ebean/annotation/CreatedTimestamp.java @@ -1,31 +1,31 @@ -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; - -/** - * For a timestamp property that is set to the datetime when the entity is - * created/inserted. - *

- * 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). - *

- *

- * The downside to this approach is that the inserted entity does not have the - * timestamp value after the insert has occurred. You need to fetch the entity - * back to get the inserted timestamp if you want to used it. - *

- * - *
- * @Column(insertable = false, updateable = false)
- * Timestamp cretimestamp;
- * 
- */ -@Target({ ElementType.FIELD, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface CreatedTimestamp { - -}; +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; + +/** + * For a timestamp property that is set to the datetime when the entity is + * created/inserted. + *

+ * 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). + *

+ *

+ * The downside to this approach is that the inserted entity does not have the + * timestamp value after the insert has occurred. You need to fetch the entity + * back to get the inserted timestamp if you want to used it. + *

+ * + *
+ * @Column(insertable = false, updateable = false)
+ * Timestamp cretimestamp;
+ * 
+ */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface CreatedTimestamp { + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/EmbeddedColumns.java b/src/main/java/com/avaje/ebean/annotation/EmbeddedColumns.java index 7393bb5eb..88feaca5c 100644 --- a/src/main/java/com/avaje/ebean/annotation/EmbeddedColumns.java +++ b/src/main/java/com/avaje/ebean/annotation/EmbeddedColumns.java @@ -1,31 +1,31 @@ -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 property name to db column mapping for Embedded beans. - *

- * This is designed to be easier to use than the AttributeOverride annotation in - * standard JPA. - *

- */ -@Target({ ElementType.FIELD, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface EmbeddedColumns { - - /** - * A list of property names mapped to DB columns. - *

- * For example currency=IN_CURR, amount=IN_AMOUNT - *

- *

- * Where currency and amount are properties and IN_CURR and IN_AMOUNT are the - * respective DB columns these properties will be mapped to. - *

- */ - String columns() default ""; - -}; +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 property name to db column mapping for Embedded beans. + *

+ * This is designed to be easier to use than the AttributeOverride annotation in + * standard JPA. + *

+ */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface EmbeddedColumns { + + /** + * A list of property names mapped to DB columns. + *

+ * For example currency=IN_CURR, amount=IN_AMOUNT + *

+ *

+ * Where currency and amount are properties and IN_CURR and IN_AMOUNT are the + * respective DB columns these properties will be mapped to. + *

+ */ + String columns() default ""; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/Encrypted.java b/src/main/java/com/avaje/ebean/annotation/Encrypted.java index b6c2aff78..a0999f817 100644 --- a/src/main/java/com/avaje/ebean/annotation/Encrypted.java +++ b/src/main/java/com/avaje/ebean/annotation/Encrypted.java @@ -1,24 +1,24 @@ -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 that the property is stored in encrypted form. - */ -@Target({ ElementType.FIELD, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Encrypted { - - /** - * When true try to use DB encryption rather than local java encryption. - */ - boolean dbEncryption() default true; - - /** - * Used to specify the DB column length. - */ - int dbLength() default 0; -}; +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 that the property is stored in encrypted form. + */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Encrypted { + + /** + * When true try to use DB encryption rather than local java encryption. + */ + boolean dbEncryption() default true; + + /** + * Used to specify the DB column length. + */ + int dbLength() default 0; +}; diff --git a/src/main/java/com/avaje/ebean/annotation/EnumMapping.java b/src/main/java/com/avaje/ebean/annotation/EnumMapping.java index 8556527fb..617c052f9 100644 --- a/src/main/java/com/avaje/ebean/annotation/EnumMapping.java +++ b/src/main/java/com/avaje/ebean/annotation/EnumMapping.java @@ -1,90 +1,90 @@ -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; - -/** - * For mapping the values of an Enum to and from Database values. - *

- * Also refer to the {@link EnumValue} approach which probably the preferred now - * (preferred over using this EnumMapping annotation). - *

- *

- * Both of the approaches defined in the JPA have significant problems!!! - *

- *

- * Using the ordinal value is VERY RISKY because that depends on the compile - * order of the enum values. Aka if you change the order of the enum values you - * have changed their ordinal values and now your DB values are WRONG - a HUGE - * disaster!!!. - *

- *

- * Using the String values of enums is fairly restrictive because in a Database - * these values are usually truncated into short codes (e.g. "A" short for - * "ACTIVE") so space used in the database is minimised. Making your enum names - * match the database values would give them very short less meaningful names - - * not a great solution. - *

- *

- * You can use this annotation to control the mapping of your enums to database - * values. - *

- *

- * The design of this using nameValuePairs is not optimal for safety or - * refactoring so if you have a better solution I'm all ears. The other - * solutions would probably involve modifying each enumeration with a method - * which may be ok. - *

- *

- * An example mapping the UserState enum. - *

- * - *
- * ...
- * @EnumMapping(nameValuePairs="NEW=N, ACTIVE=A, INACTIVE=I")
- *  public enum UserState {
- *  NEW,
- *  ACTIVE,
- *  INACTIVE;
- *  }
- * 
- * - * @see EnumValue - */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface EnumMapping { - - /** - * A comma delimited list of name=value pairs. - *

- * e.g. "ACTIVE=A, INACTIVE=I, NEW=N". - *

- *

- * Where ACTIVE, INACTIVE and NEW are the enumeration values and "A", "I" and - * "N" are the database values. - *

- *

- * This is not really an optimal approach so if you have a better one I'm all - * ears - thanks. - *

- */ - String nameValuePairs(); - - /** - * Defaults to mapping values to database VARCHAR type. If this is set to true - * then the values will be converted to INTEGER and mapped to the database - * integer type. - *

- * e.g. "ACTIVE=1, INACTIVE=0, NEW=2". - *

- */ - boolean integerType() default false; - - /** - * The length of DB column if mapping to string values. - */ - int length() default 0; -}; +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; + +/** + * For mapping the values of an Enum to and from Database values. + *

+ * Also refer to the {@link EnumValue} approach which probably the preferred now + * (preferred over using this EnumMapping annotation). + *

+ *

+ * Both of the approaches defined in the JPA have significant problems!!! + *

+ *

+ * Using the ordinal value is VERY RISKY because that depends on the compile + * order of the enum values. Aka if you change the order of the enum values you + * have changed their ordinal values and now your DB values are WRONG - a HUGE + * disaster!!!. + *

+ *

+ * Using the String values of enums is fairly restrictive because in a Database + * these values are usually truncated into short codes (e.g. "A" short for + * "ACTIVE") so space used in the database is minimised. Making your enum names + * match the database values would give them very short less meaningful names - + * not a great solution. + *

+ *

+ * You can use this annotation to control the mapping of your enums to database + * values. + *

+ *

+ * The design of this using nameValuePairs is not optimal for safety or + * refactoring so if you have a better solution I'm all ears. The other + * solutions would probably involve modifying each enumeration with a method + * which may be ok. + *

+ *

+ * An example mapping the UserState enum. + *

+ * + *
+ * ...
+ * @EnumMapping(nameValuePairs="NEW=N, ACTIVE=A, INACTIVE=I")
+ *  public enum UserState {
+ *  NEW,
+ *  ACTIVE,
+ *  INACTIVE;
+ *  }
+ * 
+ * + * @see EnumValue + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnumMapping { + + /** + * A comma delimited list of name=value pairs. + *

+ * e.g. "ACTIVE=A, INACTIVE=I, NEW=N". + *

+ *

+ * Where ACTIVE, INACTIVE and NEW are the enumeration values and "A", "I" and + * "N" are the database values. + *

+ *

+ * This is not really an optimal approach so if you have a better one I'm all + * ears - thanks. + *

+ */ + String nameValuePairs(); + + /** + * Defaults to mapping values to database VARCHAR type. If this is set to true + * then the values will be converted to INTEGER and mapped to the database + * integer type. + *

+ * e.g. "ACTIVE=1, INACTIVE=0, NEW=2". + *

+ */ + boolean integerType() default false; + + /** + * The length of DB column if mapping to string values. + */ + int length() default 0; +}; diff --git a/src/main/java/com/avaje/ebean/annotation/EnumValue.java b/src/main/java/com/avaje/ebean/annotation/EnumValue.java index de4422e74..9d39d3aaa 100644 --- a/src/main/java/com/avaje/ebean/annotation/EnumValue.java +++ b/src/main/java/com/avaje/ebean/annotation/EnumValue.java @@ -1,46 +1,46 @@ -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; - -/** - * Enables you to specify a value to use to persist for an enum value. - * - *
- * public enum Status {
- *   @EnumValue("N")
- *   NEW,
- * 
- *   @EnumValue("A")
- *   ACTIVE,
- * 
- *   @EnumValue("I")
- *   INACTIVE,
- * }
- * 
- * 
- *

- * This is an alternative to using the JPA standard approach or Ebean's - * {@link EnumMapping} annotation. - *

- *

- * Note that if all the EnumValue values are parsable as Integers then Ebean - * will persist and fetch them as integers - otherwise they will be persisted - * and fetched as strings. - *

- */ -@Target({ ElementType.FIELD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface EnumValue { - - /** - * Specify the value to persist for a specific enum value. - *

- * If all the values are parsable as Integers then Ebean will persist and - * fetch them as integers rather than strings. - *

- */ - String value(); -}; +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; + +/** + * Enables you to specify a value to use to persist for an enum value. + * + *
+ * public enum Status {
+ *   @EnumValue("N")
+ *   NEW,
+ * 
+ *   @EnumValue("A")
+ *   ACTIVE,
+ * 
+ *   @EnumValue("I")
+ *   INACTIVE,
+ * }
+ * 
+ * 
+ *

+ * This is an alternative to using the JPA standard approach or Ebean's + * {@link EnumMapping} annotation. + *

+ *

+ * Note that if all the EnumValue values are parsable as Integers then Ebean + * will persist and fetch them as integers - otherwise they will be persisted + * and fetched as strings. + *

+ */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnumValue { + + /** + * Specify the value to persist for a specific enum value. + *

+ * If all the values are parsable as Integers then Ebean will persist and + * fetch them as integers rather than strings. + *

+ */ + String value(); +}; diff --git a/src/main/java/com/avaje/ebean/annotation/Formula.java b/src/main/java/com/avaje/ebean/annotation/Formula.java index 915cf0deb..a512b90f4 100644 --- a/src/main/java/com/avaje/ebean/annotation/Formula.java +++ b/src/main/java/com/avaje/ebean/annotation/Formula.java @@ -1,103 +1,103 @@ -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; - -import com.avaje.ebean.Query; - -/** - * Assign to a property to be based on a SQL formula. - *

- * This is typically a SQL Literal value, SQL case statement, SQL function or - * similar. - *

- *

- * Any property based on a formula becomes a read only property. - *

- *

- * You may also put use the Transient annotation with the Formula annotation. - * The effect of the Transient annotation in this case is that the formula will - * NOT be included in queries by default - you have to explicitly include - * it via {@link Query#select(String)} or {@link Query#fetch(String, String, com.avaje.ebean.FetchConfig)}. - * You may want to do this if the Formula is relatively expensive and only want - * it included in the query when you explicitly state it. - *

- * - *
- * // On the Order "master" bean
- * // ... a formula using the Order details
- * // ... sum(order_qty*unit_price)
- * @Transient
- * @Formula(select = "_b${ta}.total_amount", join = "join (select order_id, sum(order_qty*unit_price) as total_amount from o_order_detail group by order_id) as _b${ta} on _b${ta}.order_id = ${ta}.id")
- * Double totalAmount;
- * 
- * 
- *

- * As the totalAmount formula is also Transient it is not included by default in - * queries - it needs to be explicitly included. - *

- * - *
- * // find by Id
- * Order o1 = Ebean.find(Order.class)
- *     .select("id, totalAmount")
- *     .setId(1).findUnique();
- * 
- * // find list ... using totalAmount in the where clause
- * List<Order> list = Ebean.find(Order.class)
- *     .select("id, totalAmount")
- *     .where()
- *     .eq("status", Order.Status.NEW)
- *     .gt("totalAmount", 10)
- *     .findList();
- * 
- * // as a join from customer
- * List<Customer> l0 = Ebean.find(Customer.class)
- *     .select("id, name")
- *     .join("orders", "status, totalAmount")
- *     .where()
- *     .gt("id", 0)
- *     .gt("orders.totalAmount", 10)
- *     .findList();
- * 
- * 
- */ -@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Formula { - - /** - * The SQL to be used in the SELECT part of the SQL to populate a property. - */ - String select(); - - /** - * OPTIONAL - the SQL to be used in the JOIN part of the SQL to support the - * formula. - *

- * This is commonly used to join a 'dynamic view' to support aggregation such - * as count, sum etc. - *

- *

- * The join string should start with either "left outer join" or "join". - *

- * - *

- * You will almost certainly use the "${ta}" as a place holder for the table - * alias of the table you are joining back to (the "base table" of the entity - * bean). - *

- *

- * The example below is used to support a total count of topics created by a - * user. - *

- * - *
-   * join (select user_id, count(*) as topic_count from f_topic group by user_id) as _tc on _tc.user_id = ${ta}.id
-   * 
- */ - String join() default ""; - -}; +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; + +import com.avaje.ebean.Query; + +/** + * Assign to a property to be based on a SQL formula. + *

+ * This is typically a SQL Literal value, SQL case statement, SQL function or + * similar. + *

+ *

+ * Any property based on a formula becomes a read only property. + *

+ *

+ * You may also put use the Transient annotation with the Formula annotation. + * The effect of the Transient annotation in this case is that the formula will + * NOT be included in queries by default - you have to explicitly include + * it via {@link Query#select(String)} or {@link Query#fetch(String, String, com.avaje.ebean.FetchConfig)}. + * You may want to do this if the Formula is relatively expensive and only want + * it included in the query when you explicitly state it. + *

+ * + *
+ * // On the Order "master" bean
+ * // ... a formula using the Order details
+ * // ... sum(order_qty*unit_price)
+ * @Transient
+ * @Formula(select = "_b${ta}.total_amount", join = "join (select order_id, sum(order_qty*unit_price) as total_amount from o_order_detail group by order_id) as _b${ta} on _b${ta}.order_id = ${ta}.id")
+ * Double totalAmount;
+ * 
+ * 
+ *

+ * As the totalAmount formula is also Transient it is not included by default in + * queries - it needs to be explicitly included. + *

+ * + *
+ * // find by Id
+ * Order o1 = Ebean.find(Order.class)
+ *     .select("id, totalAmount")
+ *     .setId(1).findUnique();
+ * 
+ * // find list ... using totalAmount in the where clause
+ * List<Order> list = Ebean.find(Order.class)
+ *     .select("id, totalAmount")
+ *     .where()
+ *     .eq("status", Order.Status.NEW)
+ *     .gt("totalAmount", 10)
+ *     .findList();
+ * 
+ * // as a join from customer
+ * List<Customer> l0 = Ebean.find(Customer.class)
+ *     .select("id, name")
+ *     .join("orders", "status, totalAmount")
+ *     .where()
+ *     .gt("id", 0)
+ *     .gt("orders.totalAmount", 10)
+ *     .findList();
+ * 
+ * 
+ */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Formula { + + /** + * The SQL to be used in the SELECT part of the SQL to populate a property. + */ + String select(); + + /** + * OPTIONAL - the SQL to be used in the JOIN part of the SQL to support the + * formula. + *

+ * This is commonly used to join a 'dynamic view' to support aggregation such + * as count, sum etc. + *

+ *

+ * The join string should start with either "left outer join" or "join". + *

+ * + *

+ * You will almost certainly use the "${ta}" as a place holder for the table + * alias of the table you are joining back to (the "base table" of the entity + * bean). + *

+ *

+ * The example below is used to support a total count of topics created by a + * user. + *

+ * + *
+   * join (select user_id, count(*) as topic_count from f_topic group by user_id) as _tc on _tc.user_id = ${ta}.id
+   * 
+ */ + String join() default ""; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/NamedUpdate.java b/src/main/java/com/avaje/ebean/annotation/NamedUpdate.java index 82ede2e27..ccc614eb4 100644 --- a/src/main/java/com/avaje/ebean/annotation/NamedUpdate.java +++ b/src/main/java/com/avaje/ebean/annotation/NamedUpdate.java @@ -1,37 +1,37 @@ -package com.avaje.ebean.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * An Update statement for a particular entity bean type. - *

- * The update can either be a sql insert,update or delete statement with tables - * and columns etc or the equivalent statement but with table names and columns - * expressed as bean types and bean properties. - *

- */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface NamedUpdate { - - /** - * The name of the update. - */ - String name(); - - /** - * The insert, update or delete statement. - */ - String update(); - - /** - * Set this to false if you do not want the cache to be notified. If true the - * cache will invalidate appropriate objects from the cache (after a - * successful transaction commit). - */ - boolean notifyCache() default true; - -}; +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; + +/** + * An Update statement for a particular entity bean type. + *

+ * The update can either be a sql insert,update or delete statement with tables + * and columns etc or the equivalent statement but with table names and columns + * expressed as bean types and bean properties. + *

+ */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NamedUpdate { + + /** + * The name of the update. + */ + String name(); + + /** + * The insert, update or delete statement. + */ + String update(); + + /** + * Set this to false if you do not want the cache to be notified. If true the + * cache will invalidate appropriate objects from the cache (after a + * successful transaction commit). + */ + boolean notifyCache() default true; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/NamedUpdates.java b/src/main/java/com/avaje/ebean/annotation/NamedUpdates.java index b753d8d5a..8b9daf804 100644 --- a/src/main/java/com/avaje/ebean/annotation/NamedUpdates.java +++ b/src/main/java/com/avaje/ebean/annotation/NamedUpdates.java @@ -1,20 +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; - -/** - * Holds an array of named Update statements for a particular entity bean type. - */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface NamedUpdates { - - /** - * An array of named updates. - */ - NamedUpdate[] value(); - -}; +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; + +/** + * Holds an array of named Update statements for a particular entity bean type. + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NamedUpdates { + + /** + * An array of named updates. + */ + NamedUpdate[] value(); + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/PrivateOwned.java b/src/main/java/com/avaje/ebean/annotation/PrivateOwned.java index b5406d871..719add194 100644 --- a/src/main/java/com/avaje/ebean/annotation/PrivateOwned.java +++ b/src/main/java/com/avaje/ebean/annotation/PrivateOwned.java @@ -1,34 +1,34 @@ -package com.avaje.ebean.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify that the elements of a OneToMany are private owned. - *

- * This means that if they are removed from the List/Set/Map they will be - * deleted when their parent object is saved. - *

- *

- * This could also be described as deleting orphans - in that beans removed from - * the List/Set/Map will be deleted automatically when the parent bean is saved. - * They are considered 'orphans' when they have been removed from the collection - * in that they are no longer associated/linked to their parent bean. - *

- */ -@Target({ ElementType.FIELD, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface PrivateOwned { - - /** - * Set this to false if you don't want cascade REMOVE on this relationship. - *

- * That is, by default PrivateOwned implicitly adds a cascade REMOVE to the - * relationship and if you don't want that you need to set this to false. - *

- */ - boolean cascadeRemove() default true; - -}; +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 that the elements of a OneToMany are private owned. + *

+ * This means that if they are removed from the List/Set/Map they will be + * deleted when their parent object is saved. + *

+ *

+ * This could also be described as deleting orphans - in that beans removed from + * the List/Set/Map will be deleted automatically when the parent bean is saved. + * They are considered 'orphans' when they have been removed from the collection + * in that they are no longer associated/linked to their parent bean. + *

+ */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PrivateOwned { + + /** + * Set this to false if you don't want cascade REMOVE on this relationship. + *

+ * That is, by default PrivateOwned implicitly adds a cascade REMOVE to the + * relationship and if you don't want that you need to set this to false. + *

+ */ + boolean cascadeRemove() default true; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/Sql.java b/src/main/java/com/avaje/ebean/annotation/Sql.java index eecbc2eb4..71f776350 100644 --- a/src/main/java/com/avaje/ebean/annotation/Sql.java +++ b/src/main/java/com/avaje/ebean/annotation/Sql.java @@ -1,24 +1,24 @@ -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 explicit sql for multiple select statements. Need to use this if you - * have more than one SqlSelect for a given bean. - *

- * FUTURE: Support explicit sql for SqlInsert, SqlUpdate and SqlDelete. - *

- */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Sql { - - /** - * The sql select statements. - */ - SqlSelect[] select() default { @SqlSelect }; - -}; +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 explicit sql for multiple select statements. Need to use this if you + * have more than one SqlSelect for a given bean. + *

+ * FUTURE: Support explicit sql for SqlInsert, SqlUpdate and SqlDelete. + *

+ */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Sql { + + /** + * The sql select statements. + */ + SqlSelect[] select() default { @SqlSelect }; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/SqlSelect.java b/src/main/java/com/avaje/ebean/annotation/SqlSelect.java index 04acb88a9..218b35cb5 100644 --- a/src/main/java/com/avaje/ebean/annotation/SqlSelect.java +++ b/src/main/java/com/avaje/ebean/annotation/SqlSelect.java @@ -1,298 +1,298 @@ -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; - -import com.avaje.ebean.Query; - -/** - * Specify an explicit sql select statement to use for querying an entity bean. - *

- * The reason for using explicit sql is that you want better control over the - * exact sql or sql that Ebean does not generate for you (such as group by, - * union, intersection, window functions, recursive queries). - *

- *

- * An example of two sql select queries deployed on the ReportTopic entity bean. - * The first one has no name specified so it becomes the default query. The - * second query extends the first adding a where clause with a named parameter. - *

- * - *
- * ...
- * @Entity
- *   @Sql(select = {
- *     @SqlSelect(query = 
- *       "select t.id, t.title, count(p.id) as score "+
- *       "from f_topic t "+
- *       "join f_topic_post p on p.topic_id = t.id "+
- *       "group by t.id, t.title"),
- *     @SqlSelect(
- *       name = "with.title",
- *       extend = "default",
- *       debug = true,
- *       where = "title like :likeTitle")
- *  })
- *  public class ReportTopic
- *    @Id Integer id;
- *    String title;
- *    Double score;
- *    ...
- * 
- * - *

- * An example using the first "default" query. - *

- * - *
- * 
- * List<ReportTopic> list =
- *     Ebean.find(ReportTopic.class)
- *         .having().gt("score", 0)
- *         .findList();
- * 
- * 
- * - *

- * The resulting sql, note the having clause has been added. - *

- * - *
- * select t.id, t.title, count(p.id) as score 
- * from f_topic t join f_topic_post p on p.topic_id = t.id 
- * group by t.id, t.title  
- * having count(p.id) > ?
- * 
- * - *

- * An example using the second query. Note the named parameter "likeTitle" must - * be set. - *

- * - *
- * List<ReportTopic> list =
- *     Ebean.find(ReportTopic.class, "with.title")
- *         .set("likeTitle", "a%")
- *         .findList();
- * 
- * - *

- * Ebean tries to parse the sql in the query to determine 4 things - *

  • Location for inserting WHERE expressions (if required)
  • - *
  • Location for inserting HAVING expressions (if required)
  • - *
  • Mapping of columns to bean properties
  • - *
  • The order by clause
  • - *

    - *

    - * If Ebean is unable to parse out this information (perhaps because the sql - * contains multiple select from keywords etc) then you need to manually specify - * it. - *

    - *

    - * Insert ${where} or ${andWhere} into the location where Ebean can insert any - * expressions added to the where clause. Use ${andWhere} if the sql already has - * the WHERE keyword and Ebean will instead start with a AND keyword. - *

    - *

    - * Insert ${having} or ${andHaving} into the location where Ebean can insert any - * expressions added to the having clause. Use ${andHaving} if the sql already - * has a HAVING keyword and Ebean will instead start with a AND keyword. - *

    - *

    - * Use the columnMapping property if Ebean is unable to determine the columns - * and map them to bean properties. - *

    - *

    - * Example with ${andWhere} & ${having}. - *

    - * - *
    - *    @SqlSelect(
    - *          name = "explicit.where",
    - *          query = 
    - *              "select t.id, t.title, count(p.id) as score "+
    - *              "from f_topic t, f_topic_post p "+
    - *              "where p.topic_id = t.id ${andWhere} "+
    - *              "group by t.id, t.title ${having}"),
    - * 
    - */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Deprecated -public @interface SqlSelect { - - /** - * The name of the query. If left blank this is assumed to be the default - * query for this bean type. - *

    - * This will default to "default" and in that case becomes the default query - * used for the bean. - *

    - */ - String name() default "default"; - - /** - * The tableAlias used when adding where expressions to the query. - */ - String tableAlias() default ""; - - /** - * The sql select statement. - *

    - * If this query extends another then this string is appended to the - * parent query string. Often when using extend you will leave the - * query part blank and just specify a where and/or having clauses. - *

    - *

    - * This sql CAN NOT contain named parameters. You have to put these - * in the separate where and/or having sections. - *

    - *

    - * Ebean automatically tries to determine the location in the sql string for - * putting in additional where or having clauses. If Ebean is unable to - * successfully determine this then you have to explicitly specify these - * locations by including - * ${where} or ${andWhere} and ${having} or ${andHaving} in the sql. - *

    - *

    - * ${where} location of where clause (and will add WHERE ... )
    - * Use this when there is no where clause in the sql. If expressions are added - * to the where clause Ebean will put them in at this location starting with - * the WHERE keyword. - *

    - *

    - * ${andWhere}
    - * Use this instead of ${where} if there IS an existing where clause in the - * sql. Ebean will add the expressions starting with the AND keyword. - *

    - * ${having} location of having clause (and will add HAVING... )
    - *

    - *

    - * ${andHaving}
    - * Use this instead of ${having} when there IS an existing HAVING clause. - * Ebean will add the expressions starting with the AND keyword. - *

    - *

    - * You can include one of ${where} OR ${andWhere} but not both. - *

    - *

    - * You can include one of ${having} OR ${andHaving} but not both. - *

    - */ - String query() default ""; - - /** - * Specify the name of a sql-select query that this one 'extends'. - *

    - * When a query is extended the sql query contents are appended together. The - * where and having clauses are NOT appended but overridden. - *

    - */ - String extend() default ""; - - /** - * Specify a where clause typically containing named parameters. - *

    - * If a where clause is specified with named parameters then they will need to - * be set on the query via {@link Query#setParameter(String, Object)}. - *

    - *

    - * In the example below the query specifies a where clause that includes a - * named parameter "likeTitle". - *

    - * - *
    -   * ...
    -   * @Entity
    -   * @Sql(select = {
    -   *  ...
    -   *  @SqlSelect(
    -   *  name = "with.title",
    -   *  extend = "default",
    -   *  debug = true,
    -   *  where = "title like :likeTitle")
    -   *  })
    -   *  public class ReportTopic
    -   *  ...
    -   * 
    - * - *

    - * Example use of the above named query. - *

    - * - *
    -   * 
    -   * Query<ReportTopic> query0 = Ebean.createQuery(ReportTopic.class, "with.title");
    -   * 
    -   * query0.set("likeTitle", "Bana%");
    -   * 
    -   * List<ReportTopic> list0 = query0.findList();
    -   * 
    - * - */ - String where() default ""; - - /** - * Specify a having clause typically containing named parameters. - *

    - * If a having clause is specified with named parameters then they will need - * to be set on the query via {@link Query#setParameter(String, Object)}. - *

    - */ - String having() default ""; - - /** - * (Optional) Explicitly specify column to property mapping. - *

    - * This is required when Ebean is unable to parse the sql. This could occur if - * the sql contains multiple select keywords etc. - *

    - *

    - * Specify the columns and property names they map to in the format. - *

    - * - *
    -   *  column1 propertyName1, column2 propertyName2, ...
    -   * 
    - * - *

    - * Optionally put a AS keyword between the column and property. - *

    - * - *
    -   *   // the AS keyword is optional
    -   *  column1 AS propertyName1, column2 propertyName2, ...
    -   * 
    - * - *

    - * column should contain the table alias if there is one - *

    - *

    - * propertyName should match the property name. - *

    - * - *

    - * Example mapping 5 columns to properties. - *

    - * - *
    -   * columnMapping="t.id, t.bug_body description, t.bug_title as title, count(p.id) as scoreValue",
    -   * 
    - * - *

    - * Without this set Ebean will parse the sql looking for the select clause and - * try to map the columns to property names. It is expected that Ebean will - * not be able to successfully parse some sql and for those cases you should - * specify the column to property mapping explicitly. - *

    - * - */ - String columnMapping() default ""; - - /** - * Set this to true to have debug output when Ebean parses the sql-select. - */ - boolean debug() default false; -}; +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; + +import com.avaje.ebean.Query; + +/** + * Specify an explicit sql select statement to use for querying an entity bean. + *

    + * The reason for using explicit sql is that you want better control over the + * exact sql or sql that Ebean does not generate for you (such as group by, + * union, intersection, window functions, recursive queries). + *

    + *

    + * An example of two sql select queries deployed on the ReportTopic entity bean. + * The first one has no name specified so it becomes the default query. The + * second query extends the first adding a where clause with a named parameter. + *

    + * + *
    + * ...
    + * @Entity
    + *   @Sql(select = {
    + *     @SqlSelect(query = 
    + *       "select t.id, t.title, count(p.id) as score "+
    + *       "from f_topic t "+
    + *       "join f_topic_post p on p.topic_id = t.id "+
    + *       "group by t.id, t.title"),
    + *     @SqlSelect(
    + *       name = "with.title",
    + *       extend = "default",
    + *       debug = true,
    + *       where = "title like :likeTitle")
    + *  })
    + *  public class ReportTopic
    + *    @Id Integer id;
    + *    String title;
    + *    Double score;
    + *    ...
    + * 
    + * + *

    + * An example using the first "default" query. + *

    + * + *
    + * 
    + * List<ReportTopic> list =
    + *     Ebean.find(ReportTopic.class)
    + *         .having().gt("score", 0)
    + *         .findList();
    + * 
    + * 
    + * + *

    + * The resulting sql, note the having clause has been added. + *

    + * + *
    + * select t.id, t.title, count(p.id) as score 
    + * from f_topic t join f_topic_post p on p.topic_id = t.id 
    + * group by t.id, t.title  
    + * having count(p.id) > ?
    + * 
    + * + *

    + * An example using the second query. Note the named parameter "likeTitle" must + * be set. + *

    + * + *
    + * List<ReportTopic> list =
    + *     Ebean.find(ReportTopic.class, "with.title")
    + *         .set("likeTitle", "a%")
    + *         .findList();
    + * 
    + * + *

    + * Ebean tries to parse the sql in the query to determine 4 things + *

  • Location for inserting WHERE expressions (if required)
  • + *
  • Location for inserting HAVING expressions (if required)
  • + *
  • Mapping of columns to bean properties
  • + *
  • The order by clause
  • + *

    + *

    + * If Ebean is unable to parse out this information (perhaps because the sql + * contains multiple select from keywords etc) then you need to manually specify + * it. + *

    + *

    + * Insert ${where} or ${andWhere} into the location where Ebean can insert any + * expressions added to the where clause. Use ${andWhere} if the sql already has + * the WHERE keyword and Ebean will instead start with a AND keyword. + *

    + *

    + * Insert ${having} or ${andHaving} into the location where Ebean can insert any + * expressions added to the having clause. Use ${andHaving} if the sql already + * has a HAVING keyword and Ebean will instead start with a AND keyword. + *

    + *

    + * Use the columnMapping property if Ebean is unable to determine the columns + * and map them to bean properties. + *

    + *

    + * Example with ${andWhere} & ${having}. + *

    + * + *
    + *    @SqlSelect(
    + *          name = "explicit.where",
    + *          query = 
    + *              "select t.id, t.title, count(p.id) as score "+
    + *              "from f_topic t, f_topic_post p "+
    + *              "where p.topic_id = t.id ${andWhere} "+
    + *              "group by t.id, t.title ${having}"),
    + * 
    + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Deprecated +public @interface SqlSelect { + + /** + * The name of the query. If left blank this is assumed to be the default + * query for this bean type. + *

    + * This will default to "default" and in that case becomes the default query + * used for the bean. + *

    + */ + String name() default "default"; + + /** + * The tableAlias used when adding where expressions to the query. + */ + String tableAlias() default ""; + + /** + * The sql select statement. + *

    + * If this query extends another then this string is appended to the + * parent query string. Often when using extend you will leave the + * query part blank and just specify a where and/or having clauses. + *

    + *

    + * This sql CAN NOT contain named parameters. You have to put these + * in the separate where and/or having sections. + *

    + *

    + * Ebean automatically tries to determine the location in the sql string for + * putting in additional where or having clauses. If Ebean is unable to + * successfully determine this then you have to explicitly specify these + * locations by including + * ${where} or ${andWhere} and ${having} or ${andHaving} in the sql. + *

    + *

    + * ${where} location of where clause (and will add WHERE ... )
    + * Use this when there is no where clause in the sql. If expressions are added + * to the where clause Ebean will put them in at this location starting with + * the WHERE keyword. + *

    + *

    + * ${andWhere}
    + * Use this instead of ${where} if there IS an existing where clause in the + * sql. Ebean will add the expressions starting with the AND keyword. + *

    + * ${having} location of having clause (and will add HAVING... )
    + *

    + *

    + * ${andHaving}
    + * Use this instead of ${having} when there IS an existing HAVING clause. + * Ebean will add the expressions starting with the AND keyword. + *

    + *

    + * You can include one of ${where} OR ${andWhere} but not both. + *

    + *

    + * You can include one of ${having} OR ${andHaving} but not both. + *

    + */ + String query() default ""; + + /** + * Specify the name of a sql-select query that this one 'extends'. + *

    + * When a query is extended the sql query contents are appended together. The + * where and having clauses are NOT appended but overridden. + *

    + */ + String extend() default ""; + + /** + * Specify a where clause typically containing named parameters. + *

    + * If a where clause is specified with named parameters then they will need to + * be set on the query via {@link Query#setParameter(String, Object)}. + *

    + *

    + * In the example below the query specifies a where clause that includes a + * named parameter "likeTitle". + *

    + * + *
    +   * ...
    +   * @Entity
    +   * @Sql(select = {
    +   *  ...
    +   *  @SqlSelect(
    +   *  name = "with.title",
    +   *  extend = "default",
    +   *  debug = true,
    +   *  where = "title like :likeTitle")
    +   *  })
    +   *  public class ReportTopic
    +   *  ...
    +   * 
    + * + *

    + * Example use of the above named query. + *

    + * + *
    +   * 
    +   * Query<ReportTopic> query0 = Ebean.createQuery(ReportTopic.class, "with.title");
    +   * 
    +   * query0.set("likeTitle", "Bana%");
    +   * 
    +   * List<ReportTopic> list0 = query0.findList();
    +   * 
    + * + */ + String where() default ""; + + /** + * Specify a having clause typically containing named parameters. + *

    + * If a having clause is specified with named parameters then they will need + * to be set on the query via {@link Query#setParameter(String, Object)}. + *

    + */ + String having() default ""; + + /** + * (Optional) Explicitly specify column to property mapping. + *

    + * This is required when Ebean is unable to parse the sql. This could occur if + * the sql contains multiple select keywords etc. + *

    + *

    + * Specify the columns and property names they map to in the format. + *

    + * + *
    +   *  column1 propertyName1, column2 propertyName2, ...
    +   * 
    + * + *

    + * Optionally put a AS keyword between the column and property. + *

    + * + *
    +   *   // the AS keyword is optional
    +   *  column1 AS propertyName1, column2 propertyName2, ...
    +   * 
    + * + *

    + * column should contain the table alias if there is one + *

    + *

    + * propertyName should match the property name. + *

    + * + *

    + * Example mapping 5 columns to properties. + *

    + * + *
    +   * columnMapping="t.id, t.bug_body description, t.bug_title as title, count(p.id) as scoreValue",
    +   * 
    + * + *

    + * Without this set Ebean will parse the sql looking for the select clause and + * try to map the columns to property names. It is expected that Ebean will + * not be able to successfully parse some sql and for those cases you should + * specify the column to property mapping explicitly. + *

    + * + */ + String columnMapping() default ""; + + /** + * Set this to true to have debug output when Ebean parses the sql-select. + */ + boolean debug() default false; +}; diff --git a/src/main/java/com/avaje/ebean/annotation/UpdateMode.java b/src/main/java/com/avaje/ebean/annotation/UpdateMode.java index 847fff403..ddb4d8ef0 100644 --- a/src/main/java/com/avaje/ebean/annotation/UpdateMode.java +++ b/src/main/java/com/avaje/ebean/annotation/UpdateMode.java @@ -1,34 +1,34 @@ -package com.avaje.ebean.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify the update mode for the specific entity type. - *

    - * Control whether all 'loaded' properties are included in an Update or whether - * just properties that have changed will be included in the update. - *

    - *

    - * Note that the default can be set via ebean.properties. - *

    - * - *
    - * ## Set to update all loaded properties
    - * ebean.updateChangesOnly=false
    - * 
    - */ -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface UpdateMode { - - /** - * Set to false if you want to include all the 'loaded' properties in the - * update. Otherwise, just the properties that have changed will be included - * in the update. - */ - boolean updateChangesOnly() default true; - -}; +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the update mode for the specific entity type. + *

    + * Control whether all 'loaded' properties are included in an Update or whether + * just properties that have changed will be included in the update. + *

    + *

    + * Note that the default can be set via ebean.properties. + *

    + * + *
    + * ## Set to update all loaded properties
    + * ebean.updateChangesOnly=false
    + * 
    + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface UpdateMode { + + /** + * Set to false if you want to include all the 'loaded' properties in the + * update. Otherwise, just the properties that have changed will be included + * in the update. + */ + boolean updateChangesOnly() default true; + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/UpdatedTimestamp.java b/src/main/java/com/avaje/ebean/annotation/UpdatedTimestamp.java index f5c8d89a7..3082faa10 100644 --- a/src/main/java/com/avaje/ebean/annotation/UpdatedTimestamp.java +++ b/src/main/java/com/avaje/ebean/annotation/UpdatedTimestamp.java @@ -1,16 +1,16 @@ -package com.avaje.ebean.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * For a timestamp property that is set to the datetime when the entity was last - * updated. - */ -@Target({ ElementType.FIELD, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -public @interface UpdatedTimestamp { - -}; +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; + +/** + * For a timestamp property that is set to the datetime when the entity was last + * updated. + */ +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface UpdatedTimestamp { + +}; diff --git a/src/main/java/com/avaje/ebean/annotation/Where.java b/src/main/java/com/avaje/ebean/annotation/Where.java index 138ef33c2..77b3c330a 100644 --- a/src/main/java/com/avaje/ebean/annotation/Where.java +++ b/src/main/java/com/avaje/ebean/annotation/Where.java @@ -1,51 +1,51 @@ -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; - -/** - * Add an Literal to add to the where clause when a many property (List, Set or - * Map) is loaded or refreshed. - * - *
    - * // on a OneToMany property...
    - * 
    - * @OneToMany
    - * @Where(clause = "deleted='y'")
    - * List<Topic> topics;
    - * 
    - * - *

    - * Note that you can include "${ta}" as a place holder for the table alias if - * you need to include the table alias in the clause. - *

    - * - *
    - * // ... including the ${ta} table alias placeholder...
    - * 
    - * @OneToMany
    - * @Where(clause = "${ta}.deleted='y'")
    - * List<Topic> topics;
    - * 
    - * - *

    - * This will be added to the where clause when lazy loading the OneToMany - * property or when there is a join to that OneToMany property. - *

    - */ -@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface Where { - - /** - * The clause added to the lazy load query. - *

    - * Note that you can include "${ta}" as a place holder for the table alias if - * you need to include the table alias in the clause. - *

    - */ - String clause(); - -}; +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; + +/** + * Add an Literal to add to the where clause when a many property (List, Set or + * Map) is loaded or refreshed. + * + *
    + * // on a OneToMany property...
    + * 
    + * @OneToMany
    + * @Where(clause = "deleted='y'")
    + * List<Topic> topics;
    + * 
    + * + *

    + * Note that you can include "${ta}" as a place holder for the table alias if + * you need to include the table alias in the clause. + *

    + * + *
    + * // ... including the ${ta} table alias placeholder...
    + * 
    + * @OneToMany
    + * @Where(clause = "${ta}.deleted='y'")
    + * List<Topic> topics;
    + * 
    + * + *

    + * This will be added to the where clause when lazy loading the OneToMany + * property or when there is a join to that OneToMany property. + *

    + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Where { + + /** + * The clause added to the lazy load query. + *

    + * Note that you can include "${ta}" as a place holder for the table alias if + * you need to include the table alias in the clause. + *

    + */ + String clause(); + +}; diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollection.java b/src/main/java/com/avaje/ebean/bean/BeanCollection.java index a421dc5f5..b915f1862 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollection.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollection.java @@ -1,201 +1,201 @@ -package com.avaje.ebean.bean; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Set; - -import com.avaje.ebean.ExpressionList; - -/** - * Lazy loading capable Maps, Lists and Sets. - *

    - * This also includes the ability to listen for additions and removals to or - * from the Map Set or List. The purpose of gathering the additions and removals - * is to support persisting ManyToMany objects. The additions and removals - * become inserts and deletes from the intersection table. - *

    - *

    - * Technically this is NOT an extension of - * java.util.Collection. The reason being that java.util.Map is not a - * Collection. I realise this makes this name confusing so I apologise for that. - *

    - */ -public interface BeanCollection extends Serializable { - - enum ModifyListenMode { - /** The common mode */ - NONE, - /** Mode used for PrivateOwned */ - REMOVALS, - /** Mode used for ManyToMany relationships */ - ALL - } - - /** - * Reset the collection back to an empty state ready for reloading. - *

    - * This is done as part of bean refresh. - */ - void reset(EntityBean ownerBean, String propertyName); - - /** - * Return true if the collection is empty and untouched. Used to detect if a - * collection was 'cleared' deliberately or just un-initialised. - */ - boolean isEmptyAndUntouched(); - - /** - * Return the bean that owns this collection. - */ - EntityBean getOwnerBean(); - - /** - * Return the bean property name this collection represents. - */ - String getPropertyName(); - - /** - * Check after the lazy load that the underlying collection is not null - * (handle case where join to many not outer). - *

    - * That is, if the collection was not loaded due to filterMany predicates etc - * then make sure the collection is set to empty. - *

    - */ - boolean checkEmptyLazyLoad(); - - /** - * Return the filter (if any) that was used in building this collection. - *

    - * This is so that the filter can be applied on refresh. - *

    - */ - ExpressionList getFilterMany(); - - /** - * Set the filter that was used in building this collection. - */ - void setFilterMany(ExpressionList filterMany); - - /** - * Set a listener to be notified when the BeanCollection is first touched. - */ - void setBeanCollectionTouched(BeanCollectionTouched notify); - - /** - * Return true if the collection has been registered with the batch loading context. - */ - boolean isRegisteredWithLoadContext(); - - /** - * Set the loader that will be used to lazy/query load this collection. - *

    - * This is effectively the batch loading context this collection is registered with. - *

    - */ - void setLoader(BeanCollectionLoader beanLoader); - - /** - * Set to true if you want the BeanCollection to be treated as read only. This - * means no elements can be added or removed etc. - */ - void setReadOnly(boolean readOnly); - - /** - * Return true if the collection should be treated as readOnly and no elements - * can be added or removed etc. - */ - boolean isReadOnly(); - - /** - * Add the bean to the collection. - *

    - * This is disallowed for BeanMap. - *

    - */ - void internalAdd(Object bean); - - /** - * Return the number of elements in the List Set or Map. - */ - int size(); - - /** - * Return true if the List Set or Map is empty. - */ - boolean isEmpty(); - - /** - * Returns the underlying collection of beans from the Set, Map or List. - */ - Collection getActualDetails(); - - /** - * Returns the underlying entries so for Maps this is a collection of - * Map.Entry. - *

    - * For maps this returns the entrySet as we need the keys of the map. - *

    - */ - Collection getActualEntries(); - - /** - * return true if there are real rows held. Return false is this is using - * Deferred fetch to lazy load the rows and the rows have not yet been - * fetched. - */ - boolean isPopulated(); - - /** - * Return true if this is a reference (lazy loading) bean collection. This is - * the same as !isPopulated(); - */ - boolean isReference(); - - /** - * Set modify listening on or off. This is used to keep track of objects that - * have been added to or removed from the list set or map. - *

    - * This is required only for ManyToMany collections. The additions and - * deletions are used to insert or delete entries from the intersection table. - * Otherwise modifyListening is false. - *

    - */ - void setModifyListening(ModifyListenMode modifyListenMode); - - /** - * Add an object to the additions list. - *

    - * This will potentially end up as an insert into a intersection table for a - * ManyToMany. - *

    - */ - void modifyAddition(E bean); - - /** - * Add an object to the deletions list. - *

    - * This will potentially end up as an delete from an intersection table for a - * ManyToMany. - *

    - */ - void modifyRemoval(Object bean); - - /** - * Return the list of objects added to the list set or map. These will used to - * insert rows into the intersection table of a ManyToMany. - */ - Set getModifyAdditions(); - - /** - * Return the list of objects removed from the list set or map. These will - * used to delete rows from the intersection table of a ManyToMany. - */ - Set getModifyRemovals(); - - /** - * Reset the set of additions and deletions. This is called after the - * additions and removals have been processed. - */ - void modifyReset(); -} +package com.avaje.ebean.bean; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Set; + +import com.avaje.ebean.ExpressionList; + +/** + * Lazy loading capable Maps, Lists and Sets. + *

    + * This also includes the ability to listen for additions and removals to or + * from the Map Set or List. The purpose of gathering the additions and removals + * is to support persisting ManyToMany objects. The additions and removals + * become inserts and deletes from the intersection table. + *

    + *

    + * Technically this is NOT an extension of + * java.util.Collection. The reason being that java.util.Map is not a + * Collection. I realise this makes this name confusing so I apologise for that. + *

    + */ +public interface BeanCollection extends Serializable { + + enum ModifyListenMode { + /** The common mode */ + NONE, + /** Mode used for PrivateOwned */ + REMOVALS, + /** Mode used for ManyToMany relationships */ + ALL + } + + /** + * Reset the collection back to an empty state ready for reloading. + *

    + * This is done as part of bean refresh. + */ + void reset(EntityBean ownerBean, String propertyName); + + /** + * Return true if the collection is empty and untouched. Used to detect if a + * collection was 'cleared' deliberately or just un-initialised. + */ + boolean isEmptyAndUntouched(); + + /** + * Return the bean that owns this collection. + */ + EntityBean getOwnerBean(); + + /** + * Return the bean property name this collection represents. + */ + String getPropertyName(); + + /** + * Check after the lazy load that the underlying collection is not null + * (handle case where join to many not outer). + *

    + * That is, if the collection was not loaded due to filterMany predicates etc + * then make sure the collection is set to empty. + *

    + */ + boolean checkEmptyLazyLoad(); + + /** + * Return the filter (if any) that was used in building this collection. + *

    + * This is so that the filter can be applied on refresh. + *

    + */ + ExpressionList getFilterMany(); + + /** + * Set the filter that was used in building this collection. + */ + void setFilterMany(ExpressionList filterMany); + + /** + * Set a listener to be notified when the BeanCollection is first touched. + */ + void setBeanCollectionTouched(BeanCollectionTouched notify); + + /** + * Return true if the collection has been registered with the batch loading context. + */ + boolean isRegisteredWithLoadContext(); + + /** + * Set the loader that will be used to lazy/query load this collection. + *

    + * This is effectively the batch loading context this collection is registered with. + *

    + */ + void setLoader(BeanCollectionLoader beanLoader); + + /** + * Set to true if you want the BeanCollection to be treated as read only. This + * means no elements can be added or removed etc. + */ + void setReadOnly(boolean readOnly); + + /** + * Return true if the collection should be treated as readOnly and no elements + * can be added or removed etc. + */ + boolean isReadOnly(); + + /** + * Add the bean to the collection. + *

    + * This is disallowed for BeanMap. + *

    + */ + void internalAdd(Object bean); + + /** + * Return the number of elements in the List Set or Map. + */ + int size(); + + /** + * Return true if the List Set or Map is empty. + */ + boolean isEmpty(); + + /** + * Returns the underlying collection of beans from the Set, Map or List. + */ + Collection getActualDetails(); + + /** + * Returns the underlying entries so for Maps this is a collection of + * Map.Entry. + *

    + * For maps this returns the entrySet as we need the keys of the map. + *

    + */ + Collection getActualEntries(); + + /** + * return true if there are real rows held. Return false is this is using + * Deferred fetch to lazy load the rows and the rows have not yet been + * fetched. + */ + boolean isPopulated(); + + /** + * Return true if this is a reference (lazy loading) bean collection. This is + * the same as !isPopulated(); + */ + boolean isReference(); + + /** + * Set modify listening on or off. This is used to keep track of objects that + * have been added to or removed from the list set or map. + *

    + * This is required only for ManyToMany collections. The additions and + * deletions are used to insert or delete entries from the intersection table. + * Otherwise modifyListening is false. + *

    + */ + void setModifyListening(ModifyListenMode modifyListenMode); + + /** + * Add an object to the additions list. + *

    + * This will potentially end up as an insert into a intersection table for a + * ManyToMany. + *

    + */ + void modifyAddition(E bean); + + /** + * Add an object to the deletions list. + *

    + * This will potentially end up as an delete from an intersection table for a + * ManyToMany. + *

    + */ + void modifyRemoval(Object bean); + + /** + * Return the list of objects added to the list set or map. These will used to + * insert rows into the intersection table of a ManyToMany. + */ + Set getModifyAdditions(); + + /** + * Return the list of objects removed from the list set or map. These will + * used to delete rows from the intersection table of a ManyToMany. + */ + Set getModifyRemovals(); + + /** + * Reset the set of additions and deletions. This is called after the + * additions and removals have been processed. + */ + void modifyReset(); +} diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java b/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java index adf321765..778f2b34f 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollectionAdd.java @@ -1,16 +1,16 @@ -package com.avaje.ebean.bean; - -/** - * Interface to define the addition of a bean to the underlying collection. - *

    - * For maps this takes into account the map key. For List and Set this simply - * adds the bean to the underlying list or set. - *

    - */ -public interface BeanCollectionAdd { - - /** - * Add a loaded bean to the collection. - */ - void addBean(EntityBean bean); -} +package com.avaje.ebean.bean; + +/** + * Interface to define the addition of a bean to the underlying collection. + *

    + * For maps this takes into account the map key. For List and Set this simply + * adds the bean to the underlying list or set. + *

    + */ +public interface BeanCollectionAdd { + + /** + * Add a loaded bean to the collection. + */ + void addBean(EntityBean bean); +} diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollectionLoader.java b/src/main/java/com/avaje/ebean/bean/BeanCollectionLoader.java index 25ee13860..1c80a0437 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollectionLoader.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollectionLoader.java @@ -1,21 +1,21 @@ -package com.avaje.ebean.bean; - -/** - * Loads a entity bean collection. - *

    - * Typically invokes lazy loading for a single or batch of collections. - *

    - */ -public interface BeanCollectionLoader { - - /** - * Return the name of the associated EbeanServer. - */ - String getName(); - - /** - * Invoke the lazy loading for this bean collection. - */ - void loadMany(BeanCollection collection, boolean onlyIds); - -} +package com.avaje.ebean.bean; + +/** + * Loads a entity bean collection. + *

    + * Typically invokes lazy loading for a single or batch of collections. + *

    + */ +public interface BeanCollectionLoader { + + /** + * Return the name of the associated EbeanServer. + */ + String getName(); + + /** + * Invoke the lazy loading for this bean collection. + */ + void loadMany(BeanCollection collection, boolean onlyIds); + +} diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollectionTouched.java b/src/main/java/com/avaje/ebean/bean/BeanCollectionTouched.java index 694c5d574..1451d9783 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollectionTouched.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollectionTouched.java @@ -1,20 +1,20 @@ -package com.avaje.ebean.bean; - -/** - * Used to specify a listener to be notified when a BeanCollection is first - * used. - *

    - * To use this you can set a BeanCollectionTouched onto a BeanCollection before - * it has been used. When the BeanCollection is first used by the client code - * then the BeanCollectionTouched is notified. It can only be notified once. - *

    - * - * @author rbygrave - */ -public interface BeanCollectionTouched { - - /** - * Notify the listener that the bean collection has been used. - */ - void notifyTouched(BeanCollection c); -} +package com.avaje.ebean.bean; + +/** + * Used to specify a listener to be notified when a BeanCollection is first + * used. + *

    + * To use this you can set a BeanCollectionTouched onto a BeanCollection before + * it has been used. When the BeanCollection is first used by the client code + * then the BeanCollectionTouched is notified. It can only be notified once. + *

    + * + * @author rbygrave + */ +public interface BeanCollectionTouched { + + /** + * Notify the listener that the bean collection has been used. + */ + void notifyTouched(BeanCollection c); +} diff --git a/src/main/java/com/avaje/ebean/bean/BeanLoader.java b/src/main/java/com/avaje/ebean/bean/BeanLoader.java index d74a7b681..9f6605064 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanLoader.java +++ b/src/main/java/com/avaje/ebean/bean/BeanLoader.java @@ -1,21 +1,21 @@ -package com.avaje.ebean.bean; - -/** - * Loads a entity bean. - *

    - * Typically invokes lazy loading for a single or batch of entity beans. - *

    - */ -public interface BeanLoader { - - /** - * Return the name of the associated EbeanServer. - */ - String getName(); - - /** - * Invoke the lazy loading for this bean. - */ - void loadBean(EntityBeanIntercept ebi); - -} +package com.avaje.ebean.bean; + +/** + * Loads a entity bean. + *

    + * Typically invokes lazy loading for a single or batch of entity beans. + *

    + */ +public interface BeanLoader { + + /** + * Return the name of the associated EbeanServer. + */ + String getName(); + + /** + * Invoke the lazy loading for this bean. + */ + void loadBean(EntityBeanIntercept ebi); + +} diff --git a/src/main/java/com/avaje/ebean/bean/CallStack.java b/src/main/java/com/avaje/ebean/bean/CallStack.java index 43d17ce0e..0f4b78f0c 100644 --- a/src/main/java/com/avaje/ebean/bean/CallStack.java +++ b/src/main/java/com/avaje/ebean/bean/CallStack.java @@ -1,118 +1,118 @@ -package com.avaje.ebean.bean; - -import java.io.Serializable; -import java.util.Arrays; - -/** - * Represent the call stack (stack trace elements). - *

    - * Used with a query to identify a CallStackQuery for AutoFetch automatic query - * tuning. - *

    - *

    - * This is used so that a single query called from different methods can be - * tuned for each different call stack. - *

    - *

    - * Note the call stack is trimmed to remove the common ebean internal elements. - *

    - */ -public final class CallStack implements Serializable { - - private static final long serialVersionUID = -8590644046907438579L; - - private final String zeroHash; - private final String pathHash; - - private final StackTraceElement[] callStack; - - public CallStack(StackTraceElement[] callStack) { - this.callStack = callStack; - this.zeroHash = enc(callStack[0].hashCode()); - int hc = 0; - for (int i = 1; i < callStack.length; i++) { - hc = 31 * hc + callStack[i].hashCode(); - } - this.pathHash = enc(hc); - } - - public int hashCode() { - int hc = 0; - for (int i = 0; i < callStack.length; i++) { - hc = 31 * hc + callStack[i].hashCode(); - } - return hc; - } - - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof CallStack)) { - return false; - } - CallStack e = (CallStack) obj; - return Arrays.equals(callStack, e.callStack); - } - - /** - * Return the first element of the call stack. - */ - public StackTraceElement getFirstStackTraceElement() { - return callStack[0]; - } - - /** - * Return the call stack. - */ - public StackTraceElement[] getCallStack() { - return callStack; - } - - /** - * Return the hash for the first stack element. - */ - public String getZeroHash() { - return zeroHash; - } - - /** - * Return the hash for the stack elements (excluding first stack element). - */ - public String getPathHash() { - return pathHash; - } - - public String toString() { - return zeroHash + ":" + pathHash + ":" + callStack[0]; - } - - public String getOriginKey(int queryHash) { - return zeroHash + "." + enc(queryHash) + "." + pathHash; - } - - private static final int radix = 1 << 6; - private static final int mask = radix - 1; - - /** - * Convert the integer to unsigned base 64. - */ - public static String enc(int i) { - char[] buf = new char[32]; - int charPos = 32; - do { - buf[--charPos] = intToBase64[i & mask]; - i >>>= 6; - } while (i != 0); - - return new String(buf, charPos, (32 - charPos)); - } - - private static final char intToBase64[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' - }; -} +package com.avaje.ebean.bean; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Represent the call stack (stack trace elements). + *

    + * Used with a query to identify a CallStackQuery for AutoFetch automatic query + * tuning. + *

    + *

    + * This is used so that a single query called from different methods can be + * tuned for each different call stack. + *

    + *

    + * Note the call stack is trimmed to remove the common ebean internal elements. + *

    + */ +public final class CallStack implements Serializable { + + private static final long serialVersionUID = -8590644046907438579L; + + private final String zeroHash; + private final String pathHash; + + private final StackTraceElement[] callStack; + + public CallStack(StackTraceElement[] callStack) { + this.callStack = callStack; + this.zeroHash = enc(callStack[0].hashCode()); + int hc = 0; + for (int i = 1; i < callStack.length; i++) { + hc = 31 * hc + callStack[i].hashCode(); + } + this.pathHash = enc(hc); + } + + public int hashCode() { + int hc = 0; + for (int i = 0; i < callStack.length; i++) { + hc = 31 * hc + callStack[i].hashCode(); + } + return hc; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CallStack)) { + return false; + } + CallStack e = (CallStack) obj; + return Arrays.equals(callStack, e.callStack); + } + + /** + * Return the first element of the call stack. + */ + public StackTraceElement getFirstStackTraceElement() { + return callStack[0]; + } + + /** + * Return the call stack. + */ + public StackTraceElement[] getCallStack() { + return callStack; + } + + /** + * Return the hash for the first stack element. + */ + public String getZeroHash() { + return zeroHash; + } + + /** + * Return the hash for the stack elements (excluding first stack element). + */ + public String getPathHash() { + return pathHash; + } + + public String toString() { + return zeroHash + ":" + pathHash + ":" + callStack[0]; + } + + public String getOriginKey(int queryHash) { + return zeroHash + "." + enc(queryHash) + "." + pathHash; + } + + private static final int radix = 1 << 6; + private static final int mask = radix - 1; + + /** + * Convert the integer to unsigned base 64. + */ + public static String enc(int i) { + char[] buf = new char[32]; + int charPos = 32; + do { + buf[--charPos] = intToBase64[i & mask]; + i >>>= 6; + } while (i != 0); + + return new String(buf, charPos, (32 - charPos)); + } + + private static final char intToBase64[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; +} diff --git a/src/main/java/com/avaje/ebean/bean/EntityBean.java b/src/main/java/com/avaje/ebean/bean/EntityBean.java index 4a077a038..81224166f 100644 --- a/src/main/java/com/avaje/ebean/bean/EntityBean.java +++ b/src/main/java/com/avaje/ebean/bean/EntityBean.java @@ -1,114 +1,114 @@ -package com.avaje.ebean.bean; - -import java.beans.PropertyChangeListener; -import java.io.Serializable; - -/** - * Bean that is aware of EntityBeanIntercept. - *

    - * This interface and implementation of these methods is added to Entity Beans - * via instrumentation. These methods have a funny _ebean_ prefix to avoid any - * clash with normal methods these beans would have. These methods are not for - * general application consumption. - *

    - */ -public interface EntityBean extends Serializable { - - String[] _ebean_getPropertyNames(); - - String _ebean_getPropertyName(int pos); - - /** - * Return the enhancement marker value. - *

    - * This is the class name of the enhanced class and used to check that all - * entity classes are enhanced (specifically not just a super class). - *

    - */ - String _ebean_getMarker(); - - /** - * Create and return a new entity bean instance. - */ - Object _ebean_newInstance(); - - /** - * Add a PropertyChangeListener to this bean. - */ - void addPropertyChangeListener(PropertyChangeListener listener); - - /** - * Remove a PropertyChangeListener from this bean. - */ - void removePropertyChangeListener(PropertyChangeListener listener); - - /** - * Generated method that sets the loaded state on all the embedded beans on - * this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o); - */ - void _ebean_setEmbeddedLoaded(); - - /** - * Return true if any embedded beans are new or dirty. - */ - boolean _ebean_isEmbeddedNewOrDirty(); - - /** - * Return the intercept for this object. - */ - EntityBeanIntercept _ebean_getIntercept(); - - /** - * Similar to _ebean_getIntercept() except it checks to see if the intercept - * field is null and will create it if required. - *

    - * This is really only required when transientInternalFields=true as an - * enhancement option. In this case the intercept field is transient and will - * be null after a bean has been deserialised. - *

    - *

    - * This transientInternalFields=true option was to support some serialization - * frameworks that can't take into account our ebean fields. - *

    - */ - EntityBeanIntercept _ebean_intercept(); - - /** - * Create a copy of this entity bean. - *

    - * This occurs when a bean is changed. The copy represents the bean as it was - * initially (oldValues) before any changes where made. This is used for - * optimistic concurrency control. - *

    - */ - Object _ebean_createCopy(); - - /** - * Set the value of a field of an entity bean of this type. - *

    - * Note that using this method bypasses any interception that otherwise occurs - * on entity beans. That means lazy loading and oldValues creation. - *

    - */ - void _ebean_setField(int fieldIndex, Object value); - - /** - * Set the field value with interception. - */ - void _ebean_setFieldIntercept(int fieldIndex, Object value); - - /** - * Return the value of a field from an entity bean of this type. - *

    - * Note that using this method bypasses any interception that otherwise occurs - * on entity beans. That means lazy loading. - *

    - */ - Object _ebean_getField(int fieldIndex); - - /** - * Return the field value with interception. - */ - Object _ebean_getFieldIntercept(int fieldIndex); - -} +package com.avaje.ebean.bean; + +import java.beans.PropertyChangeListener; +import java.io.Serializable; + +/** + * Bean that is aware of EntityBeanIntercept. + *

    + * This interface and implementation of these methods is added to Entity Beans + * via instrumentation. These methods have a funny _ebean_ prefix to avoid any + * clash with normal methods these beans would have. These methods are not for + * general application consumption. + *

    + */ +public interface EntityBean extends Serializable { + + String[] _ebean_getPropertyNames(); + + String _ebean_getPropertyName(int pos); + + /** + * Return the enhancement marker value. + *

    + * This is the class name of the enhanced class and used to check that all + * entity classes are enhanced (specifically not just a super class). + *

    + */ + String _ebean_getMarker(); + + /** + * Create and return a new entity bean instance. + */ + Object _ebean_newInstance(); + + /** + * Add a PropertyChangeListener to this bean. + */ + void addPropertyChangeListener(PropertyChangeListener listener); + + /** + * Remove a PropertyChangeListener from this bean. + */ + void removePropertyChangeListener(PropertyChangeListener listener); + + /** + * Generated method that sets the loaded state on all the embedded beans on + * this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o); + */ + void _ebean_setEmbeddedLoaded(); + + /** + * Return true if any embedded beans are new or dirty. + */ + boolean _ebean_isEmbeddedNewOrDirty(); + + /** + * Return the intercept for this object. + */ + EntityBeanIntercept _ebean_getIntercept(); + + /** + * Similar to _ebean_getIntercept() except it checks to see if the intercept + * field is null and will create it if required. + *

    + * This is really only required when transientInternalFields=true as an + * enhancement option. In this case the intercept field is transient and will + * be null after a bean has been deserialised. + *

    + *

    + * This transientInternalFields=true option was to support some serialization + * frameworks that can't take into account our ebean fields. + *

    + */ + EntityBeanIntercept _ebean_intercept(); + + /** + * Create a copy of this entity bean. + *

    + * This occurs when a bean is changed. The copy represents the bean as it was + * initially (oldValues) before any changes where made. This is used for + * optimistic concurrency control. + *

    + */ + Object _ebean_createCopy(); + + /** + * Set the value of a field of an entity bean of this type. + *

    + * Note that using this method bypasses any interception that otherwise occurs + * on entity beans. That means lazy loading and oldValues creation. + *

    + */ + void _ebean_setField(int fieldIndex, Object value); + + /** + * Set the field value with interception. + */ + void _ebean_setFieldIntercept(int fieldIndex, Object value); + + /** + * Return the value of a field from an entity bean of this type. + *

    + * Note that using this method bypasses any interception that otherwise occurs + * on entity beans. That means lazy loading. + *

    + */ + Object _ebean_getField(int fieldIndex); + + /** + * Return the field value with interception. + */ + Object _ebean_getFieldIntercept(int fieldIndex); + +} diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java index c662552a3..bbed76666 100644 --- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java @@ -1,1060 +1,1060 @@ -package com.avaje.ebean.bean; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; -import java.io.Serializable; -import java.math.BigDecimal; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import javax.persistence.EntityNotFoundException; -import javax.persistence.PersistenceException; - -import com.avaje.ebean.Ebean; -import com.avaje.ebean.ValuePair; - -/** - * This is the object added to every entity bean using byte code enhancement. - *

    - * This provides the mechanisms to support deferred fetching of reference beans - * and oldValues generation for concurrency checking. - *

    - */ -public final class EntityBeanIntercept implements Serializable { - - private static final long serialVersionUID = -3664031775464862649L; - - private static final int STATE_NEW = 0; - private static final int STATE_REFERENCE = 1; - private static final int STATE_LOADED = 2; - - private transient NodeUsageCollector nodeUsageCollector; - - private transient PropertyChangeSupport pcs; - - private transient PersistenceContext persistenceContext; - - private transient BeanLoader beanLoader; - - private String ebeanServerName; - - /** - * The actual entity bean that 'owns' this intercept. - */ - private EntityBean owner; - - private EntityBean embeddedOwner; - private int embeddedOwnerIndex; - - /** - * One of NEW, REF, UPD. - */ - private int state; - - private boolean readOnly; - - private boolean dirty; - - /** - * Flag set to disable lazy loading - typically for SQL "report" type entity beans. - */ - private boolean disableLazyLoad; - - /** - * Flag set when lazy loading failed due to the underlying bean being deleted in the DB. - */ - private boolean lazyLoadFailure; - - /** - * Used when a bean is partially filled. - */ - private boolean[] loadedProps; - - private boolean fullyLoadedBean; - - /** - * Set of changed properties. - */ - private boolean[] changedProps; - - /** - * Flags indicating if a property is a dirty embedded bean. Used to distingush - * between an embedded bean being completely overwritten and one of its - * embedded properties being made dirty. - */ - private boolean[] embeddedDirty; - - private Object[] origValues; - - private int lazyLoadProperty = -1; - - /** - * Create a intercept with a given entity. - *

    - * Refer to agent ProxyConstructor. - *

    - */ - public EntityBeanIntercept(Object ownerBean) { - this.owner = (EntityBean) ownerBean; - this.loadedProps = new boolean[owner._ebean_getPropertyNames().length]; - } - - /** - * Return the 'owning' entity bean. - */ - public EntityBean getOwner() { - return owner; - } - - /** - * Return the persistenceContext. - */ - public PersistenceContext getPersistenceContext() { - return persistenceContext; - } - - /** - * Set the persistenceContext. - */ - public void setPersistenceContext(PersistenceContext persistenceContext) { - this.persistenceContext = persistenceContext; - } - - /** - * Add a property change listener for this entity bean. - */ - public void addPropertyChangeListener(PropertyChangeListener listener) { - if (pcs == null) { - pcs = new PropertyChangeSupport(owner); - } - pcs.addPropertyChangeListener(listener); - } - - /** - * Add a property change listener for this entity bean for a specific - * property. - */ - public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { - if (pcs == null) { - pcs = new PropertyChangeSupport(owner); - } - pcs.addPropertyChangeListener(propertyName, listener); - } - - /** - * Remove a property change listener for this entity bean. - */ - public void removePropertyChangeListener(PropertyChangeListener listener) { - if (pcs != null) { - pcs.removePropertyChangeListener(listener); - } - } - - /** - * Remove a property change listener for this entity bean for a specific - * property. - */ - public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { - if (pcs != null) { - pcs.removePropertyChangeListener(propertyName, listener); - } - } - - /** - * Turn on profile collection. - */ - public void setNodeUsageCollector(NodeUsageCollector usageCollector) { - this.nodeUsageCollector = usageCollector; - } - - /** - * Return the owning bean for an embedded bean. - */ - public Object getEmbeddedOwner() { - return embeddedOwner; - } - - /** - * Return the property index (for the parent) of this embedded bean. - */ - public int getEmbeddedOwnerIndex() { - return embeddedOwnerIndex; - } - - /** - * Set the embedded beans owning bean. - */ - public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) { - this.embeddedOwner = parentBean; - this.embeddedOwnerIndex = embeddedOwnerIndex; - } - - /** - * Set the BeanLoader with PersistenceContext. - */ - public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) { - this.beanLoader = beanLoader; - this.persistenceContext = ctx; - this.ebeanServerName = beanLoader.getName(); - } - - /** - * Set the BeanLoader. - */ - public void setBeanLoader(BeanLoader beanLoader) { - this.beanLoader = beanLoader; - this.ebeanServerName = beanLoader.getName(); - } - - public boolean isFullyLoadedBean() { - return fullyLoadedBean; - } - - public void setFullyLoadedBean(boolean fullyLoadedBean) { - this.fullyLoadedBean = fullyLoadedBean; - } - - /** - * Return true if this bean has been directly modified (it has oldValues) or - * if any embedded beans are either new or dirty (and hence need saving). - */ - public boolean isDirty() { - return dirty; - } - - /** - * Called by an embedded bean onto its owner. - */ - public void setEmbeddedDirty(int embeddedProperty) { - this.dirty = true; - setEmbeddedPropertyDirty(embeddedProperty); - } - - public void setDirty(boolean dirty) { - this.dirty = dirty; - } - - /** - * Return true if this entity bean is new and not yet saved. - */ - public boolean isNew() { - return state == STATE_NEW; - } - - /** - * Return true if the entity bean is new or dirty (and should be saved). - */ - public boolean isNewOrDirty() { - return isNew() || isDirty(); - } - - /** - * Return true if only the Id property has been loaded. - */ - public boolean hasIdOnly(int idIndex) { - for (int i = 0; i < loadedProps.length; i++) { - if (i == idIndex) { - if (!loadedProps[i]) return false; - } else if (loadedProps[i]) { - return false; - } - } - return true; - } - - /** - * Return true if the entity is a reference. - */ - public boolean isReference() { - return state == STATE_REFERENCE; - } - - /** - * Set this as a reference object. - */ - public void setReference(int idPos) { - state = STATE_REFERENCE; - if (idPos > -1) { - // For cases where properties are set on constructor - // set every non Id property to unloaded (for lazy loading) - for (int i=0; i< loadedProps.length; i++) { - if (i != idPos) { - loadedProps[i] = false; - } - } - } - } - - /** - * Return true if the bean should be treated as readOnly. If a setter method - * is called when it is readOnly an Exception is thrown. - */ - public boolean isReadOnly() { - return readOnly; - } - - /** - * Set the readOnly status. If readOnly then calls to setter methods through - * an exception. - */ - public void setReadOnly(boolean readOnly) { - this.readOnly = readOnly; - } - - /** - * Return true if the entity has been loaded. - */ - public boolean isLoaded() { - return state == STATE_LOADED; - } - - /** - * Set the loaded state to true. - *

    - * Calls to setter methods after the bean is loaded can result in 'Old Values' - * being created to support ConcurrencyMode.ALL - *

    - *

    - * Worth noting that this is also set after a insert/update. By doing so it - * 'resets' the bean for making further changes and saving again. - *

    - */ - public void setLoaded() { - this.state = STATE_LOADED; - this.owner._ebean_setEmbeddedLoaded(); - this.lazyLoadProperty = -1; - this.origValues = null; - this.changedProps = null; - this.dirty = false; - } - - /** - * When finished loading for lazy or refresh on an already partially populated - * bean. - */ - public void setLoadedLazy() { - this.state = STATE_LOADED; - this.lazyLoadProperty = -1; - } - - /** - * Check if the lazy load succeeded. If not then mark this bean as having - * failed lazy loading due to the underlying row being deleted. - *

    - * We mark the bean this way rather than immediately fail as we might be batch - * lazy loading and this bean might not be used by the client code at all. - * Instead we will fail as soon as the client code tries to use this bean. - *

    - */ - public void checkLazyLoadFailure() { - if (lazyLoadProperty != -1) { - this.lazyLoadFailure = true; - } - } - - /** - * Return true if the bean is marked as having failed lazy loading. - */ - public boolean isLazyLoadFailure() { - return lazyLoadFailure; - } - - /** - * Return true if lazy loading is disabled. - */ - public boolean isDisableLazyLoad() { - return disableLazyLoad; - } - - /** - * Set true to turn off lazy loading. - *

    - * Typically used to disable lazy loading on SQL based report beans. - *

    - */ - public void setDisableLazyLoad(boolean disableLazyLoad) { - this.disableLazyLoad = disableLazyLoad; - } - - /** - * Set the loaded status for the embedded bean. - */ - public void setEmbeddedLoaded(Object embeddedBean) { - if (embeddedBean instanceof EntityBean) { - EntityBean eb = (EntityBean) embeddedBean; - eb._ebean_getIntercept().setLoaded(); - } - } - - /** - * Return true if the embedded bean is new or dirty and hence needs saving. - */ - public boolean isEmbeddedNewOrDirty(Object embeddedBean) { - - if (embeddedBean == null) { - // if it was previously set then the owning bean would - // have oldValues containing the previous embedded bean - return false; - } - if (embeddedBean instanceof EntityBean) { - return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty(); - - } else { - // non-enhanced so must assume it is new and needs to be saved - return true; - } - } - - /** - * Return the original value that was changed via an update. - */ - public Object getOrigValue(int propertyIndex) { - if (origValues == null) { - return null; - } - return origValues[propertyIndex]; - } - - /** - * Finds the index position of a given property. Returns -1 if the property - * can not be found. - */ - public int findProperty(String propertyName) { - String[] names = owner._ebean_getPropertyNames(); - for (int i = 0; i < names.length; i++) { - if (names[i].equals(propertyName)) { - return i; - } - } - return -1; - } - - /** - * Return the property name for the given property. - */ - public String getProperty(int propertyIndex) { - if (propertyIndex == -1) { - return null; - } - return owner._ebean_getPropertyName(propertyIndex); - } - - /** - * Return the number of properties.s - */ - public int getPropertyLength() { - return owner._ebean_getPropertyNames().length; - } - - /** - * Set the property to be treated as unloaded. Used for properties initialised in default - * constructor. - */ - public void setPropertyUnloaded(int propertyIndex) { - loadedProps[propertyIndex] = false; - } - - /** - * Set the property to be loaded. - */ - public void setLoadedProperty(int propertyIndex) { - loadedProps[propertyIndex] = true; - } - - /** - * Return true if the property is loaded. - */ - public boolean isLoadedProperty(int propertyIndex) { - return loadedProps[propertyIndex]; - } - - /** - * Return true if the property is considered changed. - */ - public boolean isChangedProperty(int propertyIndex) { - return (changedProps != null && changedProps[propertyIndex]); - } - - /** - * Return true if the property was changed or if it is embedded and one of its - * embedded properties is dirty. - */ - public boolean isDirtyProperty(int propertyIndex) { - return (changedProps != null && changedProps[propertyIndex] - || embeddedDirty != null && embeddedDirty[propertyIndex]); - } - - /** - * Explicitly mark a property as having been changed. - */ - public void markPropertyAsChanged(int propertyIndex) { - setChangedProperty(propertyIndex); - setDirty(true); - } - - private void setChangedProperty(int propertyIndex) { - if (changedProps == null) { - changedProps = new boolean[owner._ebean_getPropertyNames().length]; - } - changedProps[propertyIndex] = true; - } - - /** - * Set that an embedded bean has had one of its properties changed. - */ - private void setEmbeddedPropertyDirty(int propertyIndex) { - if (embeddedDirty == null) { - embeddedDirty = new boolean[owner._ebean_getPropertyNames().length]; - } - embeddedDirty[propertyIndex] = true; - } - - private void setOriginalValue(int propertyIndex, Object value) { - if (origValues == null) { - origValues = new Object[owner._ebean_getPropertyNames().length]; - } - if (origValues[propertyIndex] == null) { - origValues[propertyIndex] = value; - } - } - - /** - * For forced update on a 'New' bean set all the loaded properties to changed. - */ - public void setNewBeanForUpdate() { - - if (changedProps == null) { - changedProps = new boolean[owner._ebean_getPropertyNames().length]; - } - - for (int i=0; i< loadedProps.length; i++) { - if (loadedProps[i]) { - changedProps[i] = true; - } - } - setDirty(true); - } - - /** - * Return the set of property names for a partially loaded bean. - */ - public Set getLoadedPropertyNames() { - if (fullyLoadedBean) { - return null; - } - Set props = new LinkedHashSet(); - for (int i=0; i getDirtyPropertyNames() { - Set props = new LinkedHashSet(); - addDirtyPropertyNames(props, null); - return props; - } - - /** - * Recursively add dirty properties. - */ - public void addDirtyPropertyNames(Set props, String prefix) { - int len = getPropertyLength(); - for (int i = 0; i < len; i++) { - if (changedProps != null && changedProps[i]) { - // the property has been changed on this bean - String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i)); - props.add(propName); - } else if (embeddedDirty != null && embeddedDirty[i]) { - // an embedded property has been changed - recurse - EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); - embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i)+"."); - } - } - } - - /** - * Return a map of dirty properties with their new and old values. - */ - public Map getDirtyValues() { - Map dirtyValues = new LinkedHashMap(); - addDirtyPropertyValues(dirtyValues, null); - return dirtyValues; - } - - /** - * Recursively add dirty properties. - */ - public void addDirtyPropertyValues(Map dirtyValues, String prefix) { - int len = getPropertyLength(); - for (int i = 0; i < len; i++) { - if (changedProps != null && changedProps[i]) { - // the property has been changed on this bean - String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i)); - Object newVal = owner._ebean_getField(i); - Object oldVal = getOrigValue(i); - - dirtyValues.put(propName, new ValuePair(newVal, oldVal)); - - } else if (embeddedDirty != null && embeddedDirty[i]) { - // an embedded property has been changed - recurse - EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); - embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i)+"."); - } - } - } - - /** - * Return a dirty property hash taking into account embedded beans. - */ - public int getDirtyPropertyHash() { - return addDirtyPropertyHash(37); - } - - /** - * Add and return a dirty property hash recursing into embedded beans. - */ - public int addDirtyPropertyHash(int hash) { - int len = getPropertyLength(); - for (int i = 0; i < len; i++) { - if (changedProps != null && changedProps[i]) { - // the property has been changed on this bean - hash = hash * 31 + (i+1); - } else if (embeddedDirty != null && embeddedDirty[i]) { - // an embedded property has been changed - recurse - EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); - hash = hash * 31 + embeddedBean._ebean_getIntercept().addDirtyPropertyHash(hash); - } - } - return hash; - } - - /** - * Return the set of property names for changed properties. - */ - public boolean[] getChanged() { - return changedProps; - } - - public boolean[] getLoaded() { - return loadedProps; - } - - /** - * Return the index of the property that triggered the lazy load. - */ - public int getLazyLoadPropertyIndex() { - return lazyLoadProperty; - } - - /** - * Return the property that triggered the lazy load. - */ - public String getLazyLoadProperty() { - return getProperty(lazyLoadProperty); - } - - /** - * Load the bean when it is a reference. - */ - protected void loadBean(int loadProperty) { - - synchronized (this) { - if (beanLoader == null) { - BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName); - if (serverLoader == null) { - throw new PersistenceException("Server [" + ebeanServerName + "] was not found?"); - } - - // For stand alone reference bean or after deserialisation lazy load - // using the ebeanServer. Synchronise only on the bean. - loadBeanInternal(loadProperty, serverLoader); - return; - } - } - - synchronized (beanLoader) { - // Lazy loading using LoadBeanContext which supports batch loading - // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree') - loadBeanInternal(loadProperty, beanLoader); - } - } - - /** - * Invoke the lazy loading. This method is synchronised externally. - */ - private void loadBeanInternal(int loadProperty, BeanLoader loader) { - - if (loadedProps == null || loadedProps[loadProperty]) { - // race condition where multiple threads calling preGetter concurrently - return; - } - - if (lazyLoadFailure) { - // failed when batch lazy loaded by another bean in the batch - throw new EntityNotFoundException("Bean has been deleted - lazy loading failed"); - } - - if (lazyLoadProperty == -1) { - - lazyLoadProperty = loadProperty; - - if (nodeUsageCollector != null) { - nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty)); - } - - loader.loadBean(this); - - if (lazyLoadFailure) { - // failed when lazy loading this bean - throw new EntityNotFoundException("Bean has been deleted - lazy loading failed"); - } - - // bean should be loaded and intercepting now. setLoaded() has - // been called by the lazy loading mechanism - } - } - - /** - * Helper method to check if two objects are equal. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected boolean areEqual(Object obj1, Object obj2) { - if (obj1 == null) { - return (obj2 == null); - } - if (obj2 == null) { - return false; - } - if (obj1 == obj2) { - return true; - } - if (obj1 instanceof BigDecimal) { - // Use comparable for BigDecimal as equals - // uses scale in comparison... - if (obj2 instanceof BigDecimal) { - Comparable com1 = (Comparable) obj1; - return (com1.compareTo(obj2) == 0); - - } else { - return false; - } - } - if (obj1 instanceof URL) { - // use the string format to determine if dirty - return obj1.toString().equals(obj2.toString()); - } - return obj1.equals(obj2); - } - - /** - * Called when a BeanCollection is initialised automatically. - */ - public void initialisedMany(int propertyIndex) { - loadedProps[propertyIndex] = true; - } - - /** - * Method that is called prior to a getter method on the actual entity. - */ - public void preGetter(int propertyIndex) { - if (state == STATE_NEW || disableLazyLoad) { - return; - } - - if (!isLoadedProperty(propertyIndex)) { - loadBean(propertyIndex); - } - - if (nodeUsageCollector != null) { - nodeUsageCollector.addUsed(getProperty(propertyIndex)); - } - } - - /** - * Called for "enhancement" postSetter processing. This is around a PUTFIELD - * so no need to check the newValue afterwards. - */ - public void postSetter(PropertyChangeEvent event) { - if (pcs != null && event != null) { - pcs.firePropertyChange(event); - } - } - - /** - * Called for "subclassed" postSetter processing. Here the newValue has to be - * re-fetched (and passed into this method) in case there is code inside the - * setter that further mutates the value. - */ - public void postSetter(PropertyChangeEvent event, Object newValue) { - if (pcs != null && event != null) { - if (newValue != null && newValue.equals(event.getNewValue())) { - pcs.firePropertyChange(event); - } else { - pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue); - } - } - } - - /** - * OneToMany and ManyToMany don't have any interception so just check for - * PropertyChangeSupport. - */ - public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) { - - if (readOnly) { - throw new IllegalStateException("This bean is readOnly"); - } - - setLoadedProperty(propertyIndex); - - // Bean itself not considered dirty when many changed - if (pcs != null) { - return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); - } else { - return null; - } - } - - private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) { - - if (readOnly) { - throw new IllegalStateException("This bean is readOnly"); - } - setChangedProperty(propertyIndex); - - if (setDirtyState) { - setOriginalValue(propertyIndex, origValue); - if (!dirty) { - dirty = true; - if (embeddedOwner != null) { - // Cascade dirty state from Embedded bean to parent bean - embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex); - } - if (nodeUsageCollector != null) { - nodeUsageCollector.setModified(); - } - } - } - } - - /** - * Check to see if the values are not equal. If they are not equal then create - * the old values for use with ConcurrencyMode.ALL. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (!areEqual(oldValue, newValue)) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); - } - - - /** - * Check for primitive boolean. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); - } - - /** - * Check for primitive int. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Integer.valueOf(oldValue), Integer.valueOf(newValue)); - } - - /** - * long. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Long.valueOf(oldValue), Long.valueOf(newValue)); - } - - /** - * double. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Double.valueOf(oldValue), Double.valueOf(newValue)); - } - - /** - * float. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Float.valueOf(oldValue), Float.valueOf(newValue)); - } - - /** - * short. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Short.valueOf(oldValue), Short.valueOf(newValue)); - } - - /** - * char. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Character.valueOf(oldValue), Character.valueOf(newValue)); - } - - /** - * byte. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (oldValue != newValue) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Byte.valueOf(oldValue), Byte.valueOf(newValue)); - } - - /** - * char[]. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (!areEqualChars(oldValue, newValue)) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null: new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); - } - - /** - * byte[]. - */ - public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) { - - if (state == STATE_NEW) { - setLoadedProperty(propertyIndex); - } else if (!areEqualBytes(oldValue, newValue)) { - setChangedPropertyValue(propertyIndex, intercept, oldValue); - } else { - return null; - } - return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); - } - - private static boolean areEqualBytes(byte[] b1, byte[] b2) { - if (b1 == null) { - return (b2 == null); - - } else if (b2 == null) { - return false; - - } else if (b1 == b2) { - return true; - - } else if (b1.length != b2.length) { - return false; - } - for (int i = 0; i < b1.length; i++) { - if (b1[i] != b2[i]) { - return false; - } - } - return true; - } - - private static boolean areEqualChars(char[] b1, char[] b2) { - if (b1 == null) { - return (b2 == null); - - } else if (b2 == null) { - return false; - - } else if (b1 == b2) { - return true; - - } else if (b1.length != b2.length) { - return false; - } - for (int i = 0; i < b1.length; i++) { - if (b1[i] != b2[i]) { - return false; - } - } - return true; - } -} +package com.avaje.ebean.bean; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.Serializable; +import java.math.BigDecimal; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.persistence.EntityNotFoundException; +import javax.persistence.PersistenceException; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.ValuePair; + +/** + * This is the object added to every entity bean using byte code enhancement. + *

    + * This provides the mechanisms to support deferred fetching of reference beans + * and oldValues generation for concurrency checking. + *

    + */ +public final class EntityBeanIntercept implements Serializable { + + private static final long serialVersionUID = -3664031775464862649L; + + private static final int STATE_NEW = 0; + private static final int STATE_REFERENCE = 1; + private static final int STATE_LOADED = 2; + + private transient NodeUsageCollector nodeUsageCollector; + + private transient PropertyChangeSupport pcs; + + private transient PersistenceContext persistenceContext; + + private transient BeanLoader beanLoader; + + private String ebeanServerName; + + /** + * The actual entity bean that 'owns' this intercept. + */ + private EntityBean owner; + + private EntityBean embeddedOwner; + private int embeddedOwnerIndex; + + /** + * One of NEW, REF, UPD. + */ + private int state; + + private boolean readOnly; + + private boolean dirty; + + /** + * Flag set to disable lazy loading - typically for SQL "report" type entity beans. + */ + private boolean disableLazyLoad; + + /** + * Flag set when lazy loading failed due to the underlying bean being deleted in the DB. + */ + private boolean lazyLoadFailure; + + /** + * Used when a bean is partially filled. + */ + private boolean[] loadedProps; + + private boolean fullyLoadedBean; + + /** + * Set of changed properties. + */ + private boolean[] changedProps; + + /** + * Flags indicating if a property is a dirty embedded bean. Used to distingush + * between an embedded bean being completely overwritten and one of its + * embedded properties being made dirty. + */ + private boolean[] embeddedDirty; + + private Object[] origValues; + + private int lazyLoadProperty = -1; + + /** + * Create a intercept with a given entity. + *

    + * Refer to agent ProxyConstructor. + *

    + */ + public EntityBeanIntercept(Object ownerBean) { + this.owner = (EntityBean) ownerBean; + this.loadedProps = new boolean[owner._ebean_getPropertyNames().length]; + } + + /** + * Return the 'owning' entity bean. + */ + public EntityBean getOwner() { + return owner; + } + + /** + * Return the persistenceContext. + */ + public PersistenceContext getPersistenceContext() { + return persistenceContext; + } + + /** + * Set the persistenceContext. + */ + public void setPersistenceContext(PersistenceContext persistenceContext) { + this.persistenceContext = persistenceContext; + } + + /** + * Add a property change listener for this entity bean. + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + if (pcs == null) { + pcs = new PropertyChangeSupport(owner); + } + pcs.addPropertyChangeListener(listener); + } + + /** + * Add a property change listener for this entity bean for a specific + * property. + */ + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + if (pcs == null) { + pcs = new PropertyChangeSupport(owner); + } + pcs.addPropertyChangeListener(propertyName, listener); + } + + /** + * Remove a property change listener for this entity bean. + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + if (pcs != null) { + pcs.removePropertyChangeListener(listener); + } + } + + /** + * Remove a property change listener for this entity bean for a specific + * property. + */ + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + if (pcs != null) { + pcs.removePropertyChangeListener(propertyName, listener); + } + } + + /** + * Turn on profile collection. + */ + public void setNodeUsageCollector(NodeUsageCollector usageCollector) { + this.nodeUsageCollector = usageCollector; + } + + /** + * Return the owning bean for an embedded bean. + */ + public Object getEmbeddedOwner() { + return embeddedOwner; + } + + /** + * Return the property index (for the parent) of this embedded bean. + */ + public int getEmbeddedOwnerIndex() { + return embeddedOwnerIndex; + } + + /** + * Set the embedded beans owning bean. + */ + public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) { + this.embeddedOwner = parentBean; + this.embeddedOwnerIndex = embeddedOwnerIndex; + } + + /** + * Set the BeanLoader with PersistenceContext. + */ + public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) { + this.beanLoader = beanLoader; + this.persistenceContext = ctx; + this.ebeanServerName = beanLoader.getName(); + } + + /** + * Set the BeanLoader. + */ + public void setBeanLoader(BeanLoader beanLoader) { + this.beanLoader = beanLoader; + this.ebeanServerName = beanLoader.getName(); + } + + public boolean isFullyLoadedBean() { + return fullyLoadedBean; + } + + public void setFullyLoadedBean(boolean fullyLoadedBean) { + this.fullyLoadedBean = fullyLoadedBean; + } + + /** + * Return true if this bean has been directly modified (it has oldValues) or + * if any embedded beans are either new or dirty (and hence need saving). + */ + public boolean isDirty() { + return dirty; + } + + /** + * Called by an embedded bean onto its owner. + */ + public void setEmbeddedDirty(int embeddedProperty) { + this.dirty = true; + setEmbeddedPropertyDirty(embeddedProperty); + } + + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + /** + * Return true if this entity bean is new and not yet saved. + */ + public boolean isNew() { + return state == STATE_NEW; + } + + /** + * Return true if the entity bean is new or dirty (and should be saved). + */ + public boolean isNewOrDirty() { + return isNew() || isDirty(); + } + + /** + * Return true if only the Id property has been loaded. + */ + public boolean hasIdOnly(int idIndex) { + for (int i = 0; i < loadedProps.length; i++) { + if (i == idIndex) { + if (!loadedProps[i]) return false; + } else if (loadedProps[i]) { + return false; + } + } + return true; + } + + /** + * Return true if the entity is a reference. + */ + public boolean isReference() { + return state == STATE_REFERENCE; + } + + /** + * Set this as a reference object. + */ + public void setReference(int idPos) { + state = STATE_REFERENCE; + if (idPos > -1) { + // For cases where properties are set on constructor + // set every non Id property to unloaded (for lazy loading) + for (int i=0; i< loadedProps.length; i++) { + if (i != idPos) { + loadedProps[i] = false; + } + } + } + } + + /** + * Return true if the bean should be treated as readOnly. If a setter method + * is called when it is readOnly an Exception is thrown. + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Set the readOnly status. If readOnly then calls to setter methods through + * an exception. + */ + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + /** + * Return true if the entity has been loaded. + */ + public boolean isLoaded() { + return state == STATE_LOADED; + } + + /** + * Set the loaded state to true. + *

    + * Calls to setter methods after the bean is loaded can result in 'Old Values' + * being created to support ConcurrencyMode.ALL + *

    + *

    + * Worth noting that this is also set after a insert/update. By doing so it + * 'resets' the bean for making further changes and saving again. + *

    + */ + public void setLoaded() { + this.state = STATE_LOADED; + this.owner._ebean_setEmbeddedLoaded(); + this.lazyLoadProperty = -1; + this.origValues = null; + this.changedProps = null; + this.dirty = false; + } + + /** + * When finished loading for lazy or refresh on an already partially populated + * bean. + */ + public void setLoadedLazy() { + this.state = STATE_LOADED; + this.lazyLoadProperty = -1; + } + + /** + * Check if the lazy load succeeded. If not then mark this bean as having + * failed lazy loading due to the underlying row being deleted. + *

    + * We mark the bean this way rather than immediately fail as we might be batch + * lazy loading and this bean might not be used by the client code at all. + * Instead we will fail as soon as the client code tries to use this bean. + *

    + */ + public void checkLazyLoadFailure() { + if (lazyLoadProperty != -1) { + this.lazyLoadFailure = true; + } + } + + /** + * Return true if the bean is marked as having failed lazy loading. + */ + public boolean isLazyLoadFailure() { + return lazyLoadFailure; + } + + /** + * Return true if lazy loading is disabled. + */ + public boolean isDisableLazyLoad() { + return disableLazyLoad; + } + + /** + * Set true to turn off lazy loading. + *

    + * Typically used to disable lazy loading on SQL based report beans. + *

    + */ + public void setDisableLazyLoad(boolean disableLazyLoad) { + this.disableLazyLoad = disableLazyLoad; + } + + /** + * Set the loaded status for the embedded bean. + */ + public void setEmbeddedLoaded(Object embeddedBean) { + if (embeddedBean instanceof EntityBean) { + EntityBean eb = (EntityBean) embeddedBean; + eb._ebean_getIntercept().setLoaded(); + } + } + + /** + * Return true if the embedded bean is new or dirty and hence needs saving. + */ + public boolean isEmbeddedNewOrDirty(Object embeddedBean) { + + if (embeddedBean == null) { + // if it was previously set then the owning bean would + // have oldValues containing the previous embedded bean + return false; + } + if (embeddedBean instanceof EntityBean) { + return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty(); + + } else { + // non-enhanced so must assume it is new and needs to be saved + return true; + } + } + + /** + * Return the original value that was changed via an update. + */ + public Object getOrigValue(int propertyIndex) { + if (origValues == null) { + return null; + } + return origValues[propertyIndex]; + } + + /** + * Finds the index position of a given property. Returns -1 if the property + * can not be found. + */ + public int findProperty(String propertyName) { + String[] names = owner._ebean_getPropertyNames(); + for (int i = 0; i < names.length; i++) { + if (names[i].equals(propertyName)) { + return i; + } + } + return -1; + } + + /** + * Return the property name for the given property. + */ + public String getProperty(int propertyIndex) { + if (propertyIndex == -1) { + return null; + } + return owner._ebean_getPropertyName(propertyIndex); + } + + /** + * Return the number of properties.s + */ + public int getPropertyLength() { + return owner._ebean_getPropertyNames().length; + } + + /** + * Set the property to be treated as unloaded. Used for properties initialised in default + * constructor. + */ + public void setPropertyUnloaded(int propertyIndex) { + loadedProps[propertyIndex] = false; + } + + /** + * Set the property to be loaded. + */ + public void setLoadedProperty(int propertyIndex) { + loadedProps[propertyIndex] = true; + } + + /** + * Return true if the property is loaded. + */ + public boolean isLoadedProperty(int propertyIndex) { + return loadedProps[propertyIndex]; + } + + /** + * Return true if the property is considered changed. + */ + public boolean isChangedProperty(int propertyIndex) { + return (changedProps != null && changedProps[propertyIndex]); + } + + /** + * Return true if the property was changed or if it is embedded and one of its + * embedded properties is dirty. + */ + public boolean isDirtyProperty(int propertyIndex) { + return (changedProps != null && changedProps[propertyIndex] + || embeddedDirty != null && embeddedDirty[propertyIndex]); + } + + /** + * Explicitly mark a property as having been changed. + */ + public void markPropertyAsChanged(int propertyIndex) { + setChangedProperty(propertyIndex); + setDirty(true); + } + + private void setChangedProperty(int propertyIndex) { + if (changedProps == null) { + changedProps = new boolean[owner._ebean_getPropertyNames().length]; + } + changedProps[propertyIndex] = true; + } + + /** + * Set that an embedded bean has had one of its properties changed. + */ + private void setEmbeddedPropertyDirty(int propertyIndex) { + if (embeddedDirty == null) { + embeddedDirty = new boolean[owner._ebean_getPropertyNames().length]; + } + embeddedDirty[propertyIndex] = true; + } + + private void setOriginalValue(int propertyIndex, Object value) { + if (origValues == null) { + origValues = new Object[owner._ebean_getPropertyNames().length]; + } + if (origValues[propertyIndex] == null) { + origValues[propertyIndex] = value; + } + } + + /** + * For forced update on a 'New' bean set all the loaded properties to changed. + */ + public void setNewBeanForUpdate() { + + if (changedProps == null) { + changedProps = new boolean[owner._ebean_getPropertyNames().length]; + } + + for (int i=0; i< loadedProps.length; i++) { + if (loadedProps[i]) { + changedProps[i] = true; + } + } + setDirty(true); + } + + /** + * Return the set of property names for a partially loaded bean. + */ + public Set getLoadedPropertyNames() { + if (fullyLoadedBean) { + return null; + } + Set props = new LinkedHashSet(); + for (int i=0; i getDirtyPropertyNames() { + Set props = new LinkedHashSet(); + addDirtyPropertyNames(props, null); + return props; + } + + /** + * Recursively add dirty properties. + */ + public void addDirtyPropertyNames(Set props, String prefix) { + int len = getPropertyLength(); + for (int i = 0; i < len; i++) { + if (changedProps != null && changedProps[i]) { + // the property has been changed on this bean + String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i)); + props.add(propName); + } else if (embeddedDirty != null && embeddedDirty[i]) { + // an embedded property has been changed - recurse + EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); + embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i)+"."); + } + } + } + + /** + * Return a map of dirty properties with their new and old values. + */ + public Map getDirtyValues() { + Map dirtyValues = new LinkedHashMap(); + addDirtyPropertyValues(dirtyValues, null); + return dirtyValues; + } + + /** + * Recursively add dirty properties. + */ + public void addDirtyPropertyValues(Map dirtyValues, String prefix) { + int len = getPropertyLength(); + for (int i = 0; i < len; i++) { + if (changedProps != null && changedProps[i]) { + // the property has been changed on this bean + String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i)); + Object newVal = owner._ebean_getField(i); + Object oldVal = getOrigValue(i); + + dirtyValues.put(propName, new ValuePair(newVal, oldVal)); + + } else if (embeddedDirty != null && embeddedDirty[i]) { + // an embedded property has been changed - recurse + EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); + embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i)+"."); + } + } + } + + /** + * Return a dirty property hash taking into account embedded beans. + */ + public int getDirtyPropertyHash() { + return addDirtyPropertyHash(37); + } + + /** + * Add and return a dirty property hash recursing into embedded beans. + */ + public int addDirtyPropertyHash(int hash) { + int len = getPropertyLength(); + for (int i = 0; i < len; i++) { + if (changedProps != null && changedProps[i]) { + // the property has been changed on this bean + hash = hash * 31 + (i+1); + } else if (embeddedDirty != null && embeddedDirty[i]) { + // an embedded property has been changed - recurse + EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); + hash = hash * 31 + embeddedBean._ebean_getIntercept().addDirtyPropertyHash(hash); + } + } + return hash; + } + + /** + * Return the set of property names for changed properties. + */ + public boolean[] getChanged() { + return changedProps; + } + + public boolean[] getLoaded() { + return loadedProps; + } + + /** + * Return the index of the property that triggered the lazy load. + */ + public int getLazyLoadPropertyIndex() { + return lazyLoadProperty; + } + + /** + * Return the property that triggered the lazy load. + */ + public String getLazyLoadProperty() { + return getProperty(lazyLoadProperty); + } + + /** + * Load the bean when it is a reference. + */ + protected void loadBean(int loadProperty) { + + synchronized (this) { + if (beanLoader == null) { + BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName); + if (serverLoader == null) { + throw new PersistenceException("Server [" + ebeanServerName + "] was not found?"); + } + + // For stand alone reference bean or after deserialisation lazy load + // using the ebeanServer. Synchronise only on the bean. + loadBeanInternal(loadProperty, serverLoader); + return; + } + } + + synchronized (beanLoader) { + // Lazy loading using LoadBeanContext which supports batch loading + // Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree') + loadBeanInternal(loadProperty, beanLoader); + } + } + + /** + * Invoke the lazy loading. This method is synchronised externally. + */ + private void loadBeanInternal(int loadProperty, BeanLoader loader) { + + if (loadedProps == null || loadedProps[loadProperty]) { + // race condition where multiple threads calling preGetter concurrently + return; + } + + if (lazyLoadFailure) { + // failed when batch lazy loaded by another bean in the batch + throw new EntityNotFoundException("Bean has been deleted - lazy loading failed"); + } + + if (lazyLoadProperty == -1) { + + lazyLoadProperty = loadProperty; + + if (nodeUsageCollector != null) { + nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty)); + } + + loader.loadBean(this); + + if (lazyLoadFailure) { + // failed when lazy loading this bean + throw new EntityNotFoundException("Bean has been deleted - lazy loading failed"); + } + + // bean should be loaded and intercepting now. setLoaded() has + // been called by the lazy loading mechanism + } + } + + /** + * Helper method to check if two objects are equal. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected boolean areEqual(Object obj1, Object obj2) { + if (obj1 == null) { + return (obj2 == null); + } + if (obj2 == null) { + return false; + } + if (obj1 == obj2) { + return true; + } + if (obj1 instanceof BigDecimal) { + // Use comparable for BigDecimal as equals + // uses scale in comparison... + if (obj2 instanceof BigDecimal) { + Comparable com1 = (Comparable) obj1; + return (com1.compareTo(obj2) == 0); + + } else { + return false; + } + } + if (obj1 instanceof URL) { + // use the string format to determine if dirty + return obj1.toString().equals(obj2.toString()); + } + return obj1.equals(obj2); + } + + /** + * Called when a BeanCollection is initialised automatically. + */ + public void initialisedMany(int propertyIndex) { + loadedProps[propertyIndex] = true; + } + + /** + * Method that is called prior to a getter method on the actual entity. + */ + public void preGetter(int propertyIndex) { + if (state == STATE_NEW || disableLazyLoad) { + return; + } + + if (!isLoadedProperty(propertyIndex)) { + loadBean(propertyIndex); + } + + if (nodeUsageCollector != null) { + nodeUsageCollector.addUsed(getProperty(propertyIndex)); + } + } + + /** + * Called for "enhancement" postSetter processing. This is around a PUTFIELD + * so no need to check the newValue afterwards. + */ + public void postSetter(PropertyChangeEvent event) { + if (pcs != null && event != null) { + pcs.firePropertyChange(event); + } + } + + /** + * Called for "subclassed" postSetter processing. Here the newValue has to be + * re-fetched (and passed into this method) in case there is code inside the + * setter that further mutates the value. + */ + public void postSetter(PropertyChangeEvent event, Object newValue) { + if (pcs != null && event != null) { + if (newValue != null && newValue.equals(event.getNewValue())) { + pcs.firePropertyChange(event); + } else { + pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue); + } + } + } + + /** + * OneToMany and ManyToMany don't have any interception so just check for + * PropertyChangeSupport. + */ + public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) { + + if (readOnly) { + throw new IllegalStateException("This bean is readOnly"); + } + + setLoadedProperty(propertyIndex); + + // Bean itself not considered dirty when many changed + if (pcs != null) { + return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); + } else { + return null; + } + } + + private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) { + + if (readOnly) { + throw new IllegalStateException("This bean is readOnly"); + } + setChangedProperty(propertyIndex); + + if (setDirtyState) { + setOriginalValue(propertyIndex, origValue); + if (!dirty) { + dirty = true; + if (embeddedOwner != null) { + // Cascade dirty state from Embedded bean to parent bean + embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex); + } + if (nodeUsageCollector != null) { + nodeUsageCollector.setModified(); + } + } + } + } + + /** + * Check to see if the values are not equal. If they are not equal then create + * the old values for use with ConcurrencyMode.ALL. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (!areEqual(oldValue, newValue)) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); + } + + + /** + * Check for primitive boolean. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); + } + + /** + * Check for primitive int. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Integer.valueOf(oldValue), Integer.valueOf(newValue)); + } + + /** + * long. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Long.valueOf(oldValue), Long.valueOf(newValue)); + } + + /** + * double. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Double.valueOf(oldValue), Double.valueOf(newValue)); + } + + /** + * float. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Float.valueOf(oldValue), Float.valueOf(newValue)); + } + + /** + * short. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Short.valueOf(oldValue), Short.valueOf(newValue)); + } + + /** + * char. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Character.valueOf(oldValue), Character.valueOf(newValue)); + } + + /** + * byte. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (oldValue != newValue) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Byte.valueOf(oldValue), Byte.valueOf(newValue)); + } + + /** + * char[]. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (!areEqualChars(oldValue, newValue)) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null: new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); + } + + /** + * byte[]. + */ + public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) { + + if (state == STATE_NEW) { + setLoadedProperty(propertyIndex); + } else if (!areEqualBytes(oldValue, newValue)) { + setChangedPropertyValue(propertyIndex, intercept, oldValue); + } else { + return null; + } + return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue); + } + + private static boolean areEqualBytes(byte[] b1, byte[] b2) { + if (b1 == null) { + return (b2 == null); + + } else if (b2 == null) { + return false; + + } else if (b1 == b2) { + return true; + + } else if (b1.length != b2.length) { + return false; + } + for (int i = 0; i < b1.length; i++) { + if (b1[i] != b2[i]) { + return false; + } + } + return true; + } + + private static boolean areEqualChars(char[] b1, char[] b2) { + if (b1 == null) { + return (b2 == null); + + } else if (b2 == null) { + return false; + + } else if (b1 == b2) { + return true; + + } else if (b1.length != b2.length) { + return false; + } + for (int i = 0; i < b1.length; i++) { + if (b1[i] != b2[i]) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java b/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java index b50a5b1a2..42dc52c8c 100644 --- a/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java +++ b/src/main/java/com/avaje/ebean/bean/NodeUsageCollector.java @@ -1,126 +1,126 @@ -package com.avaje.ebean.bean; - -import java.lang.ref.WeakReference; -import java.util.HashSet; - -/** - * Collects profile information for a bean (or reference/proxy bean) at a given - * AutoFetchNode. - *

    - * The AutoFetchNode identifies the location of the bean in the object graph. - *

    - *

    - * It has to use a weak reference so as to ensure that it does not stop the - * associated bean from being garbage collected. - *

    - */ -public final class NodeUsageCollector { - - /** - * The point in the object graph for a specific query and call stack point. - */ - private final ObjectGraphNode node; - - /** - * Weak to allow garbage collection. - */ - private final WeakReference managerRef; - - /** - * The properties used at this profile point. - */ - private final HashSet used = new HashSet(); - - /** - * set to true if the bean is modified (setter called) - */ - private boolean modified; - - /** - * The property that cause a reference to lazy load. - */ - private String loadProperty; - - public NodeUsageCollector(ObjectGraphNode node, WeakReference managerRef) { - this.node = node; - // weak to allow garbage collection. - this.managerRef = managerRef; - } - - /** - * The bean has been modified by a setter method. - */ - public void setModified() { - modified = true; - } - - /** - * Add the name of a property that has been used. - */ - public void addUsed(String property) { - used.add(property); - } - - /** - * The property that invoked a lazy load. - */ - public void setLoadProperty(String loadProperty) { - this.loadProperty = loadProperty; - } - - /** - * Publish the usage info to the manager. - */ - private void publishUsageInfo() { - NodeUsageListener manager = managerRef.get(); - if (manager != null) { - manager.collectNodeUsage(this); - } - } - - /** - * publish the collected usage information when garbage collection occurs. - */ - @Override - protected void finalize() throws Throwable { - publishUsageInfo(); - super.finalize(); - } - - /** - * Return the associated node which identifies the location in the object - * graph of the bean/reference. - */ - public ObjectGraphNode getNode() { - return node; - } - - /** - * Return true if no properties where used. - */ - public boolean isEmpty() { - return used.isEmpty(); - } - - /** - * Return the set of used properties. - */ - public HashSet getUsed() { - return used; - } - - /** - * Return true if the bean was modified by a setter. - */ - public boolean isModified() { - return modified; - } - - public String getLoadProperty() { - return loadProperty; - } - - public String toString() { - return node + " read:" + used + " modified:" + modified; - } -} +package com.avaje.ebean.bean; + +import java.lang.ref.WeakReference; +import java.util.HashSet; + +/** + * Collects profile information for a bean (or reference/proxy bean) at a given + * AutoFetchNode. + *

    + * The AutoFetchNode identifies the location of the bean in the object graph. + *

    + *

    + * It has to use a weak reference so as to ensure that it does not stop the + * associated bean from being garbage collected. + *

    + */ +public final class NodeUsageCollector { + + /** + * The point in the object graph for a specific query and call stack point. + */ + private final ObjectGraphNode node; + + /** + * Weak to allow garbage collection. + */ + private final WeakReference managerRef; + + /** + * The properties used at this profile point. + */ + private final HashSet used = new HashSet(); + + /** + * set to true if the bean is modified (setter called) + */ + private boolean modified; + + /** + * The property that cause a reference to lazy load. + */ + private String loadProperty; + + public NodeUsageCollector(ObjectGraphNode node, WeakReference managerRef) { + this.node = node; + // weak to allow garbage collection. + this.managerRef = managerRef; + } + + /** + * The bean has been modified by a setter method. + */ + public void setModified() { + modified = true; + } + + /** + * Add the name of a property that has been used. + */ + public void addUsed(String property) { + used.add(property); + } + + /** + * The property that invoked a lazy load. + */ + public void setLoadProperty(String loadProperty) { + this.loadProperty = loadProperty; + } + + /** + * Publish the usage info to the manager. + */ + private void publishUsageInfo() { + NodeUsageListener manager = managerRef.get(); + if (manager != null) { + manager.collectNodeUsage(this); + } + } + + /** + * publish the collected usage information when garbage collection occurs. + */ + @Override + protected void finalize() throws Throwable { + publishUsageInfo(); + super.finalize(); + } + + /** + * Return the associated node which identifies the location in the object + * graph of the bean/reference. + */ + public ObjectGraphNode getNode() { + return node; + } + + /** + * Return true if no properties where used. + */ + public boolean isEmpty() { + return used.isEmpty(); + } + + /** + * Return the set of used properties. + */ + public HashSet getUsed() { + return used; + } + + /** + * Return true if the bean was modified by a setter. + */ + public boolean isModified() { + return modified; + } + + public String getLoadProperty() { + return loadProperty; + } + + public String toString() { + return node + " read:" + used + " modified:" + modified; + } +} diff --git a/src/main/java/com/avaje/ebean/bean/NodeUsageListener.java b/src/main/java/com/avaje/ebean/bean/NodeUsageListener.java index 5b8e7744a..dafdf28c1 100644 --- a/src/main/java/com/avaje/ebean/bean/NodeUsageListener.java +++ b/src/main/java/com/avaje/ebean/bean/NodeUsageListener.java @@ -1,16 +1,16 @@ -package com.avaje.ebean.bean; - -/** - * Collects the profile information. - */ -public interface NodeUsageListener { - - /** - * Collect node usage "profiling" information. - *

    - * This is the properties that are used for a given bean in the object graph. - * This information is used by autoFetch to tune queries. - *

    - */ - void collectNodeUsage(NodeUsageCollector collector); -} +package com.avaje.ebean.bean; + +/** + * Collects the profile information. + */ +public interface NodeUsageListener { + + /** + * Collect node usage "profiling" information. + *

    + * This is the properties that are used for a given bean in the object graph. + * This information is used by autoFetch to tune queries. + *

    + */ + void collectNodeUsage(NodeUsageCollector collector); +} diff --git a/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java b/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java index 6514f87a5..e070e43f6 100644 --- a/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java +++ b/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java @@ -1,88 +1,88 @@ -package com.avaje.ebean.bean; - -import java.io.Serializable; - -/** - * Identifies a unique node of an object graph. - *

    - * It represents a location relative to the root of an object graph and specific - * to a query and call stack hash. - *

    - */ -public final class ObjectGraphNode implements Serializable { - - private static final long serialVersionUID = 2087081778650228996L; - - /** - * Identifies the origin. - */ - private final ObjectGraphOrigin originQueryPoint; - - /** - * The path relative to the root. - */ - private final String path; - - /** - * Create at a sub level. - */ - public ObjectGraphNode(ObjectGraphNode parent, String path) { - this.originQueryPoint = parent.getOriginQueryPoint(); - this.path = parent.getChildPath(path); - } - - /** - * Create an the root level. - */ - public ObjectGraphNode(ObjectGraphOrigin originQueryPoint, String path) { - this.originQueryPoint = originQueryPoint; - this.path = path; - } - - /** - * Return the origin query point. - */ - public ObjectGraphOrigin getOriginQueryPoint() { - return originQueryPoint; - } - - private String getChildPath(String childPath) { - if (path == null) { - return childPath; - } else if (childPath == null) { - return path; - } else { - return path + "." + childPath; - } - } - - /** - * Return the path relative to the root. - */ - public String getPath() { - return path; - } - - public String toString() { - return "origin:" + originQueryPoint + " path[" + path+"]"; - } - - public int hashCode() { - int hc = 31 * originQueryPoint.hashCode(); - hc = 31 * hc + (path == null ? 0 : path.hashCode()); - return hc; - } - - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof ObjectGraphNode)) { - return false; - } - - ObjectGraphNode e = (ObjectGraphNode) obj; - return ((e.path == path) || (e.path != null && e.path.equals(path))) - && e.originQueryPoint.equals(originQueryPoint); - } -} +package com.avaje.ebean.bean; + +import java.io.Serializable; + +/** + * Identifies a unique node of an object graph. + *

    + * It represents a location relative to the root of an object graph and specific + * to a query and call stack hash. + *

    + */ +public final class ObjectGraphNode implements Serializable { + + private static final long serialVersionUID = 2087081778650228996L; + + /** + * Identifies the origin. + */ + private final ObjectGraphOrigin originQueryPoint; + + /** + * The path relative to the root. + */ + private final String path; + + /** + * Create at a sub level. + */ + public ObjectGraphNode(ObjectGraphNode parent, String path) { + this.originQueryPoint = parent.getOriginQueryPoint(); + this.path = parent.getChildPath(path); + } + + /** + * Create an the root level. + */ + public ObjectGraphNode(ObjectGraphOrigin originQueryPoint, String path) { + this.originQueryPoint = originQueryPoint; + this.path = path; + } + + /** + * Return the origin query point. + */ + public ObjectGraphOrigin getOriginQueryPoint() { + return originQueryPoint; + } + + private String getChildPath(String childPath) { + if (path == null) { + return childPath; + } else if (childPath == null) { + return path; + } else { + return path + "." + childPath; + } + } + + /** + * Return the path relative to the root. + */ + public String getPath() { + return path; + } + + public String toString() { + return "origin:" + originQueryPoint + " path[" + path+"]"; + } + + public int hashCode() { + int hc = 31 * originQueryPoint.hashCode(); + hc = 31 * hc + (path == null ? 0 : path.hashCode()); + return hc; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ObjectGraphNode)) { + return false; + } + + ObjectGraphNode e = (ObjectGraphNode) obj; + return ((e.path == path) || (e.path != null && e.path.equals(path))) + && e.originQueryPoint.equals(originQueryPoint); + } +} diff --git a/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java b/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java index f5259f412..8b62c56ca 100644 --- a/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java +++ b/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java @@ -1,84 +1,84 @@ -package com.avaje.ebean.bean; - -import java.io.Serializable; - -/** - * Represents a "origin" of an ORM object graph. This combines the call stack - * and query plan hash. - *

    - * The call stack is included so that the query can have different tuned fetches - * for each unique call stack. For example, a query to fetch a customer could be - * called by three different methods and each can be treated as a separate - * origin point (and autoFetch can tune each one separately). - *

    - */ -public final class ObjectGraphOrigin implements Serializable { - - private static final long serialVersionUID = 410937765287968708L; - - private final CallStack callStack; - - private final String beanType; - - private final int queryHash; - - private final String key; - - public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) { - this.callStack = callStack; - this.beanType = beanType; - this.queryHash = queryHash; - this.key = callStack.getOriginKey(queryHash); - } - - /** - * The key includes the queryPlan hash and the callStack hash. This becomes - * the unique identifier for a query point. - */ - public String getKey() { - return key; - } - - /** - * The type of bean the query is fetching. - */ - public String getBeanType() { - return beanType; - } - - /** - * The call stack involved. - */ - public CallStack getCallStack() { - return callStack; - } - - public String getFirstStackElement() { - return callStack.getFirstStackTraceElement().toString(); - } - - public String toString() { - return "key["+ key + "] type[" + beanType + "] " + callStack.getFirstStackTraceElement()+" "; - } - - public int hashCode() { - int hc = 31 * callStack.hashCode(); - hc = 31 * hc + beanType.hashCode(); - hc = 31 * hc + queryHash; - return hc; - } - - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof ObjectGraphOrigin)) { - return false; - } - - ObjectGraphOrigin e = (ObjectGraphOrigin) obj; - return e.queryHash == queryHash - && e.beanType.equals(beanType) - && e.callStack.equals(callStack); - } -} +package com.avaje.ebean.bean; + +import java.io.Serializable; + +/** + * Represents a "origin" of an ORM object graph. This combines the call stack + * and query plan hash. + *

    + * The call stack is included so that the query can have different tuned fetches + * for each unique call stack. For example, a query to fetch a customer could be + * called by three different methods and each can be treated as a separate + * origin point (and autoFetch can tune each one separately). + *

    + */ +public final class ObjectGraphOrigin implements Serializable { + + private static final long serialVersionUID = 410937765287968708L; + + private final CallStack callStack; + + private final String beanType; + + private final int queryHash; + + private final String key; + + public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) { + this.callStack = callStack; + this.beanType = beanType; + this.queryHash = queryHash; + this.key = callStack.getOriginKey(queryHash); + } + + /** + * The key includes the queryPlan hash and the callStack hash. This becomes + * the unique identifier for a query point. + */ + public String getKey() { + return key; + } + + /** + * The type of bean the query is fetching. + */ + public String getBeanType() { + return beanType; + } + + /** + * The call stack involved. + */ + public CallStack getCallStack() { + return callStack; + } + + public String getFirstStackElement() { + return callStack.getFirstStackTraceElement().toString(); + } + + public String toString() { + return "key["+ key + "] type[" + beanType + "] " + callStack.getFirstStackTraceElement()+" "; + } + + public int hashCode() { + int hc = 31 * callStack.hashCode(); + hc = 31 * hc + beanType.hashCode(); + hc = 31 * hc + queryHash; + return hc; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ObjectGraphOrigin)) { + return false; + } + + ObjectGraphOrigin e = (ObjectGraphOrigin) obj; + return e.queryHash == queryHash + && e.beanType.equals(beanType) + && e.callStack.equals(callStack); + } +} diff --git a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java index df78875db..d6775abd4 100644 --- a/src/main/java/com/avaje/ebean/bean/PersistenceContext.java +++ b/src/main/java/com/avaje/ebean/bean/PersistenceContext.java @@ -1,110 +1,110 @@ -package com.avaje.ebean.bean; - -/** - * Holds entity beans by there type and id. - *

    - * This is used to ensure only one instance for a given entity type and id is - * used to build object graphs from queries and lazy loading. - *

    - */ -public interface PersistenceContext { - - /** - * Put the entity bean into the PersistanceContext. - */ - void put(Object id, Object bean); - - /** - * Put the entity bean into the PersistanceContext if one is not already - * present (for this id). - *

    - * Returns an existing entity bean (if one is already there) and otherwise - * returns null. - *

    - */ - Object putIfAbsent(Object id, Object bean); - - /** - * Return an object given its type and unique id. - */ - Object get(Class beanType, Object uid); - - /** - * Get the bean from the persistence context also checked to see if it had - * been previously deleted (if so then you also can't hit the L2 cache to - * fetch the bean for this particular persistence context). - */ - WithOption getWithOption(Class beanType, Object uid); - - /** - * Clear all the references. - */ - void clear(); - - /** - * Clear all the references for a given type of entity bean. - */ - void clear(Class beanType); - - /** - * Clear the reference to a specific entity bean. - */ - void clear(Class beanType, Object uid); - - /** - * Clear the reference as a result of an entity being deleted. - */ - void deleted(Class beanType, Object id); - - /** - * Return the number of beans of the given type in the persistence context. - */ - int size(Class beanType); - - /** - * Wrapper on a bean to also indicate if a bean has been deleted. - *

    - * If a bean has been deleted then for the same persistence context is should - * not be able to be fetched from persistence context or L2 cache. - *

    - */ - class WithOption { - - /** - * The bean was previously deleted from this persistence context (can't hit - * L2 cache). - */ - public static WithOption DELETED = new WithOption(true); - - private final boolean deleted; - private final Object bean; - - private WithOption(boolean deleted) { - this.deleted = true; - this.bean = null; - } - - /** - * The bean exists in the persistence context (and not been previously deleted). - */ - public WithOption(Object bean) { - this.deleted = false; - this.bean = bean; - } - - /** - * Return true if the bean was deleted. This means you can't hit the L2 - * cache. - */ - public boolean isDeleted() { - return deleted; - } - - /** - * Return the bean (from the persistence context). - */ - public Object getBean() { - return bean; - } - } -} +package com.avaje.ebean.bean; + +/** + * Holds entity beans by there type and id. + *

    + * This is used to ensure only one instance for a given entity type and id is + * used to build object graphs from queries and lazy loading. + *

    + */ +public interface PersistenceContext { + + /** + * Put the entity bean into the PersistanceContext. + */ + void put(Object id, Object bean); + + /** + * Put the entity bean into the PersistanceContext if one is not already + * present (for this id). + *

    + * Returns an existing entity bean (if one is already there) and otherwise + * returns null. + *

    + */ + Object putIfAbsent(Object id, Object bean); + + /** + * Return an object given its type and unique id. + */ + Object get(Class beanType, Object uid); + + /** + * Get the bean from the persistence context also checked to see if it had + * been previously deleted (if so then you also can't hit the L2 cache to + * fetch the bean for this particular persistence context). + */ + WithOption getWithOption(Class beanType, Object uid); + + /** + * Clear all the references. + */ + void clear(); + + /** + * Clear all the references for a given type of entity bean. + */ + void clear(Class beanType); + + /** + * Clear the reference to a specific entity bean. + */ + void clear(Class beanType, Object uid); + + /** + * Clear the reference as a result of an entity being deleted. + */ + void deleted(Class beanType, Object id); + + /** + * Return the number of beans of the given type in the persistence context. + */ + int size(Class beanType); + + /** + * Wrapper on a bean to also indicate if a bean has been deleted. + *

    + * If a bean has been deleted then for the same persistence context is should + * not be able to be fetched from persistence context or L2 cache. + *

    + */ + class WithOption { + + /** + * The bean was previously deleted from this persistence context (can't hit + * L2 cache). + */ + public static WithOption DELETED = new WithOption(true); + + private final boolean deleted; + private final Object bean; + + private WithOption(boolean deleted) { + this.deleted = true; + this.bean = null; + } + + /** + * The bean exists in the persistence context (and not been previously deleted). + */ + public WithOption(Object bean) { + this.deleted = false; + this.bean = bean; + } + + /** + * Return true if the bean was deleted. This means you can't hit the L2 + * cache. + */ + public boolean isDeleted() { + return deleted; + } + + /** + * Return the bean (from the persistence context). + */ + public Object getBean() { + return bean; + } + } +} diff --git a/src/main/java/com/avaje/ebean/cache/ServerCache.java b/src/main/java/com/avaje/ebean/cache/ServerCache.java index 6efc6898a..00a105166 100644 --- a/src/main/java/com/avaje/ebean/cache/ServerCache.java +++ b/src/main/java/com/avaje/ebean/cache/ServerCache.java @@ -1,89 +1,89 @@ -package com.avaje.ebean.cache; - -import com.avaje.ebean.EbeanServer; - -/** - * Represents part of the "L2" server side cache. - *

    - * This is used to cache beans or query results (bean collections). - *

    - *

    - * There are 2 ServerCache's for each bean type. One is used as the 'bean cache' - * which holds beans of a given type. The other is the 'query cache' holding - * query results for a given type. - *

    - * - * @author rbygrave - */ -public interface ServerCache { - - /** - * Just after a cache is created this init method is called. This is so that a - * cache implementation can make use of the BackgroundExecutor service to - * trim/cleanup itself or use the EbeanServer to populate itself. - *

    - * This method is called after the cache is constructed but before the cache - * is made available for use. - *

    - */ - void init(EbeanServer ebeanServer); - - /** - * Return the configuration options for this cache. - */ - ServerCacheOptions getOptions(); - - /** - * Update the configuration options for this cache. - */ - void setOptions(ServerCacheOptions options); - - /** - * Return the value given the key. - */ - Object get(Object id); - - /** - * Put the value in the cache with a given id. - */ - Object put(Object id, Object value); - - /** - * Put the value in the cache but only if a matching value is not already in - * the cache. - */ - Object putIfAbsent(Object id, Object value); - - /** - * Remove a entry from the cache given its id. - */ - Object remove(Object id); - - /** - * Clear all entries from the cache. - *

    - * NOTE: Be careful using this method in that most of the time application - * code should clear BOTH the bean and query caches at the same time. This can - * be done via {@link ServerCacheManager#clear(Class)}. - *

    - */ - void clear(); - - /** - * Return the number of entries in the cache. - */ - int size(); - - /** - * Return the hit ratio the cache is currently getting. - */ - int getHitRatio(); - - /** - * Return statistics for the cache. - * - * @param reset - * if true the statistics are reset. - */ - ServerCacheStatistics getStatistics(boolean reset); -} +package com.avaje.ebean.cache; + +import com.avaje.ebean.EbeanServer; + +/** + * Represents part of the "L2" server side cache. + *

    + * This is used to cache beans or query results (bean collections). + *

    + *

    + * There are 2 ServerCache's for each bean type. One is used as the 'bean cache' + * which holds beans of a given type. The other is the 'query cache' holding + * query results for a given type. + *

    + * + * @author rbygrave + */ +public interface ServerCache { + + /** + * Just after a cache is created this init method is called. This is so that a + * cache implementation can make use of the BackgroundExecutor service to + * trim/cleanup itself or use the EbeanServer to populate itself. + *

    + * This method is called after the cache is constructed but before the cache + * is made available for use. + *

    + */ + void init(EbeanServer ebeanServer); + + /** + * Return the configuration options for this cache. + */ + ServerCacheOptions getOptions(); + + /** + * Update the configuration options for this cache. + */ + void setOptions(ServerCacheOptions options); + + /** + * Return the value given the key. + */ + Object get(Object id); + + /** + * Put the value in the cache with a given id. + */ + Object put(Object id, Object value); + + /** + * Put the value in the cache but only if a matching value is not already in + * the cache. + */ + Object putIfAbsent(Object id, Object value); + + /** + * Remove a entry from the cache given its id. + */ + Object remove(Object id); + + /** + * Clear all entries from the cache. + *

    + * NOTE: Be careful using this method in that most of the time application + * code should clear BOTH the bean and query caches at the same time. This can + * be done via {@link ServerCacheManager#clear(Class)}. + *

    + */ + void clear(); + + /** + * Return the number of entries in the cache. + */ + int size(); + + /** + * Return the hit ratio the cache is currently getting. + */ + int getHitRatio(); + + /** + * Return statistics for the cache. + * + * @param reset + * if true the statistics are reset. + */ + ServerCacheStatistics getStatistics(boolean reset); +} diff --git a/src/main/java/com/avaje/ebean/cache/ServerCacheFactory.java b/src/main/java/com/avaje/ebean/cache/ServerCacheFactory.java index a08f560da..88f5d6083 100644 --- a/src/main/java/com/avaje/ebean/cache/ServerCacheFactory.java +++ b/src/main/java/com/avaje/ebean/cache/ServerCacheFactory.java @@ -1,26 +1,26 @@ -package com.avaje.ebean.cache; - -import com.avaje.ebean.EbeanServer; - -/** - * Defines method for constructing caches for beans and queries. - */ -public interface ServerCacheFactory { - - /** - * Just after the ServerCacheFactory is constructed this method is called - * passing the EbeanServer. - *

    - * This is so that a cache implementation can utilise the EbeanServer to - * populate itself or use the BackgroundExecutor service to schedule periodic - * cache trimming/cleanup. - *

    - */ - void init(EbeanServer ebeanServer); - - /** - * Create the cache for the given type with options. - */ - ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions); - -} +package com.avaje.ebean.cache; + +import com.avaje.ebean.EbeanServer; + +/** + * Defines method for constructing caches for beans and queries. + */ +public interface ServerCacheFactory { + + /** + * Just after the ServerCacheFactory is constructed this method is called + * passing the EbeanServer. + *

    + * This is so that a cache implementation can utilise the EbeanServer to + * populate itself or use the BackgroundExecutor service to schedule periodic + * cache trimming/cleanup. + *

    + */ + void init(EbeanServer ebeanServer); + + /** + * Create the cache for the given type with options. + */ + ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions); + +} diff --git a/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java b/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java index 5b9889971..3e08718c2 100644 --- a/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java +++ b/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java @@ -1,55 +1,55 @@ -package com.avaje.ebean.cache; - -import com.avaje.ebean.EbeanServer; - -/** - * The cache service for server side caching of beans and query results. - */ -public interface ServerCacheManager { - - /** - * This method is called just after the construction of the - * ServerCacheManager. - *

    - * The EbeanServer is provided so that cache implementations can make use of - * EbeanServer and BackgroundExecutor for automatically populating and - * background trimming of the cache. - *

    - */ - void init(EbeanServer server); - - void setCaching(Class beanType, boolean useCache); - - /** - * Return true if there is an active bean cache for this type of bean. - */ - boolean isBeanCaching(Class beanType); - - /** - * Return the cache for mapping natural keys to id values. - */ - ServerCache getNaturalKeyCache(Class beanType); - - /** - * Return the cache for beans of a particular type. - */ - ServerCache getBeanCache(Class beanType); - - ServerCache getCollectionIdsCache(Class beanType, String propertyName); - - /** - * Return the cache for query results of a particular type of bean. - */ - ServerCache getQueryCache(Class beanType); - - /** - * This clears both the bean and query cache for a given type. - */ - void clear(Class beanType); - - /** - * Clear all the caches. - */ - void clearAll(); - -} +package com.avaje.ebean.cache; + +import com.avaje.ebean.EbeanServer; + +/** + * The cache service for server side caching of beans and query results. + */ +public interface ServerCacheManager { + + /** + * This method is called just after the construction of the + * ServerCacheManager. + *

    + * The EbeanServer is provided so that cache implementations can make use of + * EbeanServer and BackgroundExecutor for automatically populating and + * background trimming of the cache. + *

    + */ + void init(EbeanServer server); + + void setCaching(Class beanType, boolean useCache); + + /** + * Return true if there is an active bean cache for this type of bean. + */ + boolean isBeanCaching(Class beanType); + + /** + * Return the cache for mapping natural keys to id values. + */ + ServerCache getNaturalKeyCache(Class beanType); + + /** + * Return the cache for beans of a particular type. + */ + ServerCache getBeanCache(Class beanType); + + ServerCache getCollectionIdsCache(Class beanType, String propertyName); + + /** + * Return the cache for query results of a particular type of bean. + */ + ServerCache getQueryCache(Class beanType); + + /** + * This clears both the bean and query cache for a given type. + */ + void clear(Class beanType); + + /** + * Clear all the caches. + */ + void clearAll(); + +} diff --git a/src/main/java/com/avaje/ebean/cache/ServerCacheOptions.java b/src/main/java/com/avaje/ebean/cache/ServerCacheOptions.java index 181c85869..a26be30b7 100644 --- a/src/main/java/com/avaje/ebean/cache/ServerCacheOptions.java +++ b/src/main/java/com/avaje/ebean/cache/ServerCacheOptions.java @@ -1,110 +1,110 @@ -package com.avaje.ebean.cache; - -import com.avaje.ebean.annotation.CacheTuning; - -/** - * Options for controlling a cache. - */ -public class ServerCacheOptions { - - private int maxSize; - private int maxIdleSecs; - private int maxSecsToLive; - - /** - * Construct with no set options. - */ - public ServerCacheOptions() { - - } - - /** - * Create from the cacheTuning deployment annotation. - */ - public ServerCacheOptions(CacheTuning cacheTuning) { - this.maxSize = cacheTuning.maxSize(); - this.maxIdleSecs = cacheTuning.maxIdleSecs(); - this.maxSecsToLive = cacheTuning.maxSecsToLive(); - } - - /** - * Create merging default options with the deployment specified ones. - */ - public ServerCacheOptions(ServerCacheOptions d) { - this.maxSize = d.getMaxSize(); - this.maxIdleSecs = d.getMaxIdleSecs(); - this.maxSecsToLive = d.getMaxIdleSecs(); - } - - /** - * Apply any settings from the default settings that have not already been - * specifically set. - */ - public void applyDefaults(ServerCacheOptions defaults) { - if (maxSize == 0) { - maxSize = defaults.getMaxSize(); - } - if (maxIdleSecs == 0) { - maxIdleSecs = defaults.getMaxIdleSecs(); - } - if (maxSecsToLive == 0) { - maxSecsToLive = defaults.getMaxSecsToLive(); - } - } - - /** - * Return a copy of this object. - */ - public ServerCacheOptions copy() { - - ServerCacheOptions copy = new ServerCacheOptions(); - copy.maxSize = maxSize; - copy.maxIdleSecs = maxIdleSecs; - copy.maxSecsToLive = maxSecsToLive; - - return copy; - } - - /** - * Return the maximum cache size. - */ - public int getMaxSize() { - return maxSize; - } - - /** - * Set the maximum cache size. - */ - public void setMaxSize(int maxSize) { - this.maxSize = maxSize; - } - - /** - * Return the maximum idle time. - */ - public int getMaxIdleSecs() { - return maxIdleSecs; - } - - /** - * Set the maximum idle time. - */ - public void setMaxIdleSecs(int maxIdleSecs) { - this.maxIdleSecs = maxIdleSecs; - } - - /** - * Return the maximum time to live. - */ - public int getMaxSecsToLive() { - return maxSecsToLive; - } - - /** - * Set the maximum time to live. - */ - public void setMaxSecsToLive(int maxSecsToLive) { - this.maxSecsToLive = maxSecsToLive; - } - -} +package com.avaje.ebean.cache; + +import com.avaje.ebean.annotation.CacheTuning; + +/** + * Options for controlling a cache. + */ +public class ServerCacheOptions { + + private int maxSize; + private int maxIdleSecs; + private int maxSecsToLive; + + /** + * Construct with no set options. + */ + public ServerCacheOptions() { + + } + + /** + * Create from the cacheTuning deployment annotation. + */ + public ServerCacheOptions(CacheTuning cacheTuning) { + this.maxSize = cacheTuning.maxSize(); + this.maxIdleSecs = cacheTuning.maxIdleSecs(); + this.maxSecsToLive = cacheTuning.maxSecsToLive(); + } + + /** + * Create merging default options with the deployment specified ones. + */ + public ServerCacheOptions(ServerCacheOptions d) { + this.maxSize = d.getMaxSize(); + this.maxIdleSecs = d.getMaxIdleSecs(); + this.maxSecsToLive = d.getMaxIdleSecs(); + } + + /** + * Apply any settings from the default settings that have not already been + * specifically set. + */ + public void applyDefaults(ServerCacheOptions defaults) { + if (maxSize == 0) { + maxSize = defaults.getMaxSize(); + } + if (maxIdleSecs == 0) { + maxIdleSecs = defaults.getMaxIdleSecs(); + } + if (maxSecsToLive == 0) { + maxSecsToLive = defaults.getMaxSecsToLive(); + } + } + + /** + * Return a copy of this object. + */ + public ServerCacheOptions copy() { + + ServerCacheOptions copy = new ServerCacheOptions(); + copy.maxSize = maxSize; + copy.maxIdleSecs = maxIdleSecs; + copy.maxSecsToLive = maxSecsToLive; + + return copy; + } + + /** + * Return the maximum cache size. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Set the maximum cache size. + */ + public void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } + + /** + * Return the maximum idle time. + */ + public int getMaxIdleSecs() { + return maxIdleSecs; + } + + /** + * Set the maximum idle time. + */ + public void setMaxIdleSecs(int maxIdleSecs) { + this.maxIdleSecs = maxIdleSecs; + } + + /** + * Return the maximum time to live. + */ + public int getMaxSecsToLive() { + return maxSecsToLive; + } + + /** + * Set the maximum time to live. + */ + public void setMaxSecsToLive(int maxSecsToLive) { + this.maxSecsToLive = maxSecsToLive; + } + +} diff --git a/src/main/java/com/avaje/ebean/cache/ServerCacheStatistics.java b/src/main/java/com/avaje/ebean/cache/ServerCacheStatistics.java index 60f8eb587..969418917 100644 --- a/src/main/java/com/avaje/ebean/cache/ServerCacheStatistics.java +++ b/src/main/java/com/avaje/ebean/cache/ServerCacheStatistics.java @@ -1,124 +1,124 @@ -package com.avaje.ebean.cache; - -/** - * The statistics collected per cache. - *

    - * These can be monitored to review the effectiveness of a particular cache. - *

    - * - * @author rbygrave - * - */ -public class ServerCacheStatistics { - - protected String cacheName; - - protected int maxSize; - - protected int size; - - protected int hitCount; - - protected int missCount; - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(cacheName); - sb.append(" size:").append(size); - sb.append(" hitRatio:").append(getHitRatio()); - sb.append(" hitCount:").append(hitCount); - sb.append(" missCount:").append(missCount); - sb.append(" maxSize:").append(maxSize); - return sb.toString(); - } - - /** - * Return the name of the cache. - */ - public String getCacheName() { - return cacheName; - } - - /** - * Set the name of the cache. - */ - public void setCacheName(String cacheName) { - this.cacheName = cacheName; - } - - /** - * Return the hit count. The number of successful gets. - */ - public int getHitCount() { - return hitCount; - } - - /** - * Set the hit count. - */ - public void setHitCount(int hitCount) { - this.hitCount = hitCount; - } - - /** - * Return the miss count. The number of gets that returned null. - */ - public int getMissCount() { - return missCount; - } - - /** - * Set the miss count. - */ - public void setMissCount(int missCount) { - this.missCount = missCount; - } - - /** - * Return the size of the cache. - */ - public int getSize() { - return size; - } - - /** - * Set the size of the cache. - */ - public void setSize(int size) { - this.size = size; - } - - /** - * Return the maximum size of the cache. - *

    - * Can be used in conjunction with the size to determine if the cache use is - * being potentially limited by its maximum size. - *

    - */ - public int getMaxSize() { - return maxSize; - } - - /** - * Set the maximum size of the cache. - */ - public void setMaxSize(int maxSize) { - this.maxSize = maxSize; - } - - /** - * Returns an int from 0 to 100 (percentage) for the hit ratio. - *

    - * A hit ratio of 100 means every get request against the cache hits an entry. - *

    - */ - public int getHitRatio() { - int totalCount = hitCount + missCount; - if (totalCount == 0) { - return 0; - } else { - return hitCount * 100 / totalCount; - } - } - -} +package com.avaje.ebean.cache; + +/** + * The statistics collected per cache. + *

    + * These can be monitored to review the effectiveness of a particular cache. + *

    + * + * @author rbygrave + * + */ +public class ServerCacheStatistics { + + protected String cacheName; + + protected int maxSize; + + protected int size; + + protected int hitCount; + + protected int missCount; + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(cacheName); + sb.append(" size:").append(size); + sb.append(" hitRatio:").append(getHitRatio()); + sb.append(" hitCount:").append(hitCount); + sb.append(" missCount:").append(missCount); + sb.append(" maxSize:").append(maxSize); + return sb.toString(); + } + + /** + * Return the name of the cache. + */ + public String getCacheName() { + return cacheName; + } + + /** + * Set the name of the cache. + */ + public void setCacheName(String cacheName) { + this.cacheName = cacheName; + } + + /** + * Return the hit count. The number of successful gets. + */ + public int getHitCount() { + return hitCount; + } + + /** + * Set the hit count. + */ + public void setHitCount(int hitCount) { + this.hitCount = hitCount; + } + + /** + * Return the miss count. The number of gets that returned null. + */ + public int getMissCount() { + return missCount; + } + + /** + * Set the miss count. + */ + public void setMissCount(int missCount) { + this.missCount = missCount; + } + + /** + * Return the size of the cache. + */ + public int getSize() { + return size; + } + + /** + * Set the size of the cache. + */ + public void setSize(int size) { + this.size = size; + } + + /** + * Return the maximum size of the cache. + *

    + * Can be used in conjunction with the size to determine if the cache use is + * being potentially limited by its maximum size. + *

    + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Set the maximum size of the cache. + */ + public void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } + + /** + * Returns an int from 0 to 100 (percentage) for the hit ratio. + *

    + * A hit ratio of 100 means every get request against the cache hits an entry. + *

    + */ + public int getHitRatio() { + int totalCount = hitCount + missCount; + if (totalCount == 0) { + return 0; + } else { + return hitCount * 100 / totalCount; + } + } + +} diff --git a/src/main/java/com/avaje/ebean/common/AbstractBeanCollection.java b/src/main/java/com/avaje/ebean/common/AbstractBeanCollection.java index 58084a6a2..ab124de4c 100644 --- a/src/main/java/com/avaje/ebean/common/AbstractBeanCollection.java +++ b/src/main/java/com/avaje/ebean/common/AbstractBeanCollection.java @@ -1,222 +1,222 @@ -package com.avaje.ebean.common; - -import java.util.Set; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.Ebean; -import com.avaje.ebean.ExpressionList; -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.bean.BeanCollectionLoader; -import com.avaje.ebean.bean.BeanCollectionTouched; -import com.avaje.ebean.bean.EntityBean; - -/** - * Base class for List Set and Map implementations of BeanCollection. - * - * @author rbygrave - */ -public abstract class AbstractBeanCollection implements BeanCollection { - - private static final long serialVersionUID = 3365725236140187588L; - - protected boolean readOnly; - - /** - * The EbeanServer this is associated with. (used for lazy fetch). - */ - protected transient BeanCollectionLoader loader; - - protected transient ExpressionList filterMany; - - /** - * Flag set when registered with the batch loading context. - */ - protected boolean registeredWithLoadContext; - - protected String ebeanServerName; - - protected transient BeanCollectionTouched beanCollectionTouched; - - /** - * The owning bean (used for lazy fetch). - */ - protected EntityBean ownerBean; - - /** - * The name of this property in the owning bean (used for lazy fetch). - */ - protected String propertyName; - - protected ModifyHolder modifyHolder; - - protected ModifyListenMode modifyListenMode; - protected boolean modifyAddListening; - protected boolean modifyRemoveListening; - protected boolean modifyListening; - - /** - * Flag used to tell if empty collections have been cleared etc or just - * uninitialised. - */ - protected boolean touched; - - /** - * Constructor not non-lazy loading collection. - */ - public AbstractBeanCollection() { - } - - /** - * Used to create deferred fetch proxy. - */ - public AbstractBeanCollection(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { - this.loader = loader; - this.ebeanServerName = loader.getName(); - this.ownerBean = ownerBean; - this.propertyName = propertyName; - this.readOnly = ownerBean._ebean_getIntercept().isReadOnly(); - } - - public EntityBean getOwnerBean() { - return ownerBean; - } - - public String getPropertyName() { - return propertyName; - } - - public ExpressionList getFilterMany() { - return filterMany; - } - - public void setFilterMany(ExpressionList filterMany) { - this.filterMany = filterMany; - } - - protected void lazyLoadCollection(boolean onlyIds) { - if (loader == null) { - loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName); - } - if (loader == null) { - String msg = "Lazy loading but LazyLoadEbeanServer is null?" - + " The LazyLoadEbeanServer needs to be set after deserialization" - + " to support lazy loading."; - throw new PersistenceException(msg); - } - - loader.loadMany(this, onlyIds); - checkEmptyLazyLoad(); - } - - /** - * Set touched. If setFlag is false then typically an isEmpty() call and still - * considering that to be untouched. - */ - protected void touched(boolean setFlag) { - if (setFlag) { - touched = true; - } - if (beanCollectionTouched != null) { - // only call this once - beanCollectionTouched.notifyTouched(this); - beanCollectionTouched = null; - } - } - - public void setBeanCollectionTouched(BeanCollectionTouched notify) { - this.beanCollectionTouched = notify; - } - - public boolean isRegisteredWithLoadContext() { - return registeredWithLoadContext; - } - - public void setLoader(BeanCollectionLoader loader) { - this.registeredWithLoadContext = true; - this.loader = loader; - this.ebeanServerName = loader.getName(); - } - - public boolean isReadOnly() { - return readOnly; - } - - public void setReadOnly(boolean readOnly) { - this.readOnly = readOnly; - } - - protected void checkReadOnly() { - if (readOnly) { - String msg = "This collection is in ReadOnly mode"; - throw new IllegalStateException(msg); - } - } - - // --------------------------------------------------------- - // Support for modify additions deletions etc - ManyToMany - // --------------------------------------------------------- - - /** - * set modifyListening to be on or off. - */ - public void setModifyListening(ModifyListenMode mode) { - - this.modifyListenMode = mode; - this.modifyAddListening = ModifyListenMode.ALL.equals(mode); - this.modifyRemoveListening = modifyAddListening || ModifyListenMode.REMOVALS.equals(mode); - this.modifyListening = modifyRemoveListening || modifyAddListening; - if (modifyListening) { - // lose any existing modifications - modifyHolder = null; - } - } - - /** - * Return the modify listening mode this collection is using. - */ - public ModifyListenMode getModifyListenMode() { - return modifyListenMode; - } - - protected ModifyHolder getModifyHolder() { - if (modifyHolder == null) { - modifyHolder = new ModifyHolder(); - } - return modifyHolder; - } - - public void modifyAddition(E bean) { - if (modifyAddListening) { - getModifyHolder().modifyAddition(bean); - } - } - - public void modifyRemoval(Object bean) { - if (modifyRemoveListening) { - getModifyHolder().modifyRemoval(bean); - } - } - - public void modifyReset() { - if (modifyHolder != null) { - modifyHolder.reset(); - } - } - - public Set getModifyAdditions() { - if (modifyHolder == null) { - return null; - } else { - return modifyHolder.getModifyAdditions(); - } - } - - public Set getModifyRemovals() { - if (modifyHolder == null) { - return null; - } else { - return modifyHolder.getModifyRemovals(); - } - } -} +package com.avaje.ebean.common; + +import java.util.Set; + +import javax.persistence.PersistenceException; + +import com.avaje.ebean.Ebean; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.BeanCollectionTouched; +import com.avaje.ebean.bean.EntityBean; + +/** + * Base class for List Set and Map implementations of BeanCollection. + * + * @author rbygrave + */ +public abstract class AbstractBeanCollection implements BeanCollection { + + private static final long serialVersionUID = 3365725236140187588L; + + protected boolean readOnly; + + /** + * The EbeanServer this is associated with. (used for lazy fetch). + */ + protected transient BeanCollectionLoader loader; + + protected transient ExpressionList filterMany; + + /** + * Flag set when registered with the batch loading context. + */ + protected boolean registeredWithLoadContext; + + protected String ebeanServerName; + + protected transient BeanCollectionTouched beanCollectionTouched; + + /** + * The owning bean (used for lazy fetch). + */ + protected EntityBean ownerBean; + + /** + * The name of this property in the owning bean (used for lazy fetch). + */ + protected String propertyName; + + protected ModifyHolder modifyHolder; + + protected ModifyListenMode modifyListenMode; + protected boolean modifyAddListening; + protected boolean modifyRemoveListening; + protected boolean modifyListening; + + /** + * Flag used to tell if empty collections have been cleared etc or just + * uninitialised. + */ + protected boolean touched; + + /** + * Constructor not non-lazy loading collection. + */ + public AbstractBeanCollection() { + } + + /** + * Used to create deferred fetch proxy. + */ + public AbstractBeanCollection(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { + this.loader = loader; + this.ebeanServerName = loader.getName(); + this.ownerBean = ownerBean; + this.propertyName = propertyName; + this.readOnly = ownerBean._ebean_getIntercept().isReadOnly(); + } + + public EntityBean getOwnerBean() { + return ownerBean; + } + + public String getPropertyName() { + return propertyName; + } + + public ExpressionList getFilterMany() { + return filterMany; + } + + public void setFilterMany(ExpressionList filterMany) { + this.filterMany = filterMany; + } + + protected void lazyLoadCollection(boolean onlyIds) { + if (loader == null) { + loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName); + } + if (loader == null) { + String msg = "Lazy loading but LazyLoadEbeanServer is null?" + + " The LazyLoadEbeanServer needs to be set after deserialization" + + " to support lazy loading."; + throw new PersistenceException(msg); + } + + loader.loadMany(this, onlyIds); + checkEmptyLazyLoad(); + } + + /** + * Set touched. If setFlag is false then typically an isEmpty() call and still + * considering that to be untouched. + */ + protected void touched(boolean setFlag) { + if (setFlag) { + touched = true; + } + if (beanCollectionTouched != null) { + // only call this once + beanCollectionTouched.notifyTouched(this); + beanCollectionTouched = null; + } + } + + public void setBeanCollectionTouched(BeanCollectionTouched notify) { + this.beanCollectionTouched = notify; + } + + public boolean isRegisteredWithLoadContext() { + return registeredWithLoadContext; + } + + public void setLoader(BeanCollectionLoader loader) { + this.registeredWithLoadContext = true; + this.loader = loader; + this.ebeanServerName = loader.getName(); + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + protected void checkReadOnly() { + if (readOnly) { + String msg = "This collection is in ReadOnly mode"; + throw new IllegalStateException(msg); + } + } + + // --------------------------------------------------------- + // Support for modify additions deletions etc - ManyToMany + // --------------------------------------------------------- + + /** + * set modifyListening to be on or off. + */ + public void setModifyListening(ModifyListenMode mode) { + + this.modifyListenMode = mode; + this.modifyAddListening = ModifyListenMode.ALL.equals(mode); + this.modifyRemoveListening = modifyAddListening || ModifyListenMode.REMOVALS.equals(mode); + this.modifyListening = modifyRemoveListening || modifyAddListening; + if (modifyListening) { + // lose any existing modifications + modifyHolder = null; + } + } + + /** + * Return the modify listening mode this collection is using. + */ + public ModifyListenMode getModifyListenMode() { + return modifyListenMode; + } + + protected ModifyHolder getModifyHolder() { + if (modifyHolder == null) { + modifyHolder = new ModifyHolder(); + } + return modifyHolder; + } + + public void modifyAddition(E bean) { + if (modifyAddListening) { + getModifyHolder().modifyAddition(bean); + } + } + + public void modifyRemoval(Object bean) { + if (modifyRemoveListening) { + getModifyHolder().modifyRemoval(bean); + } + } + + public void modifyReset() { + if (modifyHolder != null) { + modifyHolder.reset(); + } + } + + public Set getModifyAdditions() { + if (modifyHolder == null) { + return null; + } else { + return modifyHolder.getModifyAdditions(); + } + } + + public Set getModifyRemovals() { + if (modifyHolder == null) { + return null; + } else { + return modifyHolder.getModifyRemovals(); + } + } +} diff --git a/src/main/java/com/avaje/ebean/common/BeanList.java b/src/main/java/com/avaje/ebean/common/BeanList.java index 9ed264223..e370441b5 100644 --- a/src/main/java/com/avaje/ebean/common/BeanList.java +++ b/src/main/java/com/avaje/ebean/common/BeanList.java @@ -1,466 +1,466 @@ -package com.avaje.ebean.common; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -import com.avaje.ebean.bean.BeanCollectionAdd; -import com.avaje.ebean.bean.BeanCollectionLoader; -import com.avaje.ebean.bean.EntityBean; - -/** - * List capable of lazy loading. - */ -public final class BeanList extends AbstractBeanCollection implements List, BeanCollectionAdd { - - private static final long serialVersionUID = 1L; - - /** - * The underlying List implementation. - */ - private List list; - - /** - * Specify the underlying List implementation. - */ - public BeanList(List list) { - super(); - this.list = list; - } - - /** - * Uses an ArrayList as the underlying List implementation. - */ - public BeanList() { - this(new ArrayList()); - } - - /** - * Used to create deferred fetch proxy. - */ - public BeanList(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { - super(loader, ownerBean, propertyName); - } - - @Override - public void reset(EntityBean ownerBean, String propertyName) { - this.ownerBean = ownerBean; - this.propertyName = propertyName; - this.list = null; - this.touched = false; - } - - @Override - public boolean isEmptyAndUntouched() { - return !touched && (list == null || list.isEmpty()); - } - - @SuppressWarnings("unchecked") - public void addBean(EntityBean bean) { - list.add((E) bean); - } - - @SuppressWarnings("unchecked") - public void internalAdd(Object bean) { - if (list == null) { - list = new ArrayList(); - } - list.add((E) bean); - } - - public boolean checkEmptyLazyLoad() { - if (list == null) { - list = new ArrayList(); - return true; - } else { - return false; - } - } - - private void initClear() { - synchronized (this) { - if (list == null) { - if (modifyListening) { - lazyLoadCollection(true); - } else { - list = new ArrayList(); - } - } - touched(true); - } - } - - private void initAsUntouched() { - init(false); - } - - private void init() { - init(true); - } - - private void init(boolean setTouched) { - synchronized (this) { - if (list == null) { - lazyLoadCollection(false); - } - touched(setTouched); - } - } - - /** - * Set the actual underlying list. - *

    - * This is primarily for the deferred fetching function. - *

    - */ - @SuppressWarnings("unchecked") - public void setActualList(List list) { - this.list = (List) list; - } - - /** - * Return the actual underlying list. - */ - public List getActualList() { - return list; - } - - public Collection getActualDetails() { - return list; - } - - @Override - public Collection getActualEntries() { - return list; - } - - /** - * Return true if the underlying list is populated. - */ - public boolean isPopulated() { - return list != null; - } - - /** - * Return true if this is a reference (lazy loading) bean collection. This is - * the same as !isPopulated(); - */ - public boolean isReference() { - return list == null; - } - - public String toString() { - StringBuffer sb = new StringBuffer(50); - sb.append("BeanList "); - if (isReadOnly()) { - sb.append("readOnly "); - } - if (list == null) { - sb.append("deferred "); - - } else { - sb.append("size[").append(list.size()).append("] "); - sb.append("list").append(list).append(""); - } - return sb.toString(); - } - - /** - * Equal if obj is a List and equal in a list sense. - *

    - * Specifically obj does not need to be a BeanList but any list. This does not - * use the FindMany, fetchedMaxRows or finishedFetch properties in the equals - * test. - *

    - */ - public boolean equals(Object obj) { - init(); - return list.equals(obj); - } - - public int hashCode() { - init(); - return list.hashCode(); - } - - // -----------------------------------------------------// - // The additional methods are here - // -----------------------------------------------------// - - // -----------------------------------------------------// - // proxy method for List - // -----------------------------------------------------// - - public void add(int index, E element) { - checkReadOnly(); - init(); - if (modifyAddListening) { - modifyAddition(element); - } - list.add(index, element); - } - - public boolean add(E o) { - checkReadOnly(); - init(); - if (modifyAddListening) { - if (list.add(o)) { - modifyAddition(o); - return true; - } else { - return false; - } - } - return list.add(o); - } - - public boolean addAll(Collection c) { - checkReadOnly(); - init(); - if (modifyAddListening) { - // all elements in c are added (no contains checking) - getModifyHolder().modifyAdditionAll(c); - } - return list.addAll(c); - } - - public boolean addAll(int index, Collection c) { - checkReadOnly(); - init(); - if (modifyAddListening) { - // all elements in c are added (no contains checking) - getModifyHolder().modifyAdditionAll(c); - } - return list.addAll(index, c); - } - - public void clear() { - checkReadOnly(); - // TODO: when clear() and not initialised could be more clever - // and fetch just the Id's - initClear(); - if (modifyRemoveListening) { - for (int i = 0; i < list.size(); i++) { - getModifyHolder().modifyRemoval(list.get(i)); - } - } - list.clear(); - } - - public boolean contains(Object o) { - init(); - return list.contains(o); - } - - public boolean containsAll(Collection c) { - init(); - return list.containsAll(c); - } - - public E get(int index) { - init(); - return list.get(index); - } - - public int indexOf(Object o) { - init(); - return list.indexOf(o); - } - - public boolean isEmpty() { - initAsUntouched(); - return list.isEmpty(); - } - - public Iterator iterator() { - init(); - if (isReadOnly()) { - return new ReadOnlyListIterator(list.listIterator()); - } - if (modifyListening) { - Iterator it = list.iterator(); - return new ModifyIterator(this, it); - } - return list.iterator(); - } - - public int lastIndexOf(Object o) { - init(); - return list.lastIndexOf(o); - } - - public ListIterator listIterator() { - init(); - if (isReadOnly()) { - return new ReadOnlyListIterator(list.listIterator()); - } - if (modifyListening) { - ListIterator it = list.listIterator(); - return new ModifyListIterator(this, it); - } - return list.listIterator(); - } - - public ListIterator listIterator(int index) { - init(); - if (isReadOnly()) { - return new ReadOnlyListIterator(list.listIterator(index)); - } - if (modifyListening) { - ListIterator it = list.listIterator(index); - return new ModifyListIterator(this, it); - } - return list.listIterator(index); - } - - public E remove(int index) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - E o = list.remove(index); - modifyRemoval(o); - return o; - } - return list.remove(index); - } - - public boolean remove(Object o) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - boolean isRemove = list.remove(o); - if (isRemove) { - modifyRemoval(o); - } - return isRemove; - } - return list.remove(o); - } - - public boolean removeAll(Collection c) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - boolean changed = false; - Iterator it = c.iterator(); - while (it.hasNext()) { - Object o = (Object) it.next(); - if (list.remove(o)) { - modifyRemoval(o); - changed = true; - } - } - return changed; - } - return list.removeAll(c); - } - - public boolean retainAll(Collection c) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - boolean changed = false; - Iterator it = list.iterator(); - while (it.hasNext()) { - Object o = (Object) it.next(); - if (!c.contains(o)) { - it.remove(); - modifyRemoval(o); - changed = true; - } - } - return changed; - } - return list.retainAll(c); - } - - public E set(int index, E element) { - checkReadOnly(); - init(); - if (modifyListening) { - E o = list.set(index, element); - modifyAddition(element); - modifyRemoval(o); - return o; - } - return list.set(index, element); - } - - public int size() { - init(); - return list.size(); - } - - public List subList(int fromIndex, int toIndex) { - init(); - if (isReadOnly()) { - return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); - } - if (modifyListening) { - return new ModifyList(this, list.subList(fromIndex, toIndex)); - } - return list.subList(fromIndex, toIndex); - } - - public Object[] toArray() { - init(); - return list.toArray(); - } - - public T[] toArray(T[] a) { - init(); - return list.toArray(a); - } - - private static class ReadOnlyListIterator implements ListIterator, Serializable { - - private static final long serialVersionUID = 3097271091406323699L; - - private final ListIterator i; - - ReadOnlyListIterator(ListIterator i) { - this.i = i; - } - - public void add(E o) { - throw new IllegalStateException("This collection is in ReadOnly mode"); - } - - public void remove() { - throw new IllegalStateException("This collection is in ReadOnly mode"); - } - - public void set(E o) { - throw new IllegalStateException("This collection is in ReadOnly mode"); - } - - public boolean hasNext() { - return i.hasNext(); - } - - public boolean hasPrevious() { - return i.hasPrevious(); - } - - public E next() { - return i.next(); - } - - public int nextIndex() { - return i.nextIndex(); - } - - public E previous() { - return i.previous(); - } - - public int previousIndex() { - return i.previousIndex(); - } - - } -} +package com.avaje.ebean.common; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.EntityBean; + +/** + * List capable of lazy loading. + */ +public final class BeanList extends AbstractBeanCollection implements List, BeanCollectionAdd { + + private static final long serialVersionUID = 1L; + + /** + * The underlying List implementation. + */ + private List list; + + /** + * Specify the underlying List implementation. + */ + public BeanList(List list) { + super(); + this.list = list; + } + + /** + * Uses an ArrayList as the underlying List implementation. + */ + public BeanList() { + this(new ArrayList()); + } + + /** + * Used to create deferred fetch proxy. + */ + public BeanList(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { + super(loader, ownerBean, propertyName); + } + + @Override + public void reset(EntityBean ownerBean, String propertyName) { + this.ownerBean = ownerBean; + this.propertyName = propertyName; + this.list = null; + this.touched = false; + } + + @Override + public boolean isEmptyAndUntouched() { + return !touched && (list == null || list.isEmpty()); + } + + @SuppressWarnings("unchecked") + public void addBean(EntityBean bean) { + list.add((E) bean); + } + + @SuppressWarnings("unchecked") + public void internalAdd(Object bean) { + if (list == null) { + list = new ArrayList(); + } + list.add((E) bean); + } + + public boolean checkEmptyLazyLoad() { + if (list == null) { + list = new ArrayList(); + return true; + } else { + return false; + } + } + + private void initClear() { + synchronized (this) { + if (list == null) { + if (modifyListening) { + lazyLoadCollection(true); + } else { + list = new ArrayList(); + } + } + touched(true); + } + } + + private void initAsUntouched() { + init(false); + } + + private void init() { + init(true); + } + + private void init(boolean setTouched) { + synchronized (this) { + if (list == null) { + lazyLoadCollection(false); + } + touched(setTouched); + } + } + + /** + * Set the actual underlying list. + *

    + * This is primarily for the deferred fetching function. + *

    + */ + @SuppressWarnings("unchecked") + public void setActualList(List list) { + this.list = (List) list; + } + + /** + * Return the actual underlying list. + */ + public List getActualList() { + return list; + } + + public Collection getActualDetails() { + return list; + } + + @Override + public Collection getActualEntries() { + return list; + } + + /** + * Return true if the underlying list is populated. + */ + public boolean isPopulated() { + return list != null; + } + + /** + * Return true if this is a reference (lazy loading) bean collection. This is + * the same as !isPopulated(); + */ + public boolean isReference() { + return list == null; + } + + public String toString() { + StringBuffer sb = new StringBuffer(50); + sb.append("BeanList "); + if (isReadOnly()) { + sb.append("readOnly "); + } + if (list == null) { + sb.append("deferred "); + + } else { + sb.append("size[").append(list.size()).append("] "); + sb.append("list").append(list).append(""); + } + return sb.toString(); + } + + /** + * Equal if obj is a List and equal in a list sense. + *

    + * Specifically obj does not need to be a BeanList but any list. This does not + * use the FindMany, fetchedMaxRows or finishedFetch properties in the equals + * test. + *

    + */ + public boolean equals(Object obj) { + init(); + return list.equals(obj); + } + + public int hashCode() { + init(); + return list.hashCode(); + } + + // -----------------------------------------------------// + // The additional methods are here + // -----------------------------------------------------// + + // -----------------------------------------------------// + // proxy method for List + // -----------------------------------------------------// + + public void add(int index, E element) { + checkReadOnly(); + init(); + if (modifyAddListening) { + modifyAddition(element); + } + list.add(index, element); + } + + public boolean add(E o) { + checkReadOnly(); + init(); + if (modifyAddListening) { + if (list.add(o)) { + modifyAddition(o); + return true; + } else { + return false; + } + } + return list.add(o); + } + + public boolean addAll(Collection c) { + checkReadOnly(); + init(); + if (modifyAddListening) { + // all elements in c are added (no contains checking) + getModifyHolder().modifyAdditionAll(c); + } + return list.addAll(c); + } + + public boolean addAll(int index, Collection c) { + checkReadOnly(); + init(); + if (modifyAddListening) { + // all elements in c are added (no contains checking) + getModifyHolder().modifyAdditionAll(c); + } + return list.addAll(index, c); + } + + public void clear() { + checkReadOnly(); + // TODO: when clear() and not initialised could be more clever + // and fetch just the Id's + initClear(); + if (modifyRemoveListening) { + for (int i = 0; i < list.size(); i++) { + getModifyHolder().modifyRemoval(list.get(i)); + } + } + list.clear(); + } + + public boolean contains(Object o) { + init(); + return list.contains(o); + } + + public boolean containsAll(Collection c) { + init(); + return list.containsAll(c); + } + + public E get(int index) { + init(); + return list.get(index); + } + + public int indexOf(Object o) { + init(); + return list.indexOf(o); + } + + public boolean isEmpty() { + initAsUntouched(); + return list.isEmpty(); + } + + public Iterator iterator() { + init(); + if (isReadOnly()) { + return new ReadOnlyListIterator(list.listIterator()); + } + if (modifyListening) { + Iterator it = list.iterator(); + return new ModifyIterator(this, it); + } + return list.iterator(); + } + + public int lastIndexOf(Object o) { + init(); + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + init(); + if (isReadOnly()) { + return new ReadOnlyListIterator(list.listIterator()); + } + if (modifyListening) { + ListIterator it = list.listIterator(); + return new ModifyListIterator(this, it); + } + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + init(); + if (isReadOnly()) { + return new ReadOnlyListIterator(list.listIterator(index)); + } + if (modifyListening) { + ListIterator it = list.listIterator(index); + return new ModifyListIterator(this, it); + } + return list.listIterator(index); + } + + public E remove(int index) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + E o = list.remove(index); + modifyRemoval(o); + return o; + } + return list.remove(index); + } + + public boolean remove(Object o) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + boolean isRemove = list.remove(o); + if (isRemove) { + modifyRemoval(o); + } + return isRemove; + } + return list.remove(o); + } + + public boolean removeAll(Collection c) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + boolean changed = false; + Iterator it = c.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (list.remove(o)) { + modifyRemoval(o); + changed = true; + } + } + return changed; + } + return list.removeAll(c); + } + + public boolean retainAll(Collection c) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + boolean changed = false; + Iterator it = list.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (!c.contains(o)) { + it.remove(); + modifyRemoval(o); + changed = true; + } + } + return changed; + } + return list.retainAll(c); + } + + public E set(int index, E element) { + checkReadOnly(); + init(); + if (modifyListening) { + E o = list.set(index, element); + modifyAddition(element); + modifyRemoval(o); + return o; + } + return list.set(index, element); + } + + public int size() { + init(); + return list.size(); + } + + public List subList(int fromIndex, int toIndex) { + init(); + if (isReadOnly()) { + return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); + } + if (modifyListening) { + return new ModifyList(this, list.subList(fromIndex, toIndex)); + } + return list.subList(fromIndex, toIndex); + } + + public Object[] toArray() { + init(); + return list.toArray(); + } + + public T[] toArray(T[] a) { + init(); + return list.toArray(a); + } + + private static class ReadOnlyListIterator implements ListIterator, Serializable { + + private static final long serialVersionUID = 3097271091406323699L; + + private final ListIterator i; + + ReadOnlyListIterator(ListIterator i) { + this.i = i; + } + + public void add(E o) { + throw new IllegalStateException("This collection is in ReadOnly mode"); + } + + public void remove() { + throw new IllegalStateException("This collection is in ReadOnly mode"); + } + + public void set(E o) { + throw new IllegalStateException("This collection is in ReadOnly mode"); + } + + public boolean hasNext() { + return i.hasNext(); + } + + public boolean hasPrevious() { + return i.hasPrevious(); + } + + public E next() { + return i.next(); + } + + public int nextIndex() { + return i.nextIndex(); + } + + public E previous() { + return i.previous(); + } + + public int previousIndex() { + return i.previousIndex(); + } + + } +} diff --git a/src/main/java/com/avaje/ebean/common/BeanMap.java b/src/main/java/com/avaje/ebean/common/BeanMap.java index f777ccb41..814a14e93 100644 --- a/src/main/java/com/avaje/ebean/common/BeanMap.java +++ b/src/main/java/com/avaje/ebean/common/BeanMap.java @@ -1,295 +1,295 @@ -package com.avaje.ebean.common; - -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import com.avaje.ebean.bean.BeanCollectionLoader; -import com.avaje.ebean.bean.EntityBean; - -/** - * Map capable of lazy loading. - */ -public final class BeanMap extends AbstractBeanCollection implements Map { - - private static final long serialVersionUID = 1L; - - /** - * The underlying map implementation. - */ - private Map map; - - /** - * Create with a given Map. - */ - public BeanMap(Map map) { - this.map = map; - } - - /** - * Create using a underlying LinkedHashMap. - */ - public BeanMap() { - this(new LinkedHashMap()); - } - - public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) { - super(ebeanServer, ownerBean, propertyName); - } - - @Override - public void reset(EntityBean ownerBean, String propertyName) { - this.ownerBean = ownerBean; - this.propertyName = propertyName; - this.map = null; - this.touched = false; - } - - public boolean isEmptyAndUntouched() { - return !touched && (map == null || map.isEmpty()); - } - - @SuppressWarnings("unchecked") - public void internalPut(Object key, Object bean) { - if (map == null) { - map = new LinkedHashMap(); - } - map.put((K)key, (E)bean); - } - - public void internalAdd(Object bean) { - throw new RuntimeException("Not allowed for map"); - } - - /** - * Return true if the underlying map has been populated. Returns false if it - * has a deferred fetch pending. - */ - public boolean isPopulated() { - return map != null; - } - - /** - * Return true if this is a reference (lazy loading) bean collection. This is - * the same as !isPopulated(); - */ - public boolean isReference() { - return map == null; - } - - public boolean checkEmptyLazyLoad() { - if (map == null) { - map = new LinkedHashMap(); - return true; - } else { - return false; - } - } - - private void initClear() { - synchronized (this) { - if (map == null) { - if (modifyListening) { - lazyLoadCollection(true); - } else { - map = new LinkedHashMap(); - } - } - touched(true); - } - } - - private void initAsUntouched() { - init(false); - } - - private void init() { - init(true); - } - - private void init(boolean setTouched) { - synchronized (this) { - if (map == null) { - lazyLoadCollection(false); - } - touched(setTouched); - } - } - - /** - * Set the actual underlying map. Used for performing lazy fetch. - */ - @SuppressWarnings("unchecked") - public void setActualMap(Map map) { - this.map = (Map) map; - } - - /** - * Return the actual underlying map. - */ - public Map getActualMap() { - return map; - } - - /** - * Returns the collection of beans (map values). - */ - public Collection getActualDetails() { - return map.values(); - } - - - /** - * Returns the map entrySet. - *

    - * This is because the key values may need to be set against the details (so - * they don't need to be set twice). - *

    - */ - public Collection getActualEntries() { - return map.entrySet(); - - } - - public String toString() { - StringBuilder sb = new StringBuilder(50); - sb.append("BeanMap "); - if (isReadOnly()) { - sb.append("readOnly "); - } - if (map == null) { - sb.append("deferred "); - - } else { - sb.append("size[").append(map.size()).append("]"); - sb.append(" map").append(map); - } - return sb.toString(); - } - - /** - * Equal if obj is a Map and equal in a Map sense. - */ - public boolean equals(Object obj) { - init(); - return map.equals(obj); - } - - public int hashCode() { - init(); - return map.hashCode(); - } - - public void clear() { - checkReadOnly(); - initClear(); - if (modifyRemoveListening) { - for (K key : map.keySet()) { - E o = map.remove(key); - modifyRemoval(o); - } - } - map.clear(); - } - - public boolean containsKey(Object key) { - init(); - return map.containsKey(key); - } - - public boolean containsValue(Object value) { - init(); - return map.containsValue(value); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Set> entrySet() { - init(); - if (isReadOnly()) { - return Collections.unmodifiableSet(map.entrySet()); - } - if (modifyListening) { - Set> s = map.entrySet(); - return new ModifySet(this, s); - } - return map.entrySet(); - } - - public E get(Object key) { - init(); - return map.get(key); - } - - public boolean isEmpty() { - initAsUntouched(); - return map.isEmpty(); - } - - public Set keySet() { - init(); - if (isReadOnly()) { - return Collections.unmodifiableSet(map.keySet()); - } - // we don't really care about modifications to the ketSet? - return map.keySet(); - } - - public E put(K key, E value) { - checkReadOnly(); - init(); - if (modifyListening) { - Object o = map.put(key, value); - modifyAddition(value); - modifyRemoval(o); - } - return map.put(key, value); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void putAll(Map t) { - checkReadOnly(); - init(); - if (modifyListening) { - Iterator it = t.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - Object o = map.put((K) entry.getKey(), (E) entry.getValue()); - modifyAddition((E) entry.getValue()); - modifyRemoval(o); - } - } - map.putAll(t); - } - - public E remove(Object key) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - E o = map.remove(key); - modifyRemoval(o); - return o; - } - return map.remove(key); - } - - public int size() { - init(); - return map.size(); - } - - public Collection values() { - init(); - if (isReadOnly()) { - return Collections.unmodifiableCollection(map.values()); - } - if (modifyListening) { - Collection c = map.values(); - return new ModifyCollection(this, c); - } - return map.values(); - } - -} +package com.avaje.ebean.common; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.EntityBean; + +/** + * Map capable of lazy loading. + */ +public final class BeanMap extends AbstractBeanCollection implements Map { + + private static final long serialVersionUID = 1L; + + /** + * The underlying map implementation. + */ + private Map map; + + /** + * Create with a given Map. + */ + public BeanMap(Map map) { + this.map = map; + } + + /** + * Create using a underlying LinkedHashMap. + */ + public BeanMap() { + this(new LinkedHashMap()); + } + + public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) { + super(ebeanServer, ownerBean, propertyName); + } + + @Override + public void reset(EntityBean ownerBean, String propertyName) { + this.ownerBean = ownerBean; + this.propertyName = propertyName; + this.map = null; + this.touched = false; + } + + public boolean isEmptyAndUntouched() { + return !touched && (map == null || map.isEmpty()); + } + + @SuppressWarnings("unchecked") + public void internalPut(Object key, Object bean) { + if (map == null) { + map = new LinkedHashMap(); + } + map.put((K)key, (E)bean); + } + + public void internalAdd(Object bean) { + throw new RuntimeException("Not allowed for map"); + } + + /** + * Return true if the underlying map has been populated. Returns false if it + * has a deferred fetch pending. + */ + public boolean isPopulated() { + return map != null; + } + + /** + * Return true if this is a reference (lazy loading) bean collection. This is + * the same as !isPopulated(); + */ + public boolean isReference() { + return map == null; + } + + public boolean checkEmptyLazyLoad() { + if (map == null) { + map = new LinkedHashMap(); + return true; + } else { + return false; + } + } + + private void initClear() { + synchronized (this) { + if (map == null) { + if (modifyListening) { + lazyLoadCollection(true); + } else { + map = new LinkedHashMap(); + } + } + touched(true); + } + } + + private void initAsUntouched() { + init(false); + } + + private void init() { + init(true); + } + + private void init(boolean setTouched) { + synchronized (this) { + if (map == null) { + lazyLoadCollection(false); + } + touched(setTouched); + } + } + + /** + * Set the actual underlying map. Used for performing lazy fetch. + */ + @SuppressWarnings("unchecked") + public void setActualMap(Map map) { + this.map = (Map) map; + } + + /** + * Return the actual underlying map. + */ + public Map getActualMap() { + return map; + } + + /** + * Returns the collection of beans (map values). + */ + public Collection getActualDetails() { + return map.values(); + } + + + /** + * Returns the map entrySet. + *

    + * This is because the key values may need to be set against the details (so + * they don't need to be set twice). + *

    + */ + public Collection getActualEntries() { + return map.entrySet(); + + } + + public String toString() { + StringBuilder sb = new StringBuilder(50); + sb.append("BeanMap "); + if (isReadOnly()) { + sb.append("readOnly "); + } + if (map == null) { + sb.append("deferred "); + + } else { + sb.append("size[").append(map.size()).append("]"); + sb.append(" map").append(map); + } + return sb.toString(); + } + + /** + * Equal if obj is a Map and equal in a Map sense. + */ + public boolean equals(Object obj) { + init(); + return map.equals(obj); + } + + public int hashCode() { + init(); + return map.hashCode(); + } + + public void clear() { + checkReadOnly(); + initClear(); + if (modifyRemoveListening) { + for (K key : map.keySet()) { + E o = map.remove(key); + modifyRemoval(o); + } + } + map.clear(); + } + + public boolean containsKey(Object key) { + init(); + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + init(); + return map.containsValue(value); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Set> entrySet() { + init(); + if (isReadOnly()) { + return Collections.unmodifiableSet(map.entrySet()); + } + if (modifyListening) { + Set> s = map.entrySet(); + return new ModifySet(this, s); + } + return map.entrySet(); + } + + public E get(Object key) { + init(); + return map.get(key); + } + + public boolean isEmpty() { + initAsUntouched(); + return map.isEmpty(); + } + + public Set keySet() { + init(); + if (isReadOnly()) { + return Collections.unmodifiableSet(map.keySet()); + } + // we don't really care about modifications to the ketSet? + return map.keySet(); + } + + public E put(K key, E value) { + checkReadOnly(); + init(); + if (modifyListening) { + Object o = map.put(key, value); + modifyAddition(value); + modifyRemoval(o); + } + return map.put(key, value); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void putAll(Map t) { + checkReadOnly(); + init(); + if (modifyListening) { + Iterator it = t.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + Object o = map.put((K) entry.getKey(), (E) entry.getValue()); + modifyAddition((E) entry.getValue()); + modifyRemoval(o); + } + } + map.putAll(t); + } + + public E remove(Object key) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + E o = map.remove(key); + modifyRemoval(o); + return o; + } + return map.remove(key); + } + + public int size() { + init(); + return map.size(); + } + + public Collection values() { + init(); + if (isReadOnly()) { + return Collections.unmodifiableCollection(map.values()); + } + if (modifyListening) { + Collection c = map.values(); + return new ModifyCollection(this, c); + } + return map.values(); + } + +} diff --git a/src/main/java/com/avaje/ebean/common/BeanSet.java b/src/main/java/com/avaje/ebean/common/BeanSet.java index f14cfae96..e3e6c166b 100644 --- a/src/main/java/com/avaje/ebean/common/BeanSet.java +++ b/src/main/java/com/avaje/ebean/common/BeanSet.java @@ -1,345 +1,345 @@ -package com.avaje.ebean.common; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -import com.avaje.ebean.bean.BeanCollectionAdd; -import com.avaje.ebean.bean.BeanCollectionLoader; -import com.avaje.ebean.bean.EntityBean; - -/** - * Set capable of lazy loading. - */ -public final class BeanSet extends AbstractBeanCollection implements Set, BeanCollectionAdd { - - private static final long serialVersionUID = 1L; - - /** - * The underlying Set implementation. - */ - private Set set; - - /** - * Create with a specific Set implementation. - */ - public BeanSet(Set set) { - this.set = set; - } - - /** - * Create using an underlying LinkedHashSet. - */ - public BeanSet() { - this(new LinkedHashSet()); - } - - public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { - super(loader, ownerBean, propertyName); - } - - @Override - public void reset(EntityBean ownerBean, String propertyName) { - this.ownerBean = ownerBean; - this.propertyName = propertyName; - this.set = null; - this.touched = false; - } - - public boolean isEmptyAndUntouched() { - return !touched && (set == null || set.isEmpty()); - } - - @SuppressWarnings("unchecked") - public void addBean(EntityBean bean) { - set.add((E) bean); - } - - @SuppressWarnings("unchecked") - public void internalAdd(Object bean) { - if (set == null) { - set = new LinkedHashSet(); - } - set.add((E) bean); - } - - /** - * Returns true if the underlying set has its data. - */ - public boolean isPopulated() { - return set != null; - } - - /** - * Return true if this is a reference (lazy loading) bean collection. This is - * the same as !isPopulated(); - */ - public boolean isReference() { - return set == null; - } - - public boolean checkEmptyLazyLoad() { - if (set == null) { - set = new LinkedHashSet(); - return true; - } else { - return false; - } - } - - private void initClear() { - synchronized (this) { - if (set == null) { - if (modifyListening) { - lazyLoadCollection(true); - } else { - set = new LinkedHashSet(); - } - } - touched(true); - } - } - - private void initAsUntouched() { - init(false); - } - - private void init() { - init(true); - } - - private void init(boolean setTouched) { - synchronized (this) { - if (set == null) { - lazyLoadCollection(true); - } - touched(setTouched); - } - } - - /** - * Set the underlying set (used for lazy fetch). - */ - @SuppressWarnings("unchecked") - public void setActualSet(Set set) { - this.set = (Set) set; - } - - /** - * Return the actual underlying set. - */ - public Set getActualSet() { - return set; - } - - public Collection getActualDetails() { - return set; - } - - @Override - public Collection getActualEntries() { - return set; - } - - /** - * Returns the underlying set. - */ - public Object getActualCollection() { - return set; - } - - public String toString() { - StringBuffer sb = new StringBuffer(50); - sb.append("BeanSet "); - if (isReadOnly()) { - sb.append("readOnly "); - } - if (set == null) { - sb.append("deferred "); - - } else { - sb.append("size[").append(set.size()).append("]"); - sb.append(" set").append(set); - } - return sb.toString(); - } - - /** - * Equal if obj is a Set and equal in a Set sense. - */ - public boolean equals(Object obj) { - init(); - return set.equals(obj); - } - - public int hashCode() { - init(); - return set.hashCode(); - } - - // -----------------------------------------------------// - // proxy method for map - // -----------------------------------------------------// - - public boolean add(E o) { - checkReadOnly(); - init(); - if (modifyAddListening) { - if (set.add(o)) { - modifyAddition(o); - return true; - } else { - return false; - } - } - return set.add(o); - } - - public boolean addAll(Collection c) { - checkReadOnly(); - init(); - if (modifyAddListening) { - boolean changed = false; - Iterator it = c.iterator(); - while (it.hasNext()) { - E o = it.next(); - if (set.add(o)) { - modifyAddition(o); - changed = true; - } - } - return changed; - } - return set.addAll(c); - } - - public void clear() { - checkReadOnly(); - initClear(); - if (modifyRemoveListening) { - Iterator it = set.iterator(); - while (it.hasNext()) { - E e = it.next(); - modifyRemoval(e); - } - } - set.clear(); - } - - public boolean contains(Object o) { - init(); - return set.contains(o); - } - - public boolean containsAll(Collection c) { - init(); - return set.containsAll(c); - } - - public boolean isEmpty() { - initAsUntouched(); - return set.isEmpty(); - } - - public Iterator iterator() { - init(); - if (isReadOnly()) { - return new ReadOnlyIterator(set.iterator()); - } - if (modifyListening) { - return new ModifyIterator(this, set.iterator()); - } - return set.iterator(); - } - - public boolean remove(Object o) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - if (set.remove(o)) { - modifyRemoval(o); - return true; - } - return false; - } - return set.remove(o); - } - - public boolean removeAll(Collection c) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - boolean changed = false; - Iterator it = c.iterator(); - while (it.hasNext()) { - Object o = (Object) it.next(); - if (set.remove(o)) { - modifyRemoval(o); - changed = true; - } - } - return changed; - } - return set.removeAll(c); - } - - public boolean retainAll(Collection c) { - checkReadOnly(); - init(); - if (modifyRemoveListening) { - boolean changed = false; - Iterator it = set.iterator(); - while (it.hasNext()) { - Object o = it.next(); - if (!c.contains(o)) { - it.remove(); - modifyRemoval(o); - changed = true; - } - } - return changed; - } - return set.retainAll(c); - } - - public int size() { - init(); - return set.size(); - } - - public Object[] toArray() { - init(); - return set.toArray(); - } - - public T[] toArray(T[] a) { - init(); - return set.toArray(a); - } - - private static class ReadOnlyIterator implements Iterator, Serializable { - - private static final long serialVersionUID = 2577697326745352605L; - - private final Iterator it; - - ReadOnlyIterator(Iterator it) { - this.it = it; - } - - public boolean hasNext() { - return it.hasNext(); - } - - public E next() { - return it.next(); - } - - public void remove() { - throw new IllegalStateException("This collection is in ReadOnly mode"); - } - } - -} +package com.avaje.ebean.common; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.avaje.ebean.bean.BeanCollectionAdd; +import com.avaje.ebean.bean.BeanCollectionLoader; +import com.avaje.ebean.bean.EntityBean; + +/** + * Set capable of lazy loading. + */ +public final class BeanSet extends AbstractBeanCollection implements Set, BeanCollectionAdd { + + private static final long serialVersionUID = 1L; + + /** + * The underlying Set implementation. + */ + private Set set; + + /** + * Create with a specific Set implementation. + */ + public BeanSet(Set set) { + this.set = set; + } + + /** + * Create using an underlying LinkedHashSet. + */ + public BeanSet() { + this(new LinkedHashSet()); + } + + public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { + super(loader, ownerBean, propertyName); + } + + @Override + public void reset(EntityBean ownerBean, String propertyName) { + this.ownerBean = ownerBean; + this.propertyName = propertyName; + this.set = null; + this.touched = false; + } + + public boolean isEmptyAndUntouched() { + return !touched && (set == null || set.isEmpty()); + } + + @SuppressWarnings("unchecked") + public void addBean(EntityBean bean) { + set.add((E) bean); + } + + @SuppressWarnings("unchecked") + public void internalAdd(Object bean) { + if (set == null) { + set = new LinkedHashSet(); + } + set.add((E) bean); + } + + /** + * Returns true if the underlying set has its data. + */ + public boolean isPopulated() { + return set != null; + } + + /** + * Return true if this is a reference (lazy loading) bean collection. This is + * the same as !isPopulated(); + */ + public boolean isReference() { + return set == null; + } + + public boolean checkEmptyLazyLoad() { + if (set == null) { + set = new LinkedHashSet(); + return true; + } else { + return false; + } + } + + private void initClear() { + synchronized (this) { + if (set == null) { + if (modifyListening) { + lazyLoadCollection(true); + } else { + set = new LinkedHashSet(); + } + } + touched(true); + } + } + + private void initAsUntouched() { + init(false); + } + + private void init() { + init(true); + } + + private void init(boolean setTouched) { + synchronized (this) { + if (set == null) { + lazyLoadCollection(true); + } + touched(setTouched); + } + } + + /** + * Set the underlying set (used for lazy fetch). + */ + @SuppressWarnings("unchecked") + public void setActualSet(Set set) { + this.set = (Set) set; + } + + /** + * Return the actual underlying set. + */ + public Set getActualSet() { + return set; + } + + public Collection getActualDetails() { + return set; + } + + @Override + public Collection getActualEntries() { + return set; + } + + /** + * Returns the underlying set. + */ + public Object getActualCollection() { + return set; + } + + public String toString() { + StringBuffer sb = new StringBuffer(50); + sb.append("BeanSet "); + if (isReadOnly()) { + sb.append("readOnly "); + } + if (set == null) { + sb.append("deferred "); + + } else { + sb.append("size[").append(set.size()).append("]"); + sb.append(" set").append(set); + } + return sb.toString(); + } + + /** + * Equal if obj is a Set and equal in a Set sense. + */ + public boolean equals(Object obj) { + init(); + return set.equals(obj); + } + + public int hashCode() { + init(); + return set.hashCode(); + } + + // -----------------------------------------------------// + // proxy method for map + // -----------------------------------------------------// + + public boolean add(E o) { + checkReadOnly(); + init(); + if (modifyAddListening) { + if (set.add(o)) { + modifyAddition(o); + return true; + } else { + return false; + } + } + return set.add(o); + } + + public boolean addAll(Collection c) { + checkReadOnly(); + init(); + if (modifyAddListening) { + boolean changed = false; + Iterator it = c.iterator(); + while (it.hasNext()) { + E o = it.next(); + if (set.add(o)) { + modifyAddition(o); + changed = true; + } + } + return changed; + } + return set.addAll(c); + } + + public void clear() { + checkReadOnly(); + initClear(); + if (modifyRemoveListening) { + Iterator it = set.iterator(); + while (it.hasNext()) { + E e = it.next(); + modifyRemoval(e); + } + } + set.clear(); + } + + public boolean contains(Object o) { + init(); + return set.contains(o); + } + + public boolean containsAll(Collection c) { + init(); + return set.containsAll(c); + } + + public boolean isEmpty() { + initAsUntouched(); + return set.isEmpty(); + } + + public Iterator iterator() { + init(); + if (isReadOnly()) { + return new ReadOnlyIterator(set.iterator()); + } + if (modifyListening) { + return new ModifyIterator(this, set.iterator()); + } + return set.iterator(); + } + + public boolean remove(Object o) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + if (set.remove(o)) { + modifyRemoval(o); + return true; + } + return false; + } + return set.remove(o); + } + + public boolean removeAll(Collection c) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + boolean changed = false; + Iterator it = c.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (set.remove(o)) { + modifyRemoval(o); + changed = true; + } + } + return changed; + } + return set.removeAll(c); + } + + public boolean retainAll(Collection c) { + checkReadOnly(); + init(); + if (modifyRemoveListening) { + boolean changed = false; + Iterator it = set.iterator(); + while (it.hasNext()) { + Object o = it.next(); + if (!c.contains(o)) { + it.remove(); + modifyRemoval(o); + changed = true; + } + } + return changed; + } + return set.retainAll(c); + } + + public int size() { + init(); + return set.size(); + } + + public Object[] toArray() { + init(); + return set.toArray(); + } + + public T[] toArray(T[] a) { + init(); + return set.toArray(a); + } + + private static class ReadOnlyIterator implements Iterator, Serializable { + + private static final long serialVersionUID = 2577697326745352605L; + + private final Iterator it; + + ReadOnlyIterator(Iterator it) { + this.it = it; + } + + public boolean hasNext() { + return it.hasNext(); + } + + public E next() { + return it.next(); + } + + public void remove() { + throw new IllegalStateException("This collection is in ReadOnly mode"); + } + } + +} diff --git a/src/main/java/com/avaje/ebean/common/ModifyCollection.java b/src/main/java/com/avaje/ebean/common/ModifyCollection.java index 22b523256..0de249bef 100644 --- a/src/main/java/com/avaje/ebean/common/ModifyCollection.java +++ b/src/main/java/com/avaje/ebean/common/ModifyCollection.java @@ -1,122 +1,122 @@ -package com.avaje.ebean.common; - -import java.util.Collection; -import java.util.Iterator; - -import com.avaje.ebean.bean.BeanCollection; - -/** - * Wraps a collection for the purposes of notifying removals and additions to - * the BeanCollection owner. - *

    - * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *

    - */ -class ModifyCollection implements Collection { - - protected final BeanCollection owner; - - protected final Collection c; - - /** - * Create with an Owner and the underlying collection this wraps. - *

    - * The owner is notified of the additions and removals. - *

    - */ - public ModifyCollection(BeanCollection owner, Collection c) { - this.owner = owner; - this.c = c; - } - - public boolean add(E o) { - if (c.add(o)) { - owner.modifyAddition(o); - return true; - } - return false; - } - - public boolean addAll(Collection collection) { - boolean changed = false; - Iterator it = collection.iterator(); - while (it.hasNext()) { - E o = it.next(); - if (c.add(o)) { - owner.modifyAddition(o); - changed = true; - } - } - return changed; - } - - public void clear() { - c.clear(); - } - - public boolean contains(Object o) { - return c.contains(o); - } - - public boolean containsAll(Collection collection) { - return c.containsAll(collection); - } - - public boolean isEmpty() { - return c.isEmpty(); - } - - public Iterator iterator() { - Iterator it = c.iterator(); - return new ModifyIterator(owner, it); - } - - public boolean remove(Object o) { - if (c.remove(o)) { - owner.modifyRemoval(o); - return true; - } - return false; - } - - public boolean removeAll(Collection collection) { - boolean changed = false; - Iterator it = collection.iterator(); - while (it.hasNext()) { - Object o = (Object) it.next(); - if (c.remove(o)) { - owner.modifyRemoval(o); - changed = true; - } - } - return changed; - } - - public boolean retainAll(Collection collection) { - boolean changed = false; - Iterator it = c.iterator(); - while (it.hasNext()) { - Object o = (Object) it.next(); - if (!collection.contains(o)) { - it.remove(); - owner.modifyRemoval(o); - changed = true; - } - } - return changed; - } - - public int size() { - return c.size(); - } - - public Object[] toArray() { - return c.toArray(); - } - - public T[] toArray(T[] a) { - return c.toArray(a); - } - -} +package com.avaje.ebean.common; + +import java.util.Collection; +import java.util.Iterator; + +import com.avaje.ebean.bean.BeanCollection; + +/** + * Wraps a collection for the purposes of notifying removals and additions to + * the BeanCollection owner. + *

    + * This is required for persisting ManyToMany objects. Additions and removals + * become inserts and deletes to the intersection table. + *

    + */ +class ModifyCollection implements Collection { + + protected final BeanCollection owner; + + protected final Collection c; + + /** + * Create with an Owner and the underlying collection this wraps. + *

    + * The owner is notified of the additions and removals. + *

    + */ + public ModifyCollection(BeanCollection owner, Collection c) { + this.owner = owner; + this.c = c; + } + + public boolean add(E o) { + if (c.add(o)) { + owner.modifyAddition(o); + return true; + } + return false; + } + + public boolean addAll(Collection collection) { + boolean changed = false; + Iterator it = collection.iterator(); + while (it.hasNext()) { + E o = it.next(); + if (c.add(o)) { + owner.modifyAddition(o); + changed = true; + } + } + return changed; + } + + public void clear() { + c.clear(); + } + + public boolean contains(Object o) { + return c.contains(o); + } + + public boolean containsAll(Collection collection) { + return c.containsAll(collection); + } + + public boolean isEmpty() { + return c.isEmpty(); + } + + public Iterator iterator() { + Iterator it = c.iterator(); + return new ModifyIterator(owner, it); + } + + public boolean remove(Object o) { + if (c.remove(o)) { + owner.modifyRemoval(o); + return true; + } + return false; + } + + public boolean removeAll(Collection collection) { + boolean changed = false; + Iterator it = collection.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (c.remove(o)) { + owner.modifyRemoval(o); + changed = true; + } + } + return changed; + } + + public boolean retainAll(Collection collection) { + boolean changed = false; + Iterator it = c.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (!collection.contains(o)) { + it.remove(); + owner.modifyRemoval(o); + changed = true; + } + } + return changed; + } + + public int size() { + return c.size(); + } + + public Object[] toArray() { + return c.toArray(); + } + + public T[] toArray(T[] a) { + return c.toArray(a); + } + +} diff --git a/src/main/java/com/avaje/ebean/common/ModifyHolder.java b/src/main/java/com/avaje/ebean/common/ModifyHolder.java index 2db4dfe0f..8c72aa850 100644 --- a/src/main/java/com/avaje/ebean/common/ModifyHolder.java +++ b/src/main/java/com/avaje/ebean/common/ModifyHolder.java @@ -1,74 +1,74 @@ -package com.avaje.ebean.common; - -import java.io.Serializable; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Holds sets of additions and deletions from a 'owner' List Set or Map. - *

    - * These sets of additions and deletions are used to support persisting - * ManyToMany relationships. The additions becoming inserts into the - * intersection table and the removals becoming deletes from the intersection - * table. - *

    - */ -class ModifyHolder implements Serializable { - - private static final long serialVersionUID = 2572572897923801083L; - - /** - * Deletions list for manyToMany persistence. - */ - private Set modifyDeletions = new LinkedHashSet(); - - /** - * Additions list for manyToMany persistence. - */ - private Set modifyAdditions = new LinkedHashSet(); - - void reset() { - modifyDeletions = new LinkedHashSet(); - modifyAdditions = new LinkedHashSet(); - } - - /** - * Used by BeanList.addAll() methods. - */ - void modifyAdditionAll(Collection c) { - if (c != null) { - for (E e : c) { - modifyAddition(e); - } - } - } - - void modifyAddition(E bean) { - if (bean != null) { - // If it is to delete then just remove the deletion - if (!modifyDeletions.remove(bean)) { - // Insert - modifyAdditions.add(bean); - } - } - } - - @SuppressWarnings("unchecked") - void modifyRemoval(Object bean) { - if (bean != null) { - // If it is to be added then just remove the addition - if (!modifyAdditions.remove((E) bean)) { - modifyDeletions.add((E) bean); - } - } - } - - Set getModifyAdditions() { - return modifyAdditions; - } - - Set getModifyRemovals() { - return modifyDeletions; - } -} +package com.avaje.ebean.common; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Holds sets of additions and deletions from a 'owner' List Set or Map. + *

    + * These sets of additions and deletions are used to support persisting + * ManyToMany relationships. The additions becoming inserts into the + * intersection table and the removals becoming deletes from the intersection + * table. + *

    + */ +class ModifyHolder implements Serializable { + + private static final long serialVersionUID = 2572572897923801083L; + + /** + * Deletions list for manyToMany persistence. + */ + private Set modifyDeletions = new LinkedHashSet(); + + /** + * Additions list for manyToMany persistence. + */ + private Set modifyAdditions = new LinkedHashSet(); + + void reset() { + modifyDeletions = new LinkedHashSet(); + modifyAdditions = new LinkedHashSet(); + } + + /** + * Used by BeanList.addAll() methods. + */ + void modifyAdditionAll(Collection c) { + if (c != null) { + for (E e : c) { + modifyAddition(e); + } + } + } + + void modifyAddition(E bean) { + if (bean != null) { + // If it is to delete then just remove the deletion + if (!modifyDeletions.remove(bean)) { + // Insert + modifyAdditions.add(bean); + } + } + } + + @SuppressWarnings("unchecked") + void modifyRemoval(Object bean) { + if (bean != null) { + // If it is to be added then just remove the addition + if (!modifyAdditions.remove((E) bean)) { + modifyDeletions.add((E) bean); + } + } + } + + Set getModifyAdditions() { + return modifyAdditions; + } + + Set getModifyRemovals() { + return modifyDeletions; + } +} diff --git a/src/main/java/com/avaje/ebean/common/ModifyIterator.java b/src/main/java/com/avaje/ebean/common/ModifyIterator.java index d17c1eb14..30f6a2e85 100644 --- a/src/main/java/com/avaje/ebean/common/ModifyIterator.java +++ b/src/main/java/com/avaje/ebean/common/ModifyIterator.java @@ -1,48 +1,48 @@ -package com.avaje.ebean.common; - -import java.util.Iterator; - -import com.avaje.ebean.bean.BeanCollection; - -/** - * Wraps an iterator for the purposes of notifying removals and additions to the - * BeanCollection owner. - *

    - * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *

    - */ -class ModifyIterator implements Iterator { - - private final BeanCollection owner; - - private final Iterator it; - - private E last; - - /** - * Create with an Owner and the underlying Iterator this wraps. - *

    - * The owner is notified of the removals. - *

    - */ - ModifyIterator(BeanCollection owner, Iterator it) { - this.owner = owner; - this.it = it; - } - - public boolean hasNext() { - return it.hasNext(); - } - - public E next() { - last = it.next(); - return last; - } - - public void remove() { - owner.modifyRemoval(last); - it.remove(); - } - -} +package com.avaje.ebean.common; + +import java.util.Iterator; + +import com.avaje.ebean.bean.BeanCollection; + +/** + * Wraps an iterator for the purposes of notifying removals and additions to the + * BeanCollection owner. + *

    + * This is required for persisting ManyToMany objects. Additions and removals + * become inserts and deletes to the intersection table. + *

    + */ +class ModifyIterator implements Iterator { + + private final BeanCollection owner; + + private final Iterator it; + + private E last; + + /** + * Create with an Owner and the underlying Iterator this wraps. + *

    + * The owner is notified of the removals. + *

    + */ + ModifyIterator(BeanCollection owner, Iterator it) { + this.owner = owner; + this.it = it; + } + + public boolean hasNext() { + return it.hasNext(); + } + + public E next() { + last = it.next(); + return last; + } + + public void remove() { + owner.modifyRemoval(last); + it.remove(); + } + +} diff --git a/src/main/java/com/avaje/ebean/common/ModifyList.java b/src/main/java/com/avaje/ebean/common/ModifyList.java index 0944db16c..96f2e4bb2 100644 --- a/src/main/java/com/avaje/ebean/common/ModifyList.java +++ b/src/main/java/com/avaje/ebean/common/ModifyList.java @@ -1,91 +1,91 @@ -package com.avaje.ebean.common; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -import com.avaje.ebean.bean.BeanCollection; - -/** - * Wraps a List for the purposes of notifying removals and additions to the - * BeanCollection owner. - *

    - * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *

    - *

    - * Note that this is created by a call to subList() on a BeanList. Thats its - * only purpose really. BeanList holds the actual List. - *

    - */ -class ModifyList extends ModifyCollection implements List { - - /** - * The underlying list. - */ - private final List list; - - /** - * Create with an Owner that is notified of any additions or deletions. - */ - ModifyList(BeanCollection owner, List list) { - super(owner, list); - this.list = list; - } - - public void add(int index, E element) { - list.add(index, element); - owner.modifyAddition(element); - } - - public boolean addAll(int index, Collection co) { - if (list.addAll(index, co)) { - Iterator it = co.iterator(); - while (it.hasNext()) { - E o = it.next(); - owner.modifyAddition(o); - } - return true; - } - return false; - } - - public E get(int index) { - return list.get(index); - } - - public int indexOf(Object o) { - return list.indexOf(o); - } - - public int lastIndexOf(Object o) { - return list.lastIndexOf(o); - } - - public ListIterator listIterator() { - return new ModifyListIterator(owner, list.listIterator()); - } - - public ListIterator listIterator(int index) { - return new ModifyListIterator(owner, list.listIterator(index)); - } - - public E remove(int index) { - E o = list.remove(index); - owner.modifyRemoval(o); - return o; - } - - public E set(int index, E element) { - E o = list.set(index, element); - owner.modifyAddition(element); - owner.modifyRemoval(o); - return o; - } - - public List subList(int fromIndex, int toIndex) { - return new ModifyList(owner, list.subList(fromIndex, toIndex)); - } - -} +package com.avaje.ebean.common; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import com.avaje.ebean.bean.BeanCollection; + +/** + * Wraps a List for the purposes of notifying removals and additions to the + * BeanCollection owner. + *

    + * This is required for persisting ManyToMany objects. Additions and removals + * become inserts and deletes to the intersection table. + *

    + *

    + * Note that this is created by a call to subList() on a BeanList. Thats its + * only purpose really. BeanList holds the actual List. + *

    + */ +class ModifyList extends ModifyCollection implements List { + + /** + * The underlying list. + */ + private final List list; + + /** + * Create with an Owner that is notified of any additions or deletions. + */ + ModifyList(BeanCollection owner, List list) { + super(owner, list); + this.list = list; + } + + public void add(int index, E element) { + list.add(index, element); + owner.modifyAddition(element); + } + + public boolean addAll(int index, Collection co) { + if (list.addAll(index, co)) { + Iterator it = co.iterator(); + while (it.hasNext()) { + E o = it.next(); + owner.modifyAddition(o); + } + return true; + } + return false; + } + + public E get(int index) { + return list.get(index); + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + return new ModifyListIterator(owner, list.listIterator()); + } + + public ListIterator listIterator(int index) { + return new ModifyListIterator(owner, list.listIterator(index)); + } + + public E remove(int index) { + E o = list.remove(index); + owner.modifyRemoval(o); + return o; + } + + public E set(int index, E element) { + E o = list.set(index, element); + owner.modifyAddition(element); + owner.modifyRemoval(o); + return o; + } + + public List subList(int fromIndex, int toIndex) { + return new ModifyList(owner, list.subList(fromIndex, toIndex)); + } + +} diff --git a/src/main/java/com/avaje/ebean/common/ModifyListIterator.java b/src/main/java/com/avaje/ebean/common/ModifyListIterator.java index 0b649b774..a88a5ccdd 100644 --- a/src/main/java/com/avaje/ebean/common/ModifyListIterator.java +++ b/src/main/java/com/avaje/ebean/common/ModifyListIterator.java @@ -1,79 +1,79 @@ -package com.avaje.ebean.common; - -import java.util.ListIterator; - -import com.avaje.ebean.bean.BeanCollection; - -/** - * Wraps a ListIterator for the purposes of notifying removals and additions to - * the BeanCollection owner. - *

    - * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *

    - */ -class ModifyListIterator implements ListIterator { - - private final BeanCollection owner; - - private final ListIterator it; - - private E last; - - /** - * Create with an Owner that is notified of any additions or deletions. - */ - ModifyListIterator(BeanCollection owner, ListIterator it) { - this.owner = owner; - this.it = it; - } - - public void add(E bean) { - owner.modifyAddition(bean); - last = null; - it.add(bean); - } - - public boolean hasNext() { - return it.hasNext(); - } - - public boolean hasPrevious() { - return it.hasPrevious(); - } - - public E next() { - last = it.next(); - return last; - } - - public int nextIndex() { - return it.nextIndex(); - } - - public E previous() { - last = it.previous(); - return last; - } - - public int previousIndex() { - return it.previousIndex(); - } - - public void remove() { - owner.modifyRemoval(last); - last = null; - it.remove(); - } - - public void set(E o) { - if (last == null) { - // in theory this is not allowed - } else { - owner.modifyRemoval(last); - owner.modifyAddition(o); - } - it.set(o); - } - -} +package com.avaje.ebean.common; + +import java.util.ListIterator; + +import com.avaje.ebean.bean.BeanCollection; + +/** + * Wraps a ListIterator for the purposes of notifying removals and additions to + * the BeanCollection owner. + *

    + * This is required for persisting ManyToMany objects. Additions and removals + * become inserts and deletes to the intersection table. + *

    + */ +class ModifyListIterator implements ListIterator { + + private final BeanCollection owner; + + private final ListIterator it; + + private E last; + + /** + * Create with an Owner that is notified of any additions or deletions. + */ + ModifyListIterator(BeanCollection owner, ListIterator it) { + this.owner = owner; + this.it = it; + } + + public void add(E bean) { + owner.modifyAddition(bean); + last = null; + it.add(bean); + } + + public boolean hasNext() { + return it.hasNext(); + } + + public boolean hasPrevious() { + return it.hasPrevious(); + } + + public E next() { + last = it.next(); + return last; + } + + public int nextIndex() { + return it.nextIndex(); + } + + public E previous() { + last = it.previous(); + return last; + } + + public int previousIndex() { + return it.previousIndex(); + } + + public void remove() { + owner.modifyRemoval(last); + last = null; + it.remove(); + } + + public void set(E o) { + if (last == null) { + // in theory this is not allowed + } else { + owner.modifyRemoval(last); + owner.modifyAddition(o); + } + it.set(o); + } + +} diff --git a/src/main/java/com/avaje/ebean/common/ModifySet.java b/src/main/java/com/avaje/ebean/common/ModifySet.java index 70eaa6598..6d9ebc2dc 100644 --- a/src/main/java/com/avaje/ebean/common/ModifySet.java +++ b/src/main/java/com/avaje/ebean/common/ModifySet.java @@ -1,24 +1,24 @@ -package com.avaje.ebean.common; - -import java.util.Set; - -import com.avaje.ebean.bean.BeanCollection; - -/** - * Wraps a Set for the purposes of notifying removals and additions to the - * BeanCollection owner. - *

    - * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *

    - */ -class ModifySet extends ModifyCollection implements Set { - - /** - * Create with an Owner that is notified of any additions or deletions. - */ - public ModifySet(BeanCollection owner, Set s) { - super(owner, s); - } - -} +package com.avaje.ebean.common; + +import java.util.Set; + +import com.avaje.ebean.bean.BeanCollection; + +/** + * Wraps a Set for the purposes of notifying removals and additions to the + * BeanCollection owner. + *

    + * This is required for persisting ManyToMany objects. Additions and removals + * become inserts and deletes to the intersection table. + *

    + */ +class ModifySet extends ModifyCollection implements Set { + + /** + * Create with an Owner that is notified of any additions or deletions. + */ + public ModifySet(BeanCollection owner, Set s) { + super(owner, s); + } + +}