mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11e02710d9 | ||
|
|
658ce3c5d3 | ||
|
|
2de78cd20f | ||
|
|
1b8b05790e | ||
|
|
444b848c22 | ||
|
|
707dda636d | ||
|
|
e8a979bc9d | ||
|
|
3fb9a7414c | ||
|
|
061652a48e | ||
|
|
57b03cf86d | ||
|
|
45dbe3bb9a | ||
|
|
24037eeb73 | ||
|
|
2c6f637dab | ||
|
|
a85f9c4be8 | ||
|
|
5d9f11a362 | ||
|
|
3d484c79a2 | ||
|
|
5881047606 | ||
|
|
333eaf08c2 | ||
|
|
a1ff83e9e4 | ||
|
|
640db2267f | ||
|
|
4059034891 | ||
|
|
c58db0033d | ||
|
|
da90eccc12 | ||
|
|
524be4ce55 | ||
|
|
649c14644e | ||
|
|
c504256579 | ||
|
|
97d803e3e0 | ||
|
|
46bc09a09b | ||
|
|
fe387cf456 | ||
|
|
b4cd66e28c | ||
|
|
7c11acaf89 | ||
|
|
7b4d6f2d17 | ||
|
|
8f100f2786 | ||
|
|
6233392548 | ||
|
|
abaa1ff934 | ||
|
|
a7b239a1b7 | ||
|
|
e8c0756ee9 | ||
|
|
e5179432a0 | ||
|
|
d25123eb23 | ||
|
|
27602c15b9 | ||
|
|
bdc81bf6f4 | ||
|
|
b9237cb8a8 | ||
|
|
99e548db03 | ||
|
|
71499d15e1 | ||
|
|
c9b0fd708a | ||
|
|
8e43ece490 | ||
|
|
fb2b8b4cf6 | ||
|
|
33f3920a60 | ||
|
|
9a912390d4 | ||
|
|
30a5769973 | ||
|
|
084c9ad433 | ||
|
|
84fa373728 | ||
|
|
12e6c0b139 | ||
|
|
6f702098bc | ||
|
|
ad3292b92d |
@@ -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>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
|
||||
/**
|
||||
* This Ebean object is effectively a singleton that holds a map of registered
|
||||
* {@link EbeanServer}s. It additionally provides a convenient way to use the
|
||||
@@ -1484,29 +1481,6 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().getBackgroundExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the cache warming queries on all bean types that have one defined for
|
||||
* the default/primary EbeanServer.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
public static void runCacheWarming() {
|
||||
serverMgr.getDefaultServer().runCacheWarming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the cache warming query for a specific bean type for the
|
||||
* default/primary EbeanServer.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
public static void runCacheWarming(Class<?> beanType) {
|
||||
|
||||
serverMgr.getDefaultServer().runCacheWarming(beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JsonContext for reading/writing JSON.
|
||||
*/
|
||||
|
||||
@@ -146,6 +146,17 @@ public interface EbeanServer {
|
||||
*/
|
||||
Object getBeanId(Object bean);
|
||||
|
||||
/**
|
||||
* Set the Id value onto the bean converting the type of the id value if necessary.
|
||||
* <p>
|
||||
* For example, if the id value passed in is a String but ought to be a Long or UUID etc
|
||||
* then it will automatically be converted.
|
||||
* </p>
|
||||
* @param bean The entity bean to set the id value on.
|
||||
* @param id The id value to set.
|
||||
*/
|
||||
Object setBeanId(Object bean, Object id);
|
||||
|
||||
/**
|
||||
* Return a map of the differences between two objects of the same type.
|
||||
* <p>
|
||||
@@ -1835,22 +1846,6 @@ public interface EbeanServer {
|
||||
*/
|
||||
BackgroundExecutor getBackgroundExecutor();
|
||||
|
||||
/**
|
||||
* Run the cache warming queries on all bean types that have one defined.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
void runCacheWarming();
|
||||
|
||||
/**
|
||||
* Run the cache warming query for a specific bean type.
|
||||
* <p>
|
||||
* A cache warming query can be defined via {@link CacheStrategy}.
|
||||
* </p>
|
||||
*/
|
||||
void runCacheWarming(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Return the JsonContext for reading/writing JSON.
|
||||
* <p>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -41,10 +41,10 @@ public class AutoTuneAllCollection {
|
||||
/**
|
||||
* Write the document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
public void writeFile(String filePrefix, boolean withNow) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
writer.write(document, filePrefix, withNow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class AutoTuneDiffCollection {
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
writer.write(document, filePrefix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-5
@@ -15,19 +15,27 @@ import java.util.Date;
|
||||
*/
|
||||
public class AutoTuneXmlWriter {
|
||||
|
||||
/**
|
||||
* Return 'now' as a string to second precision.
|
||||
*/
|
||||
public static String now() {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
return df.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the document as xml file with the given prefix.
|
||||
*/
|
||||
public void write(Autotune document, String filePrefix) {
|
||||
public void write(Autotune document, String fileName, boolean withNow) {
|
||||
|
||||
SortAutoTuneDocument.sort(document);
|
||||
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
String now = df.format(new Date());
|
||||
if (withNow) {
|
||||
fileName += "-" + now() + ".xml";
|
||||
}
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
File file = new File(filePrefix + "-" + now + ".xml");
|
||||
write(document, file);
|
||||
write(document, new File(fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-1
@@ -185,7 +185,15 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
} else {
|
||||
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
|
||||
int size = event.size();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-all");
|
||||
File existingTuning = new File(tuningFile);
|
||||
if (existingTuning.exists()) {
|
||||
// rename the existing autotune.xml file (appending 'now')
|
||||
if (!existingTuning.renameTo(new File(tuningFile+"."+AutoTuneXmlWriter.now()))) {
|
||||
logger.warn("Failed to rename autotune file [{}]", tuningFile);
|
||||
}
|
||||
}
|
||||
|
||||
event.writeFile(tuningFile, false);
|
||||
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
/**
|
||||
* A change to the cache.
|
||||
*/
|
||||
public interface CacheChange {
|
||||
|
||||
/**
|
||||
* Apply the change.
|
||||
*/
|
||||
void apply();
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
* Change to remove bean from L2 cache.
|
||||
*/
|
||||
class CacheChangeBeanRemove implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
|
||||
private final Object id;
|
||||
|
||||
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Object id) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheHandleDeleteById(id);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Put a new bean entry into the cache.
|
||||
*/
|
||||
class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
private final Object id;
|
||||
private final Map<String, Object> changes;
|
||||
private final boolean updateNaturalKey;
|
||||
private final long version;
|
||||
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
this.desc = desc;
|
||||
this.id = id;
|
||||
this.changes = changes;
|
||||
this.updateNaturalKey = updateNaturalKey;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
desc.cacheBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
* Change the natural key mapping for a bean.
|
||||
*/
|
||||
class CacheChangeNaturalKeyPut implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
private final Object id;
|
||||
private final Object newKey;
|
||||
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, Object id, Object newKey) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
this.newKey = newKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheNaturalKeyPut(id, newKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* List of changes to be applied to L2 cache.
|
||||
*/
|
||||
public class CacheChangeSet {
|
||||
|
||||
private final List<CacheChange> entries = new ArrayList<CacheChange>();
|
||||
|
||||
private final Set<BeanDescriptor> queryCaches = new HashSet<BeanDescriptor>();
|
||||
|
||||
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<ManyKey, ManyChange>();
|
||||
|
||||
/**
|
||||
* Apply all the changes to the L2 cache.
|
||||
*/
|
||||
public void apply() {
|
||||
for (BeanDescriptor entry : queryCaches) {
|
||||
entry.queryCacheClear();
|
||||
}
|
||||
for (CacheChange entry : entries) {
|
||||
entry.apply();
|
||||
}
|
||||
for (CacheChange entry : manyChangeMap.values()) {
|
||||
entry.apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to clear a query cache.
|
||||
*/
|
||||
public void addClearQuery(BeanDescriptor<?> descriptor) {
|
||||
queryCaches.add(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property clear.
|
||||
*/
|
||||
public <T> void addManyClear(BeanDescriptor<T> desc, String manyProperty) {
|
||||
many(desc, manyProperty).setClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property remove.
|
||||
*/
|
||||
public <T> void addManyRemove(BeanDescriptor<T> desc, String manyProperty, Object parentId) {
|
||||
many(desc, manyProperty).addRemove(parentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property put.
|
||||
*/
|
||||
public <T> void addManyPut(BeanDescriptor<T> desc, String manyProperty, Object parentId, CachedManyIds entry) {
|
||||
many(desc, manyProperty).addPut(parentId, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache.
|
||||
*/
|
||||
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
|
||||
entries.add(new CacheChangeBeanRemove(desc, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a bean entry.
|
||||
*/
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a natural key.
|
||||
*/
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, Object id, Object val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, id, val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ManyChange for the given descriptor and property manyProperty.
|
||||
*/
|
||||
private ManyChange many(BeanDescriptor<?> desc, String manyProperty) {
|
||||
ManyKey key = new ManyKey(desc, manyProperty);
|
||||
ManyChange manyChange = manyChangeMap.get(key);
|
||||
if (manyChange == null) {
|
||||
manyChange = new ManyChange(key);
|
||||
manyChangeMap.put(key, manyChange);
|
||||
}
|
||||
return manyChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes for a specific many property.
|
||||
*/
|
||||
private static class ManyChange implements CacheChange {
|
||||
|
||||
final ManyKey key;
|
||||
|
||||
final List<Object> removes = new ArrayList<Object>();
|
||||
|
||||
final Map<Object,CachedManyIds> puts = new LinkedHashMap<Object, CachedManyIds>();
|
||||
|
||||
boolean clear;
|
||||
|
||||
ManyChange(ManyKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all entries.
|
||||
*/
|
||||
void setClear() {
|
||||
this.clear = true;
|
||||
removes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry for the given parentId.
|
||||
*/
|
||||
void addRemove(Object parentId) {
|
||||
if (!clear) {
|
||||
removes.add(parentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put entry for the given parentId.
|
||||
*/
|
||||
void addPut(Object parentId, CachedManyIds entry) {
|
||||
puts.put(parentId, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
if (clear) {
|
||||
key.cacheClear();
|
||||
} else {
|
||||
for (Map.Entry<Object, CachedManyIds> entry : puts.entrySet()) {
|
||||
key.cachePut(entry.getKey(), entry.getValue());
|
||||
}
|
||||
for (Object parentId : removes) {
|
||||
key.cacheRemove(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for changes on a many property.
|
||||
*/
|
||||
private static class ManyKey {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
|
||||
private final String manyProperty;
|
||||
|
||||
ManyKey(BeanDescriptor<?> desc, String manyProperty) {
|
||||
this.desc = desc;
|
||||
this.manyProperty = manyProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ManyKey manyKey = (ManyKey) o;
|
||||
return desc.equals(manyKey.desc) && manyProperty.equals(manyKey.manyProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * desc.hashCode() + manyProperty.hashCode();
|
||||
}
|
||||
|
||||
void cacheClear() {
|
||||
desc.cacheManyPropClear(manyProperty);
|
||||
}
|
||||
|
||||
void cachePut(Object parentId, CachedManyIds entry) {
|
||||
desc.cacheManyPropPut(manyProperty, parentId, entry);
|
||||
}
|
||||
|
||||
void cacheRemove(Object parentId) {
|
||||
desc.cacheManyPropRemove(manyProperty, parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,92 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Data held in the bean cache for cached beans.
|
||||
*/
|
||||
public class CachedBeanData {
|
||||
public class CachedBeanData implements Externalizable {
|
||||
|
||||
private final long whenCreated;
|
||||
private final Object sharableBean;
|
||||
private final boolean[] loaded;
|
||||
private final Object[] data;
|
||||
|
||||
private final boolean naturalKeyUpdate;
|
||||
private final Object naturalKey;
|
||||
private final Object oldNaturalKey;
|
||||
private long whenCreated;
|
||||
private long version;
|
||||
private String discValue;
|
||||
private Map<String, Object> data;
|
||||
|
||||
public CachedBeanData(Object sharableBean, boolean[] loaded, Object[] data, Object naturalKey, Object oldNaturalKey) {
|
||||
/**
|
||||
* The sharable bean is effectively transient (near cache only).
|
||||
*/
|
||||
private transient Object sharableBean;
|
||||
|
||||
/**
|
||||
* Construct from a loaded bean.
|
||||
*/
|
||||
public CachedBeanData(Object sharableBean, String discValue, Map<String, Object> data, long version) {
|
||||
this.whenCreated = System.currentTimeMillis();
|
||||
this.sharableBean = sharableBean;
|
||||
this.loaded = loaded;
|
||||
this.discValue = discValue;
|
||||
this.data = data;
|
||||
this.naturalKeyUpdate = naturalKey != null;
|
||||
this.naturalKey = (naturalKey != null) ? naturalKey : oldNaturalKey;
|
||||
this.oldNaturalKey = oldNaturalKey;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from serialisation.
|
||||
*/
|
||||
public CachedBeanData() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeLong(version);
|
||||
out.writeLong(whenCreated);
|
||||
boolean hasDisc = discValue != null;
|
||||
out.writeBoolean(hasDisc);
|
||||
if (hasDisc) {
|
||||
out.writeUTF(discValue);
|
||||
}
|
||||
out.writeInt(data.size());
|
||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
out.writeObject(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
version = in.readLong();
|
||||
whenCreated = in.readLong();
|
||||
if (in.readBoolean()) {
|
||||
discValue = in.readUTF();
|
||||
}
|
||||
data = new LinkedHashMap<String, Object>();
|
||||
int count = in.readInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String key = in.readUTF();
|
||||
Object val = in.readObject();
|
||||
data.put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return Arrays.toString(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the property data.
|
||||
*/
|
||||
public Object[] copyData() {
|
||||
Object[] dest = new Object[data.length];
|
||||
System.arraycopy(data, 0, dest, 0, data.length);
|
||||
return dest;
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the loaded status for the properties.
|
||||
* Create and return a new version of CachedBeanData based on this
|
||||
* entry applying the given changes.
|
||||
*/
|
||||
public boolean[] copyLoaded() {
|
||||
boolean[] dest = new boolean[data.length];
|
||||
System.arraycopy(loaded, 0, dest, 0, dest.length);
|
||||
return dest;
|
||||
public CachedBeanData update(Map<String, Object> changes, long version) {
|
||||
|
||||
Map<String, Object> copy = new HashMap<String, Object>();
|
||||
copy.putAll(data);
|
||||
copy.putAll(changes);
|
||||
return new CachedBeanData(null, discValue, copy, version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,45 +97,44 @@ public class CachedBeanData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sharable (immutable read only) bean.
|
||||
* Return the version value.
|
||||
*/
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw discriminator value.
|
||||
*/
|
||||
public String getDiscValue() {
|
||||
return discValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sharable (immutable read only) bean. Near cache only use.
|
||||
*/
|
||||
public Object getSharableBean() {
|
||||
return sharableBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this data requires an update to the natural key cache.
|
||||
* Return true if the property is held.
|
||||
*/
|
||||
public boolean isNaturalKeyUpdate() {
|
||||
return naturalKeyUpdate;
|
||||
public boolean isLoaded(String propertyName) {
|
||||
return data.containsKey(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new/current natural key value.
|
||||
* Return the value for a given property name.
|
||||
*/
|
||||
public Object getNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the old natural key (its entry should be removed).
|
||||
*/
|
||||
public Object getOldNaturalKey() {
|
||||
return oldNaturalKey;
|
||||
public Object getData(String propertyName) {
|
||||
return data.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data for the specific property.
|
||||
* Return all the property data.
|
||||
*/
|
||||
public Object getData(int i) {
|
||||
return data[i];
|
||||
public Map<String,Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is contained in this data.
|
||||
*/
|
||||
public boolean isLoaded(int i) {
|
||||
return loaded[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-20
@@ -5,56 +5,49 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CachedBeanDataFromBean {
|
||||
|
||||
|
||||
public static CachedBeanData extract(BeanDescriptor<?> desc, EntityBean bean) {
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
Object[] data = new Object[desc.getPropertyCount()];
|
||||
boolean[] loaded = new boolean[desc.getPropertyCount()];
|
||||
|
||||
|
||||
Map<String,Object> data = new LinkedHashMap<String,Object>();
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
int propertyIndex = idProperty.getPropertyIndex();
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// extract the id property value
|
||||
data[propertyIndex] = idProperty.getCacheDataValue(bean);
|
||||
loaded[propertyIndex] = true;
|
||||
data.put(idProperty.getName(), idProperty.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
|
||||
Object naturalKey = null;
|
||||
|
||||
// extract all the non-many properties
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
BeanProperty prop = props[i];
|
||||
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
int propertyIndex = prop.getPropertyIndex();
|
||||
data[propertyIndex] = prop.getCacheDataValue(bean);
|
||||
loaded[propertyIndex] = true;
|
||||
if (prop.isNaturalKey()) {
|
||||
naturalKey = prop.getValue(bean);
|
||||
}
|
||||
data.put(prop.getName(), prop.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
|
||||
long version = desc.getVersion(bean);
|
||||
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
|
||||
|
||||
return new CachedBeanData(sharableBean, loaded, data, naturalKey, null);
|
||||
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
|
||||
}
|
||||
|
||||
private static EntityBean createSharableBean(BeanDescriptor<?> desc, EntityBean bean, EntityBeanIntercept beanEbi) {
|
||||
|
||||
|
||||
if (!desc.isCacheSharableBeans() || !beanEbi.isFullyLoadedBean()) {
|
||||
return null;
|
||||
}
|
||||
if (beanEbi.isReadOnly()) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// create a readOnly sharable instance by copying the data
|
||||
EntityBean sharableBean = desc.createEntityBean();
|
||||
BeanProperty idProp = desc.getIdProperty();
|
||||
|
||||
+7
-11
@@ -25,9 +25,9 @@ public class CachedBeanDataToBean {
|
||||
loadProperty(bean, cacheBeanData, ebi, props[i]);
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
|
||||
for (int i = 0; i < manys.length; i++) {
|
||||
manys[i].createReferenceIfNull(bean);
|
||||
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
|
||||
for (int i = 0; i < many.length; i++) {
|
||||
many[i].createReferenceIfNull(bean);
|
||||
}
|
||||
|
||||
ebi.setLoadedLazy();
|
||||
@@ -35,14 +35,10 @@ public class CachedBeanDataToBean {
|
||||
|
||||
private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop) {
|
||||
|
||||
int propertyIndex = prop.getPropertyIndex();
|
||||
if (cacheBeanData.isLoaded(propertyIndex)) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
// already loaded (lazy load on partially loaded bean)
|
||||
} else {
|
||||
Object data = cacheBeanData.getData(propertyIndex);
|
||||
prop.setCacheDataValue(bean, data);
|
||||
if (cacheBeanData.isLoaded(prop.getName())) {
|
||||
if (!ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
Object value = cacheBeanData.getData(prop.getName());
|
||||
prop.setCacheDataValue(bean, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Create a new CachedBeanData based on the existing CachedBeanData and the updated bean.
|
||||
*/
|
||||
public class CachedBeanDataUpdate {
|
||||
|
||||
/**
|
||||
* Create a new CachedBeanData based on the existing CachedBeanData and the updated bean.
|
||||
*/
|
||||
public static CachedBeanData update(BeanDescriptor<?> desc, CachedBeanData existingData, EntityBean updateBean) {
|
||||
|
||||
// take a copy of the raw data and loaded status
|
||||
boolean[] copyLoaded = existingData.copyLoaded();
|
||||
Object[] copyData = existingData.copyData();
|
||||
|
||||
EntityBeanIntercept ebi = updateBean._ebean_getIntercept();
|
||||
|
||||
Object newNaturalKey = null;
|
||||
Object oldNaturalKey = existingData.getNaturalKey();
|
||||
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
// check if the properties was in the update
|
||||
int propertyIndex = props[i].getPropertyIndex();
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
if (props[i].isNaturalKey()) {
|
||||
newNaturalKey = updateBean._ebean_getField(propertyIndex);
|
||||
}
|
||||
// set the cache safe value for the property and mark it as loaded
|
||||
copyData[propertyIndex] = props[i].getCacheDataValue(updateBean);
|
||||
copyLoaded[propertyIndex] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new CachedBeanData(null, copyLoaded, copyData, newNaturalKey, oldNaturalKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -8,18 +14,41 @@ import java.util.List;
|
||||
* This is effectively just the Id values for each of the beans in the collection.
|
||||
* </p>
|
||||
*/
|
||||
public class CachedManyIds {
|
||||
public class CachedManyIds implements Externalizable {
|
||||
|
||||
private final List<Object> idList;
|
||||
private List<Object> idList;
|
||||
|
||||
public CachedManyIds(List<Object> idList) {
|
||||
this.idList = idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for serialization.
|
||||
*/
|
||||
public CachedManyIds() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeInt(idList.size());
|
||||
for (Object id : idList) {
|
||||
out.writeObject(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
int size = in.readInt();
|
||||
idList = new ArrayList<Object>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
idList.add(in.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return idList.toString();
|
||||
}
|
||||
|
||||
|
||||
public List<Object> getIdList() {
|
||||
return idList;
|
||||
}
|
||||
|
||||
+91
-90
@@ -1,120 +1,121 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheQueryTuning;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages the construction of caches.
|
||||
*/
|
||||
public class DefaultCacheHolder {
|
||||
|
||||
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
|
||||
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
|
||||
|
||||
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
|
||||
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private final ServerCacheOptions defaultOptions;
|
||||
private final ServerCacheOptions defaultOptions;
|
||||
|
||||
private final boolean useBeanTuning;
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory the factory for creating the cache
|
||||
* @param defaultOptions the default options for tuning the cache
|
||||
*/
|
||||
public DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions defaultOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.defaultOptions = defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory
|
||||
* the factory for creating the cache
|
||||
* @param defaultOptions
|
||||
* the default options for tuning the cache
|
||||
* @param useBeanTuning
|
||||
* if true then use the bean class specific tuning. This is
|
||||
* generally false for the query cache.
|
||||
*/
|
||||
public DefaultCacheHolder(ServerCacheFactory cacheFactory,
|
||||
ServerCacheOptions defaultOptions, boolean useBeanTuning) {
|
||||
/**
|
||||
* Return the cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getCache(String cacheKey, ServerCacheType type) {
|
||||
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.useBeanTuning = useBeanTuning;
|
||||
}
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
synchronized (monitor) {
|
||||
cache = synchMap.get(cacheKey);
|
||||
if (cache == null) {
|
||||
ServerCacheOptions options = getCacheOptions(cacheKey, type);
|
||||
cache = cacheFactory.createCache(type, cacheKey, options);
|
||||
synchMap.put(cacheKey, cache);
|
||||
concMap.put(cacheKey, cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default cache options.
|
||||
*/
|
||||
public ServerCacheOptions getDefaultOptions() {
|
||||
return defaultOptions;
|
||||
}
|
||||
public void clearCache(String cacheKey) {
|
||||
|
||||
/**
|
||||
* Return the cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getCache(String cacheKey) {
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
synchronized (monitor) {
|
||||
cache = synchMap.get(cacheKey);
|
||||
if (cache == null) {
|
||||
ServerCacheOptions options = getCacheOptions(cacheKey);
|
||||
cache = cacheFactory.createCache(cacheKey, options);
|
||||
synchMap.put(cacheKey, cache);
|
||||
concMap.put(cacheKey, cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if there is an active cache for this bean type.
|
||||
*/
|
||||
public boolean isCaching(String beanType) {
|
||||
return concMap.containsKey(beanType);
|
||||
}
|
||||
|
||||
public void clearCache(String cacheKey) {
|
||||
public void clearAll() {
|
||||
for (ServerCache serverCache : concMap.values()) {
|
||||
serverCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ServerCache cache = concMap.get(cacheKey);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an active cache for this bean type.
|
||||
*/
|
||||
public boolean isCaching(String beanType) {
|
||||
return concMap.containsKey(beanType);
|
||||
}
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
ServerCacheOptions getCacheOptions(String beanType, ServerCacheType type) {
|
||||
|
||||
public void clearAll() {
|
||||
for (ServerCache serverCache : concMap.values()) {
|
||||
serverCache.clear();
|
||||
}
|
||||
}
|
||||
try {
|
||||
Class<?> cls = Class.forName(beanType);
|
||||
switch (type) {
|
||||
case QUERY:
|
||||
return getQueryOptions(cls);
|
||||
default:
|
||||
return getBeanOptions(cls);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
private ServerCacheOptions getCacheOptions(String beanType) {
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
if (useBeanTuning) {
|
||||
// read the deployment annotation
|
||||
try {
|
||||
Class<?> cls = Class.forName(beanType);
|
||||
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
|
||||
if (cacheTuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
} catch (ClassNotFoundException e){
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
private ServerCacheOptions getQueryOptions(Class<?> cls) {
|
||||
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
|
||||
if (tuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(tuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
return defaultOptions.copy();
|
||||
|
||||
}
|
||||
private ServerCacheOptions getBeanOptions(Class<?> cls) {
|
||||
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
|
||||
if (cacheTuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -4,6 +4,7 @@ import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
|
||||
|
||||
/**
|
||||
@@ -11,17 +12,17 @@ import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
*/
|
||||
public class DefaultServerCacheFactory implements ServerCacheFactory {
|
||||
|
||||
private EbeanServer ebeanServer;
|
||||
|
||||
public void init(EbeanServer ebeanServer){
|
||||
this.ebeanServer = ebeanServer;
|
||||
}
|
||||
|
||||
public ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions) {
|
||||
|
||||
ServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
|
||||
cache.init(ebeanServer);
|
||||
return cache;
|
||||
}
|
||||
|
||||
private EbeanServer ebeanServer;
|
||||
|
||||
public void init(EbeanServer ebeanServer) {
|
||||
this.ebeanServer = ebeanServer;
|
||||
}
|
||||
|
||||
public ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions) {
|
||||
|
||||
ServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
|
||||
cache.init(ebeanServer);
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+78
-83
@@ -5,41 +5,49 @@ import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
|
||||
|
||||
/**
|
||||
* Manages the bean and query caches.
|
||||
* Manages the bean and query caches.
|
||||
*/
|
||||
public class DefaultServerCacheManager implements ServerCacheManager {
|
||||
|
||||
private final DefaultCacheHolder beanCache;
|
||||
private final DefaultCacheHolder beanCache;
|
||||
|
||||
private final DefaultCacheHolder queryCache;
|
||||
private final DefaultCacheHolder queryCache;
|
||||
|
||||
private final DefaultCacheHolder naturalKeyCache;
|
||||
private final DefaultCacheHolder naturalKeyCache;
|
||||
|
||||
private final DefaultCacheHolder collectionIdsCache;
|
||||
private final DefaultCacheHolder collectionIdsCache;
|
||||
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private SpiEbeanServer ebeanServer;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*/
|
||||
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions, true);
|
||||
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
}
|
||||
|
||||
public void init(EbeanServer server) {
|
||||
cacheFactory.init(server);
|
||||
this.ebeanServer = (SpiEbeanServer)server;
|
||||
}
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private SpiEbeanServer ebeanServer;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*/
|
||||
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions);
|
||||
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct when l2 cache is disabled.
|
||||
*/
|
||||
public DefaultServerCacheManager() {
|
||||
this(new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
|
||||
}
|
||||
|
||||
public void init(EbeanServer server) {
|
||||
cacheFactory.init(server);
|
||||
this.ebeanServer = (SpiEbeanServer) server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bean caching on or off for a given bean type.
|
||||
@@ -48,68 +56,55 @@ public class DefaultServerCacheManager implements ServerCacheManager {
|
||||
ebeanServer.getBeanDescriptor(beanType).setUseCache(useCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear both the bean cache and the query cache for a
|
||||
* given bean type.
|
||||
*/
|
||||
public void clear(Class<?> beanType) {
|
||||
String beanName = beanType.getName();
|
||||
beanCache.clearCache(beanName);
|
||||
naturalKeyCache.clearCache(beanName);
|
||||
collectionIdsCache.clearCache(beanName);
|
||||
queryCache.clearCache(beanName);
|
||||
}
|
||||
/**
|
||||
* Clear both the bean cache and the query cache for a
|
||||
* given bean type.
|
||||
*/
|
||||
public void clear(Class<?> beanType) {
|
||||
String beanName = beanType.getName();
|
||||
beanCache.clearCache(beanName);
|
||||
naturalKeyCache.clearCache(beanName);
|
||||
collectionIdsCache.clearCache(beanName);
|
||||
queryCache.clearCache(beanName);
|
||||
}
|
||||
|
||||
|
||||
public void clearAll() {
|
||||
beanCache.clearAll();
|
||||
queryCache.clearAll();
|
||||
naturalKeyCache.clearAll();
|
||||
collectionIdsCache.clearAll();
|
||||
}
|
||||
|
||||
|
||||
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
|
||||
return collectionIdsCache.getCache(beanType.getName()+"."+propertyName);
|
||||
}
|
||||
|
||||
public boolean isCollectionIdsCaching(Class<?> beanType) {
|
||||
return collectionIdsCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
public ServerCache getNaturalKeyCache(Class<?> beanType) {
|
||||
return naturalKeyCache.getCache(beanType.getName());
|
||||
}
|
||||
|
||||
public boolean isNaturalKeyCaching(Class<?> beanType) {
|
||||
return naturalKeyCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getQueryCache(Class<?> beanType) {
|
||||
return queryCache.getCache(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getBeanCache(Class<?> beanType) {
|
||||
return beanCache.getCache(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an active cache for the given bean type.
|
||||
*/
|
||||
public boolean isBeanCaching(Class<?> beanType) {
|
||||
return beanCache.isCaching(beanType.getName());
|
||||
}
|
||||
public void clearAll() {
|
||||
beanCache.clearAll();
|
||||
queryCache.clearAll();
|
||||
naturalKeyCache.clearAll();
|
||||
collectionIdsCache.clearAll();
|
||||
}
|
||||
|
||||
|
||||
public boolean isQueryCaching(Class<?> beanType) {
|
||||
return queryCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
|
||||
return collectionIdsCache.getCache(beanType.getName() + "." + propertyName, ServerCacheType.COLLECTION_IDS);
|
||||
}
|
||||
|
||||
public ServerCache getNaturalKeyCache(Class<?> beanType) {
|
||||
return naturalKeyCache.getCache(beanType.getName(), ServerCacheType.NATURAL_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getQueryCache(Class<?> beanType) {
|
||||
return queryCache.getCache(beanType.getName(), ServerCacheType.QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean cache for a given bean type.
|
||||
*/
|
||||
public ServerCache getBeanCache(Class<?> beanType) {
|
||||
return beanCache.getCache(beanType.getName(), ServerCacheType.BEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is an active cache for the given bean type.
|
||||
*/
|
||||
public boolean isBeanCaching(Class<?> beanType) {
|
||||
return beanCache.isCaching(beanType.getName());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
-51
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-271
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
-250
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-143
@@ -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.
|
||||
*/
|
||||
|
||||
+267
-174
@@ -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
Reference in New Issue
Block a user