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 b9237cb8a8 [maven-release-plugin] prepare release avaje-ebeanorm-7.5.1 2016-03-31 16:03:04 +13:00
Robin Bygrave 99e548db03 Modify pom - include avaje-ebeanorm-cluster by default, version range on slf4j 2016-03-31 16:02:12 +13:00
Robin Bygrave 71499d15e1 Bump pom to 7.5.1-SNAPSHOT 2016-03-31 15:47:04 +13:00
Robin Bygrave c9b0fd708a #631 - With find by id query, and id value type conversion (e.g. String to Long) this does not hit L2 cache 2016-03-31 15:18:33 +13:00
Robin Bygrave 8e43ece490 #630 - ENH: Add support for System property "disableTestProperties" ... to enable disabling the load of test-ebean.properties 2016-03-31 14:09:15 +13:00
Robin Bygrave fb2b8b4cf6 #629 - DefaultServerCache using java 1.7 compare method, drop trim logging from DEBUG to TRACE 2016-03-31 14:05:59 +13:00
Robin Bygrave 33f3920a60 #628 Refactor Clustering - tidy 2016-03-30 16:59:02 +13:00
Robin Bygrave 9a912390d4 #628 - Refactor Clustering - extract ClusterBroadcast implementations (TCP and Multicast) 2016-03-30 16:20:24 +13:00
Robin Bygrave 30a5769973 No effective change - remove http elastic test (as that is all in avaje-ebeanorm-elastic) 2016-03-29 17:00:36 +13:00
Robin Bygrave 084c9ad433 [maven-release-plugin] prepare for next development iteration 2016-03-29 16:39:51 +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
190 changed files with 4812 additions and 6347 deletions
+20 -14
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>7.4.1</version>
<version>7.7.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -64,19 +64,32 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>1.1.2</version>
</dependency>
<!-- bring in cluster support by default -->
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-cluster</artifactId>
<version>1.1.1</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.avaje</groupId>-->
<!--<artifactId>avaje-datasource-alert</artifactId>-->
<!--<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>
<version>1.7.7</version>
<version>[1.7.1,1.7.99)</version>
</dependency>
<!-- provided scope for Spring boot loader classpath scanning support -->
@@ -153,13 +166,6 @@
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</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>
@@ -133,7 +133,7 @@ public class EbeanServerFactory {
/**
* Create the container instance using the configuration.
*/
private static SpiContainer createContainer(ContainerConfig containerConfig) {
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
String implClassName = System.getProperty("ebean.container", DEFAULT_CONTAINER);
+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) {
@@ -1,7 +1,5 @@
package com.avaje.ebean.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
@@ -12,453 +10,51 @@ import java.util.Properties;
*/
public class ContainerConfig {
protected boolean clusterActive;
protected Properties properties;
/**
* Communication mode used for clustering.
* Return true if clustering is active.
*/
public enum ClusterMode {
/**
* No clustering.
*/
NONE,
/**
* Use Multicast networking for cluster wide communication.
*/
MULTICAST,
/**
* Use TCP Sockets for cluster wide communication.
*/
SOCKET
public boolean isClusterActive() {
return clusterActive;
}
/**
* The cluster mode to use.
* Set to true for clustering to be active.
*/
ClusterMode mode = ClusterMode.NONE;
/**
* Configuration if using TCP sockets for clustering communication.
*/
SocketConfig socketConfig = new SocketConfig();
/**
* Configuration if using Multicast for clustering communication.
*/
MulticastConfig multicastConfig = new MulticastConfig();
// -------------------------------------------------------------------------------------------
// MulticastConfig
/**
* The configuration for clustering using Multicast networking.
*/
public static class MulticastConfig {
int managerSleepMillis = 80;
int lastSendTimeFreqSecs = 300;//5mins
int lastStatusTimeFreqSecs = 600;//10mins
int maxResendOutgoingAttempts = 200;
int maxResendIncomingRequests = 50;
int listenPort;
String listenAddress;
int sendPort;
String sendAddress;
// Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507
int maxSendPacketSize = 1500;
// Whether to send packets even when there are no other members online
boolean sendWithNoMembers = true;
// When multiple instances are on same box you need to broadcast back locally
boolean disableLoopback;
int listenTimeToLive = -1;
int listenTimeout = 1000;
int listenBufferSize = 65500;
// For multihomed environment the address the listener should bind to
String listenBindAddress;
/**
* Return the manager sleep millis.
*/
public int getManagerSleepMillis() {
return managerSleepMillis;
}
/**
* Set the manager sleep millis.
*/
public void setManagerSleepMillis(int managerSleepMillis) {
this.managerSleepMillis = managerSleepMillis;
}
/**
* Return the last send time frequency.
*/
public int getLastSendTimeFreqSecs() {
return lastSendTimeFreqSecs;
}
/**
* Set the last send time frequency.
*/
public void setLastSendTimeFreqSecs(int lastSendTimeFreqSecs) {
this.lastSendTimeFreqSecs = lastSendTimeFreqSecs;
}
/**
* Return the last status time frequency.
*/
public int getLastStatusTimeFreqSecs() {
return lastStatusTimeFreqSecs;
}
/**
* Set the last status time frequency.
*/
public void setLastStatusTimeFreqSecs(int lastStatusTimeFreqSecs) {
this.lastStatusTimeFreqSecs = lastStatusTimeFreqSecs;
}
/**
* Return the maximum number of times we will try to re-send a given packet before giving up sending
*/
public int getMaxResendOutgoingAttempts() {
return maxResendOutgoingAttempts;
}
/**
* Set the maximum retry attempts for outgoing messages.
*/
public void setMaxResendOutgoingAttempts(int maxResendOutgoingAttempts) {
this.maxResendOutgoingAttempts = maxResendOutgoingAttempts;
}
/**
* Return the maximum number of times we will ask for a packet to be resent to us before giving up asking.
*/
public int getMaxResendIncomingRequests() {
return maxResendIncomingRequests;
}
/**
* Set the maximum retry attempts for incoming messages.
*/
public void setMaxResendIncomingRequests(int maxResendIncomingRequests) {
this.maxResendIncomingRequests = maxResendIncomingRequests;
}
/**
* Return the listen port.
*/
public int getListenPort() {
return listenPort;
}
/**
* Set the listen port.
*/
public void setListenPort(int port) {
this.listenPort = port;
}
/**
* Return the listen address.
*/
public String getListenAddress() {
return listenAddress;
}
/**
* Set the listen address.
*/
public void setListenAddress(String listenAddress) {
this.listenAddress = listenAddress;
}
/**
* Return the send port.
*/
public int getSendPort() {
return sendPort;
}
/**
* Set the send port.
*/
public void setSendPort(int sendPort) {
this.sendPort = sendPort;
}
/**
* Return the send address.
*/
public String getSendAddress() {
return sendAddress;
}
/**
* Set the send address.
*/
public void setSendAddress(String sendAddress) {
this.sendAddress = sendAddress;
}
/**
* Return the maximum send packet size.
*/
public int getMaxSendPacketSize() {
return maxSendPacketSize;
}
/**
* Set the maximum send packet size. Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507.
*/
public void setMaxSendPacketSize(int maxSendPacketSize) {
this.maxSendPacketSize = maxSendPacketSize;
}
/**
* Return true if send messages when no other members in the cluster are up.
*/
public boolean isSendWithNoMembers() {
return sendWithNoMembers;
}
/**
* Set true if send messages when no other members in the cluster are up.
*/
public void setSendWithNoMembers(boolean sendWithNoMembers) {
this.sendWithNoMembers = sendWithNoMembers;
}
/**
* Return true if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public boolean isDisableLoopback() {
return disableLoopback;
}
/**
* Set if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public void setDisableLoopback(boolean disableLoopback) {
this.disableLoopback = disableLoopback;
}
/**
* Return the listen time to live.
*/
public int getListenTimeToLive() {
return listenTimeToLive;
}
/**
* Set the listen time to live.
*/
public void setListenTimeToLive(int listenTimeToLive) {
this.listenTimeToLive = listenTimeToLive;
}
/**
* Return the listen timeout.
*/
public int getListenTimeout() {
return listenTimeout;
}
/**
* set the listen timeout.
*/
public void setListenTimeout(int listenTimeout) {
this.listenTimeout = listenTimeout;
}
/**
* Return the listen buffer size.
*/
public int getListenBufferSize() {
return listenBufferSize;
}
/**
* Set the listen buffer size.
*/
public void setListenBufferSize(int listenBufferSize) {
this.listenBufferSize = listenBufferSize;
}
/**
* Return the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public String getListenBindAddress() {
return listenBindAddress;
}
/**
* Set the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public void setListenBindAddress(String listenBindAddress) {
this.listenBindAddress = listenBindAddress;
}
public void setClusterActive(boolean clusterActive) {
this.clusterActive = clusterActive;
}
// -------------------------------------------------------------------------------------------
// SocketConfig
/**
* Configuration for clustering using TCP sockets.
* <p>
* This is good for when there are relatively small number of cluster members.
* Return the deployment properties.
*/
public static class SocketConfig {
/**
* This local server in host:port format.
*/
String localHostPort;
/**
* All the cluster members in host:port format.
*/
List<String> members = new ArrayList<String>();
/**
* core threads for the associated thread pool.
*/
int coreThreads = 2;
/**
* Max threads for the associated thread pool.
*/
int maxThreads = 16;
String threadPoolName = "EbeanCluster";
/**
* Return the host and port for this server instance.
*/
public String getLocalHostPort() {
return localHostPort;
}
/**
* Set the host and port for this server instance.
*/
public void setLocalHostPort(String localHostPort) {
this.localHostPort = localHostPort;
}
/**
* Return all the host and port for all the members of the cluster.
*/
public List<String> getMembers() {
return members;
}
/**
* Set all the host and port for all the members of the cluster.
*/
public void setMembers(List<String> members) {
this.members = members;
}
/**
* Return the number of core threads to use.
*/
public int getCoreThreads() {
return coreThreads;
}
/**
* Set the number of core threads to use.
*/
public void setCoreThreads(int coreThreads) {
this.coreThreads = coreThreads;
}
/**
* Return the number of max threads to use.
*/
public int getMaxThreads() {
return maxThreads;
}
/**
* Set the number of max threads to use.
*/
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* Return the thread pool name.
*/
public String getThreadPoolName() {
return threadPoolName;
}
/**
* Set the thread pool name.
*/
public void setThreadPoolName(String threadPoolName) {
this.threadPoolName = threadPoolName;
}
public Properties getProperties() {
return properties;
}
// -------------------------------------------------------------------------------------------
// Members
/**
* Set the deployment properties.
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
/**
* Load the settings from properties.
*/
public void loadFromProperties(Properties properties) {
//TODO
this.properties = properties;
this.clusterActive = getProperty(properties, "ebean.cluster.active", clusterActive);
}
/**
* Return the cluster mode.
* Return the boolean property setting.
*/
public ClusterMode getMode() {
return mode;
protected boolean getProperty(Properties properties, String key, boolean defaultValue) {
return "true".equalsIgnoreCase(properties.getProperty(key, Boolean.toString(defaultValue)));
}
/**
* Set the cluster mode.
*/
public void setMode(ClusterMode mode) {
this.mode = mode;
}
/**
* Return the socket communication configuration.
*/
public SocketConfig getSocketConfig() {
return socketConfig;
}
/**
* Set the socket communication configuration.
*/
public void setSocketConfig(SocketConfig socketConfig) {
this.socketConfig = socketConfig;
}
/**
* Return the multicast communication configuration.
*/
public MulticastConfig getMulticastConfig() {
return multicastConfig;
}
/**
* Set the multicast communication configuration.
*/
public void setMulticastConfig(MulticastConfig multicastConfig) {
this.multicastConfig = multicastConfig;
}
}
@@ -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);
}
/**
@@ -31,6 +31,11 @@ public final class PropertyMap implements Serializable {
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public static boolean loadTestProperties() {
String disable = System.getProperty("disableTestProperties");
return (disable == null || !disable.equalsIgnoreCase("true"));
}
public String toString() {
return map.toString();
}
@@ -18,6 +18,9 @@ final class PropertyMapLoader {
* Load the <code>test-ebean.properties</code>.
*/
public static PropertyMap loadTestProperties() {
if (!PropertyMap.loadTestProperties()) {
return new PropertyMap();
}
return load(null, "test-ebean.properties");
}
@@ -31,7 +34,7 @@ final class PropertyMapLoader {
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
loadTestProperties = true;
loadTestProperties = PropertyMap.loadTestProperties();
fileName = "ebean.properties";
}
}
@@ -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
@@ -333,8 +352,8 @@ public class DefaultServerCache implements ServerCache {
evictByTTL.add(trimmedByTTL);
evictByLRU.add(trimmedByLRU);
if (logger.isDebugEnabled()) {
logger.debug("Executed trim of cache {} in [{}]millis idle[{}] timeToLive[{}] accessTime[{}]"
if (logger.isTraceEnabled()) {
logger.trace("Executed trim of cache {} in [{}]millis idle[{}] timeToLive[{}] accessTime[{}]"
, name, exeMicros, trimmedByIdle, trimmedByTTL, trimmedByLRU);
}
}
@@ -358,7 +377,9 @@ public class DefaultServerCache implements ServerCache {
private static final long serialVersionUID = 1L;
public int compare(CacheEntry entry1, CacheEntry entry2) {
return Long.compare(entry1.getLastAccessTime(), entry2.getLastAccessTime());
long x = entry1.getLastAccessTime();
long y = entry2.getLastAccessTime();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
@@ -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());
}
}
@@ -12,7 +12,7 @@ public interface ClusterBroadcast {
* Inform the other cluster members that this instance has come online and
* start any listeners etc.
*/
void startup(ClusterManager clusterManager);
void startup();
/**
* Inform the other cluster members that this instance is leaving and
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.server.cluster;
import java.util.Properties;
/**
* Factory to create the cluster broadcast service.
*/
public interface ClusterBroadcastFactory {
/**
* Create the cluster transport with the manager and deployment properties.
*/
ClusterBroadcast create(ClusterManager manager, Properties properties);
}
@@ -2,12 +2,12 @@ package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebeaninternal.server.cluster.mcast.McastClusterManager;
import com.avaje.ebeaninternal.server.cluster.socket.SocketClusterBroadcast;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -15,6 +15,8 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class ClusterManager {
private static final Logger clusterLogger = LoggerFactory.getLogger("org.avaje.ebean.Cluster");
private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);
private final ConcurrentHashMap<String, EbeanServer> serverMap = new ConcurrentHashMap<String, EbeanServer>();
@@ -25,30 +27,33 @@ public class ClusterManager {
private boolean started;
public ClusterManager(ContainerConfig containerConfig) {
ContainerConfig.ClusterMode mode = containerConfig.getMode();
try {
switch (mode) {
case SOCKET: {
this.broadcast = new SocketClusterBroadcast(containerConfig);
break;
}
case MULTICAST: {
this.broadcast = new McastClusterManager(containerConfig);
break;
}
default: {
this.broadcast = null;
}
}
} catch (Exception e) {
logger.error("Error initialising ClusterManager type [" + mode + "]", e);
throw new RuntimeException(e);
public ClusterManager(ContainerConfig config) {
if (!config.isClusterActive()) {
broadcast = null;
} else {
ClusterBroadcastFactory factory = createFactory();
broadcast = factory.create(this, config.getProperties());
}
}
/**
* Return the ClusterTransportFactory via ServiceLoader.
*/
private ClusterBroadcastFactory createFactory() {
ServiceLoader<ClusterBroadcastFactory> load = ServiceLoader.load(ClusterBroadcastFactory.class);
ClusterBroadcastFactory factory = null;
Iterator<ClusterBroadcastFactory> iterator = load.iterator();
if (iterator.hasNext()) {
factory = iterator.next();
}
if (factory == null) {
throw new IllegalStateException("No ClusterTransportFactory found in classpath. "
+ " Probably need to add the avaje-ebeanorm-cluster dependency");
}
return factory;
}
public void registerServer(EbeanServer server) {
synchronized (monitor) {
serverMap.put(server.getName(), server);
@@ -67,7 +72,7 @@ public class ClusterManager {
private void startup() {
started = true;
if (broadcast != null) {
broadcast.startup(this);
broadcast.startup();
}
}
@@ -81,9 +86,12 @@ public class ClusterManager {
/**
* Send the message headers and payload to every server in the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent) {
public void broadcast(RemoteTransactionEvent event) {
if (broadcast != null) {
broadcast.broadcast(remoteTransEvent);
if (clusterLogger.isDebugEnabled()) {
clusterLogger.debug("sending: {}", event);
}
broadcast.broadcast(event);
}
}
@@ -1,24 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.Serializable;
/**
* Simple holder of binary data.
* Used to use Packet based serialisation of RemoteTransactionEvent
* with simple Java Serialisation of the DataHolder.
*/
public class DataHolder implements Serializable {
private static final long serialVersionUID = 9090748723571322192L;
private final byte[] data;
public DataHolder(byte[] data) {
this.data = data;
}
public byte[] getData() {
return data;
}
}
@@ -1,193 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Represents the contents sent as a single DatagramPacket.
* <p>
* The contents is typically multiple messages (ACK,PING etc) or all or part of
* a RemoteTransactionEvent.
* </p>
* <p>
* Due to the hard limit on the size of UDP packets a RemoteTransactionEvent
* with lots of information could be broken up into multiple packets.
* </p>
*
* @author rbygrave
*/
public class Packet {
/**
* A Packet that holds protocol messages like ACK, PING etc.
*/
public static final short TYPE_MESSAGES = 1;
/**
* A Packet that holds TransactionEvent information such as Bean
* and or Table IUD information.
*/
public static final short TYPE_TRANSEVENT = 2;
/**
* The type of Packet.
*/
protected final short packetType;
/**
* The PacketId.
*/
protected final long packetId;
/**
* The timestamp the Packet was created.
*/
protected final long timestamp;
/**
* The EbeanServer name this relates to if relevant.
*/
protected final String serverName;
protected ByteArrayOutputStream buffer;
protected DataOutputStream dataOut;
protected byte[] bytes;
/**
* The number of messages in this Packet.
*/
private int messageCount;
/**
* The number of times this Packet was resent.
*/
private int resendCount;
/**
* Create a Packet for writing messages to.
*/
public static Packet forWrite(short packetType, long packetId, long timestamp, String serverName) throws IOException {
return new Packet(true, packetType, packetId, timestamp, serverName);
}
/**
* Create a Packet just reading the Header information.
*/
public static Packet readHeader(DataInput dataInput) throws IOException {
short packetType = dataInput.readShort();
long packetId = dataInput.readLong();
long timestamp = dataInput.readLong();
String serverName = dataInput.readUTF();
return new Packet(false, packetType, packetId, timestamp, serverName);
}
protected Packet(boolean write, short packetType, long packetId, long timestamp, String serverName) throws IOException {
this.packetType = packetType;
this.packetId = packetId;
this.timestamp = timestamp;
this.serverName = serverName;
if (write) {
this.buffer = new ByteArrayOutputStream();
this.dataOut = new DataOutputStream(buffer);
writeHeader();
} else {
this.buffer = null;
this.dataOut = null;
}
}
private void writeHeader() throws IOException {
dataOut.writeShort(packetType);
dataOut.writeLong(packetId);
dataOut.writeLong(timestamp);
dataOut.writeUTF(serverName);
}
public int incrementResendCount() {
return resendCount++;
}
public short getPacketType() {
return packetType;
}
public long getPacketId() {
return packetId;
}
public long getTimestamp() {
return timestamp;
}
public String getServerName() {
return serverName;
}
public void writeEof() throws IOException {
dataOut.writeBoolean(false);
}
public void read(DataInput dataInput) throws IOException {
boolean more = dataInput.readBoolean();
while (more) {
int msgType = dataInput.readInt();
readMessage(dataInput, msgType);
// see if there is more information
more = dataInput.readBoolean();
}
}
/**
* Overridden by more specific Packet implementations to read the messages.
*/
protected void readMessage(DataInput dataInput, int msgType) throws IOException {
}
/**
* Write a binary message to this packet returning true if there was
* enough room to do so. Return false if the message was too large for
* the remaining space left - in this case another Packet should be
* created to put that message into.
*/
public boolean writeBinaryMessage(BinaryMessage msg, int maxPacketSize) throws IOException {
byte[] bytes = msg.getByteArray();
if (messageCount > 0 && (bytes.length + buffer.size() > maxPacketSize)) {
// we are actually going to ignore the maxPacketSize iff we have one
// large message.
// false = no more messages
dataOut.writeBoolean(false);
return false;
}
++messageCount;
// true = another message follows
dataOut.writeBoolean(true);
dataOut.write(bytes);
return true;
}
public int getSize() {
return getBytes().length;
}
/**
* Return the Packet as raw bytes.
*/
public byte[] getBytes() {
if (bytes == null) {
bytes = buffer.toByteArray();
buffer = null;
dataOut = null;
}
return bytes;
}
}
@@ -1,69 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.server.cluster.mcast.Message;
import com.avaje.ebeaninternal.server.cluster.mcast.MessageAck;
import com.avaje.ebeaninternal.server.cluster.mcast.MessageControl;
import com.avaje.ebeaninternal.server.cluster.mcast.MessageResend;
import java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A Packet that contains Ack, Resend and Control messages.
*
* @author rbygrave
*/
public class PacketMessages extends Packet {
private final ArrayList<Message> messages;
public static PacketMessages forWrite(long packetId, long timestamp, String serverName) throws IOException {
return new PacketMessages(true, packetId, timestamp, serverName);
}
public static PacketMessages forRead(Packet header) throws IOException {
return new PacketMessages(header);
}
private PacketMessages(boolean write, long packetId, long timestamp, String serverName) throws IOException {
super(write, TYPE_MESSAGES, packetId, timestamp, serverName);
this.messages = null;
}
private PacketMessages(Packet header) throws IOException {
super(false, TYPE_MESSAGES, header.packetId, header.timestamp, header.serverName);
this.messages = new ArrayList<Message>();
}
/**
* Return the messages contained in this Packet.
*/
public List<Message> getMessages() {
return messages;
}
/**
* Read the messages (Ack, Resend or Control) contained in this packet.
*/
protected void readMessage(DataInput dataInput, int msgType) throws IOException {
switch (msgType) {
case BinaryMessage.TYPE_MSGCONTROL:
messages.add(MessageControl.readBinaryMessage(dataInput));
break;
case BinaryMessage.TYPE_MSGACK:
messages.add(MessageAck.readBinaryMessage(dataInput));
break;
case BinaryMessage.TYPE_MSGRESEND:
messages.add(MessageResend.readBinaryMessage(dataInput));
break;
default:
throw new RuntimeException("Invalid Transaction msgType " + msgType);
}
}
}
@@ -1,64 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIds;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.io.DataInput;
import java.io.IOException;
/**
* A Packet holding TransactionEvent data.
* <p>
* Due to the hard limit for UDP packet sizes a RemoteTransactionEvent
* is actually broken up into smaller messages.
* </p>
*/
public class PacketTransactionEvent extends Packet {
private final SpiEbeanServer server;
private final RemoteTransactionEvent event;
public static PacketTransactionEvent forWrite(long packetId, long timestamp, String serverName) throws IOException {
return new PacketTransactionEvent(true, packetId, timestamp, serverName);
}
private PacketTransactionEvent(boolean write, long packetId, long timestamp, String serverName) throws IOException {
super(write, TYPE_TRANSEVENT, packetId, timestamp, serverName);
this.server = null;
this.event = null;
}
private PacketTransactionEvent(Packet header, SpiEbeanServer server) throws IOException {
super(false, TYPE_TRANSEVENT, header.packetId, header.timestamp, header.serverName);
this.server = server;
this.event = new RemoteTransactionEvent(server);
}
public static PacketTransactionEvent forRead(Packet header, SpiEbeanServer server) throws IOException {
return new PacketTransactionEvent(header, server);
}
public RemoteTransactionEvent getEvent() {
return event;
}
protected void readMessage(DataInput dataInput, int msgType) throws IOException {
switch (msgType) {
case BinaryMessage.TYPE_BEANIUD:
event.addBeanPersistIds(BeanPersistIds.readBinaryMessage(server, dataInput));
break;
case BinaryMessage.TYPE_TABLEIUD:
event.addTableIUD(TableIUD.readBinaryMessage(dataInput));
break;
default:
throw new RuntimeException("Invalid Transaction msgType " + msgType);
}
}
}
@@ -1,160 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.server.cluster.mcast.Message;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Creates Packets for either RemoteTransactionEvents or Messages (Ping, ACK,
* Join, Leave etc).
*
* @author rbygrave
*/
public class PacketWriter {
private final PacketIdGenerator idGenerator;
private final PacketBuilder messagesPacketBuilder;
private final PacketBuilder transEventPacketBuilder;
/**
* Create a PacketWriter with an expected max packet size.
* <p>
* In theory we would prefer to create packets up to the MTU size which for
* Ethernet will likely be 1500. Note that the maxPacketSize is ignored for
* large single messages.
* </p>
*/
public PacketWriter(int maxPacketSize) {
this.idGenerator = new PacketIdGenerator();
this.messagesPacketBuilder = new PacketBuilder(maxPacketSize, idGenerator, new MessagesPacketFactory());
this.transEventPacketBuilder = new PacketBuilder(maxPacketSize, idGenerator, new TransPacketFactory());
}
/**
* Return the currentPacketId.
*/
public long currentPacketId() {
return idGenerator.currentPacketId();
}
/**
* Create Packets for a given list of messages.
* <p>
* Typically this creates a single Packet but there is a hard limit for UDP
* packet sizes.
* </p>
*/
public List<Packet> write(boolean requiresAck, List<? extends Message> messages) throws IOException {
BinaryMessageList binaryMsgList = new BinaryMessageList();
for (int i = 0; i < messages.size(); i++) {
Message message = messages.get(i);
message.writeBinaryMessage(binaryMsgList);
}
return messagesPacketBuilder.write(requiresAck, binaryMsgList, "");
}
/**
* Create Packets for a given RemoteTransactionEvent.
* <p>
* Typically this creates a single Packet but there is a hard limit for UDP
* packet sizes.
* </p>
*/
public List<Packet> write(RemoteTransactionEvent transEvent) throws IOException {
BinaryMessageList messageList = new BinaryMessageList();
// split into reasonably small independent messages
transEvent.writeBinaryMessage(messageList);
return transEventPacketBuilder.write(true, messageList, transEvent.getServerName());
}
/**
* Reuse the same packetIdCounter for building Packets for both Message and
* RemoteTransactionEvent
*/
private static class PacketIdGenerator {
long packetIdCounter;
public long nextPacketId() {
return ++packetIdCounter;
}
public long currentPacketId() {
return packetIdCounter;
}
}
interface PacketFactory {
Packet createPacket(long packetId, long timestamp, String serverName) throws IOException;
}
private static class TransPacketFactory implements PacketFactory {
public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException {
return PacketTransactionEvent.forWrite(packetId, timestamp, serverName);
}
}
private static class MessagesPacketFactory implements PacketFactory {
public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException {
return PacketMessages.forWrite(packetId, timestamp, serverName);
}
}
/**
* Helper class for building Packets from messages or
* RemoteTransactionEvents.
*/
private static class PacketBuilder {
private final PacketIdGenerator idGenerator;
private final PacketFactory packetFactory;
private final int maxPacketSize;
private PacketBuilder(int maxPacketSize, PacketIdGenerator idGenerator, PacketFactory packetFactory) {
this.maxPacketSize = maxPacketSize;
this.idGenerator = idGenerator;
this.packetFactory = packetFactory;
}
private List<Packet> write(boolean requiresAck, BinaryMessageList messageList, String serverName)
throws IOException {
List<BinaryMessage> list = messageList.getList();
ArrayList<Packet> packets = new ArrayList<Packet>(1);
long timestamp = System.currentTimeMillis();
long packetId = requiresAck ? idGenerator.nextPacketId() : 0;
Packet p = packetFactory.createPacket(packetId, timestamp, serverName);
packets.add(p);
for (int i = 0; i < list.size(); i++) {
BinaryMessage binMsg = list.get(i);
if (!p.writeBinaryMessage(binMsg, maxPacketSize)) {
// didn't fit into the package so put into another packet
packetId = requiresAck ? idGenerator.nextPacketId() : 0;
p = packetFactory.createPacket(packetId, timestamp, serverName);
packets.add(p);
p.writeBinaryMessage(binMsg, maxPacketSize);
}
}
p.writeEof();
return packets;
}
}
}
@@ -1,60 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.List;
/**
* Mechanism to convert RemoteTransactionEvent to/from byte[] content.
*/
public abstract class SerialiseTransactionHelper {
private static final Logger logger = LoggerFactory.getLogger(SerialiseTransactionHelper.class);
private final PacketWriter packetWriter;
public SerialiseTransactionHelper() {
packetWriter = new PacketWriter(Integer.MAX_VALUE);
}
public abstract SpiEbeanServer getEbeanServer(String serverName);
/**
* Convert the RemoteTransactionEvent to byte[] content.
*/
public DataHolder createDataHolder(RemoteTransactionEvent transEvent) throws IOException {
List<Packet> packetList = packetWriter.write(transEvent);
if (packetList.size() != 1) {
throw new RuntimeException("Always expecting 1 Packet but got " + packetList.size());
}
byte[] data = packetList.get(0).getBytes();
return new DataHolder(data);
}
/**
* Convert the byte[] content to RemoteTransactionEvent.
*/
public RemoteTransactionEvent read(DataHolder dataHolder) throws IOException {
ByteArrayInputStream bi = new ByteArrayInputStream(dataHolder.getData());
DataInputStream dataInput = new DataInputStream(bi);
Packet header = Packet.readHeader(dataInput);
SpiEbeanServer server = getEbeanServer(header.getServerName());
if (server == null) {
logger.error("server [{}] not found/registered?", header.getServerName());
}
PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server);
tranEventPacket.read(dataInput);
return tranEventPacket.getEvent();
}
}
@@ -1,41 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.util.ArrayList;
import java.util.List;
/**
* Holds a list of ACK and RESEND messages that should be sent out.
*/
public class AckResendMessages {
final ArrayList<Message> messages = new ArrayList<Message>();
public String toString() {
return messages.toString();
}
public int size() {
return messages.size();
}
/**
* Add a ACK message to send.
*/
public void add(MessageAck ack) {
messages.add(ack);
}
/**
* Add a RESEND message to send.
*/
public void add(MessageResend resend) {
messages.add(resend);
}
/**
* Return all the messages to be sent out.
*/
public List<Message> getMessages() {
return messages;
}
}
@@ -1,51 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.util.HashMap;
import java.util.List;
/**
* For this node this holds the ACK gotAllPoint for each member in the cluster.
* <p>
* As we receive messages from other members of the cluster periodically we need
* to send them ACK messages to say we got all the packets up to the gotAllPoint.
* </p>
* Thread Safety note: Object only used by McastClusterBroadcast Manager thread.
* So Single Threaded access.
*/
public class IncomingPacketsLastAck {
private final HashMap<String, MessageAck> lastAckMap = new HashMap<String, MessageAck>();
public String toString() {
return lastAckMap.values().toString();
}
/**
* Remove a member of the cluster who has left.
*/
public void remove(String memberHostPort) {
lastAckMap.remove(memberHostPort);
}
/**
* Get the last Ack point for a given member of the cluster.
*/
public MessageAck getLastAck(String memberHostPort) {
return lastAckMap.get(memberHostPort);
}
/**
* For the ACK messages in AckResendMessages update the
* last Ack packetId.
*/
public void updateLastAck(AckResendMessages ackResendMessages) {
List<Message> messages = ackResendMessages.getMessages();
for (int i = 0; i < messages.size(); i++) {
Message msg = messages.get(i);
if (msg instanceof MessageAck) {
MessageAck lastAck = (MessageAck) msg;
lastAckMap.put(lastAck.getToHostPort(), lastAck);
}
}
}
}
@@ -1,271 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* For Incoming Packets remembers the packets we have received and processed.
* <p>
* This determines the gotAllPoint per cluster member and identifies missing
* packets (gap between gotAllPoint and gotMaxPoint).
* </p>
* <p>
* This information is used by the managerThread so send ACK's for messages we
* have received and RESEND messages to fill the missing packets we have
* detected.
* </p>
*/
public class IncomingPacketsProcessed {
private final ConcurrentHashMap<String, GotAllPoint> mapByMember = new ConcurrentHashMap<String, GotAllPoint>();
private final int maxResendIncoming;
public IncomingPacketsProcessed(int maxResendIncoming) {
this.maxResendIncoming = maxResendIncoming;
}
public void removeMember(String memberKey) {
mapByMember.remove(memberKey);
}
/**
* Return true if we should process this packet. Return false if we have
* already processed the packet.
*/
public boolean isProcessPacket(String memberKey, long packetId) {
GotAllPoint memberPackets = getMemberPackets(memberKey);
return memberPackets.processPacket(packetId);
}
/**
* Build the list of ACK and RESEND messages that we should send out
* to the other members of the cluster.
*/
public AckResendMessages getAckResendMessages(IncomingPacketsLastAck lastAck) {
// Called by the McastClusterBroadcast manager thread
AckResendMessages response = new AckResendMessages();
for (GotAllPoint member : mapByMember.values()) {
MessageAck lastAckMessage = lastAck.getLastAck(member.getMemberKey());
member.addAckResendMessages(response, lastAckMessage);
}
return response;
}
private GotAllPoint getMemberPackets(String memberKey) {
// This method is only called single threaded
// by the listener thread so I'm happy that this
// put into mapByMember is ok.
GotAllPoint memberGotAllPoint = mapByMember.get(memberKey);
if (memberGotAllPoint == null) {
memberGotAllPoint = new GotAllPoint(memberKey, maxResendIncoming);
mapByMember.put(memberKey, memberGotAllPoint);
}
return memberGotAllPoint;
}
/**
* Keeps track of packets received from a particular member of the cluster.
* <p>
* It notes the packetIds of the packets received and uses those to maintain
* the 'gotAllPoint'. The 'gotAllPoint' is the packetId which we know we
* received all the previous packets.
* </p>
*/
public static class GotAllPoint {
private static final Logger logger = LoggerFactory.getLogger(GotAllPoint.class);
private final String memberKey;
private final int maxResendIncoming;
private long gotAllPoint;
private long gotMaxPoint;
/**
* Packets received out of order.
*/
private final ArrayList<Long> outOfOrderList = new ArrayList<Long>();
private final HashMap<Long, Integer> resendCountMap = new HashMap<Long, Integer>();
public GotAllPoint(String memberKey, int maxResendIncoming) {
this.memberKey = memberKey;
this.maxResendIncoming = maxResendIncoming;
}
/**
* Add ACK and RESEND messages if required.
*/
public void addAckResendMessages(AckResendMessages response, MessageAck lastAckMessage) {
synchronized (this) {
if (lastAckMessage != null && lastAckMessage.getGotAllPacketId() >= gotAllPoint) {
// nothing has changed
} else {
// ACK that we have got every packet up to gotAllPoint
response.add(new MessageAck(memberKey, gotAllPoint));
}
if (getMissingPacketCount() > 0) {
// Ask for these Packets to be RESENT
List<Long> missingPackets = getMissingPackets();
response.add(new MessageResend(memberKey, missingPackets));
}
}
}
public String getMemberKey() {
return memberKey;
}
public long getGotAllPoint() {
synchronized (this) {
return gotAllPoint;
}
}
public long getGotMaxPoint() {
synchronized (this) {
return gotMaxPoint;
}
}
private int getMissingPacketCount() {
if (gotMaxPoint <= gotAllPoint) {
if (!resendCountMap.isEmpty()) {
resendCountMap.clear();
}
return 0;
}
return (int) (gotMaxPoint - gotAllPoint) - outOfOrderList.size();
}
public List<Long> getMissingPackets() {
synchronized (this) {
ArrayList<Long> missingList = new ArrayList<Long>();
// this is not particularly efficient but expecting
// the outOfOrderList to be relatively small
boolean lostPacket = false;
for (long i = gotAllPoint + 1; i < gotMaxPoint; i++) {
Long packetId = i;
if (!outOfOrderList.contains(packetId)) {
if (incrementResendCount(packetId)) {
// request this packet be resent
missingList.add(packetId);
} else {
lostPacket = true;
}
}
}
if (lostPacket) {
checkOutOfOrderList();
}
return missingList;
}
}
/**
* Return true if this packet has not yet exceeded the maxResendCount.
*/
private boolean incrementResendCount(Long packetId) {
Integer resendCount = resendCountMap.get(packetId);
if (resendCount != null) {
int i = resendCount + 1;
if (i > maxResendIncoming) {
// we are going to give up trying to get this packet now
logger.warn("Exceeded maxResendIncoming[" + maxResendIncoming + "] for packet[" + packetId + "]. Giving up on requesting it.");
resendCountMap.remove(packetId);
outOfOrderList.add(packetId);
return false;
}
resendCount = i;
resendCountMap.put(packetId, resendCount);
} else {
resendCountMap.put(packetId, ONE);
}
return true;
}
private static final Integer ONE = 1;
public boolean processPacket(long packetId) {
synchronized (this) {
if (gotAllPoint == 0) {
gotAllPoint = packetId;
return true;
}
if (packetId <= gotAllPoint) {
// already processed this packet
return false;
}
if (!resendCountMap.isEmpty()) {
resendCountMap.remove(Long.valueOf(packetId));
}
if (packetId == gotAllPoint + 1) {
gotAllPoint = packetId;
} else {
if (packetId > gotMaxPoint) {
gotMaxPoint = packetId;
}
outOfOrderList.add(packetId);
}
checkOutOfOrderList();
return true;
}
}
private void checkOutOfOrderList() {
if (outOfOrderList.size() == 0) {
return;
}
boolean continueCheck;
do {
continueCheck = false;
long nextPoint = gotAllPoint + 1;
Iterator<Long> it = outOfOrderList.iterator();
while (it.hasNext()) {
Long id = it.next();
if (id == nextPoint) {
// we found the next one in the outOfOrderList
it.remove();
gotAllPoint = nextPoint;
continueCheck = true;
break;
}
}
} while (continueCheck);
}
}
}
@@ -1,571 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.ClusterBroadcast;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.avaje.ebeaninternal.server.cluster.PacketWriter;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
/**
* Overall Manager of the Multicast Cluster communication for this instance.
* <p>
* McastListener, McastSender and McastPacketControl are the main helpers to
* this object.
* </p>
* <p>
* This Manager (thread) periodically processes the ACK, Re-send and Control
* messages. The McastListener is handling all the incoming packets and informs
* this manager when interesting packets need to be processed by the Manager.
* </p>
* <p>
* Other threads call {@link #broadcast(RemoteTransactionEvent)} to send
* transaction even information.
* </p>
*/
public class McastClusterManager implements ClusterBroadcast, Runnable {
private static final Logger logger = LoggerFactory.getLogger(McastClusterManager.class);
private ClusterManager clusterManager;
private final Thread managerThread;
/**
* Helps co-ordinate packet information (Acks, Missing Packets etc).
*/
private final McastPacketControl packageControl;
/**
* Listeners for incoming packets.
*/
private final McastListener listener;
/**
* Sends packets out to the cluster.
*/
private final McastSender localSender;
/**
* The localSenderHostPort is used to identify this instance in the cluster.
*/
private final String localSenderHostPort;
/**
* Creates the Packets (byte[]) from Messages and RemoteTransactionEvent.
*/
private final PacketWriter packetWriter;
/**
* List of Re-send messages that the managerThread needs to process.
*/
private final ArrayList<MessageResend> resendMessages = new ArrayList<MessageResend>();
/**
* List of Control messages (Ping,PingResponse,Join,Leave) that the managerThread needs to process.
*/
private final ArrayList<MessageControl> controlMessages = new ArrayList<MessageControl>();
/**
* Cache of outgoing messages that have not been ACK'ed by the other cluster members yet.
*/
private final OutgoingPacketsCache outgoingPacketsCache = new OutgoingPacketsCache();
/**
* The last ACK we sent out to other members of the cluster.
*/
private final IncomingPacketsLastAck incomingPacketsLastAck = new IncomingPacketsLastAck();
/**
* A limit of the number of times we will try to send out a given packet.
* Once this is exceeded we will just drop that packet. Hopefully this does
* not happen but we don't want to keep trying forever producing network
* traffic.
*/
private final int maxResendOutgoing;
/**
* Instead of ACK'ing immediately we periodically wake up and in a single
* packet (typically) ACK all members of the cluster everything we got since
* the last sleep time. More frequent ACK's means less memory consumption as
* Packets are cleared from the outgoingPacketsCache quicker at the cost of
* sending more packets.
*/
private final long managerSleepMillis;
/**
* When true then packets are still sent out even when the cluster has no other online members.
*/
private final boolean sendWithNoMembers;
/**
* The current minAcked packetId processed by the managerThread.
* All packets before this have been ACK'ed by everyone in the cluster.
*/
private long minAcked;
/**
* The min packetId that has been ACKed by all the members of the cluster according
* to the McastListener. This will increase as the Listener receives ACK's and means
* we can trim out Packets from the sent cache.
*/
private long minAckedFromListener;
/**
* Start the groupSize at -1 so we have to wait until the Listener times out or gets
* a control messages (Ping, PingResponse, Join, Leave etc) before we know how many
* members of the group the listener knows about.
* <p>
* Generally speaking we only care if the groupSize == 0 meaning there are no other
* members of the cluster that are online. In this case we can potentially not send
* the packets out (depending on sendWithNoMembers) and not cache them (for re-sending
* if they where not ACK'ed).
* </p>
*/
private int currentGroupSize = -1;
/**
* The last time a packet was sent from this node.
*/
private long lastSendTime;
/**
* The max time we go without sending any packets.
*/
private final int lastSendTimeFreqMillis;
/**
* The last time the cluster status was logged.
*/
private long lastStatusTime = System.currentTimeMillis();
/**
* The max time we go before logging the cluster status.
*/
private final int lastStatusTimeFreqMillis;
private long totalTxnEventsSent;
private long totalTxnEventsReceived;
private long totalPacketsSent;
private long totalBytesSent;
private long totalPacketsResent;
private long totalBytesResent;
private long totalPacketsReceived;
private long totalBytesReceived;
public McastClusterManager(ContainerConfig containerConfig) {
ContainerConfig.MulticastConfig config = containerConfig.getMulticastConfig();
this.managerSleepMillis = config.getManagerSleepMillis();
this.lastSendTimeFreqMillis = 1000 * config.getLastSendTimeFreqSecs();
this.lastStatusTimeFreqMillis = 1000 * config.getLastStatusTimeFreqSecs();
// the maximum number of times we will try to re-send a given packet before giving up sending
this.maxResendOutgoing = config.getMaxResendOutgoingAttempts();
// the maximum number of times we will ask for a packet to be resent to us before giving up asking
int maxResendIncoming = config.getMaxResendIncomingRequests();
int port = config.getListenPort();
String addr = config.getListenAddress();
int sendPort = config.getSendPort();
String sendAddr = config.getSendAddress();
// Sender options
// Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507
int maxSendPacketSize = config.getMaxSendPacketSize();
// Whether to send packets even when there are no other members online
this.sendWithNoMembers = config.isSendWithNoMembers();
// Listener options
// When multiple instances are on same box you need to broadcast back locally
boolean disableLoopback = config.isDisableLoopback();
int ttl = config.getListenTimeToLive();
int timeout = config.getListenTimeout();
int bufferSize = config.getListenBufferSize();
// For multihomed environment the address the listener should bind to
String mcastAddr = config.getListenBindAddress();
InetAddress mcastBindAddress = null;
if (mcastAddr != null) {
try {
mcastBindAddress = InetAddress.getByName(mcastAddr);
} catch (UnknownHostException e) {
String msg = "Error getting Multicast InetAddress for " + mcastAddr;
throw new RuntimeException(msg, e);
}
}
if (port == 0 || addr == null) {
String msg = "One of these Multicast settings has not been set. " + "ebean.cluster.mcast.listen.port="
+ port + ", ebean.cluster.mcast.listen.address=" + addr;
throw new IllegalArgumentException(msg);
}
this.managerThread = new Thread(this, "EbeanClusterMcastManager");
this.packetWriter = new PacketWriter(maxSendPacketSize);
this.localSender = new McastSender(port, addr, sendPort, sendAddr);
this.localSenderHostPort = localSender.getSenderHostPort();
this.packageControl = new McastPacketControl(this, localSenderHostPort, maxResendIncoming);
this.listener = new McastListener(this, packageControl, port, addr, bufferSize, timeout, localSenderHostPort,
disableLoopback, ttl, mcastBindAddress);
}
/**
* The McastListener tells us there are no other members of the cluster that
* are currently online.
*/
protected void fromListenerTimeoutNoMembers() {
synchronized (managerThread) {
this.currentGroupSize = 0;
}
}
/**
* McastListener calls this method to get the manager to process messages.
*
* @param newMinAcked the minAcked packetId according to the listener
* @param msgControl a control message to process
* @param msgResend a Please re-send message to process
* @param groupSize the number of other online members
*/
protected void fromListener(long newMinAcked, MessageControl msgControl, MessageResend msgResend,
int groupSize, long totalPacketsReceived, long totalBytesReceived,
long totalTxnEventsReceived) {
synchronized (managerThread) {
if (newMinAcked > minAckedFromListener) {
minAckedFromListener = newMinAcked;
}
if (msgControl != null) {
controlMessages.add(msgControl);
}
if (msgResend != null) {
resendMessages.add(msgResend);
}
// mostly interested when groupSize hits 0 (we are the only instance online).
this.currentGroupSize = groupSize;
// and some stats so we know how busy the listener has been
this.totalPacketsReceived = totalPacketsReceived;
this.totalBytesReceived = totalBytesReceived;
this.totalTxnEventsReceived = totalTxnEventsReceived;
}
}
/**
* Get the overall status and activity of this cluster node.
*/
public McastStatus getStatus() {
synchronized (managerThread) {
long currentPacketId = packetWriter.currentPacketId();
String lastAcks = incomingPacketsLastAck.toString();
return new McastStatus(currentGroupSize, outgoingPacketsCache.size(), currentPacketId, minAcked, lastAcks,
totalTxnEventsSent, totalTxnEventsReceived, totalPacketsSent, totalPacketsResent,
totalPacketsReceived,
totalBytesSent, totalBytesResent, totalBytesReceived);
}
}
/**
* Periodically send out Ack, Re-send and Control messages.
*/
public void run() {
while (true) {
try {
// sleep for a little bit as we ACK packets periodically
// rather than immediately. We will typically ACK many
// messages from all cluster members in a single Packet
Thread.sleep(managerSleepMillis);
synchronized (managerThread) {
handleControlMessages();
handleResendMessages();
if (currentGroupSize == 0) {
// no members online so trim the entire outgoing packets cache
int trimmedCount = outgoingPacketsCache.trimAll();
if (trimmedCount > 0) {
logger.debug("Cluster has no other members. Trimmed " + trimmedCount);
}
} else if (minAckedFromListener > minAcked) {
// ACKs have come back so trim send packets cache
outgoingPacketsCache.trimAcknowledgedMessages(minAckedFromListener);
minAcked = minAckedFromListener;
}
// Get list of all the ACK messages required to sent since the last time.
// This is effectively one ACK message per member of the cluster. The ACK
// message covers all the packets received from the member up to
// the gotAllPoint.
// Also get any RESEND messages asking for packets that we have not
// received between the gotAllPoint and the gotMaxPoint.
AckResendMessages ackResendMessages = packageControl.getAckResendMessages(incomingPacketsLastAck);
if (ackResendMessages.size() > 0) {
// send the ACK and RESEND messages for all members of the
// cluster typically in a single Packet
if (sendMessages(false, ackResendMessages.getMessages())) {
// update the last Ack position
incomingPacketsLastAck.updateLastAck(ackResendMessages);
}
}
if (lastSendTime < System.currentTimeMillis() - lastSendTimeFreqMillis) {
// been quite for too long - send a Ping out
sendPing();
}
if (lastStatusTimeFreqMillis > 0) {
if (lastStatusTime < System.currentTimeMillis() - lastStatusTimeFreqMillis) {
McastStatus status = getStatus();
logger.info("Cluster Status: " + status.getSummary());
lastStatusTime = System.currentTimeMillis();
}
}
}
} catch (Exception e) {
logger.error("Error with Cluster Mcast Manager thread", e);
}
}
}
/**
* We have been asked to Re-send some packets.
*/
private void handleResendMessages() {
if (resendMessages.size() > 0) {
TreeSet<Long> s = new TreeSet<Long>();
for (int i = 0; i < resendMessages.size(); i++) {
MessageResend resendMsg = resendMessages.get(i);
s.addAll(resendMsg.getResendPacketIds());
}
totalPacketsResent += s.size();
for (Long resendPacketId : s) {
Packet packet = outgoingPacketsCache.getPacket(resendPacketId);
if (packet == null) {
String msg = "Cluster unable to resend packet[" + resendPacketId + "] as it is no longer in the " +
"outgoingPacketsCache";
logger.error(msg);
} else {
int resendCount = packet.incrementResendCount();
if (resendCount <= maxResendOutgoing) {
resendPacket(packet);
} else {
String msg = "Cluster maxResendOutgoing [" + maxResendOutgoing + "] hit for packet " + resendPacketId
+ ". We will not try to send it anymore, removing it from the outgoingPacketsCache.";
logger.error(msg);
outgoingPacketsCache.remove(packet);
}
}
}
}
}
/**
* Re-send a packet that a member didn't seem to receive.
*/
private void resendPacket(Packet packet) {
try {
++totalPacketsResent;
totalBytesResent += localSender.sendPacket(packet);
} catch (IOException e) {
String msg = "Error trying to resend packet " + packet.getPacketId();
logger.error(msg, e);
}
}
/**
* Handle Control messages (Join, Leave, Ping).
*/
private void handleControlMessages() {
boolean pingReponse = false;
boolean joinReponse = false;
for (int i = 0; i < controlMessages.size(); i++) {
MessageControl message = controlMessages.get(i);
short type = message.getControlType();
switch (type) {
case MessageControl.TYPE_JOIN:
// a new member online, send back a Join Response
logger.info("Cluster member Joined [" + message.getFromHostPort() + "]");
joinReponse = true;
break;
case MessageControl.TYPE_JOINRESPONSE:
logger.info("Cluster member Online [" + message.getFromHostPort() + "]");
// do nothing
break;
case MessageControl.TYPE_PING:
pingReponse = true;
break;
case MessageControl.TYPE_PINGRESPONSE:
// do nothing
break;
case MessageControl.TYPE_LEAVE:
// remove member. If/When that member comes back its
// packetIds will have been reset
incomingPacketsLastAck.remove(message.getFromHostPort());
break;
default:
break;
}
}
controlMessages.clear();
if (joinReponse) {
sendJoinResponse();
}
if (pingReponse) {
sendPingResponse();
}
}
/**
* Say 'Leaving' and shutdown.
*/
public void shutdown() {
sendLeave();
listener.shutdown();
}
/**
* Startup listeners and 'Join'.
*/
public void startup(ClusterManager clusterManager) {
this.clusterManager = clusterManager;
listener.startListening();
this.managerThread.setDaemon(true);
this.managerThread.start();
sendJoin();
}
protected SpiEbeanServer getEbeanServer(String serverName) {
return (SpiEbeanServer) clusterManager.getServer(serverName);
}
private void sendJoin() {
sendControlMessage(true, MessageControl.TYPE_JOIN);
}
private void sendLeave() {
sendControlMessage(false, MessageControl.TYPE_LEAVE);
}
private void sendJoinResponse() {
sendControlMessage(true, MessageControl.TYPE_JOINRESPONSE);
}
private void sendPingResponse() {
sendControlMessage(true, MessageControl.TYPE_PINGRESPONSE);
}
private void sendPing() {
sendControlMessage(true, MessageControl.TYPE_PING);
}
private void sendControlMessage(boolean requiresAck, short controlType) {
sendMessage(requiresAck, new MessageControl(controlType, localSenderHostPort));
}
private void sendMessage(boolean requiresAck, Message msg) {
ArrayList<Message> messages = new ArrayList<Message>(1);
messages.add(msg);
sendMessages(requiresAck, messages);
}
private boolean sendMessages(boolean requiresAck, List<? extends Message> messages) {
synchronized (managerThread) {
try {
List<Packet> packets = packetWriter.write(requiresAck, messages);
sendPackets(requiresAck, packets);
return true;
} catch (IOException e) {
String msg = "Error sending Messages " + messages;
logger.error(msg, e);
return false;
}
}
}
private boolean sendPackets(boolean requiresAck, List<Packet> packets) throws IOException {
if (currentGroupSize == 0 && !sendWithNoMembers) {
// no other members online so not sending packets
return false;
} else {
if (requiresAck) {
// cache them until they have been ACK'ed
outgoingPacketsCache.registerPackets(packets);
}
totalPacketsSent += packets.size();
totalBytesSent += localSender.sendPackets(packets);
lastSendTime = System.currentTimeMillis();
return true;
}
}
/**
* Send the remoteTransEvent to all the other members of the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent) {
synchronized (managerThread) {
try {
List<Packet> packets = packetWriter.write(remoteTransEvent);
if (sendPackets(true, packets)) {
++totalTxnEventsSent;
}
} catch (IOException e) {
String msg = "Error sending RemoteTransactionEvent " + remoteTransEvent;
logger.error(msg, e);
}
}
}
}
@@ -1,237 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.avaje.ebeaninternal.server.cluster.PacketTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
/**
* Listens for Incoming packets.
*/
public class McastListener implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(McastListener.class);
private final McastClusterManager owner;
private final McastPacketControl packetControl;
private final MulticastSocket sock;
private final Thread listenerThread;
private final String localSenderHostPort;
private final InetAddress group;
private DatagramPacket pack;
private final byte[] receiveBuffer;
private volatile boolean shutdown;
private volatile boolean shutdownComplete;
private long totalPacketsReceived;
private long totalBytesReceived;
private long totalTxnEventsReceived;
public McastListener(McastClusterManager owner, McastPacketControl packetControl, int port, String address,
int bufferSize, int timeout, String localSenderHostPort,
boolean disableLoopback, int ttl, InetAddress mcastBindAddress) {
this.owner = owner;
this.packetControl = packetControl;
this.localSenderHostPort = localSenderHostPort;
this.receiveBuffer = new byte[bufferSize];
this.listenerThread = new Thread(this, "EbeanClusterMcastListener");
String msg = "Cluster Multicast Listening address[" + address + "] port[" + port + "] disableLoopback[" + disableLoopback + "]";
if (ttl >= 0) {
msg += " ttl[" + ttl + "]";
}
if (mcastBindAddress != null) {
msg += " mcastBindAddress[" + mcastBindAddress + "]";
}
logger.info(msg);
try {
this.group = InetAddress.getByName(address);
this.sock = new MulticastSocket(port);
this.sock.setSoTimeout(timeout);
if (disableLoopback) {
sock.setLoopbackMode(true);
}
if (mcastBindAddress != null) {
// bind to a specific interface
sock.setInterface(mcastBindAddress);
}
if (ttl >= 0) {
sock.setTimeToLive(ttl);
}
sock.setReuseAddress(true);
pack = new DatagramPacket(receiveBuffer, receiveBuffer.length);
sock.joinGroup(group);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void startListening() {
this.listenerThread.setDaemon(true);
this.listenerThread.start();
logger.info("Cluster Multicast Listener up and joined Group");
}
/**
* Shutdown this listener.
*/
public void shutdown() {
shutdown = true;
synchronized (listenerThread) {
try {
// wait max 20 seconds
listenerThread.wait(20000);
} catch (InterruptedException e) {
logger.info("InterruptedException:" + e);
}
}
if (!shutdownComplete) {
String msg = "WARNING: Shutdown of McastListener did not complete?";
System.err.println(msg);
logger.warn(msg);
}
try {
sock.leaveGroup(group);
} catch (IOException e) {
// send to syserr in case logging already shutdown
e.printStackTrace();
String msg = "Error leaving Multicast group";
logger.info(msg, e);
}
try {
sock.close();
} catch (Exception e) {
// send to syserr in case logging already shutdown
e.printStackTrace();
String msg = "Error closing Multicast socket";
logger.info(msg, e);
}
}
public void run() {
while (!shutdown) {
try {
pack.setLength(receiveBuffer.length);
sock.receive(pack);
InetSocketAddress senderAddr = (InetSocketAddress) pack.getSocketAddress();
String senderHostPort = senderAddr.getAddress().getHostAddress() + ":" + senderAddr.getPort();
if (senderHostPort.equals(localSenderHostPort)) {
if (logger.isTraceEnabled()) {
logger.info("Ignoring message as sent by localSender: " + localSenderHostPort);
}
} else {
byte[] data = pack.getData();
ByteArrayInputStream bi = new ByteArrayInputStream(data);
DataInputStream dataInput = new DataInputStream(bi);
++totalPacketsReceived;
totalBytesReceived += pack.getLength();
Packet header = Packet.readHeader(dataInput);
long packetId = header.getPacketId();
boolean ackMsg = packetId == 0;
boolean processThisPacket = ackMsg || packetControl.isProcessPacket(senderHostPort, header.getPacketId());
if (!processThisPacket) {
if (logger.isTraceEnabled()) {
logger.info("Already processed packet: " + header.getPacketId() + " type:" + header.getPacketType() + " len:" + data.length);
}
} else {
if (logger.isTraceEnabled()) {
logger.info("Incoming packet:" + header.getPacketId() + " type:" + header.getPacketType() + " len:" + data.length);
}
processPacket(senderHostPort, header, dataInput);
}
}
} catch (java.net.SocketTimeoutException e) {
if (logger.isDebugEnabled()) {
logger.debug("timeout", e);
}
packetControl.onListenerTimeout();
} catch (IOException e) {
logger.info("error ?", e);
}
}
shutdownComplete = true;
synchronized (listenerThread) {
listenerThread.notifyAll();
}
}
protected void processPacket(String senderHostPort, Packet header, DataInput dataInput) {
try {
switch (header.getPacketType()) {
case Packet.TYPE_MESSAGES:
packetControl.processMessagesPacket(senderHostPort, header, dataInput,
totalPacketsReceived, totalBytesReceived, totalTxnEventsReceived);
break;
case Packet.TYPE_TRANSEVENT:
++totalTxnEventsReceived;
processTransactionEventPacket(header, dataInput);
break;
default:
String msg = "Unknown Packet type:" + header.getPacketType();
logger.error(msg);
break;
}
} catch (IOException e) {
// need to ask to get this packet resent...
String msg = "Error reading Packet " + header.getPacketId() + " type:" + header.getPacketType();
logger.error(msg, e);
}
}
private void processTransactionEventPacket(Packet header, DataInput dataInput) throws IOException {
SpiEbeanServer server = owner.getEbeanServer(header.getServerName());
PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server);
tranEventPacket.read(dataInput);
server.remoteTransactionEvent(tranEventPacket.getEvent());
}
}
@@ -1,125 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.avaje.ebeaninternal.server.cluster.PacketMessages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.DataInput;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
/**
* Helps co-ordinate Packet information between the McastListener and the
* McastClusterManager.
*
* @author rbygrave
*/
public class McastPacketControl {
private static final Logger logger = LoggerFactory.getLogger(McastPacketControl.class);
private final String localSenderHostPort;
private final McastClusterManager owner;
private final HashSet<String> groupMembers = new HashSet<String>();
private final OutgoingPacketsAcked outgoingPacketsAcked = new OutgoingPacketsAcked();
private final IncomingPacketsProcessed incomingPacketsProcessed;
public McastPacketControl(McastClusterManager owner, String localSenderHostPort, int maxResendIncoming) {
this.owner = owner;
this.localSenderHostPort = localSenderHostPort;
this.incomingPacketsProcessed = new IncomingPacketsProcessed(maxResendIncoming);
}
/**
* Handle special case where cluster doesn't have any members and we don't
* get any responses. Need to tell the sender side that the group size is 0.
*/
protected void onListenerTimeout() {
if (groupMembers.size() == 0) {
owner.fromListenerTimeoutNoMembers();
}
}
protected void processMessagesPacket(String senderHostPort, Packet header, DataInput dataInput,
long totalPacketsReceived, long totalBytesReceived, long totalTransEventsReceived) throws IOException {
PacketMessages packetMessages = PacketMessages.forRead(header);
packetMessages.read(dataInput);
List<Message> messages = packetMessages.getMessages();
if (logger.isTraceEnabled()) {
logger.trace("INCOMING Messages " + messages);
}
// messages are for all nodes in the cluster so
// we need to filter looking for messages pertaining
// to this (senderHostPort)
MessageControl control = null;
MessageAck ack = null;
MessageResend resend = null;
// filter for relevant messages to this node
for (int i = 0; i < messages.size(); i++) {
Message message = messages.get(i);
if (message.isControlMessage()) {
// any 'control' message is interesting
control = (MessageControl) message;
} else if (localSenderHostPort.equals(message.getToHostPort())) {
if (message instanceof MessageAck) {
ack = (MessageAck) message;
} else if (message instanceof MessageResend) {
resend = (MessageResend) message;
} else {
logger.error("Expecting a MessageAck or MessageResend but got a "
+ message.getClass().getName());
}
}
}
if (control != null) {
if (control.getControlType() == MessageControl.TYPE_LEAVE) {
groupMembers.remove(senderHostPort);
logger.info("Cluster member leaving [" + senderHostPort + "] " + groupMembers.size()
+ " other members left");
outgoingPacketsAcked.removeMember(senderHostPort);
incomingPacketsProcessed.removeMember(senderHostPort);
} else {
groupMembers.add(senderHostPort);
}
}
long newMin = 0;
if (ack != null) {
newMin = outgoingPacketsAcked.receivedAck(senderHostPort, ack);
}
if (newMin > 0 || control != null || resend != null) {
int groupSize = groupMembers.size();
// synchronised on the managerThread
owner.fromListener(newMin, control, resend, groupSize,
totalPacketsReceived, totalBytesReceived, totalTransEventsReceived);
}
}
/**
* Return true if we should process this packet. Return false if we have
* already processed the packet.
*/
public boolean isProcessPacket(String memberKey, long packetId) {
return incomingPacketsProcessed.isProcessPacket(memberKey, packetId);
}
public AckResendMessages getAckResendMessages(IncomingPacketsLastAck lastAck) {
return incomingPacketsProcessed.getAckResendMessages(lastAck);
}
}
@@ -1,114 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.Packet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
/**
* Handles the sending of Packets via DatagramPacket.
*/
public class McastSender {
private static final Logger logger = LoggerFactory.getLogger(McastSender.class);
private final int port;
private final InetAddress inetAddress;
private final DatagramSocket sock;
private final InetSocketAddress sendAddr;
private final String senderHostPort;
public McastSender(int port, String address, int sendPort, String sendAddress) {
try {
this.port = port;
this.inetAddress = InetAddress.getByName(address);
InetAddress sendInetAddress;
if (sendAddress != null) {
sendInetAddress = InetAddress.getByName(sendAddress);
} else {
sendInetAddress = InetAddress.getLocalHost();
}
if (sendPort > 0) {
this.sock = new DatagramSocket(sendPort, sendInetAddress);
} else {
this.sock = new DatagramSocket(new InetSocketAddress(sendInetAddress, 0));
}
String msg = "Cluster Multicast Sender on[" + sendInetAddress.getHostAddress() + ":" + sock.getLocalPort() + "]";
logger.info(msg);
this.sendAddr = new InetSocketAddress(sendInetAddress, sock.getLocalPort());
this.senderHostPort = sendInetAddress.getHostAddress() + ":" + sock.getLocalPort();
} catch (Exception e) {
String msg = "McastSender port:" + port + " sendPort:" + sendPort + " " + address;
throw new RuntimeException(msg, e);
}
}
/**
* Return the send Address so that if we have loopback messages we can
* detect if they where sent by this local sender and hence should be
* ignored.
*/
public InetSocketAddress getAddress() {
return sendAddr;
}
/**
* Return the Host and Port of the sender. This is used to uniquely identify
* this instance in the cluster.
*/
public String getSenderHostPort() {
return senderHostPort;
}
/**
* Send the packet.
*/
public int sendPacket(Packet packet) throws IOException {
byte[] pktBytes = packet.getBytes();
if (logger.isDebugEnabled()) {
logger.debug("OUTGOING packet: " + packet.getPacketId() + " size:" + pktBytes.length);
}
if (pktBytes.length > 65507) {
logger.warn("OUTGOING packet: " + packet.getPacketId() + " size:" + pktBytes.length
+ " likely to be truncated using UDP with a MAXIMUM length of 65507");
}
DatagramPacket pack = new DatagramPacket(pktBytes, pktBytes.length, inetAddress, port);
sock.send(pack);
return pktBytes.length;
}
/**
* Send the list of Packets.
*/
public int sendPackets(List<Packet> packets) throws IOException {
int totalBytes = 0;
for (int i = 0; i < packets.size(); i++) {
totalBytes += sendPacket(packets.get(i));
}
return totalBytes;
}
}
@@ -1,134 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
/**
* Gives an overall status of this Cluster instance.
* <p>
* Ideally you want to see relatively low Re-send statistics.
* </p>
*/
public class McastStatus {
private final long totalTxnEventsSent;
private final long totalTxnEventsReceived;
private final long totalPacketsSent;
private final long totalPacketsResent;
private final long totalPacketsReceived;
private final long totalBytesSent;
private final long totalBytesResent;
private final long totalBytesReceived;
private final int currentGroupSize;
private final int outgoingPacketsCacheSize;
private final long currentPacketId;
private final long minAckedPacketId;
private final String lastOutgoingAcks;
public String getSummary() {
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder(80);
sb.append("txnOut:").append(totalTxnEventsSent).append("; ");
sb.append("txnIn:").append(totalTxnEventsReceived).append("; ");
sb.append("outPackets:").append(totalPacketsSent).append("; ");
sb.append("outBytes:").append(totalBytesSent).append("; ");
sb.append("inPackets:").append(totalPacketsReceived).append("; ");
sb.append("inBytes:").append(totalBytesReceived).append("; ");
sb.append("resentPackets:").append(totalPacketsResent).append("; ");
sb.append("resentBytes:").append(totalBytesResent).append("; ");
sb.append("groupSize:").append(currentGroupSize).append("; ");
sb.append("cache:").append(outgoingPacketsCacheSize).append("; ");
sb.append("currentPacket:").append(currentPacketId).append("; ");
sb.append("minAckedPacket:").append(minAckedPacketId).append("; ");
sb.append("lastAck:").append(lastOutgoingAcks).append("; ");
return sb.toString();
}
public McastStatus(int currentGroupSize,
int outgoingPacketsCacheSize,
long currentPacketId,
long minAckedPacketId,
String lastOutgoingAcks,
long totalTransEventsSent,
long totalTransEventsReceived,
long totalPacketsSent,
long totalPacketsResent,
long totalPacketsReceived,
long totalBytesSent,
long totalBytesResent,
long totalBytesReceived) {
this.currentGroupSize = currentGroupSize;
this.outgoingPacketsCacheSize = outgoingPacketsCacheSize;
this.currentPacketId = currentPacketId;
this.minAckedPacketId = minAckedPacketId;
this.lastOutgoingAcks = lastOutgoingAcks;
this.totalTxnEventsSent = totalTransEventsSent;
this.totalTxnEventsReceived = totalTransEventsReceived;
this.totalPacketsSent = totalPacketsSent;
this.totalPacketsResent = totalPacketsResent;
this.totalPacketsReceived = totalPacketsReceived;
this.totalBytesSent = totalBytesSent;
this.totalBytesResent = totalBytesResent;
this.totalBytesReceived = totalBytesReceived;
}
public long getTotalTxnEventsReceived() {
return totalTxnEventsReceived;
}
public long getTotalPacketsReceived() {
return totalPacketsReceived;
}
public long getTotalBytesSent() {
return totalBytesSent;
}
public long getTotalBytesResent() {
return totalBytesResent;
}
public long getTotalBytesReceived() {
return totalBytesReceived;
}
public String getLastOutgoingAcks() {
return lastOutgoingAcks;
}
public int getOutgoingPacketsCacheSize() {
return outgoingPacketsCacheSize;
}
public long getCurrentPacketId() {
return currentPacketId;
}
public long getMinAckedPacketId() {
return minAckedPacketId;
}
public long getTotalTxnEventsSent() {
return totalTxnEventsSent;
}
public long getTotalPacketsSent() {
return totalPacketsSent;
}
public long getTotalPacketsResent() {
return totalPacketsResent;
}
public long getCurrentGroupSize() {
return currentGroupSize;
}
}
@@ -1,14 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.IOException;
public interface Message {
void writeBinaryMessage(BinaryMessageList msgList) throws IOException;
boolean isControlMessage();
String getToHostPort();
}
@@ -1,57 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
public class MessageAck implements Message {
private final String toHostPort;
private final long gotAllPacketId;
public MessageAck(String toHostPort, long gotAllPacketId) {
this.toHostPort = toHostPort;
this.gotAllPacketId = gotAllPacketId;
}
public String toString() {
return "Ack " + toHostPort + " " + gotAllPacketId;
}
public boolean isControlMessage() {
return false;
}
public String getToHostPort() {
return toHostPort;
}
public long getGotAllPacketId() {
return gotAllPacketId;
}
public static MessageAck readBinaryMessage(DataInput dataInput) throws IOException {
String hostPort = dataInput.readUTF();
long gotAllPacketId = dataInput.readLong();
return new MessageAck(hostPort, gotAllPacketId);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
BinaryMessage m = new BinaryMessage(toHostPort.length() * 2 + 20);
DataOutputStream os = m.getOs();
os.writeInt(BinaryMessage.TYPE_MSGACK);
os.writeUTF(toHostPort);
os.writeLong(gotAllPacketId);
os.flush();
msgList.add(m);
}
}
@@ -1,77 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
public class MessageControl implements Message {
public static final short TYPE_JOIN = 1;
public static final short TYPE_LEAVE = 2;
public static final short TYPE_PING = 3;
public static final short TYPE_JOINRESPONSE = 7;
public static final short TYPE_PINGRESPONSE = 8;
private final short controlType;
private final String fromHostPort;
public static MessageControl readBinaryMessage(DataInput dataInput) throws IOException {
short controlType = dataInput.readShort();
String hostPort = dataInput.readUTF();
return new MessageControl(controlType, hostPort);
}
public MessageControl(short controlType, String helloFromHostPort) {
this.controlType = controlType;
this.fromHostPort = helloFromHostPort;
}
public String toString() {
switch (controlType) {
case TYPE_JOIN:
return "Join " + fromHostPort;
case TYPE_LEAVE:
return "Leave " + fromHostPort;
case TYPE_PING:
return "Ping " + fromHostPort;
case TYPE_PINGRESPONSE:
return "PingResponse " + fromHostPort;
default:
throw new RuntimeException("Invalid controlType " + controlType);
}
}
public boolean isControlMessage() {
return true;
}
public short getControlType() {
return controlType;
}
public String getToHostPort() {
return "*";
}
public String getFromHostPort() {
return fromHostPort;
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
BinaryMessage m = new BinaryMessage(fromHostPort.length() * 2 + 10);
DataOutputStream os = m.getOs();
os.writeInt(BinaryMessage.TYPE_MSGCONTROL);
os.writeShort(controlType);
os.writeUTF(fromHostPort);
os.flush();
msgList.add(m);
}
}
@@ -1,77 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MessageResend implements Message {
private final String toHostPort;
private final List<Long> resendPacketIds;
public MessageResend(String toHostPort, List<Long> resendPacketIds) {
this.toHostPort = toHostPort;
this.resendPacketIds = resendPacketIds;
}
public MessageResend(String toHostPort) {
this(toHostPort, new ArrayList<Long>(4));
}
public String toString() {
return "Resend " + toHostPort + " " + resendPacketIds;
}
public boolean isControlMessage() {
return false;
}
public String getToHostPort() {
return toHostPort;
}
public void add(long packetId) {
resendPacketIds.add(packetId);
}
public List<Long> getResendPacketIds() {
return resendPacketIds;
}
public static MessageResend readBinaryMessage(DataInput dataInput) throws IOException {
String hostPort = dataInput.readUTF();
MessageResend msg = new MessageResend(hostPort);
int numberOfPacketIds = dataInput.readInt();
for (int i = 0; i < numberOfPacketIds; i++) {
long packetId = dataInput.readLong();
msg.add(packetId);
}
return msg;
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
BinaryMessage m = new BinaryMessage(toHostPort.length() * 2 + 20);
DataOutputStream os = m.getOs();
os.writeInt(BinaryMessage.TYPE_MSGRESEND);
os.writeUTF(toHostPort);
os.writeInt(resendPacketIds.size());
for (int i = 0; i < resendPacketIds.size(); i++) {
Long packetId = resendPacketIds.get(i);
os.writeLong(packetId.longValue());
}
os.flush();
msgList.add(m);
}
}
@@ -1,94 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.util.HashMap;
import java.util.Map;
public class OutgoingPacketsAcked {
private long minimumGotAllPacketId;
private final Map<String, GroupMemberAck> recievedByMap = new HashMap<String, GroupMemberAck>();
public int getGroupSize() {
synchronized (this) {
return recievedByMap.size();
}
}
public long getMinimumGotAllPacketId() {
synchronized (this) {
return minimumGotAllPacketId;
}
}
public void removeMember(String groupMember) {
synchronized (this) {
recievedByMap.remove(groupMember);
resetGotAllMin();
}
}
private boolean resetGotAllMin() {
long tempMin = Long.MAX_VALUE;
for (GroupMemberAck groupMemAck : recievedByMap.values()) {
long memberMin = groupMemAck.getGotAllPacketId();
if (memberMin < tempMin) {
tempMin = memberMin;
}
}
if (tempMin != minimumGotAllPacketId) {
minimumGotAllPacketId = tempMin;
return true;
} else {
return false;
}
}
public long receivedAck(String groupMember, MessageAck ack) {
synchronized (this) {
boolean checkMin;
GroupMemberAck groupMemberAck = recievedByMap.get(groupMember);
if (groupMemberAck == null) {
groupMemberAck = new GroupMemberAck();
groupMemberAck.setIfBigger(ack.getGotAllPacketId());
recievedByMap.put(groupMember, groupMemberAck);
checkMin = true;
} else {
checkMin = groupMemberAck.getGotAllPacketId() == minimumGotAllPacketId;
groupMemberAck.setIfBigger(ack.getGotAllPacketId());
}
boolean minChanged = false;
if (checkMin || minimumGotAllPacketId == 0) {
minChanged = resetGotAllMin();
}
return minChanged ? minimumGotAllPacketId : 0;
}
}
private static class GroupMemberAck {
private long gotAllPacketId;
private GroupMemberAck() {
}
private long getGotAllPacketId() {
return gotAllPacketId;
}
private void setIfBigger(long newGotAll) {
if (newGotAll > gotAllPacketId) {
gotAllPacketId = newGotAll;
}
}
}
}
@@ -1,63 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.server.cluster.Packet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Cache of the outgoing packets.
* <p>
* These are held until we receive ACKs from the other members of the cluster to
* say they have received the packets.
* </p>
*/
public class OutgoingPacketsCache {
private final Map<Long, Packet> packetMap = new TreeMap<Long, Packet>();
public int size() {
return packetMap.size();
}
public Packet getPacket(Long packetId) {
return packetMap.get(packetId);
}
public String toString() {
return packetMap.keySet().toString();
}
/**
* Remove the packet when we give up trying to send it out.
*/
public void remove(Packet packet) {
packetMap.remove(packet.getPacketId());
}
public void registerPackets(List<Packet> packets) {
for (int i = 0; i < packets.size(); i++) {
Packet p = packets.get(i);
packetMap.put(p.getPacketId(), p);
}
}
public int trimAll() {
int size = packetMap.size();
packetMap.clear();
return size;
}
public void trimAcknowledgedMessages(long minAcked) {
Iterator<Long> it = packetMap.keySet().iterator();
while (it.hasNext()) {
Long pktId = it.next();
if (minAcked >= pktId) {
it.remove();
}
}
}
}
@@ -1,60 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.Socket;
/**
* This parses and dispatches a request to the appropriate handler.
* <p>
* Looks up the appropriate RequestHandler
* and then gets it to process the Client request.<P>
* </p>
* Note that this is a Runnable because it is assigned to the ThreadPool.
*/
class RequestProcessor implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(RequestProcessor.class);
private final Socket clientSocket;
private final SocketClusterBroadcast owner;
private final String hostPort;
/**
* Create including the Listener (used to lookup the Request Handler) and
* the socket itself.
*/
public RequestProcessor(SocketClusterBroadcast owner, Socket clientSocket) {
this.clientSocket = clientSocket;
this.owner = owner;
this.hostPort = owner.getHostPort();
}
/**
* This will parse out the command. Lookup the appropriate Handler and
* pass the information to the handler for processing.
* <P>Dev Note: the command parsing is processed here so that it is preformed
* by the assigned thread rather than the listeners thread.</P>
*/
public void run() {
try {
logger.trace("start listening for cluster messages");
SocketConnection sc = new SocketConnection(clientSocket);
while (true) {
if (owner.process(sc)) {
// got the offline message or timeout
break;
}
}
logger.trace("disconnecting: {}", hostPort);
sc.disconnect();
} catch (Exception e) {
logger.error("Error listening for messages - " + owner.getHostPort(), e);
}
}
}
@@ -1,132 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* The client side of the socket clustering.
*/
class SocketClient {
private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
private final InetSocketAddress address;
private final String hostPort;
private boolean online;
private Socket socket;
private OutputStream os;
private ObjectOutputStream oos;
/**
* Construct with an IP address and port.
*/
public SocketClient(InetSocketAddress address) {
this.address = address;
this.hostPort = address.getHostName() + ":" + address.getPort();
}
public String toString() {
return address.toString();
}
public String getHostPort() {
return hostPort;
}
public int getPort() {
return address.getPort();
}
public boolean isOnline() {
return online;
}
public void setOnline(boolean online) throws IOException {
if (online) {
setOnline();
} else {
disconnect();
}
}
/**
* Set whether the client is thought to be online.
*/
private void setOnline() throws IOException {
connect();
this.online = true;
}
public void reconnect() throws IOException {
disconnect();
connect();
}
private void connect() throws IOException {
if (socket != null) {
throw new IllegalStateException("Already got a socket connection?");
}
Socket s = new Socket();
s.setKeepAlive(true);
s.connect(address);
this.socket = s;
this.os = socket.getOutputStream();
}
public void disconnect() {
this.online = false;
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
String msg = "Error disconnecting from Cluster member " + hostPort;
logger.info(msg, e);
}
os = null;
oos = null;
socket = null;
}
}
public boolean register(SocketClusterMessage registerMsg) {
try {
setOnline();
send(registerMsg);
return true;
} catch (IOException e) {
disconnect();
return false;
}
}
public void send(SocketClusterMessage msg) throws IOException {
if (online) {
writeObject(msg);
}
}
private void writeObject(Object object) throws IOException {
if (oos == null) {
this.oos = new ObjectOutputStream(os);
}
oos.writeObject(object);
oos.flush();
}
}
@@ -1,250 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.ClusterBroadcast;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.cluster.DataHolder;
import com.avaje.ebeaninternal.server.cluster.SerialiseTransactionHelper;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Broadcast messages across the cluster using sockets.
*/
public class SocketClusterBroadcast implements ClusterBroadcast {
private static final Logger logger = LoggerFactory.getLogger(SocketClusterBroadcast.class);
private final SocketClient local;
private final HashMap<String, SocketClient> clientMap;
private final SocketClusterListener listener;
private final SocketClient[] members;
private ClusterManager clusterManager;
private final TxnSerialiseHelper txnSerialiseHelper = new TxnSerialiseHelper();
private final AtomicInteger txnOutgoing = new AtomicInteger();
private final AtomicInteger txnIncoming = new AtomicInteger();
public SocketClusterBroadcast(ContainerConfig containerConfig) {
ContainerConfig.SocketConfig socketConfig = containerConfig.getSocketConfig();
String localHostPort = socketConfig.getLocalHostPort();
List<String> members = socketConfig.getMembers();
logger.info("Clustering using Sockets local[" + localHostPort + "] members[" + members + "]");
this.local = new SocketClient(parseFullName(localHostPort));
this.clientMap = new HashMap<String, SocketClient>();
for (String memberHostPort : members) {
InetSocketAddress member = parseFullName(memberHostPort);
SocketClient client = new SocketClient(member);
if (!local.getHostPort().equalsIgnoreCase(client.getHostPort())) {
// don't add the local one ...
clientMap.put(client.getHostPort(), client);
}
}
this.members = clientMap.values().toArray(new SocketClient[clientMap.size()]);
this.listener = new SocketClusterListener(this, local.getPort(), socketConfig.getCoreThreads(), socketConfig.getMaxThreads(), socketConfig.getThreadPoolName());
}
public String getHostPort() {
return local.getHostPort();
}
/**
* Return the current status of this instance.
*/
public SocketClusterStatus getStatus() {
// count of online members
int currentGroupSize = 0;
for (int i = 0; i < members.length; i++) {
if (members[i].isOnline()) {
++currentGroupSize;
}
}
int txnIn = txnIncoming.get();
int txnOut = txnOutgoing.get();
return new SocketClusterStatus(currentGroupSize, txnIn, txnOut);
}
public void startup(ClusterManager clusterManager) {
this.clusterManager = clusterManager;
listener.startListening();
register();
}
public void shutdown() {
deregister();
listener.shutdown();
}
/**
* Register with all the other members of the Cluster.
*/
private void register() {
SocketClusterMessage h = SocketClusterMessage.register(local.getHostPort(), true);
for (int i = 0; i < members.length; i++) {
boolean online = members[i].register(h);
logger.info("Cluster Member [{}] online[{}]", members[i].getHostPort(), online);
}
}
protected void setMemberOnline(String fullName, boolean online) throws IOException {
synchronized (clientMap) {
logger.info("Cluster Member [{}] online[{}]", fullName, online);
SocketClient member = clientMap.get(fullName);
member.setOnline(online);
}
}
private void send(SocketClient client, SocketClusterMessage msg) {
try {
// alternative would be to connect/disconnect here but prefer to use keepalive
if (logger.isTraceEnabled()) {
logger.trace("... send to member {} broadcast msg: {}", client, msg);
}
client.send(msg);
} catch (Exception ex) {
logger.error("Error sending message", ex);
try {
client.reconnect();
} catch (IOException e) {
logger.error("Error trying to reconnect", ex);
}
}
}
/**
* Send the payload to all the members of the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent) {
try {
txnOutgoing.incrementAndGet();
DataHolder dataHolder = txnSerialiseHelper.createDataHolder(remoteTransEvent);
SocketClusterMessage msg = SocketClusterMessage.transEvent(dataHolder);
broadcast(msg);
} catch (Exception e) {
logger.error("Error sending RemoteTransactionEvent " + remoteTransEvent + " to cluster members.", e);
}
}
protected void broadcast(SocketClusterMessage msg) {
if (logger.isTraceEnabled()) {
logger.trace("... broadcast msg: " + msg);
}
for (int i = 0; i < members.length; i++) {
send(members[i], msg);
}
}
/**
* Leave the cluster.
*/
private void deregister() {
SocketClusterMessage h = SocketClusterMessage.register(local.getHostPort(), false);
broadcast(h);
for (int i = 0; i < members.length; i++) {
members[i].disconnect();
}
}
/**
* Process an incoming Cluster message.
*/
protected boolean process(SocketConnection request) throws ClassNotFoundException {
try {
SocketClusterMessage h = (SocketClusterMessage) request.readObject();
if (logger.isTraceEnabled()) {
logger.trace("... received msg: {}", h);
}
if (h.isRegisterEvent()) {
setMemberOnline(h.getRegisterHost(), h.isRegister());
} else {
txnIncoming.incrementAndGet();
DataHolder dataHolder = h.getDataHolder();
RemoteTransactionEvent transEvent = txnSerialiseHelper.read(dataHolder);
transEvent.run();
}
// instance shutting down
return h.isRegisterEvent() && !h.isRegister();
} catch (InterruptedIOException e) {
logger.info("Timeout waiting for message", e);
try {
request.disconnect();
} catch (IOException ex) {
logger.info("Error disconnecting after timeout", ex);
}
return true;
} catch (EOFException e) {
logger.info("EOF disconnecting");
return true;
} catch (IOException e) {
logger.info("IO Error waiting/reading message", e);
return true;
}
}
/**
* Parse a host:port into a InetSocketAddress.
*/
private InetSocketAddress parseFullName(String hostAndPort) {
try {
hostAndPort = hostAndPort.trim();
int colonPos = hostAndPort.indexOf(":");
if (colonPos == -1) {
String msg = "No colon \":\" in " + hostAndPort;
throw new IllegalArgumentException(msg);
}
String host = hostAndPort.substring(0, colonPos);
String sPort = hostAndPort.substring(colonPos + 1, hostAndPort.length());
int port = Integer.parseInt(sPort);
return new InetSocketAddress(host, port);
} catch (Exception ex) {
throw new RuntimeException("Error parsing [" + hostAndPort + "] for the form [host:port]", ex);
}
}
class TxnSerialiseHelper extends SerialiseTransactionHelper {
@Override
public SpiEbeanServer getEbeanServer(String serverName) {
return (SpiEbeanServer) clusterManager.getServer(serverName);
}
}
}
@@ -1,143 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import com.avaje.ebeaninternal.server.lib.DaemonThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
/**
* Serverside multithreaded socket listener. Accepts connections and dispatches
* them to an appropriate handler.
* <p>
* This is designed as a single port listener, where part of the connection
* protocol determines which service the client is requesting (rather than a
* port per service).
* </p>
* <p>
* It has its own daemon background thread that handles the accept() loop on the
* ServerSocket.
* </p>
*/
class SocketClusterListener implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(SocketClusterListener.class);
/**
* The server socket used to listen for requests.
*/
private final ServerSocket serverListenSocket;
/**
* The listening thread.
*/
private final Thread listenerThread;
/**
* The pool of threads that actually do the parsing execution of requests.
*/
private final DaemonThreadPool threadPool;
private final SocketClusterBroadcast owner;
/**
* shutting down flag.
*/
boolean doingShutdown;
/**
* Whether the listening thread is busy assigning a request to a thread.
*/
boolean isActive;
/**
* Construct with a given thread pool name.
*/
public SocketClusterListener(SocketClusterBroadcast owner, int port, int coreThreads, int maxThreads, String poolName) {
this.owner = owner;
this.threadPool = new DaemonThreadPool(coreThreads, maxThreads, 60, 30, poolName);
try {
this.serverListenSocket = new ServerSocket(port);
this.serverListenSocket.setSoTimeout(60000);
this.listenerThread = new Thread(this, "EbeanClusterListener");
} catch (IOException e) {
String msg = "Error starting cluster socket listener on port " + port;
throw new RuntimeException(msg, e);
}
}
/**
* Start listening for requests.
*/
public void startListening() {
logger.trace("... startListening()");
this.listenerThread.setDaemon(true);
this.listenerThread.start();
}
/**
* Shutdown this listener.
*/
public void shutdown() {
doingShutdown = true;
try {
if (isActive) {
synchronized (listenerThread) {
try {
listenerThread.wait(1000);
} catch (InterruptedException e) {
// OK to ignore as expected to Interrupt for shutdown.
}
}
}
listenerThread.interrupt();
serverListenSocket.close();
} catch (IOException e) {
logger.error("Error shutting down listener", e);
}
threadPool.shutdown();
}
/**
* This is a runnable and so this must be public. Don't call this externally
* but rather call the startListening() method.
*/
public void run() {
// run in loop until doingShutdown is true...
while (!doingShutdown) {
try {
synchronized (listenerThread) {
Socket clientSocket = serverListenSocket.accept();
isActive = true;
Runnable request = new RequestProcessor(owner, clientSocket);
threadPool.execute(request);
isActive = false;
}
} catch (SocketException e) {
if (doingShutdown) {
logger.info("doingShutdown and accept threw:" + e.getMessage());
} else {
logger.error("Error while listening", e);
}
} catch (InterruptedIOException e) {
// this will happen when the server is very quiet.
// that is, no requests
logger.debug("Possibly expected due to accept timeout? {}", e.getMessage());
} catch (IOException e) {
// log it and continue in the loop...
logger.error("IOException processing cluster message", e);
}
}
}
}
@@ -1,78 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import com.avaje.ebeaninternal.server.cluster.DataHolder;
import com.avaje.ebeaninternal.server.cluster.Packet;
import java.io.Serializable;
/**
* The messages broadcast around the cluster.
*/
public class SocketClusterMessage implements Serializable {
private static final long serialVersionUID = 2993350408394934473L;
private final String registerHost;
private final boolean register;
private final DataHolder dataHolder;
public static SocketClusterMessage register(String registerHost, boolean register) {
return new SocketClusterMessage(registerHost, register);
}
public static SocketClusterMessage transEvent(DataHolder transEvent) {
return new SocketClusterMessage(transEvent);
}
public static SocketClusterMessage packet(Packet packet) {
DataHolder d = new DataHolder(packet.getBytes());
return new SocketClusterMessage(d);
}
/**
* Used to construct a Child AttributeMap.
*/
private SocketClusterMessage(String registerHost, boolean register) {
this.registerHost = registerHost;
this.register = register;
this.dataHolder = null;
}
private SocketClusterMessage(DataHolder dataHolder) {
this.dataHolder = dataHolder;
this.registerHost = null;
this.register = false;
}
public String toString() {
StringBuilder sb = new StringBuilder();
if (registerHost != null) {
sb.append("register ");
sb.append(register);
sb.append(" ");
sb.append(registerHost);
} else {
sb.append("transEvent ");
}
return sb.toString();
}
public boolean isRegisterEvent() {
return registerHost != null;
}
public String getRegisterHost() {
return registerHost;
}
public boolean isRegister() {
return register;
}
public DataHolder getDataHolder() {
return dataHolder;
}
}
@@ -1,39 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
/**
* The current state of this cluster member.
*/
public class SocketClusterStatus {
private final int currentGroupSize;
private final int txnIncoming;
private final int txtOutgoing;
public SocketClusterStatus(int currentGroupSize, int txnIncoming, int txnOutgoing) {
this.currentGroupSize = currentGroupSize;
this.txnIncoming = txnIncoming;
this.txtOutgoing = txnOutgoing;
}
/**
* Return the number of members of the cluster currently online.
*/
public int getCurrentGroupSize() {
return currentGroupSize;
}
/**
* Return the number of Remote transactions received.
*/
public int getTxnIncoming() {
return txnIncoming;
}
/**
* Return the number of transactions sent to the cluster.
*/
public int getTxtOutgoing() {
return txtOutgoing;
}
}
@@ -1,75 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* The client side of a TCP Sockect connection.
*/
class SocketConnection {
/**
* The underlying ObjectInputStream.
*/
ObjectInputStream ois;
/**
* The underlying inputStream.
*/
InputStream is;
/**
* The underlying outputStream.
*/
OutputStream os;
/**
* The underlying socket.
*/
Socket socket;
/**
* Create for a given Socket.
*/
public SocketConnection(Socket socket) throws IOException {
this.is = socket.getInputStream();
this.os = socket.getOutputStream();
this.socket = socket;
}
/**
* Disconnect from the server.
*/
public void disconnect() throws IOException {
os.flush();
socket.close();
}
/**
* Flush the outputStream.
*/
public void flush() throws IOException {
os.flush();
}
/**
* Read an object from the object input stream.
*/
public Object readObject() throws IOException, ClassNotFoundException {
return getObjectInputStream().readObject();
}
/**
* Get the object input stream.
*/
public ObjectInputStream getObjectInputStream() throws IOException {
if (ois == null) {
ois = new ObjectInputStream(is);
}
return ois;
}
}
@@ -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,12 +420,13 @@ 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 bean = beanCacheGetInternal(query.getId(), query.isReadOnly());
T beanCacheGet(SpiQuery<T> query, PersistenceContext context) {
Object id = desc.convertId(query.getId());
T bean = beanCacheGetInternal(id, query.isReadOnly());
if (bean != null) {
setupContext(bean, context);
}
@@ -438,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);
@@ -476,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) {
@@ -486,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);
}
@@ -499,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) {
@@ -614,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();
@@ -622,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));
}
}
}

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