Compare commits

...
Author SHA1 Message Date
Robin Bygrave 11e02710d9 [maven-release-plugin] prepare release avaje-ebeanorm-7.7.1 2016-04-18 21:00:50 +12:00
Robin Bygrave 658ce3c5d3 #653 - Change format of all Timestamp/Date time types to use epoch millis 2016-04-18 12:49:16 +12:00
Robin Bygrave 2de78cd20f #652 - ENH: L2 cache - add @CacheQueryTuning annotation 2016-04-18 09:46:42 +12:00
Robin Bygrave 1b8b05790e #650 - Refactor: Optimise L2 cache entry for serialisation 2016-04-14 11:56:31 +12:00
Robin Bygrave 444b848c22 #648 - Test that does not reproduce this issue 2016-04-13 19:36:26 +12:00
Robin Bygrave 707dda636d #649 - ENH: Add ebean.disableL2Cache=true option ... to globally disable l2 cache 2016-04-13 17:44:37 +12:00
Robin Bygrave e8a979bc9d No effective change - imports 2016-04-13 15:59:39 +12:00
Robin Bygrave 3fb9a7414c #647 - REFACTOR: L2 cache - remove cache warming support (from @CacheStrategy and API) 2016-04-12 23:44:24 +12:00
Robin Bygrave 061652a48e Bump pom to 7.7.1-SNAPSHOT 2016-04-12 23:26:09 +12:00
Robin Bygrave 57b03cf86d #646 - REFACTOR: L2 cache towards remote data grid caches (like Ignite etc) 2016-04-12 23:25:19 +12:00
Robin Bygrave 45dbe3bb9a Merge branch 'master' of github.com:ebean-orm/avaje-ebeanorm 2016-04-12 23:21:32 +12:00
Robin Bygrave 24037eeb73 No effective change - reduce test logging 2016-04-12 23:20:54 +12:00
Robin Bygrave 2c6f637dab #646 - REFACTOR: L2 cache towards remote data grid caches (like Ignite etc) 2016-04-12 23:20:16 +12:00
Robin Bygrave a85f9c4be8 #638 - Ensure natural key cache is updated regardless of bean cache entry aging out 2016-04-11 17:09:06 +12:00
Rob Bygrave 5d9f11a362 Merge pull request #645 from icode/master
review duplicate var
2016-04-08 22:16:43 +12:00
icode 3d484c79a2 review duplicate var 2016-04-08 14:32:13 +08:00
Robin Bygrave 5881047606 [maven-release-plugin] prepare for next development iteration 2016-04-08 15:17:04 +12:00
Robin Bygrave 333eaf08c2 [maven-release-plugin] prepare release avaje-ebeanorm-7.6.1 2016-04-08 15:16:44 +12:00
Robin Bygrave a1ff83e9e4 #643 - NPE on OrmQueryProperties.java:474 2016-04-08 15:15:34 +12:00
Robin Bygrave 640db2267f #642 - ENH: Add support for OneToMany expression isEmpty() / isNotEmpty() ... which is effectively an EXISTS and NOT EXISTS subquery 2016-04-08 13:44:54 +12:00
Robin Bygrave 4059034891 #642 - ENH: Add support for OneToMany expression isEmpty() / isNotEmpty() ... which is effectively an EXISTS and NOT EXISTS subquery 2016-04-08 13:41:43 +12:00
Robin Bygrave c58db0033d No effective change - add more tests for raw use of exists / not exists 2016-04-08 08:28:13 +12:00
Robin Bygrave da90eccc12 #638 - ENH: Add Apache Ignite as L2 cache option 2016-04-08 02:03:02 +12:00
Robin Bygrave 524be4ce55 #641 - ENH: Add EbeanServer.setBeanId(bean, id) ... to make it easy to convert id value to appropriate type and set 2016-04-08 01:15:34 +12:00
Robin Bygrave 649c14644e #640 - ENH: ServerConfig - support get/put arbitrary configuration objects for use by plugins 2016-04-08 00:03:37 +12:00
Robin Bygrave c504256579 No effective change - HashQuery.toString() for l2 query cache log message 2016-04-07 23:59:13 +12:00
Robin Bygrave 97d803e3e0 No effective change - HashQuery.toString() for l2 query cache log message 2016-04-07 21:45:59 +12:00
Robin Bygrave 46bc09a09b No effective change - HashQuery.toString() for l2 query cache log message 2016-04-07 21:45:47 +12:00
Robin Bygrave fe387cf456 #637 - CachedManyIds as serializable (Ignite etc) 2016-04-06 23:43:26 +12:00
Robin Bygrave b4cd66e28c #637 - ServerCacheType 2016-04-06 21:39:39 +12:00
Robin Bygrave 7c11acaf89 #637 - Change SPI for ServerCache, add ServerCacheType to allow different cache construction for query bean and bean cache 2016-04-06 21:38:54 +12:00
Robin Bygrave 7b4d6f2d17 No effective change - code format 2016-04-06 20:37:50 +12:00
Robin Bygrave 8f100f2786 No effective change - code format 2016-04-06 20:36:50 +12:00
Robin Bygrave 6233392548 ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) #636 2016-04-06 15:14:50 +12:00
Robin Bygrave abaa1ff934 #636 - ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) 2016-04-06 14:31:57 +12:00
Robin Bygrave a7b239a1b7 ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) #636 2016-04-06 14:31:20 +12:00
Robin Bygrave e8c0756ee9 Bump pom to 7.6.1-SNAPSHOT 2016-04-01 16:44:36 +13:00
Robin Bygrave e5179432a0 #634 - Refactor internal class path scanning to use avaje-classpath-scanner-api / avaje-classpath-scanner 2016-04-01 16:43:59 +13:00
Robin Bygrave d25123eb23 #633 - Remove deprecated SpiEbeanPlugin (people should have migrated to com.avaje.ebean.plugin.Plugin) 2016-04-01 15:07:49 +13:00
Robin Bygrave 27602c15b9 #632 - ENH: Alter AutoTune such that it saves back to autotune.xml (rename any existing autotune file) 2016-04-01 14:56:35 +13:00
Robin Bygrave bdc81bf6f4 [maven-release-plugin] prepare for next development iteration 2016-03-31 16:03:23 +13:00
Robin Bygrave 84fa373728 WIP update for migration runner 2016-02-10 20:51:55 +13:00
Robin Bygrave 12e6c0b139 Merge branch 'dbmig1' into dbmig2 2016-02-10 16:07:04 +13:00
Robin Bygrave 6f702098bc Update DB Migration runner 2016-02-09 12:55:41 +13:00
Robin Bygrave ad3292b92d WIP - DB Migration runner 2016-02-06 00:17:08 +13:00
149 changed files with 4612 additions and 2238 deletions
+14 -2
View File
@@ -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>
+10 -36
View File
@@ -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.
*/
+11 -16
View File
@@ -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>
+14
View File
@@ -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);
}
}
@@ -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);
}
/**
@@ -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);
}
/**
@@ -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));
}
/**
@@ -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();
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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];
}
}
@@ -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();
@@ -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;
}
@@ -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();
}
}
@@ -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
@@ -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;
}
}
@@ -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.
*/
@@ -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 +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