mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11e02710d9 | ||
|
|
658ce3c5d3 | ||
|
|
2de78cd20f | ||
|
|
1b8b05790e | ||
|
|
444b848c22 | ||
|
|
707dda636d | ||
|
|
e8a979bc9d | ||
|
|
3fb9a7414c | ||
|
|
061652a48e | ||
|
|
57b03cf86d | ||
|
|
45dbe3bb9a | ||
|
|
24037eeb73 | ||
|
|
2c6f637dab | ||
|
|
a85f9c4be8 | ||
|
|
5d9f11a362 | ||
|
|
3d484c79a2 | ||
|
|
5881047606 | ||
|
|
333eaf08c2 | ||
|
|
a1ff83e9e4 | ||
|
|
640db2267f | ||
|
|
4059034891 | ||
|
|
c58db0033d | ||
|
|
da90eccc12 | ||
|
|
524be4ce55 | ||
|
|
649c14644e | ||
|
|
c504256579 | ||
|
|
97d803e3e0 | ||
|
|
46bc09a09b | ||
|
|
fe387cf456 | ||
|
|
b4cd66e28c | ||
|
|
7c11acaf89 | ||
|
|
7b4d6f2d17 | ||
|
|
8f100f2786 | ||
|
|
6233392548 | ||
|
|
abaa1ff934 | ||
|
|
a7b239a1b7 | ||
|
|
e8c0756ee9 | ||
|
|
e5179432a0 | ||
|
|
d25123eb23 | ||
|
|
27602c15b9 | ||
|
|
bdc81bf6f4 | ||
|
|
84fa373728 | ||
|
|
12e6c0b139 | ||
|
|
6f702098bc | ||
|
|
ad3292b92d |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>7.5.1</version>
|
||||
<version>7.7.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
@@ -64,7 +64,7 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<version>1.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- bring in cluster support by default -->
|
||||
@@ -74,6 +74,18 @@
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner-api</artifactId>
|
||||
<version>2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
|
||||
/**
|
||||
* This Ebean object is effectively a singleton that holds a map of registered
|
||||
* {@link EbeanServer}s. It additionally provides a convenient way to use the
|
||||
@@ -1484,29 +1481,6 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().getBackgroundExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the cache warming queries on all bean types that have one defined for
|
||||
* the default/primary EbeanServer.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
public static void runCacheWarming() {
|
||||
serverMgr.getDefaultServer().runCacheWarming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the cache warming query for a specific bean type for the
|
||||
* default/primary EbeanServer.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
public static void runCacheWarming(Class<?> beanType) {
|
||||
|
||||
serverMgr.getDefaultServer().runCacheWarming(beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JsonContext for reading/writing JSON.
|
||||
*/
|
||||
|
||||
@@ -146,6 +146,17 @@ public interface EbeanServer {
|
||||
*/
|
||||
Object getBeanId(Object bean);
|
||||
|
||||
/**
|
||||
* Set the Id value onto the bean converting the type of the id value if necessary.
|
||||
* <p>
|
||||
* For example, if the id value passed in is a String but ought to be a Long or UUID etc
|
||||
* then it will automatically be converted.
|
||||
* </p>
|
||||
* @param bean The entity bean to set the id value on.
|
||||
* @param id The id value to set.
|
||||
*/
|
||||
Object setBeanId(Object bean, Object id);
|
||||
|
||||
/**
|
||||
* Return a map of the differences between two objects of the same type.
|
||||
* <p>
|
||||
@@ -1835,22 +1846,6 @@ public interface EbeanServer {
|
||||
*/
|
||||
BackgroundExecutor getBackgroundExecutor();
|
||||
|
||||
/**
|
||||
* Run the cache warming queries on all bean types that have one defined.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
void runCacheWarming();
|
||||
|
||||
/**
|
||||
* Run the cache warming query for a specific bean type.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
void runCacheWarming(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Return the JsonContext for reading/writing JSON.
|
||||
* <p>
|
||||
|
||||
@@ -209,6 +209,20 @@ public class Expr {
|
||||
return Ebean.getExpressionFactory().icontains(propertyName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* For collection properties that are empty (have not existing elements).
|
||||
*/
|
||||
public static Expression isEmpty(String propertyName) {
|
||||
return Ebean.getExpressionFactory().isEmpty(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* For collection properties that are not empty (have existing elements).
|
||||
*/
|
||||
public static Expression isNotEmpty(String propertyName) {
|
||||
return Ebean.getExpressionFactory().isNotEmpty(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*/
|
||||
|
||||
@@ -246,6 +246,16 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression notExists(Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Is empty expression for collection properties.
|
||||
*/
|
||||
Expression isEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Is not empty expression for collection properties.
|
||||
*/
|
||||
Expression isNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Id Equal to - ID property is equal to the value.
|
||||
*/
|
||||
|
||||
@@ -754,6 +754,16 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> notIn(String propertyName, Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Is empty expression for collection properties.
|
||||
*/
|
||||
ExpressionList<T> isEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Is not empty expression for collection properties.
|
||||
*/
|
||||
ExpressionList<T> isNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Exists expression
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
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 query caching on a specific entity type.
|
||||
* <p>
|
||||
* If this is not specified then the system default settings are used.
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CacheQueryTuning {
|
||||
|
||||
/**
|
||||
* The maximum size for the cache.
|
||||
* <p>
|
||||
* This defaults to 0 which means unlimited.
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* This defaults to 0 which means unlimited.
|
||||
* </p>
|
||||
*/
|
||||
int maxIdleSecs() default 0;
|
||||
|
||||
/**
|
||||
* The maximum time (in seconds) a cache entry is allowed to stay in the
|
||||
* cache.
|
||||
* <p>
|
||||
* This is not generally required as the cache entries are automatically
|
||||
* evicted when related data changes are committed.
|
||||
* </p>
|
||||
* <p>
|
||||
* This defaults to 0 which means unlimited.
|
||||
* </p>
|
||||
*/
|
||||
int maxSecsToLive() default 0;
|
||||
|
||||
/**
|
||||
* The frequency (in seconds) that cache trimming should occur.
|
||||
* <p>
|
||||
* This is a hint for cache implementations that use background cache trimming.
|
||||
* </p>
|
||||
*/
|
||||
int trimFrequency() default 0;
|
||||
}
|
||||
@@ -42,17 +42,4 @@ public @interface CacheStrategy {
|
||||
*/
|
||||
boolean readOnly() default false;
|
||||
|
||||
/**
|
||||
* Specify a query that can be used to warm the cache.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* The warming query will typically be executed at startup time after a short
|
||||
* delay (defaults to a 30 seconds delay).
|
||||
* </p>
|
||||
*/
|
||||
String warmingQuery() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ public interface ServerCacheFactory {
|
||||
/**
|
||||
* Create the cache for the given type with options.
|
||||
*/
|
||||
ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions);
|
||||
ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean.cache;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheQueryTuning;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
|
||||
/**
|
||||
@@ -30,13 +31,13 @@ public class ServerCacheOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create merging default options with the deployment specified ones.
|
||||
* Create from the cacheTuning deployment annotation.
|
||||
*/
|
||||
public ServerCacheOptions(ServerCacheOptions defaults) {
|
||||
this.maxSize = defaults.getMaxSize();
|
||||
this.maxIdleSecs = defaults.getMaxIdleSecs();
|
||||
this.maxSecsToLive = defaults.getMaxIdleSecs();
|
||||
this.trimFrequency = defaults.getTrimFrequency();
|
||||
public ServerCacheOptions(CacheQueryTuning cacheTuning) {
|
||||
this.maxSize = cacheTuning.maxSize();
|
||||
this.maxIdleSecs = cacheTuning.maxIdleSecs();
|
||||
this.maxSecsToLive = cacheTuning.maxSecsToLive();
|
||||
this.trimFrequency = cacheTuning.trimFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.avaje.ebean.cache;
|
||||
|
||||
/**
|
||||
* The type of L2 caches.
|
||||
*/
|
||||
public enum ServerCacheType {
|
||||
|
||||
/**
|
||||
* Bean cache.
|
||||
*/
|
||||
BEAN,
|
||||
|
||||
/**
|
||||
* Natural key cache.
|
||||
*/
|
||||
NATURAL_KEY,
|
||||
|
||||
/**
|
||||
* Collection Ids for Many properties.
|
||||
*/
|
||||
COLLECTION_IDS,
|
||||
|
||||
/**
|
||||
* Query cache.
|
||||
*/
|
||||
QUERY
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* Helper to find classes taking into account the context class loader.
|
||||
*/
|
||||
@@ -70,6 +74,13 @@ public class ClassLoadConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resources for the given name.
|
||||
*/
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
return context.getResources(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given class is present.
|
||||
*/
|
||||
@@ -122,6 +133,13 @@ public class ClassLoadConfig {
|
||||
return (loader != null) ? loader: callerLoader;
|
||||
}
|
||||
|
||||
Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (preferredLoader != null) {
|
||||
return preferredLoader.getResources(name);
|
||||
}
|
||||
return contextLoader().getResources(name);
|
||||
}
|
||||
|
||||
Class<?> forName(String name) throws ClassNotFoundException {
|
||||
|
||||
if (preferredLoader != null) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.avaje.ebean.dbmigration.DbMigration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Configuration for the DB migration processing.
|
||||
*/
|
||||
@@ -67,6 +69,36 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected String generatePendingDrop;
|
||||
|
||||
/**
|
||||
* For running migration the DB table that holds migration execution status.
|
||||
*/
|
||||
protected String metaTable = "db_migration";
|
||||
|
||||
/**
|
||||
* Flag set to true means to run any outstanding migrations on startup.
|
||||
*/
|
||||
protected boolean runMigration;
|
||||
|
||||
/**
|
||||
* Comma and equals delimited key/value placeholders to replace in DDL scripts.
|
||||
*/
|
||||
protected String runPlaceholders;
|
||||
|
||||
/**
|
||||
* Map of key/value placeholders to replace in DDL scripts.
|
||||
*/
|
||||
protected Map<String,String> runPlaceholderMap;
|
||||
|
||||
/**
|
||||
* DB user used to run the DB migration.
|
||||
*/
|
||||
protected String dbUser;
|
||||
|
||||
/**
|
||||
* DB password used to run the DB migration.
|
||||
*/
|
||||
protected String dbPassword;
|
||||
|
||||
/**
|
||||
* Return the DB platform to generate migration DDL for.
|
||||
*
|
||||
@@ -199,10 +231,119 @@ public class DbMigrationConfig {
|
||||
this.modelPath = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table name that holds the migration run details
|
||||
* (used by DB Migration runner only).
|
||||
*/
|
||||
public String getMetaTable() {
|
||||
return metaTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table name that holds the migration run details
|
||||
* (used by DB Migration runner only).
|
||||
*/
|
||||
public void setMetaTable(String metaTable) {
|
||||
this.metaTable = metaTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a comma and equals delimited placeholders that are substituted in SQL scripts when running migration
|
||||
* (used by DB Migration runner only).
|
||||
*/
|
||||
public String getRunPlaceholders() {
|
||||
// environment properties take precedence
|
||||
String placeholders = readEnvironment("ddl.migration.placeholders");
|
||||
if (placeholders != null) {
|
||||
return placeholders;
|
||||
}
|
||||
return runPlaceholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a comma and equals delimited placeholders that are substituted in SQL scripts when running migration
|
||||
* (used by DB Migration runner only).
|
||||
*/
|
||||
public void setRunPlaceholders(String runPlaceholders) {
|
||||
this.runPlaceholders = runPlaceholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of placeholder values that are substituted in SQL scripts when running migration
|
||||
* (used by DB Migration runner only).
|
||||
*/
|
||||
public Map<String, String> getRunPlaceholderMap() {
|
||||
return runPlaceholderMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a map of placeholder values that are substituted when running migration
|
||||
* (used by DB Migration runner only).
|
||||
*/
|
||||
public void setRunPlaceholderMap(Map<String, String> runPlaceholderMap) {
|
||||
this.runPlaceholderMap = runPlaceholderMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB migration should be run on startup.
|
||||
*/
|
||||
public boolean isRunMigration() {
|
||||
// environment properties take precedence
|
||||
String run = readEnvironment("ddl.migration.run");
|
||||
if (run != null) {
|
||||
return "true".equalsIgnoreCase(run.trim());
|
||||
}
|
||||
return runMigration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run the DB migration on startup.
|
||||
*/
|
||||
public void setRunMigration(boolean runMigration) {
|
||||
this.runMigration = runMigration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB user to use for running DB migrations.
|
||||
*/
|
||||
public String getDbUser() {
|
||||
// environment properties take precedence
|
||||
String user = readEnvironment("ddl.migration.user");
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
return dbUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB user to use for running DB migrations.
|
||||
*/
|
||||
public void setDbUser(String dbUser) {
|
||||
this.dbUser = dbUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB password to use for running DB migrations.
|
||||
*/
|
||||
public String getDbPassword() {
|
||||
String user = readEnvironment("ddl.migration.password");
|
||||
if (user != null) {
|
||||
return user;
|
||||
}
|
||||
return dbPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB password to use for running DB migrations.
|
||||
*/
|
||||
public void setDbPassword(String dbPassword) {
|
||||
this.dbPassword = dbPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the PropertiesWrapper.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper properties) {
|
||||
public void loadSettings(PropertiesWrapper properties, String serverName) {
|
||||
|
||||
migrationPath = properties.get("migration.migrationPath", migrationPath);
|
||||
if (properties.getBoolean("migration.singleDirectory", false)) {
|
||||
@@ -219,7 +360,19 @@ public class DbMigrationConfig {
|
||||
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
this.name = properties.get("migration.name", this.name);
|
||||
|
||||
runMigration = properties.getBoolean("migration.run", runMigration);
|
||||
metaTable = properties.get("migration.metaTable", metaTable);
|
||||
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
|
||||
|
||||
String adminUser = properties.get("datasource."+serverName+".user", dbUser);
|
||||
adminUser = properties.get("datasource."+serverName+".adminuser", adminUser);
|
||||
dbUser = properties.get("migration.dbuser", adminUser);
|
||||
|
||||
String adminPwd = properties.get("datasource."+serverName+".password", dbPassword);
|
||||
adminPwd = properties.get("datasource."+serverName+".adminpassword", adminPwd);
|
||||
dbPassword = properties.get("migration.dbpassword", adminPwd);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,8 +26,10 @@ import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
@@ -82,6 +84,12 @@ public class ServerConfig {
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Typically configuration type objects that are passed by this ServerConfig
|
||||
* to plugins. For example - IgniteConfiguration passed to Ignite plugin.
|
||||
*/
|
||||
private Map<String,Object> serviceObject = new HashMap<String, Object>();
|
||||
|
||||
private ContainerConfig containerConfig;
|
||||
|
||||
/**
|
||||
@@ -397,12 +405,34 @@ public class ServerConfig {
|
||||
|
||||
private String jodaLocalTimeMode;
|
||||
|
||||
/**
|
||||
* Set to true to globally disable L2 caching (typically for performance testing).
|
||||
*/
|
||||
private boolean disableL2Cache;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
public ServerConfig() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a service object into configuration such that it can be passed to a plugin.
|
||||
* <p>
|
||||
* For example, put IgniteConfiguration in to be passed to the Ignite plugin.
|
||||
* </p>
|
||||
*/
|
||||
public void putServiceObject(String key, Object configObject) {
|
||||
serviceObject.put(key, configObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service object given the key.
|
||||
*/
|
||||
public Object getServiceObject(String key) {
|
||||
return serviceObject.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Jackson JsonFactory to use.
|
||||
@@ -2267,7 +2297,7 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
migrationConfig.loadSettings(p);
|
||||
migrationConfig.loadSettings(p, name);
|
||||
|
||||
namingConvention = createNamingConvention(p, namingConvention);
|
||||
if (namingConvention != null) {
|
||||
@@ -2288,6 +2318,7 @@ public class ServerConfig {
|
||||
}
|
||||
loadDocStoreSettings(p);
|
||||
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
@@ -2476,6 +2507,20 @@ public class ServerConfig {
|
||||
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if L2 cache is disabled.
|
||||
*/
|
||||
public boolean isDisableL2Cache() {
|
||||
return disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to disable L2 caching. Typically useful in performance testing.
|
||||
*/
|
||||
public void setDisableL2Cache(boolean disableL2Cache) {
|
||||
this.disableL2Cache = disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -522,4 +526,28 @@ public class DatabasePlatform {
|
||||
return disallowBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table exists.
|
||||
*/
|
||||
public boolean tableExists(Connection connection, String catalog, String schema, String table) throws SQLException {
|
||||
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
ResultSet tables = metaData.getTables(catalog, schema, table, null);
|
||||
try {
|
||||
return tables.next();
|
||||
} finally {
|
||||
close(tables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the resultSet.
|
||||
*/
|
||||
protected void close(ResultSet resultSet) {
|
||||
try {
|
||||
resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing resultSet", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.avaje.ebean.dbmigration;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.model.CurrentModel;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.util.JdbcClose;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.File;
|
||||
@@ -13,6 +14,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.Reader;
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
|
||||
@@ -81,6 +83,21 @@ public class DdlGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
// get a connection without threadLocal
|
||||
Connection connection = server.createTransaction().getConnection();
|
||||
try {
|
||||
return runner.runAll(content, connection);
|
||||
} finally {
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runDropSql() throws IOException {
|
||||
if (!createOnly) {
|
||||
if (dropAllContent == null) {
|
||||
@@ -110,9 +127,8 @@ public class DdlGenerator {
|
||||
if (sqlScript != null) {
|
||||
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
|
||||
if (is != null) {
|
||||
DdlRunner runner = new DdlRunner(false, sqlScript);
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runner.runAll(content, server);
|
||||
runScript(false, content, sqlScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,13 +237,4 @@ public class DdlGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
return runner.runAll(content, server);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -37,29 +35,33 @@ public class DdlRunner {
|
||||
/**
|
||||
* Parse the content into sql statements and execute them in a transaction.
|
||||
*/
|
||||
public int runAll(String content, SpiEbeanServer server) {
|
||||
public int runAll(String content, Connection connection) {
|
||||
|
||||
List<String> statements = ddlParser.parse(new StringReader(content));
|
||||
return runStatements(statements, server);
|
||||
return runStatements(statements, connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the statements in a single transaction.
|
||||
*/
|
||||
public int runStatements(List<String> statements, SpiEbeanServer server) {
|
||||
public int runStatements(List<String> statements, Connection connection) {
|
||||
|
||||
Transaction t = server.createTransaction();
|
||||
try {
|
||||
int statementCount = runStatements(expectErrors, statements, t.getConnection());
|
||||
t.commit();
|
||||
|
||||
int statementCount = runStatements(expectErrors, statements, connection);
|
||||
connection.commit();
|
||||
return statementCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
rollback(connection);
|
||||
throw new PersistenceException("Error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
t.end();
|
||||
private void rollback(Connection connection) {
|
||||
try {
|
||||
connection.rollback();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error trying to rollback connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.config.DbMigrationConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.runner.LocalMigrationResource;
|
||||
import com.avaje.ebean.dbmigration.runner.LocalMigrationResources;
|
||||
import com.avaje.ebean.dbmigration.runner.MigrationTable;
|
||||
import com.avaje.ebeaninternal.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Runs the DB migration typically on application start.
|
||||
*/
|
||||
public class MigrationRunner {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MigrationRunner.class);
|
||||
|
||||
private final EbeanServer server;
|
||||
|
||||
private final ServerConfig config;
|
||||
|
||||
private final DbMigrationConfig migrationConfig;
|
||||
|
||||
public MigrationRunner(EbeanServer server, DbMigrationConfig migrationConfig) {
|
||||
this.server = server;
|
||||
this.config = server.getPluginApi().getServerConfig();
|
||||
this.migrationConfig = migrationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations if there are any that need running.
|
||||
*/
|
||||
public void run() {
|
||||
|
||||
LocalMigrationResources resources = new LocalMigrationResources(config, migrationConfig);
|
||||
if (!resources.readResources()) {
|
||||
logger.debug("no migrations to check");
|
||||
return;
|
||||
}
|
||||
|
||||
String migrationUser = migrationConfig.getDbUser();
|
||||
String migrationPwd = migrationConfig.getDbPassword();
|
||||
|
||||
DataSource dataSource = server.getPluginApi().getDataSource();
|
||||
|
||||
Connection connection;
|
||||
try {
|
||||
connection = dataSource.getConnection(migrationUser, migrationPwd);
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalArgumentException("Error trying to connect to database using DB Migration user [" + migrationUser + "]", e);
|
||||
}
|
||||
|
||||
try {
|
||||
connection.setAutoCommit(false);
|
||||
|
||||
runMigrations(resources, connection);
|
||||
|
||||
connection.commit();
|
||||
|
||||
} catch (Exception e) {
|
||||
JdbcClose.rollback(connection);
|
||||
throw new RuntimeException(e);
|
||||
|
||||
} finally {
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all the migrations as needed.
|
||||
*/
|
||||
private void runMigrations(LocalMigrationResources resources, Connection connection) throws SQLException, IOException {
|
||||
|
||||
MigrationTable table = new MigrationTable(server, migrationConfig, connection);
|
||||
table.createIfNeeded();
|
||||
|
||||
// get the migrations in version order
|
||||
List<LocalMigrationResource> localVersions = resources.getVersions();
|
||||
|
||||
LocalMigrationResource priorVersion = null;
|
||||
|
||||
// run migrations in order
|
||||
for (int i = 0; i < localVersions.size(); i++) {
|
||||
LocalMigrationResource localVersion = localVersions.get(i);
|
||||
if (!table.shouldRun(localVersion, priorVersion)) {
|
||||
break;
|
||||
}
|
||||
priorVersion = localVersion;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,13 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full version.
|
||||
*/
|
||||
public String getFull() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
/**
|
||||
* Calculates the checksum for the given string content.
|
||||
*/
|
||||
public class Checksum {
|
||||
|
||||
/**
|
||||
* Returns the checksum of this string.
|
||||
*/
|
||||
public static int calculate(String str) {
|
||||
|
||||
final CRC32 crc32 = new CRC32();
|
||||
|
||||
BufferedReader bufferedReader = new BufferedReader(new StringReader(str));
|
||||
try {
|
||||
String line;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
crc32.update(line.getBytes("UTF-8"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to calculate checksum", e);
|
||||
}
|
||||
|
||||
return (int) crc32.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import com.avaje.ebean.dbmigration.model.MigrationVersion;
|
||||
import org.avaje.classpath.scanner.Resource;
|
||||
|
||||
/**
|
||||
* A DB migration resource (DDL script with version).
|
||||
*/
|
||||
public class LocalMigrationResource implements Comparable<LocalMigrationResource> {
|
||||
|
||||
private final MigrationVersion version;
|
||||
|
||||
private final String location;
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
/**
|
||||
* Construct with version and resource.
|
||||
*/
|
||||
public LocalMigrationResource(MigrationVersion version, String location, Resource resource) {
|
||||
this.version = version;
|
||||
this.location = location;
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return version.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default ordering by version.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(LocalMigrationResource o) {
|
||||
return version.compareTo(o.version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying migration version.
|
||||
*/
|
||||
public MigrationVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource location.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content for the migration apply ddl script.
|
||||
*/
|
||||
public String getContent() {
|
||||
return resource.loadAsString("UTF-8");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import com.avaje.ebean.config.DbMigrationConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.model.MigrationVersion;
|
||||
import org.avaje.classpath.scanner.Resource;
|
||||
import org.avaje.classpath.scanner.ResourceFilter;
|
||||
import org.avaje.classpath.scanner.Scanner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Loads the DB migration resources and sorts them into execution order.
|
||||
*/
|
||||
public class LocalMigrationResources {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LocalMigrationResources.class);
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private final DbMigrationConfig migrationConfig;
|
||||
|
||||
private final List<LocalMigrationResource> versions = new ArrayList<LocalMigrationResource>();
|
||||
|
||||
/**
|
||||
* Construct with configuration options.
|
||||
*/
|
||||
public LocalMigrationResources(ServerConfig serverConfig, DbMigrationConfig migrationConfig) {
|
||||
this.serverConfig = serverConfig;
|
||||
this.migrationConfig = migrationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the migration resources (SQL scripts) returning true if there are versions.
|
||||
*/
|
||||
public boolean readResources() {
|
||||
|
||||
String migrationPath = migrationConfig.getMigrationPath();
|
||||
|
||||
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
|
||||
|
||||
Scanner scanner = new Scanner(classLoader);
|
||||
List<Resource> resourceList = scanner.scanForResources(migrationPath, new Match(migrationConfig));
|
||||
|
||||
logger.debug("resources: {}", resourceList);
|
||||
|
||||
for (Resource resource : resourceList) {
|
||||
String filename = resource.getFilename();
|
||||
if (filename.endsWith(migrationConfig.getApplySuffix())) {
|
||||
int pos = filename.lastIndexOf(migrationConfig.getApplySuffix());
|
||||
String mainName = filename.substring(0, pos);
|
||||
|
||||
MigrationVersion migrationVersion = MigrationVersion.parse(mainName);
|
||||
LocalMigrationResource res = new LocalMigrationResource(migrationVersion, resource.getLocation(), resource);
|
||||
versions.add(res);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(versions);
|
||||
return !versions.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of migration resources in version order.
|
||||
*/
|
||||
public List<LocalMigrationResource> getVersions() {
|
||||
return versions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter used to find the migration scripts.
|
||||
*/
|
||||
private static class Match implements ResourceFilter {
|
||||
|
||||
private final DbMigrationConfig migrationConfig;
|
||||
|
||||
Match(DbMigrationConfig migrationConfig) {
|
||||
this.migrationConfig = migrationConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatch(String name) {
|
||||
return name.endsWith(migrationConfig.getApplySuffix());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import com.avaje.ebean.SqlRow;
|
||||
import com.avaje.ebean.SqlUpdate;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/**
|
||||
* Bean holding migration execution details stored in the migration table.
|
||||
*/
|
||||
class MigrationMetaRow {
|
||||
|
||||
private int id;
|
||||
|
||||
private String status;
|
||||
|
||||
private String runVersion;
|
||||
|
||||
private String depVersion;
|
||||
|
||||
private String comment;
|
||||
|
||||
private int checksum;
|
||||
|
||||
private Timestamp runOn;
|
||||
|
||||
private String runBy;
|
||||
|
||||
/**
|
||||
* Construct for inserting into table.
|
||||
*/
|
||||
MigrationMetaRow(int id, String runVersion, String priorVersion, String comment, int checksum, String runBy) {
|
||||
this.id = id;
|
||||
this.runVersion = runVersion;
|
||||
this.depVersion = priorVersion;
|
||||
this.checksum = checksum;
|
||||
this.comment = comment;
|
||||
this.runBy = runBy;
|
||||
this.runOn = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from the SqlRow (read from table).
|
||||
*/
|
||||
MigrationMetaRow(SqlRow row) {
|
||||
id = row.getInteger("id");
|
||||
status = row.getString("status");
|
||||
runVersion = row.getString("row_version");
|
||||
depVersion = row.getString("dep_version");
|
||||
comment = row.getString("comment");
|
||||
checksum = row.getInteger("checksum");
|
||||
runOn = row.getTimestamp("run_on");
|
||||
runBy = row.getString("run_by");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id for this migration.
|
||||
*/
|
||||
int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the normalised version for this migration.
|
||||
*/
|
||||
String getRunVersion() {
|
||||
return runVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the checksum for this migration.
|
||||
*/
|
||||
int getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to the insert statement.
|
||||
*/
|
||||
void bindInsert(SqlUpdate insert) {
|
||||
insert.setParameter(1, id);
|
||||
insert.setParameter(2, "success");
|
||||
insert.setParameter(3, runVersion);
|
||||
insert.setParameter(4, depVersion);
|
||||
insert.setParameter(5, comment);
|
||||
insert.setParameter(6, checksum);
|
||||
insert.setParameter(7, runOn);
|
||||
insert.setParameter(8, runBy);
|
||||
insert.setParameter(9, "ip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL insert given the table migration meta data is stored in.
|
||||
*/
|
||||
static String insertSql(String table) {
|
||||
return "insert into " + table
|
||||
+ " (id, status, run_version, dep_version, comment, checksum, run_on, run_by, run_ip)"
|
||||
+ " values (?,?,?,?,?,?,?,?,?)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import com.avaje.ebean.dbmigration.DdlRunner;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Runs the DDL migration scripts.
|
||||
*/
|
||||
public class MigrationScriptRunner {
|
||||
|
||||
private final Connection connection;
|
||||
|
||||
/**
|
||||
* Construct with a given connection.
|
||||
*/
|
||||
public MigrationScriptRunner(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
return runner.runAll(content, connection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.SqlQuery;
|
||||
import com.avaje.ebean.SqlRow;
|
||||
import com.avaje.ebean.SqlUpdate;
|
||||
import com.avaje.ebean.config.DbMigrationConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.plugin.SpiServer;
|
||||
import com.avaje.ebeaninternal.server.transaction.ExternalJdbcTransaction;
|
||||
import com.avaje.ebeaninternal.util.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages the migration table.
|
||||
*/
|
||||
public class MigrationTable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MigrationTable.class);
|
||||
|
||||
private final Connection connection;
|
||||
|
||||
private final EbeanServer server;
|
||||
|
||||
private final DatabasePlatform databasePlatform;
|
||||
|
||||
private final String catalog;
|
||||
private final String schema;
|
||||
private final String table;
|
||||
private final ServerConfig serverConfig;
|
||||
private final String envUserName;
|
||||
|
||||
private final ScriptTransform scriptTransform;
|
||||
|
||||
private final String insertSql;
|
||||
|
||||
private final LinkedHashMap<String,MigrationMetaRow> migrations;
|
||||
|
||||
private MigrationMetaRow lastMigration;
|
||||
|
||||
/**
|
||||
* Construct with server, configuration and jdbc connection (DB admin user).
|
||||
*/
|
||||
public MigrationTable(EbeanServer server, DbMigrationConfig migrationConfig, Connection connection) {
|
||||
this.connection = connection;
|
||||
this.server = server;
|
||||
this.migrations = new LinkedHashMap<String, MigrationMetaRow>();
|
||||
|
||||
SpiServer pluginApi = server.getPluginApi();
|
||||
this.serverConfig = pluginApi.getServerConfig();
|
||||
this.databasePlatform = pluginApi.getDatabasePlatform();
|
||||
|
||||
this.catalog = null;
|
||||
this.schema = null;
|
||||
this.table = migrationConfig.getMetaTable();
|
||||
this.insertSql = MigrationMetaRow.insertSql(table);
|
||||
|
||||
this.scriptTransform = createScriptTransform(migrationConfig);
|
||||
this.envUserName = System.getProperty("user.name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ScriptTransform for placeholder key/value replacement.
|
||||
*/
|
||||
private ScriptTransform createScriptTransform(DbMigrationConfig config) {
|
||||
|
||||
Map<String, String> map = PlaceholderBuilder.build(config.getRunPlaceholders(), config.getRunPlaceholderMap());
|
||||
return new ScriptTransform(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the table is it does not exist.
|
||||
*/
|
||||
public void createIfNeeded() throws SQLException, IOException {
|
||||
|
||||
if (!tableExists(connection)) {
|
||||
createTable(connection);
|
||||
}
|
||||
|
||||
ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
|
||||
SqlQuery sqlQuery = server.createSqlQuery("select * from "+table+" order by id for update");
|
||||
List<SqlRow> metaRows = server.findList(sqlQuery, t);
|
||||
|
||||
for (SqlRow row : metaRows) {
|
||||
addMigration(new MigrationMetaRow(row));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void createTable(Connection connection) throws IOException {
|
||||
|
||||
String script = ScriptTransform.table(table, getCreateTableScript());
|
||||
|
||||
MigrationScriptRunner run = new MigrationScriptRunner(connection);
|
||||
run.runScript(false, script, "create migration table");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the create table script.
|
||||
*/
|
||||
private String getCreateTableScript() throws IOException {
|
||||
// supply a script to override the default table create script
|
||||
String script = readResource("migration-support/create-table.sql");
|
||||
if (script == null) {
|
||||
// no, just use the default script
|
||||
script = readResource("migration-support/default-create-table.sql");
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
private String readResource(String location) throws IOException {
|
||||
|
||||
Enumeration<URL> resources = serverConfig.getClassLoadConfig().getResources(location);
|
||||
if (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
return IOUtils.readUtf8(url.openStream());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table exists.
|
||||
*/
|
||||
private boolean tableExists(Connection connection) throws SQLException {
|
||||
return databasePlatform.tableExists(connection, catalog, schema, table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the migration ran successfully and false if the migration failed.
|
||||
*/
|
||||
public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) {
|
||||
|
||||
if (priorVersion != null) {
|
||||
// check priorVersion is installed
|
||||
MigrationMetaRow existing = migrations.get(priorVersion.getVersion().normalised());
|
||||
if (existing == null) {
|
||||
logger.warn("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MigrationMetaRow existing = migrations.get(localVersion.getVersion().normalised());
|
||||
if (existing == null) {
|
||||
runMigration(localVersion, priorVersion);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// check checksum and return ok, or re-run if repeatable script?
|
||||
existing.getChecksum();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migration script.
|
||||
*/
|
||||
private void runMigration(LocalMigrationResource localVersion, LocalMigrationResource prior) {
|
||||
|
||||
logger.debug("run migration "+localVersion.getLocation());
|
||||
|
||||
String script = convertScript(localVersion.getContent());
|
||||
|
||||
MigrationScriptRunner run = new MigrationScriptRunner(connection);
|
||||
run.runScript(false, script, "run migration version: "+localVersion.getVersion());
|
||||
|
||||
|
||||
int checksum = Checksum.calculate(script);
|
||||
MigrationMetaRow metaRow = createMetaRow(localVersion, prior, checksum);
|
||||
|
||||
SqlUpdate insert = server.createSqlUpdate(insertSql);
|
||||
metaRow.bindInsert(insert);
|
||||
server.execute(insert, new ExternalJdbcTransaction(connection));
|
||||
|
||||
addMigration(metaRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the MigrationMetaRow for this migration.
|
||||
*/
|
||||
private MigrationMetaRow createMetaRow(LocalMigrationResource localVersion, LocalMigrationResource prior, int checksum) {
|
||||
|
||||
int nextId = 1;
|
||||
if (lastMigration != null) {
|
||||
nextId = lastMigration.getId() + 1;
|
||||
}
|
||||
|
||||
String runVersion = localVersion.getVersion().normalised();
|
||||
String comment = getMigrationComment(localVersion);
|
||||
String priorVersion = getMigrationPriorVersion(prior);
|
||||
|
||||
return new MigrationMetaRow(nextId, runVersion, priorVersion, comment, checksum, envUserName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prior migration normalised version.
|
||||
*/
|
||||
private String getMigrationPriorVersion(LocalMigrationResource prior) {
|
||||
return prior != null ? prior.getVersion().normalised() : "-";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration comment.
|
||||
*/
|
||||
private String getMigrationComment(LocalMigrationResource localVersion) {
|
||||
String comment = localVersion.getVersion().getComment();
|
||||
return comment == null || comment.isEmpty() ? "-" : comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the placeholder key/value replacement on the script.
|
||||
*/
|
||||
private String convertScript(String script) {
|
||||
return scriptTransform.transform(script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the successfully executed migration (to allow dependant scripts to run).
|
||||
*/
|
||||
private void addMigration(MigrationMetaRow metaRow) {
|
||||
lastMigration = metaRow;
|
||||
migrations.put(metaRow.getRunVersion(), metaRow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Joins placeholder map and comma/equals delimited string.
|
||||
*/
|
||||
class PlaceholderBuilder {
|
||||
|
||||
private final Map<String,String> map = new HashMap<String,String>();
|
||||
|
||||
/**
|
||||
* Create with raw comma and equals delimited pairs plus map of key value pairs.
|
||||
*/
|
||||
public static Map<String,String> build(String commaDelimited, Map<String,String> placeholders) {
|
||||
|
||||
PlaceholderBuilder builder = new PlaceholderBuilder();
|
||||
builder.add(commaDelimited);
|
||||
builder.add(placeholders);
|
||||
|
||||
return builder.map;
|
||||
}
|
||||
|
||||
private PlaceholderBuilder() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comma and equals delimited string to parse for key value pairs.
|
||||
*/
|
||||
public void add(String commaDelimited) {
|
||||
|
||||
if (commaDelimited != null) {
|
||||
String[] split = commaDelimited.split("[,;]");
|
||||
for (String keyValue : split) {
|
||||
String[] pair = keyValue.split("=");
|
||||
if (pair.length == 2) {
|
||||
map.put(pair[0].trim(), pair[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a map of key value placeholder pairs.
|
||||
*/
|
||||
public void add(Map<String,String> placeholders) {
|
||||
if (placeholders != null) {
|
||||
map.putAll(placeholders);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.avaje.ebean.dbmigration.runner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Transforms a SQL script given a map of key/value substitutions.
|
||||
*/
|
||||
class ScriptTransform {
|
||||
|
||||
/**
|
||||
* Transform just ${table} with the table name.
|
||||
*/
|
||||
public static String table(String tableName, String script) {
|
||||
return script.replace("${table}", tableName);
|
||||
}
|
||||
|
||||
private final Map<String,String> placeholders = new HashMap<String, String>();
|
||||
|
||||
ScriptTransform(Map<String,String> map) {
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
placeholders.put(wrapKey(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private String wrapKey(String key) {
|
||||
return "${"+key+"}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this contains no placeholders.
|
||||
*/
|
||||
boolean isEmpty() {
|
||||
return placeholders.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the script replacing placeholders in the form <code>${key}</code> with <code>value</code>.
|
||||
*/
|
||||
String transform(String source) {
|
||||
|
||||
for (Map.Entry<String, String> entry : placeholders.entrySet()) {
|
||||
source = source.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -40,4 +41,14 @@ public interface SpiServer extends EbeanServer {
|
||||
* Return the bean type for a given doc store queueId.
|
||||
*/
|
||||
BeanType<?> getBeanTypeForQueueId(String queueId);
|
||||
|
||||
/**
|
||||
* Return the associated DataSource for this EbeanServer instance.
|
||||
*/
|
||||
DataSource getDataSource();
|
||||
|
||||
/**
|
||||
* Initialise the query cache for the given bean type.
|
||||
*/
|
||||
boolean initQueryCache(String key);
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter;
|
||||
import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This interface allows us to have more than one ClassPathSearch
|
||||
* to scan the resources by a customized way.
|
||||
*
|
||||
* @author Kefeng Deng (deng@51any.com)
|
||||
*/
|
||||
public interface ClassPathSearchService {
|
||||
|
||||
/**
|
||||
* Initialize this ClassPathSearchService with given parameters
|
||||
*
|
||||
* @param classLoader is current classLoader
|
||||
* @param filter is filter
|
||||
* @param matcher
|
||||
* @param classPathReaderClassName
|
||||
*/
|
||||
void init(ClassLoader classLoader, ClassPathSearchFilter filter, ClassPathSearchMatcher matcher, String classPathReaderClassName);
|
||||
|
||||
/**
|
||||
* Searches the class path for all matching classes.
|
||||
*
|
||||
* @return a collection of all matching classes
|
||||
* @throws IOException if a resource is un-reachable
|
||||
*/
|
||||
List<Class<?>> findClasses() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the set of jars that contained classes that matched.
|
||||
*/
|
||||
Set<String> getJarHits();
|
||||
|
||||
/**
|
||||
* Return the set of packages that contained classes that matched.
|
||||
*/
|
||||
Set<String> getPackageHits();
|
||||
|
||||
}
|
||||
@@ -17,6 +17,10 @@ public class HashQuery {
|
||||
this.bindHash = bindHash;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "HashQuery@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int hc = 31 * planHash.hashCode();
|
||||
hc = 31 * hc + bindHash;
|
||||
|
||||
@@ -158,7 +158,7 @@ public class LoadBeanRequest extends LoadRequest {
|
||||
EntityBean loadedBean = (EntityBean) list.get(i);
|
||||
loadedIds.add(desc.getId(loadedBean));
|
||||
if (isLoadCache()) {
|
||||
desc.cacheBeanPutData(loadedBean);
|
||||
desc.cacheBeanPut(loadedBean);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
|
||||
/**
|
||||
* Allows us to have more than one plugin (the ddl generator) active based on flags.
|
||||
*
|
||||
* author: Richard Vowles - http://gplus.to/RichardVowles
|
||||
*/
|
||||
@Deprecated
|
||||
public interface SpiEbeanPlugin {
|
||||
|
||||
/**
|
||||
* initializes the plugin.
|
||||
*
|
||||
* @param server - the ebean server with extensions for all of the Ebean internals
|
||||
* @param serverConfig - the configured server information. It allows access to pre-collected information (but not the collector PropertySource, yet)
|
||||
*/
|
||||
void setup(SpiEbeanServer server, ServerConfig serverConfig);
|
||||
|
||||
/**
|
||||
* Execute the plugin (generate DDL for example).
|
||||
*/
|
||||
void execute(boolean online);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
@@ -105,20 +106,17 @@ public class TransactionEvent implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the cache of bean changes.
|
||||
* <p>
|
||||
* This returns the TransactionEventTable so that if any
|
||||
* general table changes can also be used to invalidate
|
||||
* parts of the cache.
|
||||
* </p>
|
||||
* Build and return the cache changeSet.
|
||||
*/
|
||||
public void notifyCache() {
|
||||
public CacheChangeSet buildCacheChanges() {
|
||||
CacheChangeSet changeSet = new CacheChangeSet();
|
||||
if (eventBeans != null) {
|
||||
eventBeans.notifyCache();
|
||||
eventBeans.notifyCache(changeSet);
|
||||
}
|
||||
if (deleteByIdMap != null) {
|
||||
deleteByIdMap.notifyCache();
|
||||
deleteByIdMap.notifyCache(changeSet);
|
||||
}
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.api;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
|
||||
/**
|
||||
@@ -30,10 +31,13 @@ public class TransactionEventBeans {
|
||||
|
||||
requests.add(request);
|
||||
}
|
||||
|
||||
public void notifyCache() {
|
||||
|
||||
/**
|
||||
* Collect the cache changes.
|
||||
*/
|
||||
public void notifyCache(CacheChangeSet changeSet) {
|
||||
for (int i = 0; i < requests.size(); i++) {
|
||||
requests.get(i).notifyCache();
|
||||
requests.get(i).notifyCache(changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -41,10 +41,10 @@ public class AutoTuneAllCollection {
|
||||
/**
|
||||
* Write the document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
public void writeFile(String filePrefix, boolean withNow) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
writer.write(document, filePrefix, withNow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class AutoTuneDiffCollection {
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
writer.write(document, filePrefix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-5
@@ -15,19 +15,27 @@ import java.util.Date;
|
||||
*/
|
||||
public class AutoTuneXmlWriter {
|
||||
|
||||
/**
|
||||
* Return 'now' as a string to second precision.
|
||||
*/
|
||||
public static String now() {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
return df.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the document as xml file with the given prefix.
|
||||
*/
|
||||
public void write(Autotune document, String filePrefix) {
|
||||
public void write(Autotune document, String fileName, boolean withNow) {
|
||||
|
||||
SortAutoTuneDocument.sort(document);
|
||||
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
String now = df.format(new Date());
|
||||
if (withNow) {
|
||||
fileName += "-" + now() + ".xml";
|
||||
}
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
File file = new File(filePrefix + "-" + now + ".xml");
|
||||
write(document, file);
|
||||
write(document, new File(fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-1
@@ -185,7 +185,15 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
} else {
|
||||
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
|
||||
int size = event.size();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-all");
|
||||
File existingTuning = new File(tuningFile);
|
||||
if (existingTuning.exists()) {
|
||||
// rename the existing autotune.xml file (appending 'now')
|
||||
if (!existingTuning.renameTo(new File(tuningFile+"."+AutoTuneXmlWriter.now()))) {
|
||||
logger.warn("Failed to rename autotune file [{}]", tuningFile);
|
||||
}
|
||||
}
|
||||
|
||||
event.writeFile(tuningFile, false);
|
||||
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
/**
|
||||
* A change to the cache.
|
||||
*/
|
||||
public interface CacheChange {
|
||||
|
||||
/**
|
||||
* Apply the change.
|
||||
*/
|
||||
void apply();
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
* Change to remove bean from L2 cache.
|
||||
*/
|
||||
class CacheChangeBeanRemove implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
|
||||
private final Object id;
|
||||
|
||||
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Object id) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheHandleDeleteById(id);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Put a new bean entry into the cache.
|
||||
*/
|
||||
class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
private final Object id;
|
||||
private final Map<String, Object> changes;
|
||||
private final boolean updateNaturalKey;
|
||||
private final long version;
|
||||
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
this.desc = desc;
|
||||
this.id = id;
|
||||
this.changes = changes;
|
||||
this.updateNaturalKey = updateNaturalKey;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
desc.cacheBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
* Change the natural key mapping for a bean.
|
||||
*/
|
||||
class CacheChangeNaturalKeyPut implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
private final Object id;
|
||||
private final Object newKey;
|
||||
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, Object id, Object newKey) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
this.newKey = newKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheNaturalKeyPut(id, newKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* List of changes to be applied to L2 cache.
|
||||
*/
|
||||
public class CacheChangeSet {
|
||||
|
||||
private final List<CacheChange> entries = new ArrayList<CacheChange>();
|
||||
|
||||
private final Set<BeanDescriptor> queryCaches = new HashSet<BeanDescriptor>();
|
||||
|
||||
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<ManyKey, ManyChange>();
|
||||
|
||||
/**
|
||||
* Apply all the changes to the L2 cache.
|
||||
*/
|
||||
public void apply() {
|
||||
for (BeanDescriptor entry : queryCaches) {
|
||||
entry.queryCacheClear();
|
||||
}
|
||||
for (CacheChange entry : entries) {
|
||||
entry.apply();
|
||||
}
|
||||
for (CacheChange entry : manyChangeMap.values()) {
|
||||
entry.apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to clear a query cache.
|
||||
*/
|
||||
public void addClearQuery(BeanDescriptor<?> descriptor) {
|
||||
queryCaches.add(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property clear.
|
||||
*/
|
||||
public <T> void addManyClear(BeanDescriptor<T> desc, String manyProperty) {
|
||||
many(desc, manyProperty).setClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property remove.
|
||||
*/
|
||||
public <T> void addManyRemove(BeanDescriptor<T> desc, String manyProperty, Object parentId) {
|
||||
many(desc, manyProperty).addRemove(parentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property put.
|
||||
*/
|
||||
public <T> void addManyPut(BeanDescriptor<T> desc, String manyProperty, Object parentId, CachedManyIds entry) {
|
||||
many(desc, manyProperty).addPut(parentId, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache.
|
||||
*/
|
||||
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
|
||||
entries.add(new CacheChangeBeanRemove(desc, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a bean entry.
|
||||
*/
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a natural key.
|
||||
*/
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, Object id, Object val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, id, val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ManyChange for the given descriptor and property manyProperty.
|
||||
*/
|
||||
private ManyChange many(BeanDescriptor<?> desc, String manyProperty) {
|
||||
ManyKey key = new ManyKey(desc, manyProperty);
|
||||
ManyChange manyChange = manyChangeMap.get(key);
|
||||
if (manyChange == null) {
|
||||
manyChange = new ManyChange(key);
|
||||
manyChangeMap.put(key, manyChange);
|
||||
}
|
||||
return manyChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes for a specific many property.
|
||||
*/
|
||||
private static class ManyChange implements CacheChange {
|
||||
|
||||
final ManyKey key;
|
||||
|
||||
final List<Object> removes = new ArrayList<Object>();
|
||||
|
||||
final Map<Object,CachedManyIds> puts = new LinkedHashMap<Object, CachedManyIds>();
|
||||
|
||||
boolean clear;
|
||||
|
||||
ManyChange(ManyKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all entries.
|
||||
*/
|
||||
void setClear() {
|
||||
this.clear = true;
|
||||
removes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry for the given parentId.
|
||||
*/
|
||||
void addRemove(Object parentId) {
|
||||
if (!clear) {
|
||||
removes.add(parentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put entry for the given parentId.
|
||||
*/
|
||||
void addPut(Object parentId, CachedManyIds entry) {
|
||||
puts.put(parentId, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
if (clear) {
|
||||
key.cacheClear();
|
||||
} else {
|
||||
for (Map.Entry<Object, CachedManyIds> entry : puts.entrySet()) {
|
||||
key.cachePut(entry.getKey(), entry.getValue());
|
||||
}
|
||||
for (Object parentId : removes) {
|
||||
key.cacheRemove(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for changes on a many property.
|
||||
*/
|
||||
private static class ManyKey {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
|
||||
private final String manyProperty;
|
||||
|
||||
ManyKey(BeanDescriptor<?> desc, String manyProperty) {
|
||||
this.desc = desc;
|
||||
this.manyProperty = manyProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ManyKey manyKey = (ManyKey) o;
|
||||
return desc.equals(manyKey.desc) && manyProperty.equals(manyKey.manyProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * desc.hashCode() + manyProperty.hashCode();
|
||||
}
|
||||
|
||||
void cacheClear() {
|
||||
desc.cacheManyPropClear(manyProperty);
|
||||
}
|
||||
|
||||
void cachePut(Object parentId, CachedManyIds entry) {
|
||||
desc.cacheManyPropPut(manyProperty, parentId, entry);
|
||||
}
|
||||
|
||||
void cacheRemove(Object parentId) {
|
||||
desc.cacheManyPropRemove(manyProperty, parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,92 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Data held in the bean cache for cached beans.
|
||||
*/
|
||||
public class CachedBeanData {
|
||||
public class CachedBeanData implements Externalizable {
|
||||
|
||||
private final long whenCreated;
|
||||
private final Object sharableBean;
|
||||
private final boolean[] loaded;
|
||||
private final Object[] data;
|
||||
|
||||
private final boolean naturalKeyUpdate;
|
||||
private final Object naturalKey;
|
||||
private final Object oldNaturalKey;
|
||||
private long whenCreated;
|
||||
private long version;
|
||||
private String discValue;
|
||||
private Map<String, Object> data;
|
||||
|
||||
public CachedBeanData(Object sharableBean, boolean[] loaded, Object[] data, Object naturalKey, Object oldNaturalKey) {
|
||||
/**
|
||||
* The sharable bean is effectively transient (near cache only).
|
||||
*/
|
||||
private transient Object sharableBean;
|
||||
|
||||
/**
|
||||
* Construct from a loaded bean.
|
||||
*/
|
||||
public CachedBeanData(Object sharableBean, String discValue, Map<String, Object> data, long version) {
|
||||
this.whenCreated = System.currentTimeMillis();
|
||||
this.sharableBean = sharableBean;
|
||||
this.loaded = loaded;
|
||||
this.discValue = discValue;
|
||||
this.data = data;
|
||||
this.naturalKeyUpdate = naturalKey != null;
|
||||
this.naturalKey = (naturalKey != null) ? naturalKey : oldNaturalKey;
|
||||
this.oldNaturalKey = oldNaturalKey;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from serialisation.
|
||||
*/
|
||||
public CachedBeanData() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeLong(version);
|
||||
out.writeLong(whenCreated);
|
||||
boolean hasDisc = discValue != null;
|
||||
out.writeBoolean(hasDisc);
|
||||
if (hasDisc) {
|
||||
out.writeUTF(discValue);
|
||||
}
|
||||
out.writeInt(data.size());
|
||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
out.writeObject(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
version = in.readLong();
|
||||
whenCreated = in.readLong();
|
||||
if (in.readBoolean()) {
|
||||
discValue = in.readUTF();
|
||||
}
|
||||
data = new LinkedHashMap<String, Object>();
|
||||
int count = in.readInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String key = in.readUTF();
|
||||
Object val = in.readObject();
|
||||
data.put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Arrays.toString(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the property data.
|
||||
*/
|
||||
public Object[] copyData() {
|
||||
Object[] dest = new Object[data.length];
|
||||
System.arraycopy(data, 0, dest, 0, data.length);
|
||||
return dest;
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the loaded status for the properties.
|
||||
* Create and return a new version of CachedBeanData based on this
|
||||
* entry applying the given changes.
|
||||
*/
|
||||
public boolean[] copyLoaded() {
|
||||
boolean[] dest = new boolean[data.length];
|
||||
System.arraycopy(loaded, 0, dest, 0, dest.length);
|
||||
return dest;
|
||||
public CachedBeanData update(Map<String, Object> changes, long version) {
|
||||
|
||||
Map<String, Object> copy = new HashMap<String, Object>();
|
||||
copy.putAll(data);
|
||||
copy.putAll(changes);
|
||||
return new CachedBeanData(null, discValue, copy, version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,45 +97,44 @@ public class CachedBeanData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sharable (immutable read only) bean.
|
||||
* Return the version value.
|
||||
*/
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw discriminator value.
|
||||
*/
|
||||
public String getDiscValue() {
|
||||
return discValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sharable (immutable read only) bean. Near cache only use.
|
||||
*/
|
||||
public Object getSharableBean() {
|
||||
return sharableBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this data requires an update to the natural key cache.
|
||||
* Return true if the property is held.
|
||||
*/
|
||||
public boolean isNaturalKeyUpdate() {
|
||||
return naturalKeyUpdate;
|
||||
public boolean isLoaded(String propertyName) {
|
||||
return data.containsKey(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new/current natural key value.
|
||||
* Return the value for a given property name.
|
||||
*/
|
||||
public Object getNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the old natural key (its entry should be removed).
|
||||
*/
|
||||
public Object getOldNaturalKey() {
|
||||
return oldNaturalKey;
|
||||
public Object getData(String propertyName) {
|
||||
return data.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data for the specific property.
|
||||
* Return all the property data.
|
||||
*/
|
||||
public Object getData(int i) {
|
||||
return data[i];
|
||||
public Map<String,Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is contained in this data.
|
||||
*/
|
||||
public boolean isLoaded(int i) {
|
||||
return loaded[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-20
@@ -5,56 +5,49 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CachedBeanDataFromBean {
|
||||
|
||||
|
||||
public static CachedBeanData extract(BeanDescriptor<?> desc, EntityBean bean) {
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
Object[] data = new Object[desc.getPropertyCount()];
|
||||
boolean[] loaded = new boolean[desc.getPropertyCount()];
|
||||
|
||||
|
||||
Map<String,Object> data = new LinkedHashMap<String,Object>();
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
int propertyIndex = idProperty.getPropertyIndex();
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// extract the id property value
|
||||
data[propertyIndex] = idProperty.getCacheDataValue(bean);
|
||||
loaded[propertyIndex] = true;
|
||||
data.put(idProperty.getName(), idProperty.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
|
||||
Object naturalKey = null;
|
||||
|
||||
// extract all the non-many properties
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
BeanProperty prop = props[i];
|
||||
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
int propertyIndex = prop.getPropertyIndex();
|
||||
data[propertyIndex] = prop.getCacheDataValue(bean);
|
||||
loaded[propertyIndex] = true;
|
||||
if (prop.isNaturalKey()) {
|
||||
naturalKey = prop.getValue(bean);
|
||||
}
|
||||
data.put(prop.getName(), prop.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
|
||||
long version = desc.getVersion(bean);
|
||||
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
|
||||
|
||||
return new CachedBeanData(sharableBean, loaded, data, naturalKey, null);
|
||||
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
|
||||
}
|
||||
|
||||
private static EntityBean createSharableBean(BeanDescriptor<?> desc, EntityBean bean, EntityBeanIntercept beanEbi) {
|
||||
|
||||
|
||||
if (!desc.isCacheSharableBeans() || !beanEbi.isFullyLoadedBean()) {
|
||||
return null;
|
||||
}
|
||||
if (beanEbi.isReadOnly()) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// create a readOnly sharable instance by copying the data
|
||||
EntityBean sharableBean = desc.createEntityBean();
|
||||
BeanProperty idProp = desc.getIdProperty();
|
||||
|
||||
+7
-11
@@ -25,9 +25,9 @@ public class CachedBeanDataToBean {
|
||||
loadProperty(bean, cacheBeanData, ebi, props[i]);
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
|
||||
for (int i = 0; i < manys.length; i++) {
|
||||
manys[i].createReferenceIfNull(bean);
|
||||
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
|
||||
for (int i = 0; i < many.length; i++) {
|
||||
many[i].createReferenceIfNull(bean);
|
||||
}
|
||||
|
||||
ebi.setLoadedLazy();
|
||||
@@ -35,14 +35,10 @@ public class CachedBeanDataToBean {
|
||||
|
||||
private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop) {
|
||||
|
||||
int propertyIndex = prop.getPropertyIndex();
|
||||
if (cacheBeanData.isLoaded(propertyIndex)) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// already loaded (lazy load on partially loaded bean)
|
||||
} else {
|
||||
Object data = cacheBeanData.getData(propertyIndex);
|
||||
prop.setCacheDataValue(bean, data);
|
||||
if (cacheBeanData.isLoaded(prop.getName())) {
|
||||
if (!ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
Object value = cacheBeanData.getData(prop.getName());
|
||||
prop.setCacheDataValue(bean, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Create a new CachedBeanData based on the existing CachedBeanData and the updated bean.
|
||||
*/
|
||||
public class CachedBeanDataUpdate {
|
||||
|
||||
/**
|
||||
* Create a new CachedBeanData based on the existing CachedBeanData and the updated bean.
|
||||
*/
|
||||
public static CachedBeanData update(BeanDescriptor<?> desc, CachedBeanData existingData, EntityBean updateBean) {
|
||||
|
||||
// take a copy of the raw data and loaded status
|
||||
boolean[] copyLoaded = existingData.copyLoaded();
|
||||
Object[] copyData = existingData.copyData();
|
||||
|
||||
EntityBeanIntercept ebi = updateBean._ebean_getIntercept();
|
||||
|
||||
Object newNaturalKey = null;
|
||||
Object oldNaturalKey = existingData.getNaturalKey();
|
||||
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
// check if the properties was in the update
|
||||
int propertyIndex = props[i].getPropertyIndex();
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
if (props[i].isNaturalKey()) {
|
||||
newNaturalKey = updateBean._ebean_getField(propertyIndex);
|
||||
}
|
||||
// set the cache safe value for the property and mark it as loaded
|
||||
copyData[propertyIndex] = props[i].getCacheDataValue(updateBean);
|
||||
copyLoaded[propertyIndex] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new CachedBeanData(null, copyLoaded, copyData, newNaturalKey, oldNaturalKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -8,18 +14,41 @@ import java.util.List;
|
||||
* This is effectively just the Id values for each of the beans in the collection.
|
||||
* </p>
|
||||
*/
|
||||
public class CachedManyIds {
|
||||
public class CachedManyIds implements Externalizable {
|
||||
|
||||
private final List<Object> idList;
|
||||
private List<Object> idList;
|
||||
|
||||
public CachedManyIds(List<Object> idList) {
|
||||
this.idList = idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for serialization.
|
||||
*/
|
||||
public CachedManyIds() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeInt(idList.size());
|
||||
for (Object id : idList) {
|
||||
out.writeObject(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
int size = in.readInt();
|
||||
idList = new ArrayList<Object>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
idList.add(in.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return idList.toString();
|
||||
}
|
||||
|
||||
|
||||
public List<Object> getIdList() {
|
||||
return idList;
|
||||
}
|
||||
|
||||
+91
-90
@@ -1,120 +1,121 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheQueryTuning;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages the construction of caches.
|
||||
*/
|
||||
public class DefaultCacheHolder {
|
||||
|
||||
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
|
||||
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
|
||||
|
||||
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
|
||||
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private final ServerCacheOptions defaultOptions;
|
||||
private final ServerCacheOptions defaultOptions;
|
||||
|
||||
private final boolean useBeanTuning;
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory the factory for creating the cache
|
||||
* @param defaultOptions the default options for tuning the cache
|
||||
*/
|
||||
public DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions defaultOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.defaultOptions = defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory
|
||||
* the factory for creating the cache
|
||||
* @param defaultOptions
|
||||
* the default options for tuning the cache
|
||||
* @param useBeanTuning
|
||||
* if true then use the bean class specific tuning. This is
|
||||
* generally false for the query cache.
|
||||
*/
|
||||
public DefaultCacheHolder(ServerCacheFactory cacheFactory,
|
||||
ServerCacheOptions defaultOptions, boolean useBeanTuning) {
|
||||
/**
|
||||
* Return the cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getCache(String cacheKey, ServerCacheType type) {
|
||||
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.useBeanTuning = useBeanTuning;
|
||||
}
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
synchronized (monitor) {
|
||||
cache = synchMap.get(cacheKey);
|
||||
if (cache == null) {
|
||||
ServerCacheOptions options = getCacheOptions(cacheKey, type);
|
||||
cache = cacheFactory.createCache(type, cacheKey, options);
|
||||
synchMap.put(cacheKey, cache);
|
||||
concMap.put(cacheKey, cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default cache options.
|
||||
*/
|
||||
public ServerCacheOptions getDefaultOptions() {
|
||||
return defaultOptions;
|
||||
}
|
||||
public void clearCache(String cacheKey) {
|
||||
|
||||
/**
|
||||
* Return the cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getCache(String cacheKey) {
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
synchronized (monitor) {
|
||||
cache = synchMap.get(cacheKey);
|
||||
if (cache == null) {
|
||||
ServerCacheOptions options = getCacheOptions(cacheKey);
|
||||
cache = cacheFactory.createCache(cacheKey, options);
|
||||
synchMap.put(cacheKey, cache);
|
||||
concMap.put(cacheKey, cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if there is an active cache for this bean type.
|
||||
*/
|
||||
public boolean isCaching(String beanType) {
|
||||
return concMap.containsKey(beanType);
|
||||
}
|
||||
|
||||
public void clearCache(String cacheKey) {
|
||||
public void clearAll() {
|
||||
for (ServerCache serverCache : concMap.values()) {
|
||||
serverCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an active cache for this bean type.
|
||||
*/
|
||||
public boolean isCaching(String beanType) {
|
||||
return concMap.containsKey(beanType);
|
||||
}
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
ServerCacheOptions getCacheOptions(String beanType, ServerCacheType type) {
|
||||
|
||||
public void clearAll() {
|
||||
for (ServerCache serverCache : concMap.values()) {
|
||||
serverCache.clear();
|
||||
}
|
||||
}
|
||||
try {
|
||||
Class<?> cls = Class.forName(beanType);
|
||||
switch (type) {
|
||||
case QUERY:
|
||||
return getQueryOptions(cls);
|
||||
default:
|
||||
return getBeanOptions(cls);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
private ServerCacheOptions getCacheOptions(String beanType) {
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
if (useBeanTuning) {
|
||||
// read the deployment annotation
|
||||
try {
|
||||
Class<?> cls = Class.forName(beanType);
|
||||
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
|
||||
if (cacheTuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
} catch (ClassNotFoundException e){
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
private ServerCacheOptions getQueryOptions(Class<?> cls) {
|
||||
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
|
||||
if (tuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(tuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
return defaultOptions.copy();
|
||||
|
||||
}
|
||||
private ServerCacheOptions getBeanOptions(Class<?> cls) {
|
||||
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
|
||||
if (cacheTuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-3
@@ -10,7 +10,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -30,7 +34,6 @@ public class DefaultServerCache implements ServerCache {
|
||||
*/
|
||||
public static final CompareByLastAccess BY_LAST_ACCESS = new CompareByLastAccess();
|
||||
|
||||
|
||||
/**
|
||||
* The underlying map (ConcurrentHashMap or similar)
|
||||
*/
|
||||
@@ -85,7 +88,23 @@ public class DefaultServerCache implements ServerCache {
|
||||
this.maxSize = maxSize;
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
this.trimFrequency = trimFrequency;
|
||||
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
|
||||
*/
|
||||
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
if (trimFrequency > 0) {
|
||||
return trimFrequency;
|
||||
}
|
||||
if (maxIdleSecs > 0) {
|
||||
return maxIdleSecs / 2 - 1;
|
||||
}
|
||||
if (maxSecsToLive > 0) {
|
||||
return maxSecsToLive / 2 - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+14
-13
@@ -4,6 +4,7 @@ import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
|
||||
|
||||
/**
|
||||
@@ -11,17 +12,17 @@ import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
*/
|
||||
public class DefaultServerCacheFactory implements ServerCacheFactory {
|
||||
|
||||
private EbeanServer ebeanServer;
|
||||
|
||||
public void init(EbeanServer ebeanServer){
|
||||
this.ebeanServer = ebeanServer;
|
||||
}
|
||||
|
||||
public ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions) {
|
||||
|
||||
ServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
|
||||
cache.init(ebeanServer);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private EbeanServer ebeanServer;
|
||||
|
||||
public void init(EbeanServer ebeanServer) {
|
||||
this.ebeanServer = ebeanServer;
|
||||
}
|
||||
|
||||
public ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions) {
|
||||
|
||||
ServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
|
||||
cache.init(ebeanServer);
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+78
-83
@@ -5,41 +5,49 @@ import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
|
||||
|
||||
/**
|
||||
* Manages the bean and query caches.
|
||||
* Manages the bean and query caches.
|
||||
*/
|
||||
public class DefaultServerCacheManager implements ServerCacheManager {
|
||||
|
||||
private final DefaultCacheHolder beanCache;
|
||||
private final DefaultCacheHolder beanCache;
|
||||
|
||||
private final DefaultCacheHolder queryCache;
|
||||
private final DefaultCacheHolder queryCache;
|
||||
|
||||
private final DefaultCacheHolder naturalKeyCache;
|
||||
private final DefaultCacheHolder naturalKeyCache;
|
||||
|
||||
private final DefaultCacheHolder collectionIdsCache;
|
||||
private final DefaultCacheHolder collectionIdsCache;
|
||||
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private SpiEbeanServer ebeanServer;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*/
|
||||
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions, true);
|
||||
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
}
|
||||
|
||||
public void init(EbeanServer server) {
|
||||
cacheFactory.init(server);
|
||||
this.ebeanServer = (SpiEbeanServer)server;
|
||||
}
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private SpiEbeanServer ebeanServer;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*/
|
||||
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions);
|
||||
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct when l2 cache is disabled.
|
||||
*/
|
||||
public DefaultServerCacheManager() {
|
||||
this(new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
|
||||
}
|
||||
|
||||
public void init(EbeanServer server) {
|
||||
cacheFactory.init(server);
|
||||
this.ebeanServer = (SpiEbeanServer) server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bean caching on or off for a given bean type.
|
||||
@@ -48,68 +56,55 @@ public class DefaultServerCacheManager implements ServerCacheManager {
|
||||
ebeanServer.getBeanDescriptor(beanType).setUseCache(useCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear both the bean cache and the query cache for a
|
||||
* given bean type.
|
||||
*/
|
||||
public void clear(Class<?> beanType) {
|
||||
String beanName = beanType.getName();
|
||||
beanCache.clearCache(beanName);
|
||||
naturalKeyCache.clearCache(beanName);
|
||||
collectionIdsCache.clearCache(beanName);
|
||||
queryCache.clearCache(beanName);
|
||||
}
|
||||
/**
|
||||
* Clear both the bean cache and the query cache for a
|
||||
* given bean type.
|
||||
*/
|
||||
public void clear(Class<?> beanType) {
|
||||
String beanName = beanType.getName();
|
||||
beanCache.clearCache(beanName);
|
||||
naturalKeyCache.clearCache(beanName);
|
||||
collectionIdsCache.clearCache(beanName);
|
||||
queryCache.clearCache(beanName);
|
||||
}
|
||||
|
||||
|
||||
public void clearAll() {
|
||||
beanCache.clearAll();
|
||||
queryCache.clearAll();
|
||||
naturalKeyCache.clearAll();
|
||||
collectionIdsCache.clearAll();
|
||||
}
|
||||
|
||||
|
||||
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
|
||||
return collectionIdsCache.getCache(beanType.getName()+"."+propertyName);
|
||||
}
|
||||
|
||||
public boolean isCollectionIdsCaching(Class<?> beanType) {
|
||||
return collectionIdsCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
public ServerCache getNaturalKeyCache(Class<?> beanType) {
|
||||
return naturalKeyCache.getCache(beanType.getName());
|
||||
}
|
||||
|
||||
public boolean isNaturalKeyCaching(Class<?> beanType) {
|
||||
return naturalKeyCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getQueryCache(Class<?> beanType) {
|
||||
return queryCache.getCache(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getBeanCache(Class<?> beanType) {
|
||||
return beanCache.getCache(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an active cache for the given bean type.
|
||||
*/
|
||||
public boolean isBeanCaching(Class<?> beanType) {
|
||||
return beanCache.isCaching(beanType.getName());
|
||||
}
|
||||
public void clearAll() {
|
||||
beanCache.clearAll();
|
||||
queryCache.clearAll();
|
||||
naturalKeyCache.clearAll();
|
||||
collectionIdsCache.clearAll();
|
||||
}
|
||||
|
||||
|
||||
public boolean isQueryCaching(Class<?> beanType) {
|
||||
return queryCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
|
||||
return collectionIdsCache.getCache(beanType.getName() + "." + propertyName, ServerCacheType.COLLECTION_IDS);
|
||||
}
|
||||
|
||||
public ServerCache getNaturalKeyCache(Class<?> beanType) {
|
||||
return naturalKeyCache.getCache(beanType.getName(), ServerCacheType.NATURAL_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getQueryCache(Class<?> beanType) {
|
||||
return queryCache.getCache(beanType.getName(), ServerCacheType.QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getBeanCache(Class<?> beanType) {
|
||||
return beanCache.getCache(beanType.getName(), ServerCacheType.BEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an active cache for the given bean type.
|
||||
*/
|
||||
public boolean isBeanCaching(Class<?> beanType) {
|
||||
return beanCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassPathSearchService;
|
||||
import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import org.avaje.classpath.scanner.ClassPathScanner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -10,118 +10,54 @@ import java.util.*;
|
||||
/**
|
||||
* Searches for interesting classes such as Entities, Embedded and ScalarTypes.
|
||||
*/
|
||||
public class BootupClassPathSearch {
|
||||
class BootupClassPathSearch {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BootupClassPathSearch.class);
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final List<String> packages;
|
||||
|
||||
private final List<String> jars;
|
||||
|
||||
private List<ClassPathSearchService> classPathSearchServices;
|
||||
|
||||
private BootupClasses bootupClasses;
|
||||
|
||||
private final String classPathReaderClassName;
|
||||
private final List<ClassPathScanner> scanners;
|
||||
|
||||
/**
|
||||
* Construct and search for interesting classes.
|
||||
* Search the classPath for the classes we are interested in returning
|
||||
* them as BootupClasses.
|
||||
*/
|
||||
public BootupClassPathSearch(ClassLoader classLoader, List<String> packages, List<String> jars, String classPathReaderClassName) {
|
||||
this.classLoader = (classLoader == null) ? getClass().getClassLoader() : classLoader;
|
||||
this.packages = packages;
|
||||
this.jars = jars;
|
||||
this.classPathReaderClassName = classPathReaderClassName;
|
||||
public static BootupClasses search(ServerConfig serverConfig) {
|
||||
|
||||
loadAndInitializeClassPathSearchServices();
|
||||
return new BootupClassPathSearch(serverConfig).getBootupClasses();
|
||||
}
|
||||
|
||||
public BootupClasses getBootupClasses() {
|
||||
synchronized (monitor) {
|
||||
|
||||
if (bootupClasses == null) {
|
||||
bootupClasses = search();
|
||||
}
|
||||
|
||||
return bootupClasses;
|
||||
}
|
||||
private BootupClassPathSearch(ServerConfig serverConfig) {
|
||||
this.packages = serverConfig.getPackages();
|
||||
this.scanners = ClassPathScanners.find(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the classPath for the classes we are interested in.
|
||||
*/
|
||||
private BootupClasses search() {
|
||||
synchronized (monitor) {
|
||||
try {
|
||||
private BootupClasses getBootupClasses() {
|
||||
|
||||
BootupClasses bc = new BootupClasses();
|
||||
try {
|
||||
BootupClasses bc = new BootupClasses();
|
||||
|
||||
long st = System.currentTimeMillis();
|
||||
|
||||
ClassPathSearchFilter filter = createFilter();
|
||||
|
||||
Set<String> foundJars = new HashSet<String>();
|
||||
Set<String> foundPkgs = new HashSet<String>();
|
||||
|
||||
for (ClassPathSearchService finder : this.classPathSearchServices) {
|
||||
finder.init(classLoader, filter, bc, classPathReaderClassName);
|
||||
finder.findClasses();
|
||||
foundJars.addAll(finder.getJarHits());
|
||||
foundPkgs.addAll(finder.getPackageHits());
|
||||
long st = System.currentTimeMillis();
|
||||
for (ClassPathScanner finder : this.scanners) {
|
||||
if (packages != null && packages.size() > 0) {
|
||||
for (String packageName : packages) {
|
||||
finder.scanForClasses(packageName, bc);
|
||||
}
|
||||
} else {
|
||||
// scan locally
|
||||
finder.scanForClasses("", bc);
|
||||
}
|
||||
|
||||
long searchTime = System.currentTimeMillis() - st;
|
||||
|
||||
logger.info("Classpath search hits in jars {} pkgs {} searchTime [{}]", foundJars, foundPkgs, searchTime);
|
||||
return bc;
|
||||
|
||||
} catch (Exception ex) {
|
||||
String msg = "Error in classpath search (looking for entities etc)";
|
||||
throw new RuntimeException(msg, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ClassPathSearchFilter createFilter() {
|
||||
|
||||
ClassPathSearchFilter filter = new ClassPathSearchFilter();
|
||||
filter.addDefaultExcludePackages();
|
||||
|
||||
if (packages != null && packages.size() > 0) {
|
||||
for (String packageName : packages) {
|
||||
filter.includePackage(packageName);
|
||||
}
|
||||
|
||||
// if they specified include packages, they don't want by default to include everything
|
||||
filter.setDefaultPackageMatch(false);
|
||||
}
|
||||
long searchTime = System.currentTimeMillis() - st;
|
||||
logger.info("Classpath search entities[{}] searchTime [{}]", bc.getEntities().size(), searchTime);
|
||||
return bc;
|
||||
|
||||
if (jars != null && jars.size() > 0) {
|
||||
for (String jarName : jars) {
|
||||
filter.includeJar(jarName);
|
||||
}
|
||||
|
||||
// if they specified jars to specifically include, they don't want everything included
|
||||
filter.setDefaultJarMatch(false);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and initialize all ClassPathSearchServices
|
||||
*/
|
||||
private void loadAndInitializeClassPathSearchServices() {
|
||||
if (this.classPathSearchServices == null) {
|
||||
this.classPathSearchServices = new ArrayList<ClassPathSearchService>();
|
||||
}
|
||||
|
||||
for (ClassPathSearchService searchService : ServiceLoader.load(ClassPathSearchService.class, classLoader)) {
|
||||
this.classPathSearchServices.add(searchService);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error in classpath search (looking for entities etc)", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.avaje.ebean.event.readaudit.ReadAuditLogger;
|
||||
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher;
|
||||
import org.avaje.classpath.scanner.ClassFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -33,7 +34,7 @@ import java.util.List;
|
||||
* Interesting classes for a EbeanServer such as Embeddable, Entity,
|
||||
* ScalarTypes, Finders, Listeners and Controllers.
|
||||
*/
|
||||
public class BootupClasses implements ClassPathSearchMatcher {
|
||||
public class BootupClasses implements ClassPathSearchMatcher, ClassFilter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BootupClasses.class);
|
||||
|
||||
@@ -370,6 +371,7 @@ public class BootupClasses implements ClassPathSearchMatcher {
|
||||
return compoundTypeList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatch(Class<?> cls) {
|
||||
|
||||
if (isEmbeddable(cls)) {
|
||||
|
||||
@@ -11,8 +11,6 @@ public class CacheOptions {
|
||||
|
||||
private String naturalKey;
|
||||
|
||||
private String warmingQuery;
|
||||
|
||||
private int maxIdleSecs;
|
||||
|
||||
private long maxSecsToLive;
|
||||
@@ -51,20 +49,6 @@ public class CacheOptions {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query used to warm the cache.
|
||||
*/
|
||||
public String getWarmingQuery() {
|
||||
return warmingQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cache warming query.
|
||||
*/
|
||||
public void setWarmingQuery(String warmingQuery) {
|
||||
this.warmingQuery = warmingQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a natural key is set.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import org.avaje.classpath.scanner.ClassPathScanner;
|
||||
import org.avaje.classpath.scanner.ClassPathScannerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Utility to finds and return the list of ClassPathScanner services.
|
||||
*/
|
||||
public class ClassPathScanners {
|
||||
|
||||
/**
|
||||
* Return the list of ClassPathScanner services using serverConfig service loader.
|
||||
*/
|
||||
public static List<ClassPathScanner> find(ServerConfig serverConfig) {
|
||||
|
||||
List<ClassPathScanner> scanners = new ArrayList<ClassPathScanner>();
|
||||
|
||||
ServiceLoader<ClassPathScannerFactory> scannerLoader = serverConfig.serviceLoad(ClassPathScannerFactory.class);
|
||||
for (ClassPathScannerFactory factory : scannerLoader) {
|
||||
ClassPathScanner scanner = factory.createScanner(serverConfig.getClassLoadConfig().getClassLoader());
|
||||
scanners.add(scanner);
|
||||
}
|
||||
|
||||
return scanners;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
@@ -32,8 +31,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Default Server side implementation of ServerFactory.
|
||||
@@ -138,13 +135,6 @@ public class DefaultContainer implements SpiContainer {
|
||||
// register the server once it has been created
|
||||
clusterManager.registerServer(server);
|
||||
}
|
||||
|
||||
// warm the cache in 30 seconds
|
||||
long sleepMillis = 1000 * serverConfig.getCacheWarmingDelay();
|
||||
if (sleepMillis > 0) {
|
||||
Timer timer = new Timer("EbeanCacheWarmer", true);
|
||||
timer.schedule(new CacheWarmer(server, timer), sleepMillis);
|
||||
}
|
||||
}
|
||||
|
||||
// start any services after registering with clusterManager
|
||||
@@ -159,6 +149,10 @@ public class DefaultContainer implements SpiContainer {
|
||||
*/
|
||||
private ServerCacheManager getCacheManager(ServerConfig serverConfig) {
|
||||
|
||||
if (serverConfig.isDisableL2Cache()) {
|
||||
return new DefaultServerCacheManager();
|
||||
}
|
||||
|
||||
ServerCacheManager serverCacheManager = serverConfig.getServerCacheManager();
|
||||
if (serverCacheManager != null) {
|
||||
return serverCacheManager;
|
||||
@@ -222,11 +216,10 @@ public class DefaultContainer implements SpiContainer {
|
||||
List<Class<?>> entityClasses = serverConfig.getClasses();
|
||||
if (serverConfig.isDisableClasspathSearch() || (entityClasses != null && entityClasses.size() > 0)) {
|
||||
// use classes we explicitly added via configuration
|
||||
return new BootupClasses(serverConfig.getClasses());
|
||||
return new BootupClasses(entityClasses);
|
||||
}
|
||||
|
||||
BootupClassPathSearch search = new BootupClassPathSearch(null, serverConfig.getPackages(), serverConfig.getJars(), serverConfig.getClassPathReaderClassName());
|
||||
return search.getBootupClasses();
|
||||
return BootupClassPathSearch.search(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,21 +358,4 @@ public class DefaultContainer implements SpiContainer {
|
||||
}
|
||||
}
|
||||
|
||||
private static class CacheWarmer extends TimerTask {
|
||||
|
||||
private final EbeanServer server;
|
||||
|
||||
private final Timer timer;
|
||||
|
||||
CacheWarmer(EbeanServer server, Timer timer) {
|
||||
this.server = server;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
server.runCacheWarming();
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ import com.avaje.ebean.config.EncryptKeyManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.dbmigration.DdlGenerator;
|
||||
import com.avaje.ebean.dbmigration.MigrationRunner;
|
||||
import com.avaje.ebean.event.BeanPersistController;
|
||||
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
|
||||
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import com.avaje.ebean.meta.MetaInfoManager;
|
||||
import com.avaje.ebean.plugin.BeanType;
|
||||
import com.avaje.ebean.plugin.SpiServer;
|
||||
import com.avaje.ebean.plugin.Plugin;
|
||||
import com.avaje.ebean.plugin.SpiServer;
|
||||
import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
import com.avaje.ebeaninternal.api.LoadBeanRequest;
|
||||
@@ -28,7 +29,6 @@ import com.avaje.ebeaninternal.api.LoadManyRequest;
|
||||
import com.avaje.ebeaninternal.api.ScopeTrans;
|
||||
import com.avaje.ebeaninternal.api.ScopedTransaction;
|
||||
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery.Type;
|
||||
@@ -73,6 +73,7 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -80,7 +81,6 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.FutureTask;
|
||||
@@ -177,11 +177,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
private final int queryBatchSize;
|
||||
|
||||
/**
|
||||
* holds plugins (e.g. ddl generator) detected by the service loader
|
||||
*/
|
||||
private List<SpiEbeanPlugin> ebeanPlugins;
|
||||
|
||||
private final boolean updateAllPropertiesInBatch;
|
||||
|
||||
private final boolean collectQueryOrigins;
|
||||
@@ -242,9 +237,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.serverPlugins = config.getPlugins();
|
||||
this.ddlGenerator = new DdlGenerator(this, serverConfig);
|
||||
|
||||
// load normal plugins late and call setup on all
|
||||
loadAndInitializePlugins(config.getServerConfig());
|
||||
|
||||
configureServerPlugins();
|
||||
|
||||
// Register with the JVM Shutdown hook
|
||||
@@ -260,35 +252,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadAndInitializePlugins(ServerConfig config) {
|
||||
|
||||
List<SpiEbeanPlugin> spiPlugins = new ArrayList<SpiEbeanPlugin>();
|
||||
|
||||
for (SpiEbeanPlugin plugin : ServiceLoader.load(SpiEbeanPlugin.class)) {
|
||||
spiPlugins.add(plugin);
|
||||
plugin.setup(this, config);
|
||||
}
|
||||
|
||||
ebeanPlugins = Collections.unmodifiableList(spiPlugins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of registered plugins.
|
||||
*/
|
||||
public List<SpiEbeanPlugin> getSpiEbeanPlugins() {
|
||||
return ebeanPlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the plugins with an online flag indicating the DB is up or not.
|
||||
*/
|
||||
public void executePlugins(boolean online) {
|
||||
|
||||
ddlGenerator.execute(online);
|
||||
|
||||
for (SpiEbeanPlugin plugin : ebeanPlugins) {
|
||||
plugin.execute(online);
|
||||
}
|
||||
for (Plugin plugin : serverPlugins) {
|
||||
plugin.online(online);
|
||||
}
|
||||
@@ -339,6 +308,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return autoTuneService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return transactionManager.getDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadAuditPrepare getReadAuditPrepare() {
|
||||
return readAuditPrepare;
|
||||
@@ -371,6 +345,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
if (migrationConfig != null) {
|
||||
migrationConfig.generateOnStart(this);
|
||||
}
|
||||
|
||||
if (migrationConfig.isRunMigration()) {
|
||||
new MigrationRunner(this, migrationConfig).run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,26 +421,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the cache warming queries on all beans that have them defined.
|
||||
*/
|
||||
public void runCacheWarming() {
|
||||
List<BeanDescriptor<?>> descList = beanDescriptorManager.getBeanDescriptorList();
|
||||
for (int i = 0; i < descList.size(); i++) {
|
||||
descList.get(i).runCacheWarming();
|
||||
}
|
||||
}
|
||||
|
||||
public void runCacheWarming(Class<?> beanType) {
|
||||
BeanDescriptor<?> desc = beanDescriptorManager.getBeanDescriptor(beanType);
|
||||
if (desc == null) {
|
||||
String msg = "Is " + beanType + " an entity? Could not find a BeanDescriptor";
|
||||
throw new PersistenceException(msg);
|
||||
} else {
|
||||
desc.runCacheWarming();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a query. Only valid for ORM queries.
|
||||
*/
|
||||
@@ -1979,14 +1937,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return typeInfo != null && getBeanDescriptor(typeInfo.getBeanType()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setBeanId(Object bean, Object id) {
|
||||
EntityBean eb = checkEntityBean(bean);
|
||||
BeanDescriptor<?> desc = getBeanDescriptor(bean.getClass());
|
||||
if (desc == null) {
|
||||
throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?");
|
||||
}
|
||||
return desc.convertSetId(id, eb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBeanId(Object bean) {
|
||||
EntityBean eb = checkEntityBean(bean);
|
||||
BeanDescriptor<?> desc = getBeanDescriptor(bean.getClass());
|
||||
if (desc == null) {
|
||||
String m = bean.getClass().getName() + " is NOT an Entity Bean registered with this server?";
|
||||
throw new PersistenceException(m);
|
||||
throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?");
|
||||
}
|
||||
|
||||
return desc.getId(eb);
|
||||
}
|
||||
|
||||
@@ -2043,6 +2010,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return beanDescriptorManager.getBeanDescriptorByClassName(beanClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initQueryCache(String key) {
|
||||
BeanDescriptor<?> desc = getBeanDescriptorById(key);
|
||||
if (desc != null) {
|
||||
desc.queryCacheInit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Another server in the cluster sent this event so that we can inform local
|
||||
* BeanListeners of inserts updates and deletes that occurred remotely (on
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.api.DerivedRelationshipData;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanManager;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
@@ -134,6 +135,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private boolean requestUpdateAllLoadedProps;
|
||||
|
||||
private long version;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
|
||||
@@ -298,38 +301,47 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return intercept.getDirtyValues();
|
||||
}
|
||||
|
||||
public boolean isNotify() {
|
||||
/**
|
||||
* Set the cache notify status.
|
||||
*/
|
||||
public void setNotifyCache() {
|
||||
this.notifyCache = beanDescriptor.isCacheNotify(publish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this change should notify cache, listener or doc store.
|
||||
*/
|
||||
public boolean isNotify() {
|
||||
return notifyCache || isNotifyPersistListener() || isDocStoreNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request should updateAdd an ElasticSearch index
|
||||
* by queuing an event or direct updateAdd (via Bulk API).
|
||||
* Return true if this request should update the document store.
|
||||
*/
|
||||
private boolean isDocStoreNotify() {
|
||||
return docStoreMode != DocStoreMode.IGNORE;
|
||||
}
|
||||
|
||||
public boolean isNotifyPersistListener() {
|
||||
private boolean isNotifyPersistListener() {
|
||||
return beanPersistListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify/Update the local L2 cache after the transaction has successfully committed.
|
||||
*/
|
||||
public void notifyCache() {
|
||||
public void notifyCache(CacheChangeSet changeSet) {
|
||||
if (notifyCache) {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
beanDescriptor.cacheHandleInsert(this);
|
||||
beanDescriptor.cacheHandleInsert(this, changeSet);
|
||||
break;
|
||||
case UPDATE:
|
||||
beanDescriptor.cacheHandleUpdate(idValue, this);
|
||||
beanDescriptor.cacheHandleUpdate(idValue, this, changeSet);
|
||||
break;
|
||||
case DELETE:
|
||||
case SOFT_DELETE:
|
||||
// Bean deleted from cache early via postDelete()
|
||||
beanDescriptor.cacheHandleDelete(idValue, this, changeSet);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid type " + type);
|
||||
@@ -714,7 +726,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private void postDelete() {
|
||||
beanDescriptor.contextClear(transaction.getPersistenceContext(), idValue);
|
||||
beanDescriptor.cacheHandleDelete(idValue, this);
|
||||
}
|
||||
|
||||
private void changeLog() {
|
||||
@@ -734,8 +745,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
setNotifyCache();
|
||||
|
||||
if (type == Type.UPDATE && docStoreMode == DocStoreMode.UPDATE) {
|
||||
if (type == Type.UPDATE && (notifyCache || docStoreMode == DocStoreMode.UPDATE)) {
|
||||
// get the dirty properties for update notification to the doc store
|
||||
dirtyProperties = intercept.getDirtyProperties();
|
||||
}
|
||||
@@ -870,12 +882,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
* cache on post commit.
|
||||
*/
|
||||
public void addUpdatedManyProperty(BeanPropertyAssocMany<?> updatedAssocMany) {
|
||||
// if (notifyCache) {
|
||||
if (updatedManys == null) {
|
||||
updatedManys = new ArrayList<BeanPropertyAssocMany<?>>(5);
|
||||
}
|
||||
updatedManys.add(updatedAssocMany);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -897,6 +907,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
this.idValue = beanDescriptor.getId(entityBean);
|
||||
}
|
||||
updatedManysOnly = true;
|
||||
setNotifyCache();
|
||||
addEvent();
|
||||
}
|
||||
}
|
||||
@@ -1003,7 +1014,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
* Set the value of the Version property on the bean.
|
||||
*/
|
||||
public void setVersionValue(Object versionValue) {
|
||||
beanDescriptor.getVersionProperty().setValueIntercept(entityBean, versionValue);
|
||||
version = beanDescriptor.setVersion(entityBean, versionValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version in long form (if set).
|
||||
*/
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
|
||||
import com.avaje.ebeaninternal.server.core.CacheOptions;
|
||||
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import com.avaje.ebeaninternal.server.core.DiffHelp;
|
||||
@@ -210,12 +212,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
/**
|
||||
* Map of BeanProperty Linked so as to preserve order.
|
||||
*/
|
||||
private final LinkedHashMap<String, BeanProperty> propMap;
|
||||
protected final LinkedHashMap<String, BeanProperty> propMap;
|
||||
|
||||
/**
|
||||
* The type of bean this describes.
|
||||
*/
|
||||
private final Class<T> beanType;
|
||||
|
||||
protected final Class<?> rootBeanType;
|
||||
|
||||
/**
|
||||
@@ -226,8 +229,6 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
|
||||
private final String[] properties;
|
||||
|
||||
private final int propertyCount;
|
||||
|
||||
/**
|
||||
* Intercept pre post on insert,update, and delete .
|
||||
*/
|
||||
@@ -337,6 +338,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
* All non transient properties excluding the id properties.
|
||||
*/
|
||||
private final BeanProperty[] propertiesNonTransient;
|
||||
protected final BeanProperty[] propertiesIndex;
|
||||
|
||||
/**
|
||||
* The bean class name or the table name for MapBeans.
|
||||
@@ -403,7 +405,6 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
this.serverName = owner.getServerName();
|
||||
this.entityType = deploy.getEntityType();
|
||||
this.properties = deploy.getProperties();
|
||||
this.propertyCount = this.properties.length;
|
||||
this.name = InternString.intern(deploy.getName());
|
||||
this.baseTableAlias = "t0";
|
||||
this.fullName = InternString.intern(deploy.getFullName());
|
||||
@@ -512,12 +513,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
this.idPropertyIndex = -1;
|
||||
this.versionPropertyIndex = -1;
|
||||
this.unloadProperties = new int[0];
|
||||
this.propertiesIndex = new BeanProperty[0];
|
||||
|
||||
} else {
|
||||
EntityBeanIntercept ebi = prototypeEntityBean._ebean_getIntercept();
|
||||
this.idPropertyIndex = (idProperty == null) ? -1 : ebi.findProperty(idProperty.getName());
|
||||
this.versionPropertyIndex = (versionProperty == null) ? -1 : ebi.findProperty(versionProperty.getName());
|
||||
this.unloadProperties = derivePropertiesToUnload(prototypeEntityBean);
|
||||
this.propertiesIndex = new BeanProperty[ebi.getPropertyLength()];
|
||||
for (int i = 0; i < propertiesIndex.length; i++) {
|
||||
propertiesIndex[i] = propMap.get(ebi.getProperty(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,10 +601,6 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
public int getPropertyCount() {
|
||||
return propertyCount;
|
||||
}
|
||||
|
||||
public String[] getProperties() {
|
||||
return properties;
|
||||
}
|
||||
@@ -711,6 +713,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
/**
|
||||
* Initialise the document mapping.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initialiseDocMapping() {
|
||||
for (int i = 0; i < propertiesMany.length; i++) {
|
||||
propertiesMany[i].initialisePostTarget();
|
||||
@@ -892,13 +895,6 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return owner.getEncryptKey(tableName, columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the warming cache query (if defined) and load the cache.
|
||||
*/
|
||||
public void runCacheWarming() {
|
||||
cacheHelp.runCacheWarming(ebeanServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean type has a default select clause that is not
|
||||
* simply select all properties.
|
||||
@@ -1075,6 +1071,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return cacheHelp.isCacheNotify();
|
||||
}
|
||||
|
||||
public void queryCacheInit() {
|
||||
cacheHelp.queryCacheInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the query cache.
|
||||
*/
|
||||
@@ -1096,6 +1096,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
cacheHelp.queryCachePut(id, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a query cache clear into the changeSet.
|
||||
*/
|
||||
public void queryCacheClear(CacheChangeSet changeSet) {
|
||||
cacheHelp.queryCacheClear(changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load the beanCollection from cache return true if successful.
|
||||
*/
|
||||
@@ -1110,39 +1117,70 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
cacheHelp.manyPropPut(many, bc, parentId);
|
||||
}
|
||||
|
||||
public void cacheManyPropRemove(Object parentId, String propertyName) {
|
||||
cacheHelp.manyPropRemove(parentId, propertyName);
|
||||
/**
|
||||
* Update the bean collection entry in the cache.
|
||||
*/
|
||||
public void cacheManyPropPut(String name, Object parentId, CachedManyIds entry) {
|
||||
cacheHelp.cachePutManyIds(parentId, name, entry);
|
||||
}
|
||||
|
||||
public void cacheManyPropRemove(String propertyName, Object parentId) {
|
||||
cacheHelp.manyPropRemove(propertyName, parentId);
|
||||
}
|
||||
|
||||
public void cacheManyPropClear(String propertyName) {
|
||||
cacheHelp.manyPropClear(propertyName);
|
||||
}
|
||||
|
||||
public void cacheBeanPut(T bean) {
|
||||
cacheBeanPutData((EntityBean) bean);
|
||||
/**
|
||||
* Extract the raw cache data from the embedded bean.
|
||||
*/
|
||||
public CachedBeanData cacheEmbeddedBeanExtract(EntityBean bean) {
|
||||
return cacheHelp.beanExtractData(this, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the raw cache data from the bean.
|
||||
* Load the embedded bean (taking into account inheritance).
|
||||
*/
|
||||
public CachedBeanData cacheBeanExtractData(EntityBean bean) {
|
||||
return cacheHelp.beanExtractData(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the raw cache data into the bean.
|
||||
*/
|
||||
public void cacheBeanLoadData(EntityBean bean, CachedBeanData data) {
|
||||
cacheHelp.beanLoadData(bean, data);
|
||||
public EntityBean cacheEmbeddedBeanLoad(CachedBeanData data) {
|
||||
return cacheHelp.embeddedBeanLoad(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache.
|
||||
* Load the embedded bean as the root type.
|
||||
*/
|
||||
public void cacheBeanPutData(EntityBean bean) {
|
||||
EntityBean cacheEmbeddedBeanLoadDirect(CachedBeanData data) {
|
||||
return cacheHelp.embeddedBeanLoadDirect(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the entity bean as the correct bean type.
|
||||
*/
|
||||
EntityBean cacheBeanLoadDirect(Object id, Boolean readOnly, CachedBeanData data) {
|
||||
return cacheHelp.loadBeanDirect(id, readOnly, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the cache.
|
||||
*/
|
||||
public void cacheBeanPut(T bean) {
|
||||
cacheBeanPut((EntityBean) bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache (taking into account inheritance).
|
||||
*/
|
||||
public void cacheBeanPut(EntityBean bean) {
|
||||
cacheHelp.beanCachePut(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the cache as the correct type.
|
||||
*/
|
||||
void cacheBeanPutDirect(EntityBean bean) {
|
||||
cacheHelp.beanCachePutDirect(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a bean from the bean cache (or null).
|
||||
*/
|
||||
@@ -1153,7 +1191,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
public void cacheBeanRemove(Object id) {
|
||||
public void cacheHandleDeleteById(Object id) {
|
||||
cacheHelp.beanCacheRemove(id);
|
||||
}
|
||||
|
||||
@@ -1180,6 +1218,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return cacheHelp.naturalKeyLookup(query, t);
|
||||
}
|
||||
|
||||
public void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
cacheHelp.cacheNaturalKeyPut(id, newKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate parts of cache due to SqlUpdate or external modification etc.
|
||||
*/
|
||||
@@ -1188,21 +1230,38 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
* Handle a delete by id request adding an cache change into the changeSet.
|
||||
*/
|
||||
public void cacheHandleDelete(Object id, PersistRequestBean<T> deleteRequest) {
|
||||
cacheHelp.handleDelete(id, deleteRequest);
|
||||
}
|
||||
|
||||
public void cacheHandleInsert(PersistRequestBean<T> insertRequest) {
|
||||
cacheHelp.handleInsert(insertRequest);
|
||||
public void cacheHandleDeleteById(Object id, CacheChangeSet changeSet) {
|
||||
cacheHelp.handleDelete(id, changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cached bean data.
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
public void cacheHandleUpdate(Object id, PersistRequestBean<T> updateRequest) {
|
||||
cacheHelp.handleUpdate(id, updateRequest);
|
||||
public void cacheHandleDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
|
||||
cacheHelp.handleDelete(id, deleteRequest, changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the insert changes to the changeSet.
|
||||
*/
|
||||
public void cacheHandleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
|
||||
cacheHelp.handleInsert(insertRequest, changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the update to the changeSet.
|
||||
*/
|
||||
public void cacheHandleUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
|
||||
cacheHelp.handleUpdate(id, updateRequest, changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the update to the cache.
|
||||
*/
|
||||
public void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
cacheHelp.cacheBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2096,6 +2155,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return inheritInfo.getDiscriminatorColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the discriminator value for this bean type (or null when there is no inheritance).
|
||||
*/
|
||||
public String getDiscValue() {
|
||||
return inheritInfo == null ? null : inheritInfo.getDiscriminatorStringValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T createBeanUsingDisc(Object discValue) {
|
||||
@@ -2530,6 +2596,25 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return versionPropertyIndex > -1 && ebi.isLoadedProperty(versionPropertyIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version value returning it in primitive long form.
|
||||
*/
|
||||
public long setVersion(EntityBean entityBean, Object versionValue) {
|
||||
versionProperty.setValueIntercept(entityBean, versionValue);
|
||||
return versionProperty.scalarType.asVersion(versionValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version value in primitive long form (if exists and set).
|
||||
*/
|
||||
public long getVersion(EntityBean entityBean) {
|
||||
if (versionProperty == null) {
|
||||
return 0;
|
||||
}
|
||||
Object value = versionProperty.getValue(entityBean);
|
||||
return value == null ? 0 : versionProperty.scalarType.asVersion(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for mutable scalar types and mark as dirty if necessary.
|
||||
*/
|
||||
|
||||
+265
-173
@@ -1,14 +1,5 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
@@ -18,29 +9,36 @@ import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanDataFromBean;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanDataToBean;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanDataUpdate;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
|
||||
import com.avaje.ebeaninternal.server.core.CacheOptions;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper for BeanDescriptor that manages the bean, query and collection caches.
|
||||
*
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
public final class BeanDescriptorCacheHelp<T> {
|
||||
final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private static final Logger queryLog = LoggerFactory.getLogger("org.avaje.ebean.cache.QUERY");
|
||||
private static final Logger beanLog = LoggerFactory.getLogger("org.avaje.ebean.cache.BEAN");
|
||||
private static final Logger manyLog = LoggerFactory.getLogger("org.avaje.ebean.cache.COLL");
|
||||
private static final Logger natLog = LoggerFactory.getLogger("org.avaje.ebean.cache.NATKEY");
|
||||
|
||||
public static final Logger queryLog = LoggerFactory.getLogger("org.avaje.ebean.cache.QUERY");
|
||||
public static final Logger beanLog = LoggerFactory.getLogger("org.avaje.ebean.cache.BEAN");
|
||||
public static final Logger manyLog = LoggerFactory.getLogger("org.avaje.ebean.cache.COLL");
|
||||
public static final Logger natLog = LoggerFactory.getLogger("org.avaje.ebean.cache.NATKEY");
|
||||
|
||||
|
||||
private final BeanDescriptor<T> desc;
|
||||
|
||||
private final ServerCacheManager cacheManager;
|
||||
@@ -52,26 +50,28 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
private final boolean cacheSharableBeans;
|
||||
|
||||
private final Class<T> beanType;
|
||||
private final Class<?> beanType;
|
||||
|
||||
private final String cacheName;
|
||||
|
||||
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
|
||||
private final String naturalKeyProperty;
|
||||
|
||||
private ServerCache beanCache;
|
||||
private ServerCache naturalKeyCache;
|
||||
private ServerCache queryCache;
|
||||
private volatile ServerCache queryCache;
|
||||
|
||||
public BeanDescriptorCacheHelp(BeanDescriptor<T> desc, ServerCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
BeanDescriptorCacheHelp(BeanDescriptor<T> desc, ServerCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
|
||||
this.desc = desc;
|
||||
this.beanType = desc.getBeanType();
|
||||
this.beanType = desc.rootBeanType;
|
||||
this.cacheName = beanType.getSimpleName();
|
||||
this.cacheManager = cacheManager;
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.cacheSharableBeans = cacheSharableBeans;
|
||||
this.propertiesOneImported = propertiesOneImported;
|
||||
this.naturalKeyProperty = cacheOptions.getNaturalKey();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,26 +86,6 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the warming cache query (if defined) and load the cache.
|
||||
*/
|
||||
public void runCacheWarming(EbeanServer ebeanServer) {
|
||||
if (cacheOptions == null) {
|
||||
return;
|
||||
}
|
||||
String warmingQuery = cacheOptions.getWarmingQuery();
|
||||
if (warmingQuery != null && warmingQuery.trim().length() > 0) {
|
||||
Query<?> query = ebeanServer.createQuery(beanType, warmingQuery);
|
||||
query.setUseCache(true);
|
||||
query.setReadOnly(true);
|
||||
query.setLoadBeanCache(true);
|
||||
List<?> list = query.findList();
|
||||
if (beanLog.isInfoEnabled()) {
|
||||
beanLog.info("Loaded {} cache with [{}] beans", cacheName, list.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setUseCache(boolean useCache) {
|
||||
if (useCache) {
|
||||
getBeanCache();
|
||||
@@ -118,21 +98,21 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Return true if there is currently query caching for this type of bean.
|
||||
*/
|
||||
public boolean isQueryCaching() {
|
||||
private boolean isQueryCaching() {
|
||||
return queryCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is currently bean caching for this type of bean.
|
||||
*/
|
||||
public boolean isBeanCaching() {
|
||||
boolean isBeanCaching() {
|
||||
return beanCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the persist request needs to notify the cache.
|
||||
*/
|
||||
public boolean isCacheNotify() {
|
||||
boolean isCacheNotify() {
|
||||
|
||||
if (isBeanCaching() || isQueryCaching()) {
|
||||
return true;
|
||||
@@ -145,14 +125,25 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
public CacheOptions getCacheOptions() {
|
||||
CacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the query cache if required
|
||||
* (as some node in the cluster already has it).
|
||||
*/
|
||||
void queryCacheInit() {
|
||||
if (queryCache == null) {
|
||||
queryLog.debug(" init {}", cacheName);
|
||||
queryCache = cacheManager.getQueryCache(beanType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the query cache.
|
||||
*/
|
||||
public void queryCacheClear() {
|
||||
void queryCacheClear() {
|
||||
if (queryCache != null) {
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" CLEAR {}", cacheName);
|
||||
@@ -161,34 +152,50 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query cache clear to the changeSet.
|
||||
*/
|
||||
void queryCacheClear(CacheChangeSet changeSet) {
|
||||
if (queryCache != null) {
|
||||
changeSet.addClearQuery(desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query result from the query cache.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public BeanCollection<T> queryCacheGet(Object id) {
|
||||
BeanCollection<T> queryCacheGet(Object id) {
|
||||
if (queryCache == null) {
|
||||
return null;
|
||||
} else {
|
||||
return (BeanCollection<T>) queryCache.get(id);
|
||||
BeanCollection<T> list = (BeanCollection<T>) queryCache.get(id);
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
if (list == null) {
|
||||
queryLog.debug(" GET {}({}) - cache miss", cacheName, id);
|
||||
} else {
|
||||
queryLog.debug(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query result into the query cache.
|
||||
*/
|
||||
public void queryCachePut(Object id, BeanCollection<T> query) {
|
||||
void queryCachePut(Object id, BeanCollection<T> query) {
|
||||
if (queryCache == null) {
|
||||
queryCache = cacheManager.getQueryCache(beanType);
|
||||
}
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" PUT {} {}", cacheName, id);
|
||||
queryLog.debug(" PUT {}({})", cacheName, id);
|
||||
}
|
||||
queryCache.put(id, query);
|
||||
}
|
||||
|
||||
|
||||
public void manyPropRemove(Object parentId, String propertyName) {
|
||||
void manyPropRemove(String propertyName, Object parentId) {
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
|
||||
if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" REMOVE {}({}).{}", cacheName, parentId, propertyName);
|
||||
@@ -196,7 +203,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
collectionIdsCache.remove(parentId);
|
||||
}
|
||||
|
||||
public void manyPropClear(String propertyName) {
|
||||
void manyPropClear(String propertyName) {
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
|
||||
if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" CLEAR {}(*).{} ", cacheName, propertyName);
|
||||
@@ -207,7 +214,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Return the CachedManyIds for a given bean many property. Returns null if not in the cache.
|
||||
*/
|
||||
public CachedManyIds manyPropGet(Object parentId, String propertyName) {
|
||||
private CachedManyIds manyPropGet(Object parentId, String propertyName) {
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
|
||||
CachedManyIds entry = (CachedManyIds) collectionIdsCache.get(parentId);
|
||||
if (entry == null) {
|
||||
@@ -223,7 +230,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Try to load the bean collection from cache return true if successful.
|
||||
*/
|
||||
public boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
|
||||
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
|
||||
|
||||
CachedManyIds entry = manyPropGet(parentId, many.getName());
|
||||
if (entry == null) {
|
||||
@@ -254,30 +261,37 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Put the beanCollection into the cache.
|
||||
*/
|
||||
public void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
ArrayList<Object> idList = new ArrayList<Object>();
|
||||
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
|
||||
|
||||
// get the underlying collection of beans (in the List, Set or Map)
|
||||
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
|
||||
|
||||
for (Object bean : actualDetails) {
|
||||
// Collect the id values
|
||||
idList.add(targetDescriptor.getId((EntityBean) bean));
|
||||
}
|
||||
|
||||
CachedManyIds entry = new CachedManyIds(idList);
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, many.getName());
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
cachePutManyIds(parentId, many.getName(), entry);
|
||||
}
|
||||
|
||||
void cachePutManyIds(Object parentId, String manyName, CachedManyIds entry) {
|
||||
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName);
|
||||
if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, many.getName(), entry);
|
||||
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, manyName, entry);
|
||||
}
|
||||
collectionIdsCache.put(parentId, entry);
|
||||
}
|
||||
|
||||
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
|
||||
public T naturalKeyLookup(SpiQuery<T> query, SpiTransaction t) {
|
||||
List<Object> idList = new ArrayList<Object>();
|
||||
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
|
||||
for (Object bean : actualDetails) {
|
||||
idList.add(targetDescriptor.getId((EntityBean) bean));
|
||||
}
|
||||
return new CachedManyIds(idList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the bean using the natural key lookup if available.
|
||||
*/
|
||||
T naturalKeyLookup(SpiQuery<T> query, SpiTransaction t) {
|
||||
|
||||
if (!isNaturalKeyCaching(query.isUseBeanCache())) {
|
||||
// no natural key caching for this query
|
||||
@@ -327,8 +341,6 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
return propName != null && propName.equals(cacheOptions.getNaturalKey());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* For a bean built from the cache this sets up its persistence context for future lazy loading etc.
|
||||
*/
|
||||
@@ -359,7 +371,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Clear the bean cache.
|
||||
*/
|
||||
public void beanCacheClear() {
|
||||
private void beanCacheClear() {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" CLEAR {}", cacheName);
|
||||
@@ -368,29 +380,37 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public CachedBeanData beanExtractData(EntityBean bean) {
|
||||
return CachedBeanDataFromBean.extract(desc, bean);
|
||||
CachedBeanData beanExtractData(BeanDescriptor<?> targetDesc, EntityBean bean) {
|
||||
return CachedBeanDataFromBean.extract(targetDesc, bean);
|
||||
}
|
||||
|
||||
public void beanLoadData(EntityBean bean, CachedBeanData data) {
|
||||
CachedBeanDataToBean.load(desc, bean, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache.
|
||||
*/
|
||||
public void beanCachePut(EntityBean bean) {
|
||||
void beanCachePut(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(bean);
|
||||
if (desc.inheritInfo != null) {
|
||||
desc.inheritInfo.readType(bean.getClass()).getBeanDescriptor().cacheBeanPutDirect(bean);
|
||||
} else {
|
||||
beanCachePutDirect(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
void beanCachePutDirect(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
|
||||
Object id = desc.getId(bean);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" PUT {}({})", cacheName, id);
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, id, beanData);
|
||||
}
|
||||
getBeanCache().put(id, beanData);
|
||||
|
||||
if (beanData.isNaturalKeyUpdate() && naturalKeyCache != null) {
|
||||
Object naturalKey = beanData.getNaturalKey();
|
||||
|
||||
if (naturalKeyProperty != null) {
|
||||
Object naturalKey = beanData.getData(naturalKeyProperty);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
|
||||
@@ -400,11 +420,11 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public CachedBeanData beanCacheGetData(Object id) {
|
||||
CachedBeanData beanCacheGetData(Object id) {
|
||||
return (CachedBeanData) getBeanCache().get(id);
|
||||
}
|
||||
|
||||
public T beanCacheGet(SpiQuery<T> query, PersistenceContext context) {
|
||||
T beanCacheGet(SpiQuery<T> query, PersistenceContext context) {
|
||||
Object id = desc.convertId(query.getId());
|
||||
T bean = beanCacheGetInternal(id, query.isReadOnly());
|
||||
if (bean != null) {
|
||||
@@ -439,30 +459,80 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
return (T)loadBean(id, readOnly, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the entity bean taking into account inheritance.
|
||||
*/
|
||||
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data) {
|
||||
|
||||
String discValue = data.getDiscValue();
|
||||
if (discValue == null) {
|
||||
return loadBeanDirect(id, readOnly, data);
|
||||
} else {
|
||||
return rootDescriptor(discValue).cacheBeanLoadDirect(id, readOnly, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root BeanDescriptor for inheritance.
|
||||
*/
|
||||
private BeanDescriptor<?> rootDescriptor(String discValue) {
|
||||
InheritInfo inheritInfo = desc.inheritInfo.readType(discValue);
|
||||
return inheritInfo.getBeanDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the entity bean from cache data given this is the root bean type.
|
||||
*/
|
||||
EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data) {
|
||||
|
||||
EntityBean bean = desc.createEntityBean();
|
||||
desc.convertSetId(id, bean);
|
||||
CachedBeanDataToBean.load(desc, bean, data);
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
ebi.setBeanLoader(desc.getEbeanServer());
|
||||
|
||||
if (Boolean.TRUE.equals(readOnly)) {
|
||||
ebi.setReadOnly(true);
|
||||
}
|
||||
|
||||
beanLoadData(bean, data);
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
if (desc.isReadAuditing()) {
|
||||
desc.readAuditBean("l2", "", bean);
|
||||
}
|
||||
return (T) bean;
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the embedded bean checking for inheritance.
|
||||
*/
|
||||
EntityBean embeddedBeanLoad(CachedBeanData data) {
|
||||
|
||||
String discValue = data.getDiscValue();
|
||||
if (discValue == null) {
|
||||
return embeddedBeanLoadDirect(data);
|
||||
} else {
|
||||
return rootDescriptor(discValue).cacheEmbeddedBeanLoadDirect(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the embedded bean given this is the bean type.
|
||||
*/
|
||||
EntityBean embeddedBeanLoadDirect(CachedBeanData data) {
|
||||
EntityBean bean = desc.createEntityBean();
|
||||
CachedBeanDataToBean.load(desc, bean, data);
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
public void beanCacheRemove(Object id) {
|
||||
void beanCacheRemove(Object id) {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, id);
|
||||
@@ -477,7 +547,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
public boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id) {
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id);
|
||||
if (cacheData == null) {
|
||||
@@ -487,7 +557,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
return false;
|
||||
}
|
||||
int lazyLoadProperty = ebi.getLazyLoadPropertyIndex();
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(lazyLoadProperty)) {
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(ebi.getLazyLoadProperty())) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property", cacheName, id);
|
||||
}
|
||||
@@ -500,112 +570,83 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
* Add appropriate cache changes to support delete.
|
||||
*/
|
||||
public void handleDelete(Object id, PersistRequestBean<T> deleteRequest) {
|
||||
if (queryCache != null) {
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" CLEAR {}(*) - delete trigger", cacheName);
|
||||
}
|
||||
queryCache.clear();
|
||||
}
|
||||
void handleDelete(Object id, CacheChangeSet changeSet) {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, id);
|
||||
}
|
||||
beanCache.remove(id);
|
||||
}
|
||||
for (int i = 0; i < propertiesOneImported.length; i++) {
|
||||
BeanPropertyAssocMany<?> many = propertiesOneImported[i].getRelationshipProperty();
|
||||
if (many != null) {
|
||||
propertiesOneImported[i].cacheDelete(true, deleteRequest.getEntityBean());
|
||||
}
|
||||
changeSet.addBeanRemove(desc, id);
|
||||
}
|
||||
cacheDeleteImported(true, null, changeSet);
|
||||
}
|
||||
|
||||
public void handleInsert(PersistRequestBean<T> insertRequest) {
|
||||
if (queryCache != null) {
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" CLEAR {}(*) - insert trigger", cacheName);
|
||||
}
|
||||
queryCache.clear();
|
||||
/**
|
||||
* Add appropriate cache changes to support delete.
|
||||
*/
|
||||
void handleDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache != null) {
|
||||
changeSet.addBeanRemove(desc, id);
|
||||
}
|
||||
cacheDeleteImported(true, deleteRequest.getEntityBean(), changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add appropriate cache changes to support insert.
|
||||
*/
|
||||
void handleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
|
||||
queryCacheClear(changeSet);
|
||||
cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet);
|
||||
}
|
||||
|
||||
private void cacheDeleteImported(boolean clear, EntityBean entityBean, CacheChangeSet changeSet) {
|
||||
for (int i = 0; i < propertiesOneImported.length; i++) {
|
||||
propertiesOneImported[i].cacheDelete(false, insertRequest.getEntityBean());
|
||||
propertiesOneImported[i].cacheDelete(clear, entityBean, changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cached bean data.
|
||||
* Add appropriate changes to support update.
|
||||
*/
|
||||
public void handleUpdate(Object id, PersistRequestBean<T> updateRequest) {
|
||||
void handleUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
|
||||
|
||||
if (queryCache != null) {
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" CLEAR {}(*) - update trigger", cacheName);
|
||||
}
|
||||
queryCache.clear();
|
||||
}
|
||||
queryCacheClear(changeSet);
|
||||
|
||||
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
|
||||
if (manyCollections != null) {
|
||||
// clear the appropriate manyProp caches first
|
||||
for (int i = 0; i < manyCollections.size(); i++) {
|
||||
manyPropRemove(id, manyCollections.get(i).getName());
|
||||
BeanPropertyAssocMany<?> many = manyCollections.get(i);
|
||||
Object details = many.getValue(updateRequest.getEntityBean());
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
changeSet.addManyPut(desc, many.getName(), id, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// check if the bean itself was updated
|
||||
if (!updateRequest.isUpdatedManysOnly()) {
|
||||
|
||||
// update the bean cache entry if it exists
|
||||
ServerCache cache = getBeanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(id);
|
||||
if (existingData != null) {
|
||||
|
||||
if (isCachedDataTooOld(existingData)) {
|
||||
// just remove the entry from the cache
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({}) - entry too old", cacheName, id);
|
||||
}
|
||||
cache.remove(id);
|
||||
|
||||
} else {
|
||||
// Update the cache data with the changes from our update
|
||||
CachedBeanData newData = CachedBeanDataUpdate.update(desc, existingData, updateRequest.getEntityBean());
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" UPDATE {}({})", cacheName, id);
|
||||
}
|
||||
cache.put(id, newData);
|
||||
if (newData.isNaturalKeyUpdate() && naturalKeyCache != null) {
|
||||
|
||||
Object oldKey = newData.getOldNaturalKey();
|
||||
Object newKey = newData.getNaturalKey();
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} PUT({}, {}) REMOVE({})", cacheName, newKey, id, oldKey);
|
||||
}
|
||||
|
||||
if (oldKey != null) {
|
||||
naturalKeyCache.remove(oldKey);
|
||||
}
|
||||
if (newKey != null) {
|
||||
naturalKeyCache.put(newKey, id);
|
||||
|
||||
boolean updateNaturalKey = false;
|
||||
|
||||
Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
||||
EntityBean bean = updateRequest.getEntityBean();
|
||||
boolean[] dirtyProperties = updateRequest.getDirtyProperties();
|
||||
for (int i = 0; i < dirtyProperties.length; i++) {
|
||||
if (dirtyProperties[i]) {
|
||||
BeanProperty property = desc.propertiesIndex[i];
|
||||
if (property.isCacheDataInclude()) {
|
||||
Object val = property.getCacheDataValue(bean);
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(desc, id, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeSet.addBeanUpdate(desc, id, changes, updateNaturalKey, updateRequest.getVersion());
|
||||
}
|
||||
|
||||
if (manyCollections != null) {
|
||||
for (int i = 0; i < manyCollections.size(); i++) {
|
||||
BeanPropertyAssocMany<?> many = manyCollections.get(i);
|
||||
Object manyValue = many.getValue(updateRequest.getEntityBean());
|
||||
manyPropPut(many, manyValue, id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean isCachedDataTooOld(CachedBeanData existingData) {
|
||||
@@ -615,7 +656,7 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Invalidate parts of cache due to SqlUpdate or external modification etc.
|
||||
*/
|
||||
public void handleBulkUpdate(TableIUD tableIUD) {
|
||||
void handleBulkUpdate(TableIUD tableIUD) {
|
||||
// inserts don't invalidate the bean cache
|
||||
if (tableIUD.isUpdateOrDelete()) {
|
||||
beanCacheClear();
|
||||
@@ -623,4 +664,55 @@ public final class BeanDescriptorCacheHelp<T> {
|
||||
// any change invalidates the query cache
|
||||
queryCacheClear();
|
||||
}
|
||||
|
||||
void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
if (newKey != null) {
|
||||
naturalKeyCache.put(newKey, id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply changes to the bean cache entry.
|
||||
*/
|
||||
void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
|
||||
ServerCache cache = getBeanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(id);
|
||||
if (existingData != null) {
|
||||
if (isCachedDataTooOld(existingData)) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({}) - entry too old", cacheName, id);
|
||||
}
|
||||
cache.remove(id);
|
||||
} else {
|
||||
long currentVersion = existingData.getVersion();
|
||||
if (version > 0 && version < currentVersion) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, id, currentVersion, version);
|
||||
}
|
||||
cache.remove(id);
|
||||
} else {
|
||||
if (version == 0) {
|
||||
version = currentVersion;
|
||||
}
|
||||
CachedBeanData newData = existingData.update(changes, version);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, id, changes);
|
||||
}
|
||||
cache.put(id, newData);
|
||||
}
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = existingData.getData(naturalKeyProperty);
|
||||
if (oldKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
|
||||
}
|
||||
naturalKeyCache.remove(oldKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
|
||||
String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig);
|
||||
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix);
|
||||
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig.isDisableL2Cache());
|
||||
this.bootupClasses = config.getBootupClasses();
|
||||
this.createProperties = config.getDeployCreateProperties();
|
||||
this.namingConvention = serverConfig.getNamingConvention();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.StringParser;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
|
||||
/**
|
||||
@@ -123,6 +124,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
|
||||
throw new RuntimeException("Not Supported or Expected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false as not an AssocOne.
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.config.EncryptKey;
|
||||
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeanservice.docstore.api.mapping.DocMappingBuilder;
|
||||
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyMapping;
|
||||
import com.avaje.ebean.plugin.Property;
|
||||
@@ -732,11 +733,42 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
return prefix + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + "] threw error";
|
||||
}
|
||||
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
return getValue(bean);
|
||||
/**
|
||||
* Return true if this property should be included in the cache bean data.
|
||||
*/
|
||||
public boolean isCacheDataInclude() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for this property which we hold in the L2 cache entry.
|
||||
* <p>
|
||||
* This uses format() where possible to store the value as a string and this
|
||||
* is done to make any resulting Java object serialisation content smaller as
|
||||
* strings get special treatment.
|
||||
* </p>
|
||||
*/
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
Object value = getValue(bean);
|
||||
if (value == null || scalarType.isBinaryType()) {
|
||||
return value;
|
||||
} else {
|
||||
// convert to string as an optimisation for java object serialisation
|
||||
return scalarType.format(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value for this property from L2 cache entry and set it to the bean.
|
||||
* <p>
|
||||
* This uses parse() as per the comment in getCacheDataValue().
|
||||
* </p>
|
||||
*/
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData) {
|
||||
if (cacheData instanceof String) {
|
||||
// parse back from string to support optimisation of java object serialisation
|
||||
cacheData = scalarType.parse((String)cacheData);
|
||||
}
|
||||
setValue(bean, cacheData);
|
||||
}
|
||||
|
||||
@@ -834,6 +866,12 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
|
||||
// overridden in BanePropertyAssocMany
|
||||
throw new RuntimeException("Not Supported or Expected");
|
||||
}
|
||||
|
||||
public Object[] getAssocIdValues(EntityBean bean) {
|
||||
// Returns null as not an AssocOne.
|
||||
return null;
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.avaje.ebean.bean.BeanCollectionAdd;
|
||||
import com.avaje.ebean.bean.BeanCollectionLoader;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
|
||||
@@ -325,6 +326,15 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
return server.findIds(q, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude many properties from bean cache data.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCacheDataInclude() {
|
||||
// this would change for DB Array type support
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the loaded current bean to its associated parent.
|
||||
*/
|
||||
@@ -481,6 +491,28 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
help.add(collection, bean, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
SpiQuery<?> query = request.getQueryRequest().getQuery();
|
||||
if (manyToMany) {
|
||||
sb.append(query.isAsDraft() ? intersectionDraftTable : intersectionPublishTable);
|
||||
} else {
|
||||
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
|
||||
}
|
||||
sb.append(" where ");
|
||||
for (int i = 0; i < exportedProperties.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
exportedProperties[i].appendWhere(sb, path);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id values from the given bean.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.avaje.ebean.SqlUpdate;
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.ValuePair;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
|
||||
import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
|
||||
@@ -142,19 +143,20 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
void cacheDelete(boolean clearOnNull, EntityBean bean) {
|
||||
void cacheDelete(boolean clear, EntityBean bean, CacheChangeSet changeSet) {
|
||||
|
||||
if (targetDescriptor.isBeanCaching() && relationshipProperty != null) {
|
||||
Object assocBean = getValue(bean);
|
||||
if (assocBean != null) {
|
||||
Object parentId = targetDescriptor.getId((EntityBean) assocBean);
|
||||
if (parentId != null) {
|
||||
targetDescriptor.cacheManyPropRemove(parentId, relationshipProperty.getName());
|
||||
return;
|
||||
if (clear) {
|
||||
changeSet.addManyClear(targetDescriptor, relationshipProperty.getName());
|
||||
} else {
|
||||
Object assocBean = getValue(bean);
|
||||
if (assocBean != null) {
|
||||
Object parentId = targetDescriptor.getId((EntityBean) assocBean);
|
||||
if (parentId != null) {
|
||||
changeSet.addManyRemove(targetDescriptor, relationshipProperty.getName(), parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clearOnNull) {
|
||||
targetDescriptor.cacheManyPropClear(relationshipProperty.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,30 +385,31 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
return getPropertyType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
Object ap = getValue(bean);
|
||||
if (ap == null) {
|
||||
return null;
|
||||
}
|
||||
if (embedded) {
|
||||
return targetDescriptor.cacheBeanExtractData((EntityBean) ap);
|
||||
|
||||
return targetDescriptor.cacheEmbeddedBeanExtract((EntityBean) ap);
|
||||
} else {
|
||||
return targetDescriptor.getId((EntityBean) ap);
|
||||
return targetDescriptor.getIdProperty().getCacheDataValue((EntityBean) ap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData) {
|
||||
if (cacheData != null) {
|
||||
if (cacheData == null) {
|
||||
setValue(bean, null);
|
||||
} else {
|
||||
if (embedded) {
|
||||
EntityBean embeddedBean = targetDescriptor.createEntityBean();
|
||||
targetDescriptor.cacheBeanLoadData(embeddedBean, (CachedBeanData) cacheData);
|
||||
setValue(bean, embeddedBean);
|
||||
|
||||
setValue(bean, targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData));
|
||||
} else {
|
||||
T ref = targetDescriptor.createReference(Boolean.FALSE, cacheData);
|
||||
setValue(bean, ref);
|
||||
if (cacheData instanceof String) {
|
||||
cacheData = targetDescriptor.getIdProperty().scalarType.parse((String)cacheData);
|
||||
}
|
||||
setValue(bean, targetDescriptor.createReference(Boolean.FALSE, cacheData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,43 +11,53 @@ import com.avaje.ebeaninternal.server.core.InternString;
|
||||
*/
|
||||
public class ExportedProperty {
|
||||
|
||||
private final String foreignDbColumn;
|
||||
|
||||
private final BeanProperty property;
|
||||
private final String foreignDbColumn;
|
||||
|
||||
private final boolean embedded;
|
||||
|
||||
public ExportedProperty(boolean embedded, String foreignDbColumn, BeanProperty property) {
|
||||
this.embedded = embedded;
|
||||
this.foreignDbColumn = InternString.intern(foreignDbColumn);
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is part of an embedded concatinated key.
|
||||
*/
|
||||
public boolean isEmbedded() {
|
||||
return embedded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value from the bean.
|
||||
*/
|
||||
public Object getValue(EntityBean bean){
|
||||
return property.getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign database column matching this property.
|
||||
* <p>
|
||||
* We use this foreign database column in the query predicates
|
||||
* in preference to a parentProperty.idProperty = value.
|
||||
* Just using the foreign database column avoids triggering
|
||||
* a join to the 'parent' table.
|
||||
* </p>
|
||||
*/
|
||||
public String getForeignDbColumn() {
|
||||
return foreignDbColumn;
|
||||
}
|
||||
private final BeanProperty property;
|
||||
|
||||
private final boolean embedded;
|
||||
|
||||
public ExportedProperty(boolean embedded, String foreignDbColumn, BeanProperty property) {
|
||||
this.embedded = embedded;
|
||||
this.foreignDbColumn = InternString.intern(foreignDbColumn);
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is part of an embedded concatinated key.
|
||||
*/
|
||||
public boolean isEmbedded() {
|
||||
return embedded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value from the bean.
|
||||
*/
|
||||
public Object getValue(EntityBean bean) {
|
||||
return property.getValue(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign database column matching this property.
|
||||
* <p>
|
||||
* We use this foreign database column in the query predicates
|
||||
* in preference to a parentProperty.idProperty = value.
|
||||
* Just using the foreign database column avoids triggering
|
||||
* a join to the 'parent' table.
|
||||
* </p>
|
||||
*/
|
||||
public String getForeignDbColumn() {
|
||||
return foreignDbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a logical where for the foreign db column to logical property name,
|
||||
*/
|
||||
public void appendWhere(StringBuilder sb, String path) {
|
||||
sb.append(foreignDbColumn).append(" = ");
|
||||
if (path != null) {
|
||||
sb.append(path).append(".");
|
||||
}
|
||||
sb.append(property.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,16 @@ public class AnnotationClass extends AnnotationParser {
|
||||
|
||||
private final String versionsBetweenSuffix;
|
||||
|
||||
private final boolean disableL2Cache;
|
||||
|
||||
/**
|
||||
* Create for normal early parse of class level annotations.
|
||||
*/
|
||||
public AnnotationClass(DeployBeanInfo<?> info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix) {
|
||||
public AnnotationClass(DeployBeanInfo<?> info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix, boolean disableL2Cache) {
|
||||
super(info, validationAnnotations);
|
||||
this.asOfViewSuffix = asOfViewSuffix;
|
||||
this.versionsBetweenSuffix = versionsBetweenSuffix;
|
||||
this.disableL2Cache = disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +63,7 @@ public class AnnotationClass extends AnnotationParser {
|
||||
super(info, false);
|
||||
this.asOfViewSuffix = null;
|
||||
this.versionsBetweenSuffix = null;
|
||||
this.disableL2Cache = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,6 +214,9 @@ public class AnnotationClass extends AnnotationParser {
|
||||
|
||||
private void readCacheStrategy(CacheStrategy cacheStrategy, CacheTuning cacheTuning) {
|
||||
|
||||
if (disableL2Cache) {
|
||||
return;
|
||||
}
|
||||
CacheOptions cacheOptions = descriptor.getCacheOptions();
|
||||
if (cacheTuning != null) {
|
||||
cacheOptions.setMaxSecsToLive(cacheTuning.maxSecsToLive());
|
||||
@@ -218,7 +225,6 @@ public class AnnotationClass extends AnnotationParser {
|
||||
if (cacheStrategy != null) {
|
||||
cacheOptions.setUseCache(cacheStrategy.useBeanCache());
|
||||
cacheOptions.setReadOnly(cacheStrategy.readOnly());
|
||||
cacheOptions.setWarmingQuery(cacheStrategy.warmingQuery());
|
||||
if (cacheStrategy.naturalKey().length() > 0) {
|
||||
String propName = cacheStrategy.naturalKey().trim();
|
||||
DeployBeanProperty beanProperty = descriptor.getBeanProperty(propName);
|
||||
|
||||
@@ -32,12 +32,15 @@ public class ReadAnnotations {
|
||||
*/
|
||||
private final boolean jacksonAnnotations;
|
||||
|
||||
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix) {
|
||||
private final boolean disableL2Cache;
|
||||
|
||||
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, boolean disableL2Cache) {
|
||||
this.generatedPropFactory = generatedPropFactory;
|
||||
this.asOfViewSuffix = asOfViewSuffix;
|
||||
this.versionsBetweenSuffix = versionsBetweenSuffix;
|
||||
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
|
||||
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
|
||||
this.disableL2Cache = disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +53,7 @@ public class ReadAnnotations {
|
||||
public void readInitial(DeployBeanInfo<?> info, boolean eagerFetchLobs) {
|
||||
|
||||
try {
|
||||
new AnnotationClass(info, javaxValidationAnnotations, asOfViewSuffix, versionsBetweenSuffix).parse();
|
||||
new AnnotationClass(info, javaxValidationAnnotations, asOfViewSuffix, versionsBetweenSuffix, disableL2Cache).parse();
|
||||
new AnnotationFields(generatedPropFactory, info, javaxValidationAnnotations, jacksonAnnotations, eagerFetchLobs).parse();
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.el;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.StringParser;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
@@ -154,6 +155,11 @@ public class ElPropertyChain implements ElPropertyValue {
|
||||
return lastElPropertyValue.isLocalEncrypted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
|
||||
return lastElPropertyValue.getAssocIsEmpty(request, path);
|
||||
}
|
||||
|
||||
public Object[] getAssocIdValues(EntityBean bean) {
|
||||
// Don't navigate the object graph as bean
|
||||
// is assumed to be the appropriate type
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.el;
|
||||
|
||||
import com.avaje.ebean.plugin.ExpressionPath;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
/**
|
||||
* The expression language object that can get values.
|
||||
@@ -20,6 +21,11 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
|
||||
*/
|
||||
String getAssocIdInExpr(String prefix);
|
||||
|
||||
/**
|
||||
* Return the logical where clause to support "Is empty".
|
||||
*/
|
||||
String getAssocIsEmpty(SpiExpressionRequest request, String path);
|
||||
|
||||
/**
|
||||
* Return true if this is an ManyToOne or OneToOne associated bean property.
|
||||
*/
|
||||
|
||||
@@ -28,12 +28,19 @@ public abstract class AbstractExpression implements SpiExpression {
|
||||
@Override
|
||||
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
|
||||
|
||||
if (propName != null) {
|
||||
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propName);
|
||||
propertyContainsMany(propName, desc, manyWhereJoin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the logical property path for containing a 'many' property.
|
||||
*/
|
||||
protected void propertyContainsMany(String propertyName, BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
|
||||
if (propertyName != null) {
|
||||
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName);
|
||||
if (elProp != null) {
|
||||
if (elProp.containsFormulaWithJoin()) {
|
||||
// for findRowCount query select clause
|
||||
manyWhereJoin.addFormulaWithJoin(propName);
|
||||
manyWhereJoin.addFormulaWithJoin(propertyName);
|
||||
}
|
||||
if (elProp.containsMany()) {
|
||||
// for findRowCount we join to a many property
|
||||
|
||||
@@ -348,6 +348,15 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
return new ExistsQueryExpression((SpiQuery<?>) subQuery, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression isEmpty(String propertyName) {
|
||||
return new IsEmptyExpression(propertyName, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression isNotEmpty(String propertyName) {
|
||||
return new IsEmptyExpression(propertyName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Id Equal to - ID property is equal to the value.
|
||||
|
||||
@@ -742,6 +742,18 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> isEmpty(String propertyName) {
|
||||
add(expr.isEmpty(propertyName));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> isNotEmpty(String propertyName) {
|
||||
add(expr.isNotEmpty(propertyName));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> exists(Query<?> subQuery) {
|
||||
add(expr.exists(subQuery));
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.ManyWhereJoins;
|
||||
import com.avaje.ebeaninternal.api.SpiExpression;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class IsEmptyExpression extends AbstractExpression {
|
||||
|
||||
private final boolean empty;
|
||||
|
||||
private final String propertyPath;
|
||||
|
||||
IsEmptyExpression(String propertyName, boolean empty) {
|
||||
super(propertyName);
|
||||
this.empty = empty;
|
||||
this.propertyPath = SplitName.split(propertyName)[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDocQuery(DocQueryContext context) throws IOException {
|
||||
context.writeExists(!empty, propName);
|
||||
}
|
||||
|
||||
public final String getPropName() {
|
||||
return propName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindValues(SpiExpressionRequest request) {
|
||||
// no bind values
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
|
||||
// we don't want the extra join
|
||||
propertyContainsMany(propertyPath, desc, manyWhereJoin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
|
||||
ElPropertyValue prop = getElProp(request);
|
||||
if (prop == null) {
|
||||
throw new IllegalStateException("Property [" + propName + "] not found");
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
request.append("not ");
|
||||
}
|
||||
|
||||
request
|
||||
.append("exists (select 1 from ")
|
||||
.append(prop.getAssocIsEmpty(request, propertyPath))
|
||||
.append(")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the type and propertyName.
|
||||
*/
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(IsEmptyExpression.class).add(propName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int queryBindHash() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof IsEmptyExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IsEmptyExpression that = (IsEmptyExpression) other;
|
||||
return this.propName.equals(that.propName)
|
||||
&& this.empty == that.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
return (other instanceof IsEmptyExpression);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ import java.util.Set;
|
||||
*/
|
||||
class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, ExpressionList<T> {
|
||||
|
||||
protected final DefaultExpressionList<T> exprList;
|
||||
private final DefaultExpressionList<T> exprList;
|
||||
|
||||
protected final Junction.Type type;
|
||||
|
||||
@@ -547,6 +547,16 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
|
||||
return exprList.notIn(propertyName, subQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> isEmpty(String propertyName) {
|
||||
return exprList.isEmpty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> isNotEmpty(String propertyName) {
|
||||
return exprList.isNotEmpty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> exists(Query<?> subQuery) {
|
||||
return exprList.exists(subQuery);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
|
||||
Collection<T> c = result.getActualDetails();
|
||||
for (T bean : c) {
|
||||
descriptor.cacheBeanPutData((EntityBean) bean);
|
||||
descriptor.cacheBeanPut((EntityBean) bean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
}
|
||||
|
||||
if (result != null && request.isUseBeanCache()) {
|
||||
request.getBeanDescriptor().cacheBeanPutData((EntityBean) result);
|
||||
request.getBeanDescriptor().cacheBeanPut((EntityBean) result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -469,7 +469,9 @@ public class OrmQueryProperties implements Serializable {
|
||||
*/
|
||||
public boolean isSameByAutoTune(OrmQueryProperties p2) {
|
||||
if (included == null) {
|
||||
return p2.included == null;
|
||||
return p2 == null || p2.included == null;
|
||||
} else if (p2 == null) {
|
||||
return false;
|
||||
}
|
||||
return included.equals(p2.included);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ public class BeanPersistIds {
|
||||
if (updateIds != null) {
|
||||
for (int i = 0; i < updateIds.size(); i++) {
|
||||
Object id = updateIds.get(i);
|
||||
beanDescriptor.cacheBeanRemove(id);
|
||||
beanDescriptor.cacheHandleDeleteById(id);
|
||||
if (listener != null) {
|
||||
listener.remoteUpdate(id);
|
||||
}
|
||||
@@ -244,7 +244,7 @@ public class BeanPersistIds {
|
||||
if (deleteIds != null) {
|
||||
for (int i = 0; i < deleteIds.size(); i++) {
|
||||
Object id = deleteIds.get(i);
|
||||
beanDescriptor.cacheBeanRemove(id);
|
||||
beanDescriptor.cacheHandleDeleteById(id);
|
||||
if (listener != null) {
|
||||
listener.remoteDelete(id);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
@@ -24,18 +25,17 @@ public final class DeleteByIdMap {
|
||||
return beanMap.toString();
|
||||
}
|
||||
|
||||
public void notifyCache() {
|
||||
public void notifyCache(CacheChangeSet changeSet) {
|
||||
for (BeanPersistIds deleteIds : beanMap.values()) {
|
||||
BeanDescriptor<?> d = deleteIds.getBeanDescriptor();
|
||||
List<Object> idValues = deleteIds.getDeleteIds();
|
||||
if (idValues != null) {
|
||||
d.queryCacheClear();
|
||||
d.queryCacheClear(changeSet);
|
||||
for (int i = 0; i < idValues.size(); i++) {
|
||||
d.cacheBeanRemove(idValues.get(i));
|
||||
d.cacheHandleDeleteById(idValues.get(i), changeSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
|
||||
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
@@ -44,6 +45,8 @@ public final class PostCommitProcessing {
|
||||
|
||||
private final int txnDocStoreBatchSize;
|
||||
|
||||
private CacheChangeSet cacheChanges;
|
||||
|
||||
/**
|
||||
* Create for an external modification.
|
||||
*/
|
||||
@@ -83,7 +86,7 @@ public final class PostCommitProcessing {
|
||||
*/
|
||||
void notifyLocalCache() {
|
||||
processTableEvents(event.getEventTables());
|
||||
event.notifyCache();
|
||||
cacheChanges = event.buildCacheChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,6 +148,9 @@ public final class PostCommitProcessing {
|
||||
Runnable backgroundNotify() {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
if (cacheChanges != null) {
|
||||
cacheChanges.apply();
|
||||
}
|
||||
localPersistListenersNotify();
|
||||
notifyCluster();
|
||||
processDocStoreUpdates();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.StringParser;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
|
||||
@@ -52,6 +53,11 @@ public class CtCompoundPropertyElAdapter implements ElPropertyValue {
|
||||
throw new RuntimeException("Not Supported or Expected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
|
||||
throw new RuntimeException("Not Supported or Expected");
|
||||
}
|
||||
|
||||
public Object[] getAssocIdValues(EntityBean bean) {
|
||||
throw new RuntimeException("Not Supported or Expected");
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ import java.sql.SQLException;
|
||||
*/
|
||||
public interface ScalarType<T> extends StringParser, StringFormatter, ScalarDataReader<T> {
|
||||
|
||||
/**
|
||||
* Return true if this is a binary type and can not support parse() and format() from/to string.
|
||||
* This allows Ebean to optimise marshalling types to string.
|
||||
*/
|
||||
boolean isBinaryType();
|
||||
|
||||
/**
|
||||
* Return true if this is a mutable scalar type (like hstore).
|
||||
*/
|
||||
@@ -181,6 +187,11 @@ public interface ScalarType<T> extends StringParser, StringFormatter, ScalarData
|
||||
*/
|
||||
boolean isDateTimeCapable();
|
||||
|
||||
/**
|
||||
* Convert the value into a long version value.
|
||||
*/
|
||||
long asVersion(T value);
|
||||
|
||||
/**
|
||||
* Convert the systemTimeMillis into the appropriate java object.
|
||||
* <p>
|
||||
|
||||
@@ -17,6 +17,17 @@ public abstract class ScalarTypeBase<T> implements ScalarType<T> {
|
||||
this.jdbcType = jdbcType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(T value) {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
// override for binary/byte based types
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of mutable false.
|
||||
*/
|
||||
|
||||
@@ -28,6 +28,11 @@ public abstract class ScalarTypeBaseDateTime<T> extends ScalarTypeBase<T> {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(T value) {
|
||||
return convertToMillis(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the value to a Timestamp.
|
||||
*/
|
||||
@@ -126,14 +131,20 @@ public abstract class ScalarTypeBaseDateTime<T> extends ScalarTypeBase<T> {
|
||||
}
|
||||
|
||||
public String formatValue(T value) {
|
||||
return convertToTimestamp(value).toString();
|
||||
// format all timestamps into epoch millis
|
||||
long epochMillis = convertToMillis(value);
|
||||
return Long.toString(epochMillis);
|
||||
}
|
||||
|
||||
public T parse(String value) {
|
||||
return convertFromTimestamp(Timestamp.valueOf(value));
|
||||
try {
|
||||
long epochMillis = Long.parseLong(value);
|
||||
return convertFromMillis(epochMillis);
|
||||
} catch (NumberFormatException e) {
|
||||
return convertFromTimestamp(Timestamp.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isDateTimeCapable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ public class ScalarTypeByte extends ScalarTypeBase<Byte> {
|
||||
super(Byte.class, true, Types.TINYINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void bind(DataBind b, Byte value) throws SQLException {
|
||||
if (value == null) {
|
||||
b.setNull(Types.TINYINT);
|
||||
|
||||
@@ -20,6 +20,11 @@ public abstract class ScalarTypeBytesBase extends ScalarTypeBase<byte[]> {
|
||||
super(byte[].class, jdbcNative, jdbcType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] convertToBytes(Object value) {
|
||||
return (byte[]) value;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ public class ScalarTypeBytesEncrypted implements ScalarType<byte[]> {
|
||||
this.dataEncryptSupport = dataEncryptSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(byte[] value) {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
|
||||
@@ -23,6 +23,16 @@ public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T> {
|
||||
this.dataEncryptSupport = dataEncryptSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(T value) {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return wrapped.isBinaryType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return wrapped.isMutable();
|
||||
|
||||
@@ -24,6 +24,16 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(Object value) {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IN values for DB constraint construction.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,11 @@ public class ScalarTypeFile extends ScalarTypeBase<File> {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private InputStream getInputStream(File value) throws IOException {
|
||||
FileInputStream fi = new FileInputStream(value);
|
||||
return new BufferedInputStream(fi, bufferSize);
|
||||
|
||||
@@ -54,6 +54,11 @@ public class ScalarTypeInteger extends ScalarTypeBase<Integer> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(Integer value) {
|
||||
return value.longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toJdbcType(Object value) {
|
||||
return BasicTypeConverter.toInteger(value);
|
||||
|
||||
@@ -55,6 +55,11 @@ public class ScalarTypeLong extends ScalarTypeBase<Long> {
|
||||
return Long.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(Long value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long convertFromMillis(long systemTimeMillis) {
|
||||
return systemTimeMillis;
|
||||
|
||||
@@ -41,6 +41,17 @@ public class ScalarTypeWrapper<B, S> implements ScalarType<B> {
|
||||
return "ScalarTypeWrapper " + wrapperType + " to " + scalarType.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(B value) {
|
||||
S unwrapValue = converter.unwrapValue(value);
|
||||
return scalarType.asVersion(unwrapValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return scalarType.isBinaryType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return scalarType.isMutable();
|
||||
|
||||
@@ -1,566 +0,0 @@
|
||||
package com.avaje.ebeaninternal.server.util;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassPathSearchService;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.loader.jar.JarEntryData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* Can search the class path for classes using a ClassPathSearchMatcher. A
|
||||
* ClassPathSearch should only be used once in a single threaded manor. It is
|
||||
* not safe for multithreaded use.
|
||||
* <p>
|
||||
* For example, used to find all the Entity beans and ScalarTypes for Ebean.
|
||||
* </p>
|
||||
*/
|
||||
public class ClassPathSearch implements ClassPathSearchService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClassPathSearch.class);
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private final List<Object> classPath = new ArrayList<Object>();
|
||||
|
||||
private ClassPathSearchFilter filter;
|
||||
|
||||
private ClassPathSearchMatcher matcher;
|
||||
|
||||
private final ArrayList<Class<?>> matchList = new ArrayList<Class<?>>();
|
||||
|
||||
private final HashSet<String> jarHits = new HashSet<String>();
|
||||
|
||||
private final HashSet<String> packageHits = new HashSet<String>();
|
||||
|
||||
private ClassPathReader classPathReader = new DefaultClassPathReader();
|
||||
|
||||
private final ArrayList<URI> scannedUris = new ArrayList<URI>();
|
||||
|
||||
public ClassPathSearch() {
|
||||
// Default Construct
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ClassLoader classLoader, ClassPathSearchFilter filter, ClassPathSearchMatcher matcher, String classPathReaderClassName) {
|
||||
this.classLoader = classLoader;
|
||||
this.filter = filter;
|
||||
this.matcher = matcher;
|
||||
initClassPaths(classPathReaderClassName);
|
||||
}
|
||||
|
||||
private void initClassPaths(String classPathReaderCN) {
|
||||
|
||||
try {
|
||||
|
||||
if (classPathReaderCN != null) {
|
||||
// use a user defined classPathReader
|
||||
logger.info("Using [" + classPathReaderCN + "] to read the searchable class path");
|
||||
classPathReader = (ClassPathReader) ClassUtil.newInstance(classPathReaderCN);
|
||||
}
|
||||
|
||||
Object[] rawClassPaths = classPathReader.readPath(classLoader);
|
||||
|
||||
if (rawClassPaths == null || rawClassPaths.length == 0) {
|
||||
logger.warn("ClassPath is EMPTY using ClassPathReader [" + classPathReader + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rawClassPaths.length; i++) {
|
||||
// check for a jarfile with a manifest classpath (e.g. maven surefire)
|
||||
List<URI> classPathFromManifest = getClassPathFromManifest(rawClassPaths[i]);
|
||||
if (classPathFromManifest.isEmpty()) {
|
||||
classPath.add(rawClassPaths[i]);
|
||||
} else {
|
||||
classPath.addAll(classPathFromManifest);
|
||||
}
|
||||
}
|
||||
|
||||
if (rawClassPaths.length == 1) {
|
||||
// look to add an 'outer' jar when it contains a manifest classpath
|
||||
if (!classPath.contains(rawClassPaths[0])) {
|
||||
classPath.add(rawClassPaths[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
for (Object entry : classPath) {
|
||||
logger.debug("Classpath Entry: {}", entry);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error trying to read the classpath entries", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of jars that contained classes that matched.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getJarHits() {
|
||||
return jarHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of packages that contained classes that matched.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> getPackageHits() {
|
||||
return packageHits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register where matching classes where found.
|
||||
* <p>
|
||||
* Could use this info to speed up future searches.
|
||||
* </p>
|
||||
*/
|
||||
private void registerHit(String jarFileName, Class<?> cls) {
|
||||
if (jarFileName != null) {
|
||||
jarHits.add(jarFileName);
|
||||
}
|
||||
Package pkg = cls.getPackage();
|
||||
if (pkg != null) {
|
||||
packageHits.add(pkg.getName());
|
||||
} else {
|
||||
packageHits.add("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the class path for all matching classes.
|
||||
*/
|
||||
@Override
|
||||
public List<Class<?>> findClasses() throws IOException {
|
||||
|
||||
if (classPath.isEmpty()) {
|
||||
// returning an empty list
|
||||
return matchList;
|
||||
}
|
||||
|
||||
int classPathSize = classPath.size();
|
||||
for (int i = 0; i < classPathSize; i++) {
|
||||
|
||||
ClassPathElement element = getClassPathElement(classPath.get(i));
|
||||
|
||||
if (element.isDirectory()) {
|
||||
scanDirectory(element);
|
||||
|
||||
} else if (element.isJarOrWar()) {
|
||||
// search name including the ! offset if it is there
|
||||
if (classPathSize == 1 || filter.isSearchJar(element.getJarNameWithOffset(), element.getJarOffset())) {
|
||||
scanJar(element);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.error("Error: expected classPath entry [" + element + "] to be a directory or a .jar file but it is not either of those?");
|
||||
}
|
||||
}
|
||||
|
||||
if (matchList.isEmpty()) {
|
||||
logger.warn("No Entities found in ClassPath using ClassPathReader [" + classPathReader + "] Classpath Searched[" + classPath + "]");
|
||||
}
|
||||
|
||||
return matchList;
|
||||
}
|
||||
|
||||
private ClassPathElement getClassPathElement(Object classPathEntry) throws MalformedURLException {
|
||||
|
||||
URL fileUrl;
|
||||
|
||||
if (URI.class.isInstance(classPathEntry)) {
|
||||
fileUrl = ((URI) classPathEntry).toURL();
|
||||
|
||||
} else if (!URL.class.isInstance(classPathEntry)) {
|
||||
// assumed to be a file path
|
||||
return new ClassPathElement(classPathEntry.toString());
|
||||
|
||||
} else {
|
||||
fileUrl = (URL) classPathEntry;
|
||||
}
|
||||
|
||||
if (!fileUrl.getPath().contains("!")) {
|
||||
return new ClassPathElement(new File(fileUrl.getFile()));
|
||||
}
|
||||
|
||||
// jar:file:..../file.war!/WEB-INF/classes typically
|
||||
String[] parts = fileUrl.getPath().split("!");
|
||||
String fileName = parts[0];
|
||||
String jarOffset = parts[1];
|
||||
if (fileName.startsWith("file:")) {
|
||||
fileName = fileName.substring("file:".length());
|
||||
}
|
||||
return new ClassPathElement(new File(fileName), jarOffset);
|
||||
}
|
||||
|
||||
private void scanDirectory(ClassPathElement classPathEntry) {
|
||||
scanDirectory(classPathEntry.classPath);
|
||||
}
|
||||
|
||||
private void scanDirectory(File directory) {
|
||||
List<String> directoryFiles = getDirectoryFiles(directory);
|
||||
searchFiles(Collections.enumeration(directoryFiles), null, null, null);
|
||||
}
|
||||
|
||||
private void scanUri(URI uri) throws IOException {
|
||||
|
||||
if (uri.getScheme().equals("file") && scannedUris.add(uri)) {
|
||||
File file = new File(uri);
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
scanDirectory(file);
|
||||
} else {
|
||||
scanJar(new ClassPathElement(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void scanJar(ClassPathElement classPathEntry) throws IOException {
|
||||
|
||||
JarFile module = null;
|
||||
try {
|
||||
// our resource is a jar
|
||||
File file = classPathEntry.classPath;
|
||||
module = new JarFile(file);
|
||||
|
||||
logger.trace("scanJar file:{}", file);
|
||||
|
||||
List<URI> classPathFromManifest = getClassPathFromManifest(file, module.getManifest());
|
||||
for (URI uri : classPathFromManifest) {
|
||||
scanUri(uri);
|
||||
}
|
||||
|
||||
searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset, file);
|
||||
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IOException("Bad classpath error: ", ex);
|
||||
|
||||
} finally {
|
||||
if (module != null) {
|
||||
try {
|
||||
// close the jar if it was used
|
||||
module.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error closing jar", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getDirectoryFiles(File classPath) {
|
||||
|
||||
// list of file names (latter checked as Classes)
|
||||
ArrayList<String> fileNameList = new ArrayList<String>();
|
||||
|
||||
Set<String> includePkgs = filter.getIncludePackages();
|
||||
if (includePkgs.size() > 0) {
|
||||
// just search the relevant directories based on the
|
||||
// list of included packages
|
||||
for (String pkg : includePkgs) {
|
||||
String relativePath = pkg.replace('.', '/');
|
||||
File dir = new File(classPath, relativePath);
|
||||
if (dir.exists()) {
|
||||
recursivelyListDir(fileNameList, dir, new StringBuilder(relativePath));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// get a recursive listing of this classPath
|
||||
recursivelyListDir(fileNameList, classPath, new StringBuilder());
|
||||
}
|
||||
|
||||
return fileNameList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through the Java Archive (jar or war file) looking for classes
|
||||
* that match our requirements.
|
||||
* @param entries - all of the entries in the Java Archive, this is an enumeration
|
||||
* provided by the Jar file
|
||||
* @param jarFileName - the name of the java archive
|
||||
* @param jarOffset - an offset inside the archive to chop off the name of the class -
|
||||
* this is used when we have bang path offsets (e.g.
|
||||
* @param module the containing jar/war file (used for spring boot embedded jar scanning)
|
||||
*/
|
||||
private void searchFiles(Enumeration<?> entries, String jarFileName, String jarOffset, File module) {
|
||||
|
||||
if (entries == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("searchFiles jarFileName:{} jarOffset:{}", jarFileName, jarOffset);
|
||||
|
||||
// Strips the first character off as all entries in a jar file have no /
|
||||
// prefix. We want to come out with a name like WEB-INF/classes/ to ensure
|
||||
// we filter the contents of the war/jar file by this.
|
||||
|
||||
if ("/".equals(jarOffset)) {
|
||||
// root level for runnable jar (spring boot etc)
|
||||
jarOffset = null;
|
||||
|
||||
} else if (jarOffset != null) {
|
||||
if (jarOffset.startsWith("/")) {
|
||||
jarOffset = jarOffset.substring(1);
|
||||
}
|
||||
|
||||
if (!jarOffset.endsWith("/")) {
|
||||
jarOffset += "/";
|
||||
}
|
||||
}
|
||||
|
||||
while (entries.hasMoreElements()) {
|
||||
|
||||
Object element = entries.nextElement();
|
||||
String entryName = element.toString();
|
||||
|
||||
if (isEntryEmbeddedJar(module, entryName)) {
|
||||
scanSpringBootEmbeddedJar(jarFileName, module, entryName);
|
||||
}
|
||||
|
||||
if (isEntryClass(jarOffset, entryName)) {
|
||||
// check if it an 'interesting' class - entity etc
|
||||
registerScannedClass(jarFileName, jarOffset, entryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an embedded jar that should be scanned.
|
||||
*/
|
||||
private boolean isEntryEmbeddedJar(File module, String entryName) {
|
||||
return entryName.endsWith(".jar") && module != null && filter.isSearchJar(entryName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a class that should be checked (for entity, interesting interface etc).
|
||||
*/
|
||||
private boolean isEntryClass(String jarOffset, String entryName) {
|
||||
return entryName.endsWith(".class") && (jarOffset == null || entryName.startsWith(jarOffset));
|
||||
}
|
||||
|
||||
private void scanSpringBootEmbeddedJar(String jarFileName, File module, String fileName) {
|
||||
// spring boot embedded jar
|
||||
logger.debug("spring boot embedded:{} : module:{}", fileName, module.getAbsoluteFile());
|
||||
try {
|
||||
org.springframework.boot.loader.jar.JarFile jarFile = new org.springframework.boot.loader.jar.JarFile(module);
|
||||
org.springframework.boot.loader.jar.JarFile jarEntryFile = jarFile.getNestedJarFile(jarFile.getJarEntryData(fileName));
|
||||
Iterator<JarEntryData> iterator = jarEntryFile.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
JarEntryData jarEntryData = iterator.next();
|
||||
if (jarEntryData.getName().toString().endsWith(".class")) {
|
||||
logger.debug("... spring boot class entry:{}", jarEntryData.getName().toString());
|
||||
registerScannedClass(jarFileName, null, jarEntryData.getName().toString());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerScannedClass(String jarFileName, String jarOffset, String fileName) {
|
||||
|
||||
if (jarOffset != null) {
|
||||
// we got through here only if there is an offset and we
|
||||
// matched it, so strip it off the file
|
||||
// as we are trying to find the className
|
||||
fileName = fileName.substring(jarOffset.length());
|
||||
}
|
||||
|
||||
String className = fileName.replace('/', '.').substring(0, fileName.length() - 6);
|
||||
int lastPeriod = className.lastIndexOf(".");
|
||||
|
||||
String pckgName;
|
||||
if (lastPeriod > 0) {
|
||||
pckgName = className.substring(0, lastPeriod);
|
||||
} else {
|
||||
pckgName = "";
|
||||
}
|
||||
|
||||
if (filter.isSearchPackage(pckgName)) {
|
||||
// get the class for our class name
|
||||
try {
|
||||
Class<?> theClass = Class.forName(className, false, classLoader);
|
||||
|
||||
if (matcher.isMatch(theClass)) {
|
||||
matchList.add(theClass);
|
||||
registerHit(jarFileName, theClass);
|
||||
}
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
// expected to get this hence trace
|
||||
logger.trace("Error searching classpath" + e.getMessage());
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// expected to get this hence trace
|
||||
logger.trace("Error searching classpath" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void recursivelyListDir(List<String> fileNameList, File dir, StringBuilder relativePath) {
|
||||
|
||||
if (!dir.isDirectory()) {
|
||||
// add class fileName to the list
|
||||
fileNameList.add(relativePath.toString());
|
||||
|
||||
} else {
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
// store our original relative path string length
|
||||
int prevLen = relativePath.length();
|
||||
relativePath.append(prevLen == 0 ? "" : "/").append(files[i].getName());
|
||||
|
||||
recursivelyListDir(fileNameList, files[i], relativePath);
|
||||
|
||||
// delete sub directory from our relative path
|
||||
relativePath.delete(prevLen, relativePath.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If URL and actually a jarfile with manifest return the derived classpath.
|
||||
*/
|
||||
private static List<URI> getClassPathFromManifest(Object classPathElement) {
|
||||
|
||||
try {
|
||||
if (classPathElement instanceof URL) {
|
||||
File file = new File(((URL) classPathElement).getFile());
|
||||
if (file.isDirectory()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JarFile jarFile = new JarFile(file);
|
||||
try {
|
||||
return getClassPathFromManifest(file, jarFile.getManifest());
|
||||
} finally {
|
||||
jarFile.close();
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
|
||||
} catch (IOException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a jarfile with a manifest classpath return that.
|
||||
*/
|
||||
private static List<URI> getClassPathFromManifest(File jarFile, Manifest manifest) {
|
||||
|
||||
if (manifest == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<URI> list = new ArrayList<URI>();
|
||||
String classpathAttribute = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString());
|
||||
|
||||
if (classpathAttribute != null) {
|
||||
String[] split = classpathAttribute.split(" ");
|
||||
for (String path : split) {
|
||||
try {
|
||||
path = path.trim();
|
||||
if (path.length() > 0) {
|
||||
URI uri = getClassPathEntry(jarFile, path);
|
||||
list.add(uri);
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
// Ignore bad entry
|
||||
logger.warn("Invalid Class-Path entry: " + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static URI getClassPathEntry(File jarFile, String path) throws URISyntaxException {
|
||||
URI uri = new URI(path);
|
||||
if (uri.isAbsolute()) {
|
||||
return uri;
|
||||
} else {
|
||||
return new File(jarFile.getParentFile(), path.replace('/', File.separatorChar)).toURI();
|
||||
}
|
||||
}
|
||||
|
||||
private static File decodePath(File classPath) {
|
||||
|
||||
try {
|
||||
String charsetName = Charset.defaultCharset().name();
|
||||
|
||||
// URL Decode the path replacing %20 to space characters.
|
||||
String path = URLDecoder.decode(classPath.getAbsolutePath(), charsetName);
|
||||
return new File(path);
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Element that has both underlying file and ! jarOffset.
|
||||
*/
|
||||
private static class ClassPathElement {
|
||||
|
||||
private final File classPath;
|
||||
private final String jarOffset;
|
||||
|
||||
ClassPathElement(String path) {
|
||||
this(new File(path));
|
||||
}
|
||||
|
||||
ClassPathElement(File file) {
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
ClassPathElement(File file, String jarOffset) {
|
||||
classPath = decodePath(file);
|
||||
this.jarOffset = jarOffset;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return classPath.getAbsolutePath();
|
||||
}
|
||||
|
||||
boolean isDirectory() {
|
||||
return classPath.isDirectory();
|
||||
}
|
||||
|
||||
boolean isJarOrWar() {
|
||||
return classPath.getName().endsWith(".jar") || classPath.getName().endsWith(".war");
|
||||
}
|
||||
|
||||
String getJarName() {
|
||||
return classPath.getName();
|
||||
}
|
||||
|
||||
String getJarNameWithOffset() {
|
||||
return (jarOffset == null) ? classPath.getName() : classPath.getName() + "!" + jarOffset;
|
||||
}
|
||||
|
||||
String getJarOffset() {
|
||||
return jarOffset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.avaje.ebeaninternal.util;
|
||||
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
|
||||
/**
|
||||
* Utilities for encoding and decoding strings.
|
||||
*/
|
||||
public final class EncodeUtil {
|
||||
|
||||
private EncodeUtil() {
|
||||
/* no instances */
|
||||
}
|
||||
|
||||
/**
|
||||
* URL-encodes the specified UTF-8 string.
|
||||
*/
|
||||
public static String urlEncode(String string) {
|
||||
if (string == null) return null;
|
||||
try {
|
||||
return URLEncoder.encode(string, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URL-decodes the specified string as a UTF-8 string.
|
||||
*/
|
||||
public static String urlDecode(String string) {
|
||||
if (string == null) return null;
|
||||
try {
|
||||
return URLDecoder.decode(string, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes corresponding to the specified ASCII string.
|
||||
*/
|
||||
public static byte[] asciiToBytes(String string) {
|
||||
if (string == null) return null;
|
||||
try {
|
||||
return string.getBytes("US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Support for US-ASCII is mandated by the Java spec", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ASCII string corresponding to the specified bytes.
|
||||
*/
|
||||
public static String bytesToAscii(byte[] data) {
|
||||
try {
|
||||
return new String(data, "US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Support for US-ASCII is mandated by the Java spec", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bytes corresponding to the specified UTF-8 string.
|
||||
*/
|
||||
public static byte[] utf8ToBytes(String string) {
|
||||
if (string == null) return null;
|
||||
try {
|
||||
return string.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 string corresponding to the specified bytes.
|
||||
*/
|
||||
public static String bytesToUtf8(byte[] data) {
|
||||
try {
|
||||
return new String(data, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 string corresponding to the specified bytes.
|
||||
*/
|
||||
public static String decodeBytes(byte[] data, String encoding) {
|
||||
try {
|
||||
return new String(data, encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Error decoding bytes with "+encoding, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.avaje.ebeaninternal.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* Utilities for IO.
|
||||
*/
|
||||
public class IOUtils {
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the specified input stream and returns them
|
||||
* as a byte array.
|
||||
*/
|
||||
public static byte[] read(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
pump(in, buffer);
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the specified input stream and returns them
|
||||
* as an ASCII string.
|
||||
*/
|
||||
public static String readAscii(InputStream in) throws IOException {
|
||||
return EncodeUtil.bytesToAscii(read(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the specified input stream and returns them
|
||||
* as UTF-8 string.
|
||||
*/
|
||||
public static String readUtf8(InputStream in) throws IOException {
|
||||
return EncodeUtil.bytesToUtf8(read(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the specified input stream and returns them
|
||||
* as a string using the encoding supplied.
|
||||
*/
|
||||
public static String readEncoded(InputStream in, String encoding) throws IOException {
|
||||
return EncodeUtil.decodeBytes(read(in), encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the entire contents from the reader returning as a String.
|
||||
*/
|
||||
public static String read(Reader reader) throws IOException {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
char[] buffer = new char[4096];
|
||||
for (; ; ) {
|
||||
int len = reader.read(buffer);
|
||||
if (len < 0) {
|
||||
break;
|
||||
}
|
||||
sb.append(buffer, 0, len);
|
||||
}
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the specified input stream and copies it to the specified
|
||||
* output stream, until the input stream is at EOF. Both streams are then
|
||||
* closed.
|
||||
*
|
||||
* @throws IOException if the input or output stream is <code>null</code>
|
||||
*/
|
||||
public static void pump(InputStream in, OutputStream out) throws IOException {
|
||||
|
||||
if (in == null) throw new IOException("Input stream is null");
|
||||
if (out == null) throw new IOException("Output stream is null");
|
||||
|
||||
try {
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
for (; ; ) {
|
||||
int bytes = in.read(buffer);
|
||||
if (bytes < 0) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, bytes);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.avaje.ebeaninternal.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Utility for closing raw Jdbc resources.
|
||||
*/
|
||||
public class JdbcClose {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JdbcClose.class);
|
||||
|
||||
/**
|
||||
* Close the connection logging if an error occurs.
|
||||
*/
|
||||
public static void close(Connection connection) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Error closing connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the connection logging if an error occurs.
|
||||
*/
|
||||
public static void rollback(Connection connection) {
|
||||
try {
|
||||
connection.rollback();
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Error on connection rollback", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
com.avaje.ebeaninternal.server.util.ClassPathSearch
|
||||
@@ -0,0 +1,13 @@
|
||||
create table ${table} (
|
||||
id integer not null,
|
||||
status varchar(10) not null,
|
||||
run_version varchar(150) not null,
|
||||
dep_version varchar(150) not null,
|
||||
comment varchar(150),
|
||||
checksum integer not null,
|
||||
run_on timestamp not null,
|
||||
run_by varchar(30) not null,
|
||||
run_ip varchar(30),
|
||||
constraint pk_${table} primary key (id)
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user