Compare commits

..
Author SHA1 Message Date
rob bygrave 3ee4034a76 [maven-release-plugin] prepare release ebean-spring-txn-12.2.1 2020-03-13 17:12:15 +13:00
rob bygrave 21d377546e Bump to 12.2.1 2020-03-13 17:10:21 +13:00
rob bygrave 8c9dccc6a3 [maven-release-plugin] prepare for next development iteration 2019-10-14 14:53:28 +13:00
rob bygrave 6fdf681993 [maven-release-plugin] prepare release ebean-spring-txn-12.1.1 2019-10-14 14:53:19 +13:00
rob bygrave 7e8f413578 No effective change - sync to Ebean 12.1.1 2019-10-14 14:52:46 +13:00
rob bygrave 6705e5a962 Bump Ebean provided dependency to 11.42.1 2019-08-08 22:14:44 +12:00
Rob Bygrave d9126a936d [maven-release-plugin] prepare for next development iteration 2018-03-12 21:45:57 +13:00
Rob Bygrave 4ee024b1ce [maven-release-plugin] prepare release ebean-spring-txn-11.10.4 2018-03-12 21:45:45 +13:00
Rob Bygrave 19491d5ae6 #19 - java.lang.IllegalStateException: Transaction is Inactive ... when using Ebean transactions with Ebean's Spring transaction manager 2018-03-12 21:45:14 +13:00
Rob Bygrave db816594c5 No effective change - tidy tests 2018-02-23 10:50:51 +13:00
Rob Bygrave fa73b7791f [maven-release-plugin] prepare for next development iteration 2018-02-09 23:49:03 +13:00
Rob Bygrave c61da5b0e5 [maven-release-plugin] prepare release ebean-spring-txn-11.10.3 2018-02-09 23:48:50 +13:00
Rob Bygrave 49d724ad65 Bump pom version to align with Ebean version 2018-02-09 23:48:01 +13:00
Rob Bygrave 3ee2410a79 #18 - SpringJdbcTransactionManager inifinite recursion (Ebean #1254)
Update pom ebean version
2018-02-09 23:46:38 +13:00
Rob Bygrave c2ddc70565 #18 - SpringJdbcTransactionManager inifinite recursion (Ebean #1254) 2018-02-09 23:05:27 +13:00
Rob Bygrave 2f66e07c25 Add test for https://github.com/ebean-orm/ebean/issues/1254 2018-02-09 22:20:57 +13:00
Rob Bygrave 0807569ee2 Update readme 2018-02-03 00:14:00 +13:00
Rob Bygrave 206dec1bf8 [maven-release-plugin] prepare for next development iteration 2018-02-03 00:05:12 +13:00
Rob Bygrave 0e852f5bb5 [maven-release-plugin] prepare release ebean-spring-txn-11.10.1 2018-02-03 00:04:59 +13:00
Rob Bygrave a272fb4e02 #17 - Refactor to use the better internals in Ebean 11.10.1 2018-02-03 00:04:22 +13:00
Rob Bygrave cbdc6e09aa [maven-release-plugin] prepare for next development iteration 2018-01-30 23:53:23 +13:00
Rob Bygrave 211087f636 [maven-release-plugin] prepare release ebean-spring-txn-11.5.1 2018-01-30 23:53:10 +13:00
Rob Bygrave dca824b431 #16 - Bump to support Ebean 11.5.1+ 2018-01-30 23:52:40 +13:00
Rob Bygrave 6bcc8eb0f2 Update Readme 2016-12-21 21:11:00 +13:00
Rob Bygrave 844df4d5c9 Update scm due to rename to ebean-spring-txn 2016-12-21 21:04:47 +13:00
Rob Bygrave 6360d5fe1c [maven-release-plugin] prepare for next development iteration 2016-12-21 20:59:11 +13:00
Rob Bygrave e57a074b68 [maven-release-plugin] prepare release ebean-spring-txn-10.1.1 2016-12-21 20:59:00 +13:00
Rob Bygrave 052632239e pom - update name 2016-12-21 20:58:28 +13:00
Rob Bygrave 16aa4caceb #15 - Rename artifact to ebean-spring-txn ... (as this is specific and only for integration with spring transactions) 2016-12-21 20:57:28 +13:00
Rob Bygrave 83d84f6079 #14 - Remove agent loading feature for spring boot as it has been moved to ebean-spring-agent 2016-12-21 20:53:33 +13:00
Rob Bygrave 23c278d878 pom dependencies - ebean and slf4j-api as provided (versions expected to be controlled externally) 2016-12-21 20:44:47 +13:00
Rob Bygrave 7074b0de71 No effective change - use enhanced for 2016-12-19 16:40:00 +13:00
Rob BygraveandGitHub 2a9b025e88 Merge pull request #12 from ksperling/agent-autoconfig
Agent autoconfig and misc changes
2016-12-19 13:07:30 +13:00
Karsten Sperling 16b3a0b1c1 Rename from springtxn to spring since boot support is included as well (and Environment configuration support to come) 2016-12-16 23:38:36 +13:00
Karsten Sperling e0c4f73600 POM tidying 2016-12-16 23:38:36 +13:00
Karsten Sperling 6eb1947a6e Add EbeanAgentAutoConfiguration for Spring Boot 2016-12-16 23:38:36 +13:00
Rob Bygrave 2f793c29dc #11 - Repackage to io.ebean.springtxn ... remove factory bean support (move it to src/test as example) 2016-12-13 21:50:11 +13:00
Rob Bygrave 326fc221bf pom - modify scm to use ssh 2016-11-17 12:29:02 +13:00
Rob Bygrave d49018363c [maven-release-plugin] prepare for next development iteration 2016-11-17 12:19:27 +13:00
Rob Bygrave 2a2a0ad1f4 [maven-release-plugin] prepare release ebean-spring-7.2.1 2016-11-17 12:19:12 +13:00
Rob Bygrave 463b03ef7c bump pom to 7.2.1-SNAPSHOT 2016-11-17 12:18:01 +13:00
Rob BygraveandGitHub 80d39775ba Merge pull request #10 from ksperling/logging-tidyup
Use slf4j for logging and tidy up dependencies.
2016-11-17 12:14:08 +13:00
Karsten Sperling 92cdd25ca9 Use slf4j for logging and tidy up dependencies. 2016-11-17 09:37:41 +13:00
Rob BygraveandGitHub 53680ba53b Merge pull request #8 from ksperling/spring-tx-flush
Propagate 'flush' calls from Spring TX synchronization to the ebean
2016-11-14 15:23:26 +13:00
Karsten Sperling aa85c5fa3b Propagate 'flush' calls from Spring TX synchronization to the ebean transaction. 2016-11-14 13:30:03 +13:00
Robin Bygrave a1ce6f8a81 [maven-release-plugin] prepare for next development iteration 2016-08-03 17:46:37 +12:00
Robin Bygrave 7480904792 [maven-release-plugin] prepare release ebean-spring-7.1.2 2016-08-03 17:46:18 +12:00
Robin Bygrave b12cfafc21 Change groupId to ... org.avaje.ebean, and artifactId to ... ebean-spring 2016-08-03 17:45:51 +12:00
Robin Bygrave f07727651e [maven-release-plugin] prepare for next development iteration 2016-05-18 08:33:54 +12:00
Robin Bygrave 6090eaa167 [maven-release-plugin] prepare release avaje-ebeanorm-spring-7.1.1 2016-05-18 08:33:34 +12:00
Robin Bygrave 40eada4e80 #6 - Modify POM dependencies, change to provided scope for Ebean and Spring context 2016-05-18 08:33:04 +12:00
Robin Bygrave 2d375b3596 [maven-release-plugin] prepare for next development iteration 2015-11-04 08:34:33 +13:00
Robin Bygrave d864e2c723 [maven-release-plugin] prepare release avaje-ebeanorm-spring-6.1.1 2015-11-04 08:34:08 +13:00
Robin Bygrave e50c372562 Bump pom to 6.1.1-SNAPSHOT 2015-11-04 08:33:37 +13:00
Robin Bygrave e2cb033119 #5 - Error creating bean with name 'serverConfig' defined in class path resource [init-database.xml]: Cannot create inner bean 'com.avaje.ebean.config.AutofetchConfig#2e385cce' of type [com.avaje.ebean.config.AutofetchConfig] while setting bean property 'autofetchConfig' 2015-11-04 08:33:16 +13:00
Robin Bygrave 9620c57f19 [maven-release-plugin] prepare for next development iteration 2015-07-30 05:03:49 +12:00
Robin Bygrave 07cc0fcbb6 [maven-release-plugin] prepare release avaje-ebeanorm-spring-4.5.3 2015-07-30 05:03:28 +12:00
Robin Bygrave 6d1beb5c03 Add javadoc-plugin to pom 2015-07-30 05:03:05 +12:00
Robin Bygrave 1667107422 Update spring xsd to spring-beans-4.1.xsd 2015-07-30 04:57:03 +12:00
Robin Bygrave de5cf1df2c No effective change - format 2015-07-30 04:54:25 +12:00
Robin Bygrave a83114ced2 Update tests and dependencies 2015-07-30 04:53:25 +12:00
Rob Bygrave d6983422bf Merge pull request #3 from abguorui0928/master
Create AgentLoaderSupport.java
2015-05-14 22:45:43 +12:00
guor 6d4b3ad84e Create AgentLoaderSupport.java
add support for setup enhancement in spring
2015-05-11 21:01:58 +08:00
Eddie Mc Greal a3df7919b5 Updated to correct ebeanorm dependency range 2015-01-20 09:54:21 +01:00
Rob Bygrave 6f1aa39cc9 Merge pull request #2 from nedge/master
Update Version to 4.1.9 and edited MANIFEST for OSGi
2014-10-15 19:56:11 +13:00
Eddie Mc Greal f18c24c1d1 Update Version to 4.1.9 and edited MANIFEST for OSGi 2014-10-14 08:11:32 +02:00
Rob Bygrave 068401c29c [maven-release-plugin] prepare for next development iteration 2014-04-02 22:15:51 +13:00
Rob Bygrave 6fbbd8b107 [maven-release-plugin] prepare release avaje-ebeanorm-spring-3.3.1 2014-04-02 22:15:25 +13:00
Rob Bygrave 6379bd9e5d Set pom to 3.3.1-SNAPSHOT, ready for release 2014-04-02 22:12:31 +13:00
Rob Bygrave 94d58dfa7c Merge pull request #1 from nedge/develop
Changed to correct version and removed old logging config stuff
2014-04-02 22:08:21 +13:00
Eddie Mc Greal 035fb80959 Changed to correct version and removed old logging config stuff 2014-03-06 22:17:55 +01:00
Robin Bygrave 3b97f77083 [maven-release-plugin] prepare for next development iteration 2013-04-29 21:27:36 +12:00
Robin Bygrave a7d47202e6 [maven-release-plugin] prepare release avaje-ebeanorm-spring-3.2.1 2013-04-29 21:27:19 +12:00
Robin Bygrave 199a9d3118 Opps, fixed scm location 2013-04-29 21:26:37 +12:00
Robin Bygrave 5df3ac4b04 Change package of test beans etc for enhancement 2013-04-29 21:13:37 +12:00
Robin Bygrave 782f77b688 [maven-release-plugin] prepare for next development iteration 2012-09-15 01:32:05 +12:00
Robin Bygrave 1d570a233e [maven-release-plugin] prepare release avaje-ebeanorm-spring-3.1.1 2012-09-15 01:31:49 +12:00
Robin Bygrave 4a6b58bd9b Remove commons logging from dependency 2012-09-15 01:30:21 +12:00
rbygrave d64073bef0 Change license to Apache2, clean up dependencies 2012-09-15 01:15:37 +12:00
rbygrave 6d8a9236f8 initial add of EbeanORM spring from v2.8.1 2012-09-14 01:06:56 +12:00
Rob Bygrave 1665b4f2fa Initial commit 2012-09-13 05:59:29 -07:00
1187 changed files with 853 additions and 122076 deletions
+3
View File
@@ -4,3 +4,6 @@
.settings/
target/
logs/
.idea/
*.iml
-15
View File
@@ -1,15 +0,0 @@
EbeanORM
Copyright 2012 Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+27 -2
View File
@@ -1,4 +1,29 @@
avaje-ebeanorm-server
ebean-spring-txn
=====================
The internal server implementation of EbeanORM API
Integration with Spring managed transactions.
This project provides an Ebean `ExternalTransactionManager` specifically
to integrate with Springs JDBC Transaction manager.
## To use
```java
ServerConfig serverConfig = new ServerConfig();
// set SpringJdbcTransactionManager ... as the external transaction manager
serverConfig.setExternalTransactionManager(new SpringJdbcTransactionManager());
...
EbeanServer server = EbeanServerFactory.create(serverConfig);
```
## Notes
You can use Ebean in Spring/Spring Boot *without* this and that case Ebean
manages the Transactions itself. With Ebean managing the transactions there
are some benefits with more control over JDBC batch, getGeneratedKeys
and a simpler abstraction (as Spring transactions is designed to manage
multiple resources such as JDBC Transactions and JPA EntityManager and Ebean
only needs to manage JDBC Transactions).
+79 -110
View File
@@ -1,166 +1,135 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.avaje</groupId>
<artifactId>avaje-javaparent</artifactId>
<version>1.1</version>
<artifactId>java8-oss</artifactId>
<version>2.1</version>
</parent>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-server</artifactId>
<version>3.1.1</version>
<packaging>jar</packaging>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>12.2.1</version>
<name>avaje-ebeanorm-server</name>
<url>http://www.avaje.org</url>
<name>ebean-spring-txn</name>
<packaging>jar</packaging>
<description>Ebean support for Spring transactions</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.framework.version>4.3.4.RELEASE</spring.framework.version>
<spring.boot.version>1.4.2.RELEASE</spring.boot.version>
</properties>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rbygrave</id>
<name>Rob Bygrave</name>
<email>robin.bygrave@gmail.com</email>
</developer>
</developers>
<url>http://ebean-orm.github.io/</url>
<scm>
<connection>scm:git:https://github.com/rbygrave/avaje-ebeanorm-server.git</connection>
<developerConnection>scm:git:https://github.com/rbygrave/avaje-ebeanorm-server.git</developerConnection>
<url>https://github.com/rbygrave/avaje-ebeanorm-server.git</url>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean-spring-txn.git</developerConnection>
<tag>ebean-spring-txn-12.2.1</tag>
</scm>
<dependencies>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.2.1</version>
<scope>provided</scope>
</dependency>
<!-- Provided: Spring, Spring Boot -->
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-api</artifactId>
<version>[3.1.1]</version>
</dependency>
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>[3.1.1]</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6</version>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.framework.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.10.0-M6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.153</version>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.framework.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.15</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>[1.7,)</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Enhance the meta beans -->
<build>
<plugins>
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>main</id>
<phase>process-test-classes</phase>
<configuration>
<classSource>target/test-classes</classSource>
<packages>com.avaje.tests.**</packages>
<transformArgs>debug=1</transformArgs>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.16</version>
<extensions>true</extensions>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<failIfNoTests>false</failIfNoTests>
<includes>
<include>**/Test*.java</include>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<tiles>
<tile>io.ebean.tile:enhancement:12.2.1</tile>
</tiles>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,95 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.List;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import javax.persistence.PersistenceException;
/**
* Wrapper of the list of Id's adding support for background fetching
* future object.
*
* @author rbygrave
*/
public class BeanIdList {
private final List<Object> idList;
private boolean hasMore = true;
private FutureTask<Integer> fetchFuture;
public BeanIdList(List<Object> idList) {
this.idList = idList;
}
/**
* Return true if the fetch is continuing in a background thread.
*/
public boolean isFetchingInBackground() {
return fetchFuture != null;
}
/**
* Set the FutureTask that is continuing the fetch in a background thread.
*/
public void setBackgroundFetch(FutureTask<Integer> fetchFuture) {
this.fetchFuture = fetchFuture;
}
/**
* Wait for the background fetching to complete with a timeout.
*/
public void backgroundFetchWait(long wait, TimeUnit timeUnit) {
if (fetchFuture != null){
try {
fetchFuture.get(wait, timeUnit);
} catch (Exception e) {
throw new PersistenceException(e);
}
}
}
/**
* Wait for the background fetching to complete.
*/
public void backgroundFetchWait() {
if (fetchFuture != null){
try {
fetchFuture.get();
} catch (Exception e) {
throw new PersistenceException(e);
}
}
}
/**
* Add an Id to the list.
*/
public void add(Object id){
idList.add(id);
}
/**
* Return the list of Id's.
*/
public List<Object> getIdList() {
return idList;
}
/**
* Return true if max rows was hit and there is more rows to fetch.
*/
public boolean isHasMore() {
return hasMore;
}
/**
* Set to true when max rows is hit and there are more rows to fetch.
*/
public void setHasMore(boolean hasMore) {
this.hasMore = hasMore;
}
}
@@ -1,517 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
/**
* Parameters used for binding to a statement.
* <p>
* Used by FindByNativeSql and UpdateSql to support ordered and named
* parameters. Note that you can use either ordered OR named parameters.
* </p>
*/
public class BindParams implements Serializable {
private static final long serialVersionUID = 4541081933302086285L;
private ArrayList<Param> positionedParameters = new ArrayList<Param>();
private HashMap<String, Param> namedParameters = new HashMap<String, Param>();
/**
* Need to create a hash when binding collection values (for in clauses).
*/
private int queryPlanHash = 1;
/**
* This is the sql. For named parameters this is the sql after the named
* parameters have been replaced with question mark place holders and the
* parameters have been ordered by addNamedParamInOrder().
*/
private String preparedSql;
/**
* Return a deep copy of the BindParams.
*/
public BindParams copy() {
BindParams copy = new BindParams();
for (Param p : positionedParameters) {
copy.positionedParameters.add(p.copy());
}
Iterator<Entry<String, Param>> it = namedParameters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Param> entry = (Map.Entry<String, Param>) it.next();
copy.namedParameters.put(entry.getKey(), entry.getValue().copy());
}
return copy;
}
public int queryBindHash() {
int hc = namedParameters.hashCode();
for (int i = 0; i < positionedParameters.size(); i++) {
hc = hc * 31 + positionedParameters.get(i).hashCode();
}
return hc;
}
public int hashCode() {
int hc = getClass().hashCode();
hc = hc * 31 + namedParameters.hashCode();
for (int i = 0; i < positionedParameters.size(); i++) {
hc = hc * 31 + positionedParameters.get(i).hashCode();
}
hc = hc * 31 + (preparedSql == null ? 0 : preparedSql.hashCode());
return hc;
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
if (o instanceof BindParams) {
return hashCode() == o.hashCode();
}
return false;
}
/**
* Return true if there are no bind parameters.
*/
public boolean isEmpty() {
return positionedParameters.isEmpty() && namedParameters.isEmpty();
}
/**
* Return a Natural Key bind param if supported.
*/
public NaturalKeyBindParam getNaturalKeyBindParam() {
if (positionedParameters != null){
return null;
}
if (namedParameters != null && namedParameters.size() == 1){
Entry<String, Param> e = namedParameters.entrySet().iterator().next();
return new NaturalKeyBindParam(e.getKey(), e.getValue().getInValue());
}
return null;
}
public int size() {
return positionedParameters.size();
}
/**
* Return true if named parameters are being used and they have not yet been
* ordered. The sql needs to be prepared (named replaced with ?) and the
* parameters ordered.
*/
public boolean requiresNamedParamsPrepare() {
return !namedParameters.isEmpty() && positionedParameters.isEmpty();
}
/**
* Set a null parameter using position.
*/
public void setNullParameter(int position, int jdbcType) {
Param p = getParam(position);
p.setInNullType(jdbcType);
}
/**
* Set an In Out parameter using position.
*/
public void setParameter(int position, Object value, int outType) {
addToQueryPlanHash(String.valueOf(position), value);
Param p = getParam(position);
p.setInValue(value);
p.setOutType(outType);
}
/**
* Using position set the In value of a parameter. Note that for nulls you
* must use setNullParameter.
*/
public void setParameter(int position, Object value) {
addToQueryPlanHash(String.valueOf(position), value);
Param p = getParam(position);
p.setInValue(value);
}
/**
* Register the parameter as an Out parameter using position.
*/
public void registerOut(int position, int outType) {
Param p = getParam(position);
p.setOutType(outType);
}
private Param getParam(String name) {
Param p = (Param) namedParameters.get(name);
if (p == null) {
p = new Param();
namedParameters.put(name, p);
}
return p;
}
private Param getParam(int position) {
int more = position - positionedParameters.size();
if (more > 0) {
for (int i = 0; i < more; i++) {
positionedParameters.add(new Param());
}
}
return (Param) positionedParameters.get(position - 1);
}
/**
* Set a named In Out parameter.
*/
public void setParameter(String name, Object value, int outType) {
addToQueryPlanHash(name, value);
Param p = getParam(name);
p.setInValue(value);
p.setOutType(outType);
}
/**
* Set a named In parameter that is null.
*/
public void setNullParameter(String name, int jdbcType) {
Param p = getParam(name);
p.setInNullType(jdbcType);
}
/**
* Set a named In parameter that is not null.
*/
public Param setParameter(String name, Object value) {
addToQueryPlanHash(name, value);
Param p = getParam(name);
p.setInValue(value);
return p;
}
/**
* For binding collections calculate a hash to be used for the query plan.
*/
private void addToQueryPlanHash(String name, Object value){
if (value != null){
if (value instanceof Collection<?>){
queryPlanHash = queryPlanHash * 31 + name.hashCode();
queryPlanHash = queryPlanHash * 31 + ((Collection<?>)value).size();
}
}
}
/**
* Return the hash that should be included with the query plan.
* <p>
* This is to handle binding collections to in clauses. The number
* of values in the collection effects the query (number of bind values)
* and so must be taken into account when calculating the query hash.
* </p>
*/
public int getQueryPlanHash() {
return queryPlanHash;
}
/**
* Set an encryption key as a bind value.
* <p>
* Needs special treatment as the value should not be included in a log.
* </p>
*/
public Param setEncryptionKey(String name, Object value) {
Param p = getParam(name);
p.setEncryptionKey(value);
return p;
}
/**
* Register the named parameter as an Out parameter.
*/
public void registerOut(String name, int outType) {
Param p = getParam(name);
p.setOutType(outType);
}
/**
* Return the Parameter for a given position.
*/
public Param getParameter(int position) {
// Used to read Out value by CallableSql
return getParam(position);
}
/**
* Return the named parameter.
*/
public Param getParameter(String name) {
return getParam(name);
}
/**
* Return the values of ordered parameters.
*/
public List<Param> positionedParameters() {
return positionedParameters;
}
/**
* Set the sql with named parameters replaced with place holder ?.
*/
public void setPreparedSql(String preparedSql) {
this.preparedSql = preparedSql;
}
/**
* Return the sql with ? place holders (named parameters have been processed
* and ordered).
*/
public String getPreparedSql() {
return preparedSql;
}
/**
* The bind parameters in the correct binding order.
* <p>
* This is the result of converting sql with named parameters
* into sql with ? and ordered parameters.
* </p>
*/
public static final class OrderedList {
final List<Param> paramList;
final StringBuilder preparedSql;
public OrderedList() {
this(new ArrayList<Param>());
}
public OrderedList(List<Param> paramList) {
this.paramList = paramList;
this.preparedSql = new StringBuilder();
}
/**
* Add a parameter in the correct binding order.
*/
public void add(Param param) {
paramList.add(param);
}
/**
* Return the number of bind parameters in this list.
*/
public int size() {
return paramList.size();
}
/**
* Returns the ordered list of bind parameters.
*/
public List<Param> list() {
return paramList;
}
/**
* Append parsedSql that has named parameters converted into ?.
*/
public void appendSql(String parsedSql) {
preparedSql.append(parsedSql);
}
public String getPreparedSql() {
return preparedSql.toString();
}
}
/**
* A In Out capable parameter for the CallableStatement.
*/
public static final class Param implements Serializable {
private static final long serialVersionUID = 1L;
private boolean encryptionKey;
private boolean isInParam;
private boolean isOutParam;
private int type;
private Object inValue;
private Object outValue;
private int textLocation;
/**
* Construct a Parameter.
*/
public Param() {
}
/**
* Create a deep copy of the Param.
*/
public Param copy() {
Param copy = new Param();
copy.isInParam = isInParam;
copy.isOutParam = isOutParam;
copy.type = type;
copy.inValue = inValue;
copy.outValue = outValue;
return copy;
}
public int hashCode() {
int hc = getClass().hashCode();
hc = hc * 31 + (isInParam ? 0 : 1);
hc = hc * 31 + (isOutParam ? 0 : 1);
hc = hc * 31 + (type);
hc = hc * 31 + (inValue == null ? 0 : inValue.hashCode());
return hc;
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
if (o instanceof Param) {
return hashCode() == o.hashCode();
}
return false;
}
/**
* Return true if this is an In parameter that needs to be bound before
* execution.
*/
public boolean isInParam() {
return isInParam;
}
/**
* Return true if this is an out parameter that needs to be registered
* before execution.
*/
public boolean isOutParam() {
return isOutParam;
}
/**
* Return the jdbc type of this parameter. Used for registering Out
* parameters and setting NULL In parameters.
*/
public int getType() {
return type;
}
/**
* Set the Out parameter type.
*/
public void setOutType(int type) {
this.type = type;
this.isOutParam = true;
}
/**
* Set the In value.
*/
public void setInValue(Object in) {
this.inValue = in;
this.isInParam = true;
}
/**
* Set an encryption key (which can not be logged).
*/
public void setEncryptionKey(Object in) {
this.inValue = in;
this.isInParam = true;
this.encryptionKey = true;
}
/**
* Specify that the In parameter is NULL and the specific type that it
* is.
*/
public void setInNullType(int type) {
this.type = type;
this.inValue = null;
this.isInParam = true;
}
/**
* Return the OUT value that was retrieved. This value is set after
* CallableStatement was executed.
*/
public Object getOutValue() {
return outValue;
}
/**
* Return the In value. If this is null, then the type should be used to
* specify the type of the null.
*/
public Object getInValue() {
return inValue;
}
/**
* Set the OUT value returned by a CallableStatement after it has
* executed.
*/
public void setOutValue(Object out) {
this.outValue = out;
}
/**
* Return the location this parameter was found in the sql text.
*/
public int getTextLocation() {
return textLocation;
}
/**
* Set the location in the sql text this parameter was located. This is
* used to control order for named parameters.
*/
public void setTextLocation(int textLocation) {
this.textLocation = textLocation;
}
/**
* If true do not include this value in a transaction log.
*/
public boolean isEncryptionKey() {
return encryptionKey;
}
}
}
@@ -1,160 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Wraps the caller and context class loaders.
* <p>
* Helper for ClassUtil.
* </p>
*
* @author rbygrave
*
*/
class ClassLoadContext {
private static final Logger logger = Logger.getLogger(ClassLoadContext.class.getName());
private final ClassLoader callerLoader;
private final ClassLoader contextLoader;
private final boolean preferContext;
private boolean ambiguous;
public static ClassLoadContext of(Class<?> caller, boolean preferContext) {
return new ClassLoadContext(caller, preferContext);
}
/**
* This constructor is package-private to restrict instantiation to
* {@link ClassLoadContextFactory} only.
*/
ClassLoadContext(final Class<?> caller, boolean preferContext) {
if (caller == null){
throw new IllegalArgumentException("caller is null");
}
this.callerLoader = caller.getClassLoader();
this.contextLoader = Thread.currentThread().getContextClassLoader();
this.preferContext = preferContext;
}
public Class<?> forName(String name) throws ClassNotFoundException {
ClassLoader defaultLoader = getDefault(preferContext);
try {
return Class.forName(name, true, defaultLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == defaultLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
/**
* Return the expected class loader to use.
* <p>
* Works on the assumption that the child of the caller or context class
* loader is preferred.
* </p>
*/
public ClassLoader getDefault(boolean preferContext) {
if (contextLoader == null){
if (logger.isLoggable(Level.FINE)){
logger.fine("No Context ClassLoader, using "+callerLoader.getClass().getName());
}
return callerLoader;
}
if (contextLoader == callerLoader){
if (logger.isLoggable(Level.FINE)){
logger.fine("Context and Caller ClassLoader's same instance of "+contextLoader.getClass().getName());
}
return callerLoader;
}
if (isChild(contextLoader, callerLoader)) {
if (logger.isLoggable(Level.FINE)){
logger.info("Caller ClassLoader "+callerLoader.getClass().getName()
+" child of ContextLoader "+contextLoader.getClass().getName());
}
return callerLoader;
} else if (isChild(callerLoader, contextLoader)) {
if (logger.isLoggable(Level.FINE)){
logger.info("Context ClassLoader "+contextLoader.getClass().getName()
+" child of Caller ClassLoader "+callerLoader.getClass().getName());
}
return contextLoader;
} else {
// ambiguous case, perhaps both null
logger.info("Ambiguous ClassLoader choice preferContext:"+preferContext
+" Context:"+contextLoader.getClass().getName()+" Caller:"+callerLoader.getClass().getName());
ambiguous = true;
return preferContext ? contextLoader : callerLoader;
}
}
/**
* Return true if the 'default' class loader is ambiguous.
*/
public boolean isAmbiguous() {
return ambiguous;
}
/**
* Return the ClassLoader of the caller.
*/
public ClassLoader getCallerLoader() {
return callerLoader;
}
/**
* Return the Thread Context ClassLoader.
*/
public ClassLoader getContextLoader() {
return contextLoader;
}
/**
* Return the ClassLoader for this class.
*/
public ClassLoader getThisLoader() {
return this.getClass().getClassLoader();
}
/**
* Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
* 'loader1'=='loader2'].
*/
private boolean isChild(final ClassLoader loader1, ClassLoader loader2) {
// if (loader1 == loader2) {
// logger.info("Context and Caller ClassLoader's same "+loader1.getClass().getName());
// return true;
// }
// if (loader2 == null) {
// logger.info(msg+" ClassLoader is null");
// return false;
// }
// if (loader1 == null) {
// logger.info("Using "+msg+" ClassLoader as other is null");
// return true;
// }
for (; loader2 != null; loader2 = loader2.getParent()) {
if (loader2 == loader1) {
return true;
}
}
return false;
}
}
@@ -1,92 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.logging.Logger;
/**
* Helper to find classes taking into account the context class loader.
*
* @author rbygrave
*/
public class ClassUtil {
private static final Logger logger = Logger.getLogger(ClassUtil.class.getName());
private static boolean preferContext = true;
/**
* Load a class taking into account a context class loader (if present).
*/
public static Class<?> forName(String name) throws ClassNotFoundException {
return forName(name, null);
}
/**
* Load a class taking into account a context class loader (if present).
*/
public static Class<?> forName(String name, Class<?> caller) throws ClassNotFoundException {
if (caller == null){
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, preferContext);
return ctx.forName(name);
}
public static ClassLoader getClassLoader(Class<?> caller, boolean preferContext) {
if (caller == null){
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, preferContext);
ClassLoader classLoader = ctx.getDefault(preferContext);
if (ctx.isAmbiguous()){
logger.info("Ambigous ClassLoader (Context vs Caller) chosen "+classLoader);
}
return classLoader;
}
/**
* Return true if the given class is present.
*/
public static boolean isPresent(String className) {
return isPresent(className, null);
}
/**
* Return true if the given class is present.
*/
public static boolean isPresent(String className, Class<?> caller) {
try {
forName(className, caller);
return true;
} catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className) {
return newInstance(className,null);
}
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className, Class<?> caller) {
try {
Class<?> cls = forName(className, caller);
return cls.newInstance();
} catch (Exception e){
String msg = "Error constructing "+className;
throw new IllegalArgumentException(msg, e);
}
}
}
@@ -1,27 +0,0 @@
package com.avaje.ebeaninternal.api;
public final class DerivedRelationshipData {
private final Object assocBean;
private final String logicalName;
private final Object bean;
public DerivedRelationshipData(Object assocBean, String logicalName, Object bean) {
this.assocBean = assocBean;
this.logicalName = logicalName;
this.bean = bean;
}
public Object getAssocBean() {
return assocBean;
}
public String getLogicalName() {
return logicalName;
}
public Object getBean() {
return bean;
}
}
@@ -1,36 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.TxScope;
/**
* Helper object to make AOP generated code simpler.
*/
public class HelpScopeTrans {
/**
* Create a ScopeTrans for a given methods TxScope.
*/
public static ScopeTrans createScopeTrans(TxScope txScope) {
EbeanServer server = Ebean.getServer(txScope.getServerName());
SpiEbeanServer iserver = (SpiEbeanServer)server;
return iserver.createScopeTrans(txScope);
}
/**
* Exiting the method in an expected fashion.
* <p>
* That is returning successfully or via a caught exception.
* Unexpected exceptions are caught via the Thread uncaughtExceptionHandler.
* </p>
* @param returnOrThrowable the return or throwable object
* @param opCode the opcode for ATHROW or ARETURN etc
* @param scopeTrans the scoped transaction the method was run with.
*/
public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans){
scopeTrans.onExit(returnOrThrowable, opCode);
}
}
@@ -1,39 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Controls the loading of ManyToOne and OneToOne relationships.
*
* @author rbygrave
*/
public interface LoadBeanContext extends LoadSecondaryQuery {
/**
* Configure the query to load beans for this node/path.
*/
public void configureQuery(SpiQuery<?> query, String lazyLoadProperty);
/**
* Return the full path of this node from the root object.
*/
public String getFullPath();
/**
* Return the persistence context used for all queries
* related to this object graph.
*/
public PersistenceContext getPersistenceContext();
/**
* Return the BeanDescriptor for beans for this node.
*/
public BeanDescriptor<?> getBeanDescriptor();
/**
* Return the batchSize used for lazy loading beans.
*/
public int getBatchSize();
}
@@ -1,63 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.EntityBeanIntercept;
/**
* Request for loading ManyToOne and OneToOne relationships.
*/
public class LoadBeanRequest extends LoadRequest {
private final List<EntityBeanIntercept> batch;
private final LoadBeanContext loadContext;
private final String lazyLoadProperty;
private final boolean loadCache;
public LoadBeanRequest(LoadBeanContext loadContext, List<EntityBeanIntercept> batch,
Transaction transaction, int batchSize, boolean lazy, String lazyLoadProperty, boolean loadCache) {
super(transaction, batchSize, lazy);
this.loadContext = loadContext;
this.batch = batch;
this.lazyLoadProperty = lazyLoadProperty;
this.loadCache = loadCache;
}
public boolean isLoadCache() {
return loadCache;
}
public String getDescription() {
String fullPath = loadContext.getFullPath();
String s = "path:" + fullPath + " batch:" + batchSize + " actual:"
+ batch.size();
return s;
}
/**
* Return the batch of beans to actually load.
*/
public List<EntityBeanIntercept> getBatch() {
return batch;
}
/**
* Return the load context.
*/
public LoadBeanContext getLoadContext() {
return loadContext;
}
/**
* Return the property that invoked the lazy loading.
*/
public String getLazyLoadProperty() {
return lazyLoadProperty;
}
}
@@ -1,60 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* Controls the loading of reference objects for a query instance.
*/
public interface LoadContext {
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
public int getSecondaryQueriesMinBatchSize(OrmQueryRequest<?> parentRequest, int defaultQueryBatch);
/**
* Execute any secondary (+query) queries if there are any defined.
* @param parentRequest the originating query request
*/
public void executeSecondaryQueries(OrmQueryRequest<?> parentRequest, int defaultQueryBatch);
/**
* Register any secondary queries (+query or +lazy) with their
* appropriate LoadBeanContext or LoadManyContext.
* <p>
* This is so the LoadBeanContext or LoadManyContext use the
* defined query for +query and +lazy execution.
* </p>
*/
public void registerSecondaryQueries(SpiQuery<?> query);
/**
* Return the node for a given path which is used by autofetch profiling.
*/
public ObjectGraphNode getObjectGraphNode(String path);
/**
* Return the persistence context used by this query and future lazy loading.
*/
public PersistenceContext getPersistenceContext();
/**
* Set the persistence context used by this query and future lazy loading.
*/
public void setPersistenceContext(PersistenceContext persistenceContext);
/**
* Register a Bean for lazy loading.
*/
public void register(String path, EntityBeanIntercept ebi);
/**
* Register a collection for lazy loading.
*/
public void register(String path, BeanCollection<?> bc);
}
@@ -1,54 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
/**
* Controls the loading of OneToMany and ManyToMany relationships.
*
* @author rbygrave
*/
public interface LoadManyContext extends LoadSecondaryQuery {
/**
* Configure the query to load beans for this node/path.
*/
public void configureQuery(SpiQuery<?> query);
/**
* Return the full path of this node from the root object.
*/
public String getFullPath();
/**
* Return the node location for this node/path.
*/
public ObjectGraphNode getObjectGraphNode();
/**
* Return the persistence context used for all queries
* related to this object graph.
*/
public PersistenceContext getPersistenceContext();
/**
* Return the batchSize used for lazy loading beans.
*/
public int getBatchSize();
/**
* Return the BeanDescriptor for beans for this node.
*/
public BeanDescriptor<?> getBeanDescriptor();
/**
* Return the associated Many bean property.
*/
public BeanPropertyAssocMany<?> getBeanProperty();
}
@@ -1,74 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.BeanCollection;
/**
* Request for loading Associated One Beans.
*/
public class LoadManyRequest extends LoadRequest {
private final List<BeanCollection<?>> batch;
private final LoadManyContext loadContext;
private final boolean onlyIds;
private final boolean loadCache;
public LoadManyRequest(LoadManyContext loadContext,
List<BeanCollection<?>> batch, Transaction transaction,
int batchSize, boolean lazy, boolean onlyIds, boolean loadCache) {
super(transaction, batchSize, lazy);
this.loadContext = loadContext;
this.batch = batch;
this.onlyIds = onlyIds;
this.loadCache = loadCache;
}
public String getDescription() {
String fullPath = loadContext.getFullPath();
String s = "path:" + fullPath + " batch:" + batchSize + " actual:"
+ batch.size();
return s;
}
/**
* Return the batch of collections to actually load.
*/
public List<BeanCollection<?>> getBatch() {
return batch;
}
/**
* Return the load context.
*/
public LoadManyContext getLoadContext() {
return loadContext;
}
/**
* Return true if lazy loading should only load the id values.
* <p>
* This for use when lazy loading is invoked on methods such
* as clear() and removeAll() where it generally makes sense to
* only fetch the Id values as the other property information is
* not used.
* </p>
*/
public boolean isOnlyIds() {
return onlyIds;
}
/**
* Return true if we should load the Collection ids into the cache.
*/
public boolean isLoadCache() {
return loadCache;
}
}
@@ -1,48 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Transaction;
/**
* Request for loading Associated One Beans.
*/
public abstract class LoadRequest {
protected final boolean lazy;
protected final int batchSize;
protected final Transaction transaction;
public LoadRequest(Transaction transaction, int batchSize, boolean lazy) {
this.transaction = transaction;
this.batchSize = batchSize;
this.lazy = lazy;
}
/**
* Return true if this is a lazy load and false if it is a secondary query.
*/
public boolean isLazy() {
return lazy;
}
/**
* Return the requested batch size.
*/
public int getBatchSize() {
return batchSize;
}
/**
* Return the transaction to use if this is a secondary query.
* <p>
* Lazy loading queries run in their own transaction.
* </p>
*/
public Transaction getTransaction() {
return transaction;
}
}
@@ -1,21 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
* Defines the method for executing secondary queries.
* <p>
* That is +query nodes in a orm query get executed after
* the initial query as 'secondary' queries.
* </p>
*/
public interface LoadSecondaryQuery {
/**
* Execute the secondary query with a given batch size.
*
* @param parentRequest
* the originating query request
*/
public void loadSecondaryQuery(OrmQueryRequest<?> parentRequest, int requestedBatchSize, boolean all);
}
@@ -1,76 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.io.Serializable;
import java.util.Set;
import java.util.TreeSet;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.query.SplitName;
/**
* Holds the joins needs to support the many where predicates.
* These joins are independent of any 'fetch' joins on the many.
*/
public class ManyWhereJoins implements Serializable {
private static final long serialVersionUID = -6490181101871795417L;
private final TreeSet<String> joins = new TreeSet<String>();
/**
* Add a many where join.
*/
public void add(ElPropertyDeploy elProp) {
String join = elProp.getElPrefix();
BeanProperty p = elProp.getBeanProperty();
if (p instanceof BeanPropertyAssocMany<?>){
join = addManyToJoin(join, p.getName());
}
if (join != null){
joins.add(join);
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
if (secondaryTableJoinPrefix != null) {
joins.add(join+"."+secondaryTableJoinPrefix);
}
addParentJoins(join);
}
}
/**
* For 'many' properties we also need to add the name of the
* many property to get the full logical name of the join.
*/
private String addManyToJoin(String join, String manyPropName){
if (join == null){
return manyPropName;
} else {
return join+"."+manyPropName;
}
}
private void addParentJoins(String join) {
String[] split = SplitName.split(join);
if (split[0] != null){
joins.add(split[0]);
addParentJoins(split[0]);
}
}
/**
* Return true if there are no extra many where joins.
*/
public boolean isEmpty() {
return joins.isEmpty();
}
/**
* Return the set of many where joins.
*/
public Set<String> getJoins() {
return joins;
}
}
@@ -1,12 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.io.Serializable;
/**
* Object used as a synchronization monitor that is serializable.
*/
public class Monitor implements Serializable {
private static final long serialVersionUID = -2741687226680981940L;
}
@@ -1,202 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import com.avaje.ebean.TxScope;
/**
* Used internally to handle the scoping of transactions for methods.
*/
public class ScopeTrans implements Thread.UncaughtExceptionHandler {
private static final int OPCODE_ATHROW = 191;
//private static final int OPCODE_ATHROW = com.avaje.ebean.enhance.asm.Opcodes.ATHROW;
private final SpiTransactionScopeManager scopeMgr;
/**
* The suspended transaction (can be null).
*/
private final SpiTransaction suspendedTransaction;
/**
* The transaction in scope (can be null).
*/
private final SpiTransaction transaction;
/**
* If true by default rollback on Checked exceptions.
*/
private final boolean rollbackOnChecked;
/**
* True if the transaction was created and hence should be committed
* on finally if it hasn't already been rolled back.
*/
private final boolean created;
/**
* Explicit set of Exceptions that DO NOT cause a rollback to occur.
*/
private final ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Explicit set of Exceptions that DO cause a rollback to occur.
*/
private final ArrayList<Class<? extends Throwable>> rollbackFor;
private final UncaughtExceptionHandler originalUncaughtHandler;
/**
* Flag set when a rollback has occurred.
*/
private boolean rolledBack;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope,
SpiTransaction suspendedTransaction, SpiTransactionScopeManager scopeMgr) {
this.rollbackOnChecked = rollbackOnChecked;
this.created = created;
this.transaction = transaction;
this.suspendedTransaction = suspendedTransaction;
this.scopeMgr = scopeMgr;
this.noRollbackFor = txScope.getNoRollbackFor();
this.rollbackFor = txScope.getRollbackFor();
Thread t = Thread.currentThread();
originalUncaughtHandler = t.getUncaughtExceptionHandler();
t.setUncaughtExceptionHandler(this);
}
/**
* Called when the Thread catches any uncaught exception.
* For example, an unexpected NullPointerException or Error.
*/
public void uncaughtException(Thread thread, Throwable e) {
// rollback transaction if required
caughtThrowable(e);
// reinstate suspended transaction and
// original uncaughtExceptionHandler if required
onFinally();
if (originalUncaughtHandler != null){
originalUncaughtHandler.uncaughtException(thread, e);
}
}
/**
* Returned via RETURN or expected Exception from the method.
* @param returnOrThrowable the return value or Throwable
* @param opCode indicates
*/
public void onExit(Object returnOrThrowable, int opCode) {
if (opCode == OPCODE_ATHROW){
// exited with a Throwable
caughtThrowable((Throwable)returnOrThrowable);
}
onFinally();
}
/**
* Commit if the transaction exists and has not already been rolled back.
* Also reinstate the suspended transaction if there was one.
*/
public void onFinally() {
try {
if (originalUncaughtHandler != null){
Thread.currentThread().setUncaughtExceptionHandler(originalUncaughtHandler);
}
if (!rolledBack && created) {
transaction.commit();
}
} finally {
if (suspendedTransaction != null){
// put the previously suspended transaction
// back onto the ThreadLocal or equivalent
scopeMgr.replace(suspendedTransaction);
}
}
}
/**
* An Error was caught and this ALWAYS causes a rollback to occur.
* Returns the error and this should be thrown by the calling code.
*/
public Error caughtError(Error e) {
rollback(e);
return e;
}
/**
* An Exception was caught and may or may not cause a rollback to occur.
* Returns the exception and this should be thrown by the calling code.
*/
public <T extends Throwable> T caughtThrowable(T e) {
if (isRollbackThrowable(e)) {
rollback(e);
}
return e;
}
private void rollback(Throwable e) {
if (transaction != null && transaction.isActive()) {
// transaction is null for NOT_SUPPORTED and sometimes SUPPORTS
// and Inactive (already rolled back) if nested REQUIRED
transaction.rollback(e);
}
rolledBack = true;
}
/**
* Return true if this throwable should cause a rollback to occur.
*/
private boolean isRollbackThrowable(Throwable e) {
if (e instanceof Error){
return true;
}
if (noRollbackFor != null){
for (int i = 0; i < noRollbackFor.size(); i++) {
if (noRollbackFor.get(i).equals(e.getClass())) {
// explicit no rollback for this one
return false;
}
}
}
if (rollbackFor != null){
for (int i = 0; i < rollbackFor.size(); i++) {
if (rollbackFor.get(i).equals(e.getClass())) {
// explicit rollback for this one
return true;
}
}
}
if (e instanceof RuntimeException) {
return true;
} else {
// checked exceptions...
// EJB defaults this to false which is not intuitive IMO
// Ebean makes this configurable (default to true)
return rollbackOnChecked;
}
}
}
@@ -1,14 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.BackgroundExecutor;
/**
* Internal Extension to BackgroundExecutor with shutdown.
*/
public interface SpiBackgroundExecutor extends BackgroundExecutor {
/**
* Shutdown any associated thread pools.
*/
public void shutdown();
}
@@ -1,10 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.CallableSql;
public interface SpiCallableSql extends CallableSql {
public BindParams getBindParams();
public TransactionEventTable getTransactionEventTable();
}
@@ -1,191 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Query;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.TxScope;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.BeanLoader;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.core.PstmtBatch;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.ddl.DdlGenerator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* Service Provider extension to EbeanServer.
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return true if DeleteMissingChildren defaults to true for stateless updates.
*/
public boolean isDefaultDeleteMissingChildren();
/**
* Return true if UpdateNullProperties defaults to true for stateless updates.
*/
public boolean isDefaultUpdateNullProperties();
/**
* Return true if vanilla beans should be returned by queries by default.
*/
public boolean isVanillaMode();
/**
* Return the DatabasePlatform for this server.
*/
public DatabasePlatform getDatabasePlatform();
/**
* Return a JDBC driver specific handler for batching.
* <p>
* Required for Oracle specific batch handling.
* </p>
*/
public PstmtBatch getPstmtBatch();
/**
* Create an object to represent the current CallStack.
* <p>
* Typically used to identify the origin of queries for Autofetch
* and object graph costing.
* </p>
*/
public CallStack createCallStack();
/**
* Return the DDL generator.
*/
public DdlGenerator getDdlGenerator();
/**
* Return the AutoFetchListener.
*/
public AutoFetchManager getAutoFetchManager();
/**
* Clear the query execution statistics.
*/
public void clearQueryStatistics();
/**
* Return all the descriptors.
*/
public List<BeanDescriptor<?>> getBeanDescriptors();
/**
* Return the BeanDescriptor for a given type of bean.
*/
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> type);
/**
* Return BeanDescriptor using it's unique id.
*/
public BeanDescriptor<?> getBeanDescriptorById(String descriptorId);
/**
* Return BeanDescriptors mapped to this table.
*/
public List<BeanDescriptor<?>> getBeanDescriptors(String tableName);
/**
* Process committed changes from another framework.
* <p>
* This notifies this instance of the framework that beans have been
* committed externally to it. Either by another framework or clustered
* server. It uses this to maintain its cache and text indexes
* appropriately.
* </p>
*/
public void externalModification(TransactionEventTable event);
/**
* Create a ServerTransaction.
* <p>
* To specify to use the default transaction isolation use a value of -1.
* </p>
*/
public SpiTransaction createServerTransaction(boolean isExplicit, int isolationLevel);
/**
* Return the current transaction or null if there is no current
* transaction.
*/
public SpiTransaction getCurrentServerTransaction();
/**
* Create a ScopeTrans for a method for the given scope definition.
*/
public ScopeTrans createScopeTrans(TxScope txScope);
/**
* Create a ServerTransaction for query purposes.
*/
public SpiTransaction createQueryTransaction();
/**
* An event from another server in the cluster used to notify local
* BeanListeners of remote inserts updates and deletes.
*/
public void remoteTransactionEvent(RemoteTransactionEvent event);
/**
* Create a query request object.
*/
public <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> q, Transaction t);
/**
* Compile a query.
*/
public <T> CQuery<T> compileQuery(Query<T> query, Transaction t);
/**
* Return the queryEngine for this server.
*/
public CQueryEngine getQueryEngine();
/**
* Execute the findId's query but without copying the query.
* <p>
* Used so that the list of Id's can be made accessible to client code
* before the query has finished (if executing in a background thread).
* </p>
*/
public <T> List<Object> findIdsWithCopy(Query<T> query, Transaction t);
/**
* Execute the findRowCount query but without copying the query.
*/
public <T> int findRowCountWithCopy(Query<T> query, Transaction t);
/**
* Load a batch of Associated One Beans.
*/
public void loadBean(LoadBeanRequest loadRequest);
/**
* Lazy load a batch of Many's.
*/
public void loadMany(LoadManyRequest loadRequest);
/**
* Return the default batch size for lazy loading.
*/
public int getLazyLoadBatchSize();
/**
* Return true if the type is known as an Entity or Xml type
* or a List Set or Map of known bean types.
*/
public boolean isSupportedType(java.lang.reflect.Type genericType);
}
@@ -1,70 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Expression;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* An expression that becomes part of a Where clause or Having clause.
*/
public interface SpiExpression extends Expression {
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent
* join clause.
* </p>
*/
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins);
/**
* Calculate a hash value used to identify a query for AutoFetch tuning.
* <p>
* That is, if the hash changes then the query will be considered different
* from an AutoFetch perspective and get different tuning.
* </p>
*/
public int queryAutoFetchHash();
/**
* Calculate a hash value for the expression.
* This includes the expression type and property but should exclude
* the bind values.
* <p>
* This is used where queries are the same except for the bind values, in which
* case the query execution plan can be reused.
* </p>
*/
public int queryPlanHash(BeanQueryRequest<?> request);
/**
* Return the hash value for the values that will be bound.
*/
public int queryBindHash();
/**
* Add some sql to the query.
* <p>
* This will contain ? as a place holder for each associated bind values.
* </p>
* <p>
* The 'sql' added to the query can contain object property names rather
* than db tables and columns. This 'sql' is later parsed converting the
* logical property names to their full database column names.
* </p>
* @param request
* the associated request.
*/
public void addSql(SpiExpressionRequest request);
/**
* Add the parameter values to be set against query. For each ? place holder
* there should be a corresponding value that is added to the bindList.
*
* @param request
* the associated request.
*/
public void addBindValues(SpiExpressionRequest request);
}
@@ -1,13 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebeaninternal.server.expression.FilterExprPath;
public interface SpiExpressionFactory extends ExpressionFactory {
/**
* Create another expression factory with a given sub path.
*/
public ExpressionFactory createExpressionFactory(FilterExprPath prefix);
}
@@ -1,71 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Internal extension of ExpressionList.
*/
public interface SpiExpressionList<T> extends ExpressionList<T> {
/**
* Return the underlying list of expressions.
*/
public List<SpiExpression> getUnderlyingList();
/**
* Trim the path for filterMany() expressions.
*/
public void trimPath(int prefixTrim);
/**
* Restore the ExpressionFactory after deserialisation.
*/
public void setExpressionFactory(ExpressionFactory expr);
/**
* Process "Many" properties populating ManyWhereJoins.
* <p>
* Predicates on Many properties require an extra independent
* join clause.
* </p>
*/
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoins);
/**
* Return true if this list is empty.
*/
public boolean isEmpty();
/**
* Concatenate the expression sql into a String.
* <p>
* The list of expressions are evaluated in order building a sql statement
* with bind parameters.
* </p>
*/
public String buildSql(SpiExpressionRequest request);
/**
* Combine the expression bind values into a list.
* <p>
* Expressions are evaluated in order and all the resulting bind values are
* returned as a List.
* </p>
*
* @return the list of all the bind values in order.
*/
public ArrayList<Object> buildBindValues(SpiExpressionRequest request);
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
public int queryPlanHash(BeanQueryRequest<?> request);
}
@@ -1,52 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Request object used for gathering expression sql and bind values.
*/
public interface SpiExpressionRequest {
/**
* Parse the logical property name to the deployment name.
*/
public String parseDeploy(String logicalProp);
/**
* Return the bean descriptor for the root type.
*/
public BeanDescriptor<?> getBeanDescriptor();
/**
* Return the associated QueryRequest.
*/
public SpiOrmQueryRequest<?> getQueryRequest();
/**
* Append to the expression sql.
*/
public SpiExpressionRequest append(String sql);
/**
* Add a bind value to this request.
*/
public void addBindValue(Object bindValue);
/**
* Return the accumulated expression sql for all expressions in this request.
*/
public String getSql();
/**
* Return the ordered list of bind values for all expressions in this request.
*/
public ArrayList<Object> getBindValues();
/**
* Increments the parameter index and returns that value.
*/
public int nextParameter();
}
@@ -1,590 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.Query;
import com.avaje.ebean.QueryListener;
import com.avaje.ebean.bean.BeanCollectionTouched;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.util.ArrayList;
import java.util.List;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T> {
public enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
Mode(boolean loadContextBean) {
this.loadContextBean = loadContextBean;
}
private final boolean loadContextBean;
public boolean isLoadContextBean() {
return loadContextBean;
}
}
/**
* The type of query result.
*/
public enum Type {
/**
* Find by Id or unique returning a single bean.
*/
BEAN,
/**
* Find returning a List.
*/
LIST,
/**
* Find returning a Set.
*/
SET,
/**
* Find returning a Map.
*/
MAP,
/**
* Find the Id's.
*/
ID_LIST,
/**
* Find rowCount.
*/
ROWCOUNT,
/**
* A subquery used as part of a where clause.
*/
SUBQUERY
}
/**
* Set total hits when querying against lucene.
*/
public void setTotalHits(int totalHits);
/**
* Return true if select all properties was used to ensure the property
* invoking a lazy load was included in the query.
*/
public boolean selectAllForLazyLoadProperty();
/**
* Set the query mode.
*/
public void setMode(Mode m);
/**
* Return the query mode.
*/
public Mode getMode();
/**
* Return a listener that wants to be notified when the bean collection is
* first used.
*/
public BeanCollectionTouched getBeanCollectionTouched();
/**
* Set a listener to be notified when the bean collection has been touched
* (when the list/set/map is first used).
*/
public void setBeanCollectionTouched(BeanCollectionTouched notify);
/**
* Set the list of Id's that is being populated.
* <p>
* This is a mutating list of id's and we are setting this so that other
* threads have access to the id's before the id query has finished.
* </p>
*/
public void setIdList(List<Object> ids);
/**
* Return the list of Id's that is currently being fetched by a background
* thread.
*/
public List<Object> getIdList();
/**
* Return a copy of the query.
*/
public SpiQuery<T> copy();
/**
* Return the type of query (List, Set, Map, Bean, rowCount etc).
*/
public Type getType();
/**
* Set the query type (List, Set etc).
*/
public void setType(Type type);
/**
* Return a more detailed description of the lazy or query load.
*/
public String getLoadDescription();
/**
* Return the load mode (+lazy or +query).
*/
public String getLoadMode();
/**
* Set the load mode (+lazy or +query) and the load description.
*
* @param loadMode
* @param loadDescription
*/
public void setLoadDescription(String loadMode, String loadDescription);
/**
* Set the BeanDescriptor for the root type of this query.
*/
public void setBeanDescriptor(BeanDescriptor<?> desc);
/**
* Initialise/determine the joins required to support 'many' where clause predicates.
*/
public boolean initManyWhereJoins();
/**
* Return the joins required to support predicates on the many properties.
*/
public ManyWhereJoins getManyWhereJoins();
/**
* Convert this natural key query into a find by id query.
*/
public void convertWhereNaturalKeyToId(Object idValue);
/**
* Return a Natural Key bind parameter if supported by this query.
*/
public NaturalKeyBindParam getNaturalKeyBindParam();
/**
* Set the query to select the id property only.
*/
public void setSelectId();
/**
* Set a filter to a join path.
*/
public void setFilterMany(String prop, ExpressionList<?> filterMany);
/**
* Remove the query joins from query detail.
* <p>
* These are registered with the Load Context.
* </p>
*/
public List<OrmQueryProperties> removeQueryJoins();
/**
* Remove the lazy joins from query detail.
* <p>
* These are registered with the Load Context.
* </p>
*/
public List<OrmQueryProperties> removeLazyJoins();
/**
* Set the path of the many when +query/+lazy loading query is executed.
*/
public void setLazyLoadManyPath(String lazyLoadManyPath);
/**
* Convert any many joins fetch joins to query joins.
*/
public void convertManyFetchJoinsToQueryJoins(boolean allowOne, int queryBatch);
/**
* Return the TransactionContext.
* <p>
* If no TransactionContext is present on the query then the
* TransactionContext from the Transaction is used (transaction scoped
* persistence context).
* </p>
*/
public PersistenceContext getPersistenceContext();
/**
* Set an explicit TransactionContext (typically for a refresh query).
* <p>
* If no TransactionContext is present on the query then the
* TransactionContext from the Transaction is used (transaction scoped
* persistence context).
* </p>
*/
public void setPersistenceContext(PersistenceContext transactionContext);
/**
* Return true if the query detail has neither select or joins specified.
*/
public boolean isDetailEmpty();
/**
* Return explicit autoFetch setting or null. If null then not explicitly
* set so we use the default behaviour.
*/
public Boolean isAutofetch();
// /**
// * Return explicit forUpdate setting or null.
// */
// public boolean isForUpdate();
/**
* If return null then no autoFetch profiling for this query. If a
* AutoFetchManager is returned this implies that profiling is turned on for
* this query (and all the objects this query creates).
*/
public AutoFetchManager getAutoFetchManager();
/**
* This has the effect of turning on autoFetch profiling for this query.
*/
public void setAutoFetchManager(AutoFetchManager manager);
/**
* Return the origin point for the query.
* <p>
* This MUST be call prior to a query being changed via tuning. This is
* because the queryPlanHash is used to identify the query point.
* </p>
*/
public ObjectGraphNode setOrigin(CallStack callStack);
/**
* Set the profile point of the bean or collection that is lazy loading.
* <p>
* This enables use to hook this back to the original 'root' query by the
* queryPlanHash and stackPoint.
* </p>
*/
public void setParentNode(ObjectGraphNode node);
/**
* Set the property that invoked the lazy load and MUST be included in the
* lazy loading query.
*/
public void setLazyLoadProperty(String lazyLoadProperty);
/**
* Return the property that invoked lazy load.
*/
public String getLazyLoadProperty();
/**
* Return the lazy load path.
*/
public String getLazyLoadManyPath();
/**
* Used to hook back a lazy loading query to the original query (query
* point).
* <p>
* This will return null or an "original" query.
* </p>
*/
public ObjectGraphNode getParentNode();
/**
* Return false when this is a lazy load or refresh query for a bean.
* <p>
* We just take/copy the data from those beans and don't collect autoFetch
* usage profiling on those lazy load or refresh beans.
* </p>
*/
public boolean isUsageProfiling();
/**
* Set to false if this query should not be included in the autoFetch usage
* profiling information.
*/
public void setUsageProfiling(boolean usageProfiling);
/**
* Return the query name.
*/
public String getName();
/**
* Calculate a hash used by AutoFetch to identify when a query has changed
* (and hence potentially needs a new tuned query plan to be developed).
* <p>
* Excludes bind values and occurs prior to AutoFetch potentially
* tuning/modifying the query.
* </p>
*/
public int queryAutofetchHash();
/**
* Identifies queries that are the same bar the bind variables.
* <p>
* This is used AFTER AutoFetch has potentially tuned the query. This is
* used to identify and reused query plans (the final SQL string and
* associated SqlTree object).
* </p>
* <p>
* Excludes the actual bind values (as they don't effect the query plan).
* </p>
*/
public int queryPlanHash(BeanQueryRequest<?> request);
/**
* Calculate a hash based on the bind values used in the query.
* <p>
* Combined with queryPlanHash() to return getQueryHash (a unique hash for a
* query).
* </p>
*/
public int queryBindHash();
/**
* Identifies queries that are exactly the same including bind variables.
*/
public int queryHash();
/**
* Return true if this is a query based on a SqlSelect rather than
* generated.
*/
public boolean isSqlSelect();
/**
* Return true if this is a RawSql query.
*/
public boolean isRawSql();
/**
* Return the Order By clause or null if there is none defined.
*/
public OrderBy<T> getOrderBy();
/**
* Return additional where clause. This should be added to any where clause
* that was part of the original query.
*/
public String getAdditionalWhere();
/**
* Can return null if no expressions where added to the where clause.
*/
public SpiExpressionList<T> getWhereExpressions();
/**
* Can return null if no expressions where added to the having clause.
*/
public SpiExpressionList<T> getHavingExpressions();
/**
* Return additional having clause. Where raw String expressions are added
* to having clause rather than Expression objects.
*/
public String getAdditionalHaving();
/**
* Returns true if either firstRow or maxRows has been set.
*/
public boolean hasMaxRowsOrFirstRow();
/**
* Return true if this query should use/check the bean cache.
*/
public Boolean isUseBeanCache();
/**
* Return true if this query should use/check the query cache.
*/
public boolean isUseQueryCache();
/**
* Return true if the beans from this query should be loaded into the bean
* cache.
*/
public boolean isLoadBeanCache();
/**
* Return true if the beans returned by this query should be read only.
*/
public Boolean isReadOnly();
/**
* Adds this bean to the persistence context prior to executing the query.
*/
public void contextAdd(EntityBean bean);
/**
* Return the type of beans queries.
*/
public Class<T> getBeanType();
/**
* Return the query timeout.
*/
public int getTimeout();
/**
* Return the objects that should be added to the persistence context prior
* to executing the query.
*/
public ArrayList<EntityBean> getContextAdditions();
/**
* Return the bind parameters.
*/
public BindParams getBindParams();
/**
* Get the orm query as a String. Only available if the query was built from
* a string.
*/
public String getQuery();
/**
* Replace the query detail. This is used by the autoFetch feature to as a
* fast way to set the query properties and joins.
* <p>
* Note care must be taken to keep the where, orderBy, firstRows and maxRows
* held in the detail attributes.
* </p>
*/
public void setDetail(OrmQueryDetail detail);
/**
* Autofetch tune the detail specifying properties to select on already defined joins
* and adding extra joins where they are missing.
*/
public boolean tuneFetchProperties(OrmQueryDetail detail);
/**
* Set to true if this query has been tuned by autoFetch.
*/
public void setAutoFetchTuned(boolean autoFetchTuned);
/**
* Return the query detail.
*/
public OrmQueryDetail getDetail();
public TableJoin getIncludeTableJoin();
public void setIncludeTableJoin(TableJoin includeTableJoin);
/**
* Return the property used to specify keys for a map.
*/
public String getMapKey();
/**
* Return the number of rows after which fetching should occur in a
* background thread.
*/
public int getBackgroundFetchAfter();
/**
* Return the maximum number of rows to return in the query.
*/
public int getMaxRows();
/**
* Return the index of the first row to return in the query.
*/
public int getFirstRow();
/**
* return true if this query uses DISTINCT.
*/
public boolean isDistinct();
/**
* Return true if this query should build and return vanilla objects.
*/
public boolean isVanillaMode(boolean serverDefaultVanillaMode);
/**
* Set default select clauses where none have been explicitly defined.
*/
public void setDefaultSelectClause();
/**
* Return the where clause from a parsed string query.
*/
public String getRawWhereClause();
/**
* Return the Id value.
*/
public Object getId();
/**
* Return the queryListener.
*/
public QueryListener<T> getListener();
/**
* Return true if this query should use its own transaction.
* <p>
* This is true for background fetching and when using QueryListener.
* </p>
*/
public boolean createOwnTransaction();
/**
* Set the generated sql for debug purposes.
*
* @param generatedSql
*/
public void setGeneratedSql(String generatedSql);
/**
* Return the hint for Statement.setFetchSize().
*/
public int getBufferFetchSizeHint();
/**
* Return true if this is a query executing in the background.
*/
public boolean isFutureFetch();
/**
* Set to true to indicate the query is executing in a background thread
* asynchronously.
*/
public void setFutureFetch(boolean futureFetch);
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
public void setCancelableQuery(CancelableQuery cancelableQuery);
/**
* Return true if this query has been cancelled.
*/
public boolean isCancelled();
}
@@ -1,77 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.sql.PreparedStatement;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlQueryListener;
/**
* SQL query - Internal extension to SqlQuery.
*/
public interface SpiSqlQuery extends SqlQuery {
/**
* Return the named or positioned parameters.
*/
public BindParams getBindParams();
/**
* return the query.
*/
public String getQuery();
/**
* Return the queryListener.
*/
public SqlQueryListener getListener();
/**
* Return the first row to fetch.
*/
public int getFirstRow();
/**
* Return the maximum number of rows to fetch.
*/
public int getMaxRows();
/**
* Return the number of rows after which background fetching occurs.
*/
public int getBackgroundFetchAfter();
/**
* Return the key property for maps.
*/
public String getMapKey();
/**
* Return the query timeout.
*/
public int getTimeout();
/**
* Return the hint for Statement.setFetchSize().
*/
public int getBufferFetchSizeHint();
/**
* Return true if this is a future fetch type query.
*/
public boolean isFutureFetch();
/**
* Set to true if this is a future fetch type query.
*/
public void setFutureFetch(boolean futureFetch);
/**
* Set the PreparedStatement for the purposes of supporting cancel.
*/
public void setPreparedStatement(PreparedStatement pstmt);
/**
* Return true if the query has been cancelled.
*/
public boolean isCancelled();
}
@@ -1,8 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.SqlUpdate;
public interface SpiSqlUpdate extends SqlUpdate {
public BindParams getBindParams();
}
@@ -1,184 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.sql.Connection;
import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.transaction.TransactionLogBuffer;
/**
* Extends Transaction with additional API required on server.
* <p>
* Provides support for batching and TransactionContext.
* </p>
*/
public interface SpiTransaction extends Transaction {
/**
* Return true if generated SQL and Bind values should be logged to the
* transaction log.
*/
public boolean isLogSql();
/**
* Return true if summary level events should be logged to the transaction
* log.
*/
public boolean isLogSummary();
/**
* Log a comment to the transaction log for Ebean INTERNAL use. There should
* always be an external LogLevel check prior to calling this method.
*/
public void logInternal(String msg);
/**
* Return the buffer containing transaction log messages.
*/
public TransactionLogBuffer getLogBuffer();
/**
* Register a "Derived Relationship" (that requires an additional update).
*/
public void registerDerivedRelationship(DerivedRelationshipData assocBean);
/**
* Return the list of "Derived Relationships" that must be maintained after
* insert.
*/
public List<DerivedRelationshipData> getDerivedRelationship(Object bean);
/**
* Add a deleting bean to the registered list.
* <p>
* This is to handle bi-directional relationships where both sides Cascade.
* </p>
*/
public void registerDeleteBean(Integer hash);
/**
* Unregister the hash of the bean.
*/
public void unregisterDeleteBean(Integer hash);
/**
* Return true if this is a bean that has already been saved/deleted.
*/
public boolean isRegisteredDeleteBean(Integer hash);
/**
* Unregister the persisted bean.
*/
public void unregisterBean(Object bean);
/**
* Return true if this is a bean that has already been persisted in the
* current recursive save request. The goal is to stop recursively saving
* the bean when cascade persist is on both sides of a relationship).
* <p>
* This will register the bean if it is not already.
* </p>
*/
public boolean isRegisteredBean(Object bean);
/**
* Returns a String used to identify the transaction. This id is used for
* Transaction logging.
*/
public String getId();
/**
* Return the batchSize specifically set for this transaction or 0.
* <p>
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
public int getBatchSize();
/**
* Modify and return the current 'depth' of the transaction.
* <p>
* As we cascade save or delete we traverse the object graph tree. Going up
* to Assoc Ones the depth decreases and going down to Assoc Manys the depth
* increases.
* </p>
* <p>
* The depth is used for ordering batching statements. The lowest depth get
* executed first during save.
* </p>
*/
public int depth(int diff);
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
*/
public boolean isExplicit();
/**
* Get the object that holds the event details.
* <p>
* This information is used maintain the table state, cache and text
* indexes. On commit the Table modifications this generates is broadcast
* around the cluster (if you have a cluster).
* </p>
*/
public TransactionEvent getEvent();
/**
* Whether persistCascade is on for save and delete.
*/
public boolean isPersistCascade();
/**
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
public boolean isBatchThisRequest();
/**
* Return the queue used to batch up persist requests.
*/
public BatchControl getBatchControl();
/**
* Set the queue used to batch up persist requests. There should only be one
* PersistQueue set per transaction.
*/
public void setBatchControl(BatchControl control);
/**
* Return the persistence context associated with this transaction.
* <p>
* You may wish to hold onto this and set it against another transaction
* later. This is along the lines of 'extended persistence context'
* behaviour.
* </p>
*/
public PersistenceContext getPersistenceContext();
/**
* Set the persistence context to this transaction.
* <p>
* This could be considered similar to 'EJB3 Extended Persistence Context'.
* In that you can get the PersistenceContext from a transaction, hold onto
* it, and then set it back later to a second transaction. In general there
* is one PersistenceContext per Transaction. The getPersistenceContext()
* and setPersistenceContext() enable a developer to reuse a single
* PersistenceContext with multiple transactions.
* </p>
*/
public void setPersistenceContext(PersistenceContext context);
/**
* Return the underlying Connection for internal use.
* <p>
* If the connection is made public from Transaction and the user code calls
* that method we can no longer trust the query only status of a
* Transaction.
* </p>
*/
public Connection getInternalConnection();
}
@@ -1,6 +0,0 @@
package com.avaje.ebeaninternal.api;
public interface SpiTransactionScopeManager {
public void replace(SpiTransaction t);
}
@@ -1,76 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.Update;
/**
* Internal extension to the Update interface.
*/
public interface SpiUpdate<T> extends Update<T> {
/**
* The type of the update request.
*/
enum OrmUpdateType {
INSERT{
public String toString() {
return "Insert";
}
},
UPDATE{
public String toString() {
return "Update";
}
},
DELETE{
public String toString() {
return "Delete";
}
},
UNKNOWN{
public String toString() {
return "Unknown";
}
};
}
/**
* Return the type of bean being updated.
*/
public Class<?> getBeanType();
/**
* Return the type of this - insert, update or delete.
*/
public OrmUpdateType getOrmUpdateType();
/**
* Return the name of the table being modified.
*/
public String getBaseTable();
/**
* Return the update statement. This could be either sql or an orm update with bean types and property names.
*/
public String getUpdateStatement();
/**
* Return the timeout in seconds.
*/
public int getTimeout();
/**
* Return true if the cache should be notified to invalidate objects.
*/
public boolean isNotifyCache();
/**
* Return the bind parameters.
*/
public BindParams getBindParams();
/**
* Set the generated sql used.
*/
public void setGeneratedSql(String sql);
}
@@ -1,75 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.sql.SQLException;
import java.util.Set;
import com.avaje.ebeaninternal.server.core.ConcurrencyMode;
import com.avaje.ebeaninternal.server.persist.dml.DmlHandler;
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
/**
* A plan for executing bean updates for a given set of changed properties.
* <p>
* This is a cachable plan with the purpose of being being able to skip some
* phases of the update bean processing.
* </p>
* <p>
* The plans are cached by the BeanDescriptors.
* </>
*
* @author rbygrave
*/
public interface SpiUpdatePlan {
/**
* Return true if the set clause has no columns.
* <p>
* Can occur when the only columns updated have a updatable=false in their
* deployment.
* </p>
*/
public boolean isEmptySetClause();
/**
* Bind given the request and bean. The bean could be the oldValues bean
* when binding a update or delete where clause with ALL concurrency mode.
*/
public void bindSet(DmlHandler bind, Object bean) throws SQLException;
/**
* Return the time this plan was created.
*/
public long getTimeCreated();
/**
* Return the time this plan was last used.
*/
public Long getTimeLastUsed();
/**
* Return the hash key for this plan.
*/
public Integer getKey();
/**
* Return the concurrency mode for this plan.
*/
public ConcurrencyMode getMode();
/**
* Return the update SQL statement.
*/
public String getSql();
/**
* Return the set of bindable update properties.
*/
public Bindable getSet();
/**
* Return the properties that where changed and should be included in the
* update statement.
*/
public Set<String> getProperties();
}
@@ -1,202 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.transaction.BeanDelta;
import com.avaje.ebeaninternal.server.transaction.DeleteByIdMap;
import com.avaje.ebeaninternal.server.transaction.IndexInvalidate;
/**
* Holds information for a transaction. There is one TransactionEvent instance
* per Transaction instance.
* <p>
* When the associated Transaction commits or rollback this information is sent
* to the TransactionEventManager.
* </p>
*/
public class TransactionEvent implements Serializable {
private static final Logger logger = Logger.getLogger(TransactionEvent.class.getName());
private static final long serialVersionUID = 7230903304106097120L;
/**
* Flag indicating this is a local transaction (not from another server in
* the cluster).
*/
private transient boolean local;
private boolean invalidateAll;
private TransactionEventTable eventTables;
private transient TransactionEventBeans eventBeans;
private transient List<BeanDelta> beanDeltas;
private transient DeleteByIdMap deleteByIdMap;
private transient Set<IndexInvalidate> indexInvalidations;
private transient Set<String> pauseIndexInvalidate;
/**
* Create the TransactionEvent, one per Transaction.
*/
public TransactionEvent() {
this.local = true;
}
/**
* Set this to true to invalidate all table dependent cached objects.
*/
public void setInvalidateAll(boolean isInvalidateAll) {
this.invalidateAll = isInvalidateAll;
}
/**
* Return true if all table states should be invalidated. This will cause
* all cached objects to be invalidated.
*/
public boolean isInvalidateAll() {
return invalidateAll;
}
/**
* Temporarily pause/ignore any index invalidation for this bean type.
*/
public void pauseIndexInvalidate(Class<?> beanType) {
if (pauseIndexInvalidate == null){
pauseIndexInvalidate = new HashSet<String>();
}
pauseIndexInvalidate.add(beanType.getName());
}
/**
* Resume listening for index invalidation for this bean type.
*/
public void resumeIndexInvalidate(Class<?> beanType) {
if (pauseIndexInvalidate != null){
pauseIndexInvalidate.remove(beanType.getName());
}
}
/**
* Add an IndexInvalidation notices to the transaction.
*/
public void addIndexInvalidate(IndexInvalidate indexEvent){
if (pauseIndexInvalidate != null && pauseIndexInvalidate.contains(indexEvent.getIndexName())){
logger.fine("--- IGNORE Invalidate on "+indexEvent.getIndexName());
return;
}
if (indexInvalidations == null){
indexInvalidations = new HashSet<IndexInvalidate>();
}
indexInvalidations.add(indexEvent);
}
public void addDeleteById(BeanDescriptor<?> desc, Object id){
if (deleteByIdMap == null){
deleteByIdMap = new DeleteByIdMap();
}
deleteByIdMap.add(desc, id);
}
public void addDeleteByIdList(BeanDescriptor<?> desc, List<Object> idList) {
if (deleteByIdMap == null) {
deleteByIdMap = new DeleteByIdMap();
}
deleteByIdMap.addList(desc, idList);
}
public DeleteByIdMap getDeleteByIdMap() {
return deleteByIdMap;
}
public void addBeanDelta(BeanDelta delta) {
if (beanDeltas == null) {
beanDeltas = new ArrayList<BeanDelta>();
}
beanDeltas.add(delta);
}
public List<BeanDelta> getBeanDeltas() {
return beanDeltas;
}
/**
* Return true if this was a local transaction. Returns false if this
* transaction originated on another server in the cluster.
*/
public boolean isLocal() {
return local;
}
/**
* For BeanListeners the requests they are interested in.
*/
public TransactionEventBeans getEventBeans() {
return eventBeans;
}
public TransactionEventTable getEventTables() {
return eventTables;
}
public Set<IndexInvalidate> getIndexInvalidations() {
return indexInvalidations;
}
public void add(String tableName, boolean inserts, boolean updates, boolean deletes){
if (eventTables == null){
eventTables = new TransactionEventTable();
}
eventTables.add(tableName, inserts, updates, deletes);
}
public void add(TransactionEventTable table){
if (eventTables == null){
eventTables = new TransactionEventTable();
}
eventTables.add(table);
}
/**
* Add a inserted updated or deleted bean to the event.
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify(this)){
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
}
eventBeans.add(request);
}
}
/**
* 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>
*/
public void notifyCache(){
if (eventBeans != null){
eventBeans.notifyCache();
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache();
}
}
}
@@ -1,40 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
/**
* Lists of inserted updated and deleted beans that have a BeanPersistListener.
* <p>
* These beans will be sent to the appropriate BeanListeners after a successful
* commit of the transaction.
* </p>
*/
public class TransactionEventBeans {
ArrayList<PersistRequestBean<?>> requests = new ArrayList<PersistRequestBean<?>>();
/**
* Return the list of PersistRequests that BeanListeners are interested in.
*/
public List<PersistRequestBean<?>> getRequests() {
return requests;
}
/**
* Add a bean for BeanListener notification.
*/
public void add(PersistRequestBean<?> request) {
requests.add(request);
}
public void notifyCache() {
for (int i = 0; i < requests.size(); i++) {
requests.get(i).notifyCache();
}
}
}
@@ -1,143 +0,0 @@
package com.avaje.ebeaninternal.api;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.avaje.ebean.event.BulkTableEvent;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
public final class TransactionEventTable implements Serializable {
private static final long serialVersionUID = 2236555729767483264L;
private final Map<String, TableIUD> map = new HashMap<String, TableIUD>();
public String toString() {
return "TransactionEventTable " + map.values();
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
for (TableIUD tableIud : map.values()) {
tableIud.writeBinaryMessage(msgList);
}
}
public void readBinaryMessage(DataInput dataInput) throws IOException {
TableIUD tableIud = TableIUD.readBinaryMessage(dataInput);
map.put(tableIud.getTableName(), tableIud);
}
public void add(TransactionEventTable table){
for (TableIUD iud : table.values()) {
add(iud);
}
}
public void add(String table, boolean insert, boolean update, boolean delete){
table = table.toUpperCase();
add(new TableIUD(table, insert, update, delete));
}
public void add(TableIUD newTableIUD){
TableIUD existingTableIUD = map.put(newTableIUD.getTableName(), newTableIUD);
if (existingTableIUD != null){
newTableIUD.add(existingTableIUD);
}
}
public boolean isEmpty() {
return map.isEmpty();
}
public Collection<TableIUD> values() {
return map.values();
}
public static class TableIUD implements Serializable, BulkTableEvent {
private static final long serialVersionUID = -1958317571064162089L;
private String table;
private boolean insert;
private boolean update;
private boolean delete;
private TableIUD(String table, boolean insert, boolean update, boolean delete){
this.table = table;
this.insert = insert;
this.update = update;
this.delete = delete;
}
public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException {
String table = dataInput.readUTF();
boolean insert = dataInput.readBoolean();
boolean update = dataInput.readBoolean();
boolean delete = dataInput.readBoolean();
return new TableIUD(table, insert, update, delete);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
BinaryMessage msg = new BinaryMessage(table.length()+10);
DataOutputStream os = msg.getOs();
os.writeInt(BinaryMessage.TYPE_TABLEIUD);
os.writeUTF(table);
os.writeBoolean(insert);
os.writeBoolean(update);
os.writeBoolean(delete);
msgList.add(msg);
}
public String toString() {
return "TableIUD "+table+" i:"+insert+" u:"+update+" d:"+delete;
}
private void add(TableIUD other) {
if (other.insert){
insert = true;
}
if (other.update){
update = true;
}
if (other.delete){
delete = true;
}
}
public String getTableName() {
return table;
}
public boolean isInsert() {
return insert;
}
public boolean isUpdate() {
return update;
}
public boolean isDelete() {
return delete;
}
public boolean isUpdateOrDelete() {
return update || delete;
}
}
}
@@ -1 +0,0 @@
package com.avaje.ebeaninternal.api;
@@ -1,254 +0,0 @@
package com.avaje.ebeaninternal.jdbc;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
public class ConnectionDelegator implements Connection {
private final Connection delegate;
public ConnectionDelegator(Connection delegate) {
this.delegate = delegate;
}
@Override
public void setSchema(String schema) throws SQLException {
delegate.setSchema(schema);
}
@Override
public String getSchema() throws SQLException {
return delegate.getSchema();
}
@Override
public void abort(Executor executor) throws SQLException {
delegate.abort(executor);
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
delegate.setNetworkTimeout(executor, milliseconds);
}
@Override
public int getNetworkTimeout() throws SQLException {
return delegate.getNetworkTimeout();
}
public Statement createStatement() throws SQLException {
return delegate.createStatement();
}
public PreparedStatement prepareStatement(String sql) throws SQLException {
return delegate.prepareStatement(sql);
}
public CallableStatement prepareCall(String sql) throws SQLException {
return delegate.prepareCall(sql);
}
public String nativeSQL(String sql) throws SQLException {
return delegate.nativeSQL(sql);
}
public void setAutoCommit(boolean autoCommit) throws SQLException {
delegate.setAutoCommit(autoCommit);
}
public boolean getAutoCommit() throws SQLException {
return delegate.getAutoCommit();
}
public void commit() throws SQLException {
delegate.commit();
}
public void rollback() throws SQLException {
delegate.rollback();
}
public void close() throws SQLException {
delegate.close();
}
public boolean isClosed() throws SQLException {
return delegate.isClosed();
}
public DatabaseMetaData getMetaData() throws SQLException {
return delegate.getMetaData();
}
public void setReadOnly(boolean readOnly) throws SQLException {
delegate.setReadOnly(readOnly);
}
public boolean isReadOnly() throws SQLException {
return delegate.isReadOnly();
}
public void setCatalog(String catalog) throws SQLException {
delegate.setCatalog(catalog);
}
public String getCatalog() throws SQLException {
return delegate.getCatalog();
}
public void setTransactionIsolation(int level) throws SQLException {
delegate.setTransactionIsolation(level);
}
public int getTransactionIsolation() throws SQLException {
return delegate.getTransactionIsolation();
}
public SQLWarning getWarnings() throws SQLException {
return delegate.getWarnings();
}
public void clearWarnings() throws SQLException {
delegate.clearWarnings();
}
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return delegate.createStatement(resultSetType, resultSetConcurrency);
}
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency);
}
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return delegate.prepareCall(sql, resultSetType, resultSetConcurrency);
}
public Map<String, Class<?>> getTypeMap() throws SQLException {
return delegate.getTypeMap();
}
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
delegate.setTypeMap(map);
}
public void setHoldability(int holdability) throws SQLException {
delegate.setHoldability(holdability);
}
public int getHoldability() throws SQLException {
return delegate.getHoldability();
}
public Savepoint setSavepoint() throws SQLException {
return delegate.setSavepoint();
}
public Savepoint setSavepoint(String name) throws SQLException {
return delegate.setSavepoint(name);
}
public void rollback(Savepoint savepoint) throws SQLException {
delegate.rollback(savepoint);
}
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
delegate.releaseSavepoint(savepoint);
}
public Statement createStatement(int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
return delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
}
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
return delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return delegate.prepareStatement(sql, autoGeneratedKeys);
}
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return delegate.prepareStatement(sql, columnIndexes);
}
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return delegate.prepareStatement(sql, columnNames);
}
public Clob createClob() throws SQLException {
return delegate.createClob();
}
public Blob createBlob() throws SQLException {
return delegate.createBlob();
}
public NClob createNClob() throws SQLException {
return delegate.createNClob();
}
public SQLXML createSQLXML() throws SQLException {
return delegate.createSQLXML();
}
public boolean isValid(int timeout) throws SQLException {
return delegate.isValid(timeout);
}
public void setClientInfo(String name, String value) throws SQLClientInfoException {
delegate.setClientInfo(name, value);
}
public void setClientInfo(Properties properties) throws SQLClientInfoException {
delegate.setClientInfo(properties);
}
public String getClientInfo(String name) throws SQLException {
return delegate.getClientInfo(name);
}
public Properties getClientInfo() throws SQLException {
return delegate.getClientInfo();
}
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return delegate.createArrayOf(typeName, elements);
}
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return delegate.createStruct(typeName, attributes);
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return delegate.unwrap(iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return delegate.isWrapperFor(iface);
}
}
@@ -1,435 +0,0 @@
package com.avaje.ebeaninternal.jdbc;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
public class PreparedStatementDelegator implements PreparedStatement {
private final PreparedStatement delegate;
public PreparedStatementDelegator(PreparedStatement delegate) {
this.delegate = delegate;
}
@Override
public void closeOnCompletion() throws SQLException {
delegate.closeOnCompletion();
}
@Override
public boolean isCloseOnCompletion() throws SQLException {
return delegate.isCloseOnCompletion();
}
public ResultSet executeQuery() throws SQLException {
return delegate.executeQuery();
}
public int executeUpdate() throws SQLException {
return delegate.executeUpdate();
}
public void setNull(int parameterIndex, int sqlType) throws SQLException {
delegate.setNull(parameterIndex, sqlType);
}
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
delegate.setBoolean(parameterIndex, x);
}
public void setByte(int parameterIndex, byte x) throws SQLException {
delegate.setByte(parameterIndex, x);
}
public void setShort(int parameterIndex, short x) throws SQLException {
delegate.setShort(parameterIndex, x);
}
public void setInt(int parameterIndex, int x) throws SQLException {
delegate.setInt(parameterIndex, x);
}
public void setLong(int parameterIndex, long x) throws SQLException {
delegate.setLong(parameterIndex, x);
}
public void setFloat(int parameterIndex, float x) throws SQLException {
delegate.setFloat(parameterIndex, x);
}
public void setDouble(int parameterIndex, double x) throws SQLException {
delegate.setDouble(parameterIndex, x);
}
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
delegate.setBigDecimal(parameterIndex, x);
}
public void setString(int parameterIndex, String x) throws SQLException {
delegate.setString(parameterIndex, x);
}
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
delegate.setBytes(parameterIndex, x);
}
public void setDate(int parameterIndex, Date x) throws SQLException {
delegate.setDate(parameterIndex, x);
}
public void setTime(int parameterIndex, Time x) throws SQLException {
delegate.setTime(parameterIndex, x);
}
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
delegate.setTimestamp(parameterIndex, x);
}
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
delegate.setAsciiStream(parameterIndex, x, length);
}
@SuppressWarnings("deprecation")
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
delegate.setUnicodeStream(parameterIndex, x, length);
}
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
delegate.setBinaryStream(parameterIndex, x, length);
}
public void clearParameters() throws SQLException {
delegate.clearParameters();
}
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
delegate.setObject(parameterIndex, x, targetSqlType);
}
public void setObject(int parameterIndex, Object x) throws SQLException {
delegate.setObject(parameterIndex, x);
}
public boolean execute() throws SQLException {
return delegate.execute();
}
public void addBatch() throws SQLException {
delegate.addBatch();
}
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
delegate.setCharacterStream(parameterIndex, reader, length);
}
public void setRef(int parameterIndex, Ref x) throws SQLException {
delegate.setRef(parameterIndex, x);
}
public void setBlob(int parameterIndex, Blob x) throws SQLException {
delegate.setBlob(parameterIndex, x);
}
public void setClob(int parameterIndex, Clob x) throws SQLException {
delegate.setClob(parameterIndex, x);
}
public void setArray(int parameterIndex, Array x) throws SQLException {
delegate.setArray(parameterIndex, x);
}
public ResultSetMetaData getMetaData() throws SQLException {
return delegate.getMetaData();
}
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
delegate.setDate(parameterIndex, x, cal);
}
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
delegate.setTime(parameterIndex, x, cal);
}
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
delegate.setTimestamp(parameterIndex, x, cal);
}
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
delegate.setNull(parameterIndex, sqlType, typeName);
}
public void setURL(int parameterIndex, URL x) throws SQLException {
delegate.setURL(parameterIndex, x);
}
public ParameterMetaData getParameterMetaData() throws SQLException {
return delegate.getParameterMetaData();
}
public void setRowId(int parameterIndex, RowId x) throws SQLException {
delegate.setRowId(parameterIndex, x);
}
public void setNString(int parameterIndex, String value) throws SQLException {
delegate.setNString(parameterIndex, value);
}
public void setNCharacterStream(int parameterIndex, Reader value, long length)
throws SQLException {
delegate.setNCharacterStream(parameterIndex, value, length);
}
public void setNClob(int parameterIndex, NClob value) throws SQLException {
delegate.setNClob(parameterIndex, value);
}
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
delegate.setClob(parameterIndex, reader, length);
}
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
delegate.setBlob(parameterIndex, inputStream, length);
}
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
delegate.setNClob(parameterIndex, reader, length);
}
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
delegate.setSQLXML(parameterIndex, xmlObject);
}
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)
throws SQLException {
delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength);
}
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
delegate.setAsciiStream(parameterIndex, x, length);
}
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
delegate.setBinaryStream(parameterIndex, x, length);
}
public void setCharacterStream(int parameterIndex, Reader reader, long length)
throws SQLException {
delegate.setCharacterStream(parameterIndex, reader, length);
}
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
delegate.setAsciiStream(parameterIndex, x);
}
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
delegate.setBinaryStream(parameterIndex, x);
}
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
delegate.setCharacterStream(parameterIndex, reader);
}
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
delegate.setNCharacterStream(parameterIndex, value);
}
public void setClob(int parameterIndex, Reader reader) throws SQLException {
delegate.setClob(parameterIndex, reader);
}
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
delegate.setBlob(parameterIndex, inputStream);
}
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
delegate.setNClob(parameterIndex, reader);
}
public ResultSet executeQuery(String sql) throws SQLException {
return delegate.executeQuery(sql);
}
public int executeUpdate(String sql) throws SQLException {
return delegate.executeUpdate(sql);
}
public void close() throws SQLException {
delegate.close();
}
public int getMaxFieldSize() throws SQLException {
return delegate.getMaxFieldSize();
}
public void setMaxFieldSize(int max) throws SQLException {
delegate.setMaxFieldSize(max);
}
public int getMaxRows() throws SQLException {
return delegate.getMaxRows();
}
public void setMaxRows(int max) throws SQLException {
delegate.setMaxRows(max);
}
public void setEscapeProcessing(boolean enable) throws SQLException {
delegate.setEscapeProcessing(enable);
}
public int getQueryTimeout() throws SQLException {
return delegate.getQueryTimeout();
}
public void setQueryTimeout(int seconds) throws SQLException {
delegate.setQueryTimeout(seconds);
}
public void cancel() throws SQLException {
delegate.cancel();
}
public SQLWarning getWarnings() throws SQLException {
return delegate.getWarnings();
}
public void clearWarnings() throws SQLException {
delegate.clearWarnings();
}
public void setCursorName(String name) throws SQLException {
delegate.setCursorName(name);
}
public boolean execute(String sql) throws SQLException {
return delegate.execute(sql);
}
public ResultSet getResultSet() throws SQLException {
return delegate.getResultSet();
}
public int getUpdateCount() throws SQLException {
return delegate.getUpdateCount();
}
public boolean getMoreResults() throws SQLException {
return delegate.getMoreResults();
}
public void setFetchDirection(int direction) throws SQLException {
delegate.setFetchDirection(direction);
}
public int getFetchDirection() throws SQLException {
return delegate.getFetchDirection();
}
public void setFetchSize(int rows) throws SQLException {
delegate.setFetchSize(rows);
}
public int getFetchSize() throws SQLException {
return delegate.getFetchSize();
}
public int getResultSetConcurrency() throws SQLException {
return delegate.getResultSetConcurrency();
}
public int getResultSetType() throws SQLException {
return delegate.getResultSetType();
}
public void addBatch(String sql) throws SQLException {
delegate.addBatch(sql);
}
public void clearBatch() throws SQLException {
delegate.clearBatch();
}
public int[] executeBatch() throws SQLException {
return delegate.executeBatch();
}
public Connection getConnection() throws SQLException {
return delegate.getConnection();
}
public boolean getMoreResults(int current) throws SQLException {
return delegate.getMoreResults(current);
}
public ResultSet getGeneratedKeys() throws SQLException {
return delegate.getGeneratedKeys();
}
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
return delegate.executeUpdate(sql, autoGeneratedKeys);
}
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
return delegate.executeUpdate(sql, columnIndexes);
}
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
return delegate.executeUpdate(sql, columnNames);
}
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
return delegate.execute(sql, autoGeneratedKeys);
}
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
return delegate.execute(sql, columnIndexes);
}
public boolean execute(String sql, String[] columnNames) throws SQLException {
return delegate.execute(sql, columnNames);
}
public int getResultSetHoldability() throws SQLException {
return delegate.getResultSetHoldability();
}
public boolean isClosed() throws SQLException {
return delegate.isClosed();
}
public void setPoolable(boolean poolable) throws SQLException {
delegate.setPoolable(poolable);
}
public boolean isPoolable() throws SQLException {
return delegate.isPoolable();
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return delegate.unwrap(iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return delegate.isWrapperFor(iface);
}
}
@@ -1,244 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.util.Iterator;
import com.avaje.ebean.Query;
import com.avaje.ebean.bean.NodeUsageListener;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.AutofetchMode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
/**
* Collects and manages the the profile information.
* <p>
* The profile information is periodically converted into "tuned query details" -
* which is used to automatically tune the queries that use autoFetch.
* </p>
* <p>
* The "tuned query details" effectively are part of the query that has the
* select() and join() information (but not the where clause, order by, limits
* etc). These are applied to the query when tuneQuery() is called.
* </p>
*/
public interface AutoFetchManager extends NodeUsageListener {
/**
* Set the owning ebean server.
*/
public void setOwner(SpiEbeanServer server, ServerConfig serverConfig);
/**
* Clear the query execution statistics.
*/
public void clearQueryStatistics();
/**
* Clear all the tuned query info.
* <p>
* Should only need do this for testing and playing around.
* </p>
*/
public int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
*/
public int clearProfilingInfo();
/**
* On shutdown fire garbage collection and collect statistics. Note that
* usually we add a little delay (100 milliseconds) to give the garbage
* collector plenty of time to do its thing and collect the profile
* information.
*/
public void shutdown();
/**
* Return the current tuned fetch information for a given queryPoint key.
*/
public TunedQueryInfo getTunedQueryInfo(String queryPointKey);
/**
* Return the current Statistics for a given queryPoint key.
*/
public Statistics getStatistics(String queryPointKey);
/**
* Iterate the tuned fetch info.
* <p>
* This should be a read only iteration.
* </p>
*/
public Iterator<TunedQueryInfo> iterateTunedQueryInfo();
/**
* Iterate the node usage statistics.
* <p>
* This should be a read only iteration.
* </p>
*/
public Iterator<Statistics> iterateStatistics();
/**
* Return true if profiling is enabled.
*/
public boolean isProfiling();
/**
* Set to true to enable profiling.
* <p>
* We rely on garbage collection to collect the profiling information. This
* means there is a unknown delay between when a query is executed and when
* we actually collect the usage profile information.
* </p>
* <p>
* Due to this garbage collection delay, when turning off profiling while
* the application is running you should consider calling
* collectUsageViaGC() <em>BEFORE</em> setProfiling(false). This hints to
* the JVM to perform garbage collection, and hopefully collects the
* profiling information.
* </p>
*/
public void setProfiling(boolean enable);
/**
* Return true if automatic query tuning is enabled.
*/
public boolean isQueryTuning();
/**
* Set to true to enable automatic query tuning.
*/
public void setQueryTuning(boolean enable);
/**
* This controls whether autoFetch is used when it has not been explicitly
* set on a query via {@link Query#setAutoFetch(boolean)}.
*/
public AutofetchMode getMode();
/**
* Set the auto fetch mode used when a query has not had
* {@link Query#setAutoFetch(boolean)}.
*/
public void setMode(AutofetchMode Mode);
/**
* Return the profiling rate (int between 0 and 100).
*/
public double getProfilingRate();
/**
* Set the profiling rate (int between 0 and 100).
*/
public void setProfilingRate(double rate);
/**
* Return the max number of queries profiled (per query point).
* <p>
* The number of queries profiled is collected per query point. Once a query
* point has profiled this number of queries it does not profile any more.
* </p>
*/
public int getProfilingBase();
/**
* Set a max number of queries to profile per query point.
* <p>
* This number should provide a level of confidence that no more profiling
* is required for this query point.
* </p>
*/
public void setProfilingBase(int profilingMax);
/**
* Return the minimum number of queries profiled before autoFetch will start
* automatically tuning the queries.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
public int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before
* autoFetch will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
public void setProfilingMin(int autoFetchMinThreshold);
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather the usage profiling information.
*/
public String collectUsageViaGC(long waitMillis);
/**
* This will take the current profiling information and update the "tuned
* query detail".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
* <p>
* This returns a string summary of the updates that occurred.
* </p>
*/
public String updateTunedQueryInfo();
/**
* Called when a query thinks it should be automatically tuned by autoFetch.
* <p>
* This internally checks that autoFetch is enabled, there is a "tuned query
* detail" to tune the query with and that the autoFetchMinThreshold has
* been reached.
* </p>
* <p>
* This will also determine if the query should be profiled.
* </p>
*/
public boolean tuneQuery(SpiQuery<?> query);
/**
* Collect query profiling information.
* <p>
* This is for the original query as well as any subsequent lazy loading
* queries that are required as the object graph is traversed.
* </p>
*
* @param node
* the node path in the object graph.
* @param beans
* the number of beans loaded by the query.
* @param micros
* the query executing time in microseconds
*/
public void collectQueryInfo(ObjectGraphNode node, int beans, int micros);
/**
* Return the number of queries tuned by AutoFetch.
*/
public int getTotalTunedQueryCount();
/**
* Return the size of the TuneQuery map.
*/
public int getTotalTunedQuerySize();
/**
* Return the size of the profile map.
*/
public int getTotalProfileSize();
}
@@ -1,97 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.resource.ResourceManager;
public class AutoFetchManagerFactory {
private static final Logger logger = Logger.getLogger(AutoFetchManagerFactory.class.getName());
public static AutoFetchManager create(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) {
AutoFetchManagerFactory me = new AutoFetchManagerFactory();
return me.createAutoFetchManager(server, serverConfig, resourceManager);
}
private AutoFetchManager createAutoFetchManager(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager){
AutoFetchManager manager = createAutoFetchManager(server.getName(), resourceManager);
manager.setOwner(server, serverConfig);
return manager;
}
private AutoFetchManager createAutoFetchManager(String serverName, ResourceManager resourceManager) {
File autoFetchFile = getAutoFetchFile(serverName, resourceManager);
AutoFetchManager autoFetchManager = null;
boolean readFile = GlobalProperties.getBoolean("autofetch.readfromfile", true);
if (readFile) {
autoFetchManager = deserializeAutoFetch(autoFetchFile);
}
if (autoFetchManager == null) {
// not deserialized from file so create as empty
// It will be populated automatically by querying the
// database meta data
autoFetchManager = new DefaultAutoFetchManager(autoFetchFile.getAbsolutePath());
}
return autoFetchManager;
}
private AutoFetchManager deserializeAutoFetch(File autoFetchFile) {
try {
if (!autoFetchFile.exists()) {
return null;
}
FileInputStream fi = new FileInputStream(autoFetchFile);
ObjectInputStream ois = new ObjectInputStream(fi);
AutoFetchManager profListener = (AutoFetchManager) ois.readObject();
logger.info("AutoFetch deserialized from file ["+autoFetchFile.getAbsolutePath()+"]");
return profListener;
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error loading autofetch file "+autoFetchFile.getAbsolutePath(), ex);
return null;
}
}
/**
* Return the file name of the autoFetch meta data.
*/
private File getAutoFetchFile(String serverName, ResourceManager resourceManager) {
String fileName = ".ebean."+serverName+".autofetch";
File dir = resourceManager.getAutofetchDirectory();
if (!dir.exists()) {
// automatically create the directory if it does not exist.
// this is probably a fairly reasonable thing to do
if (!dir.mkdirs()) {
String m = "Unable to create directory [" + dir + "] for autofetch file ["+ fileName + "]";
throw new PersistenceException(m);
}
}
return new File(dir, fileName);
}
}
@@ -1,582 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.config.AutofetchConfig;
import com.avaje.ebean.config.AutofetchMode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
/**
* The manager of all the usage/query statistics as well as the tuned fetch
* information.
*/
public class DefaultAutoFetchManager implements AutoFetchManager, Serializable {
private static final long serialVersionUID = -6826119882781771722L;
private final String statisticsMonitor = new String();
private final String fileName;
/**
* Map of the usage and query statistics gathered.
*/
private Map<String, Statistics> statisticsMap = new ConcurrentHashMap<String, Statistics>();
/**
* Map of the tuned query details per profile query point.
*/
private Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<String, TunedQueryInfo>();
private transient long defaultGarbageCollectionWait = 100;
/**
* Left without synchronized for now.
*/
private transient int tunedQueryCount;
/**
* Converted from a 0-100 int to a double. Effectively a percentage rate at
* which to collect profiling information.
*/
private transient double profilingRate = 0.1d;
private transient int profilingBase = 10;
private transient int profilingMin = 1;
private transient boolean profiling;
private transient boolean queryTuning;
private transient boolean queryTuningAddVersion;
private transient AutofetchMode mode;
private transient boolean useFileLogging;
/**
* Server that owns this Profile Listener.
*/
private transient SpiEbeanServer server;
/**
* The logger.
*/
private transient DefaultAutoFetchManagerLogging logging;
public DefaultAutoFetchManager(String fileName) {
this.fileName = fileName;
}
/**
* Set up this profile listener before it is active.
*/
public void setOwner(SpiEbeanServer server, ServerConfig serverConfig) {
this.server = server;
this.logging = new DefaultAutoFetchManagerLogging(serverConfig, this);
AutofetchConfig autofetchConfig = serverConfig.getAutofetchConfig();
useFileLogging = autofetchConfig.isUseFileLogging();
queryTuning = autofetchConfig.isQueryTuning();
queryTuningAddVersion = autofetchConfig.isQueryTuningAddVersion();
profiling = autofetchConfig.isProfiling();
profilingMin = autofetchConfig.getProfilingMin();
profilingBase = autofetchConfig.getProfilingBase();
setProfilingRate(autofetchConfig.getProfilingRate());
defaultGarbageCollectionWait = (long) autofetchConfig.getGarbageCollectionWait();
// determine the mode to use when Query.setAutoFetch() was
// not explicitly set
mode = autofetchConfig.getMode();
if (profiling || queryTuning) {
// log the guts of the autoFetch setup
String msg = "AutoFetch queryTuning[" + queryTuning + "] profiling[" + profiling
+ "] mode[" + mode + "] profiling rate[" + profilingRate
+ "] min[" + profilingMin + "] base[" + profilingBase + "]";
logging.logToJavaLogger(msg);
}
}
public void clearQueryStatistics() {
server.clearQueryStatistics();
}
/**
* Return the number of queries tuned by AutoFetch.
*/
public int getTotalTunedQueryCount(){
return tunedQueryCount;
}
/**
* Return the size of the TuneQuery map.
*/
public int getTotalTunedQuerySize(){
return tunedQueryInfoMap.size();
}
/**
* Return the size of the profile map.
*/
public int getTotalProfileSize(){
return statisticsMap.size();
}
public int clearTunedQueryInfo() {
// reset the rough count as well
tunedQueryCount = 0;
// clear the map...
int size = tunedQueryInfoMap.size();
tunedQueryInfoMap.clear();
return size;
}
public int clearProfilingInfo() {
int size = statisticsMap.size();
statisticsMap.clear();
return size;
}
public void serialize() {
File autoFetchFile = new File(fileName);
try {
FileOutputStream fout = new FileOutputStream(autoFetchFile);
ObjectOutputStream oout = new ObjectOutputStream(fout);
oout.writeObject(this);
oout.flush();
oout.close();
} catch (Exception e) {
String msg = "Error serializing autofetch file";
logging.logError(Level.SEVERE, msg, e);
}
}
/**
* Return the current Tuned query info for a given origin key.
*/
public TunedQueryInfo getTunedQueryInfo(String originKey) {
return tunedQueryInfoMap.get(originKey);
}
/**
* Return the current Statistics for a given originKey key.
*/
public Statistics getStatistics(String originKey) {
return statisticsMap.get(originKey);
}
public Iterator<TunedQueryInfo> iterateTunedQueryInfo() {
return tunedQueryInfoMap.values().iterator();
}
public Iterator<Statistics> iterateStatistics() {
return statisticsMap.values().iterator();
}
public boolean isProfiling() {
return profiling;
}
/**
* When the application is running, BEFORE turning off profiling you
* probably should call collectUsageViaGC() as there is a delay (waiting for
* garbage collection) collecting usage profiling information.
*/
public void setProfiling(boolean profiling) {
this.profiling = profiling;
}
public boolean isQueryTuning() {
return queryTuning;
}
public void setQueryTuning(boolean queryTuning) {
this.queryTuning = queryTuning;
}
public double getProfilingRate() {
return profilingRate;
}
public AutofetchMode getMode() {
return mode;
}
public void setMode(AutofetchMode mode) {
this.mode = mode;
}
public void setProfilingRate(double rate) {
if (rate < 0) {
rate = 0d;
} else if (rate > 1) {
rate = 1d;
}
profilingRate = rate;
}
public int getProfilingBase() {
return profilingBase;
}
public void setProfilingBase(int profilingBase) {
this.profilingBase = profilingBase;
}
public int getProfilingMin() {
return profilingMin;
}
public void setProfilingMin(int profilingMin) {
this.profilingMin = profilingMin;
}
/**
* Shutdown the listener.
* <p>
* We should try to collect the usage statistics by calling a System.gc().
* This is necessary for use with short lived applications where garbage
* collection may not otherwise occur at all.
* </p>
*/
public void shutdown() {
if (useFileLogging) {
collectUsageViaGC(-1);
serialize();
}
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
* Really only want to do this sparingly but useful just prior to shutdown
* for short run application where garbage collection may otherwise not
* occur at all.
* </p>
* <p>
* waitMillis will do a thread sleep to give the garbage collection a little
* time to do its thing assuming we are shutting down the VM.
* </p>
* <p>
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
* defaults to 100 milliseconds.
* </p>
*/
public String collectUsageViaGC(long waitMillis) {
System.gc();
try {
if (waitMillis < 0) {
waitMillis = defaultGarbageCollectionWait;
}
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
String msg = "Error while sleeping after System.gc() request.";
logging.logError(Level.SEVERE, msg, e);
return msg;
}
return updateTunedQueryInfo();
}
/**
* Update the tuned fetch plans from the current usage information.
*/
public String updateTunedQueryInfo() {
if (!profiling) {
// we are not collecting any profiling information at
// the moment so don't try updating the tuned query plans.
return "Not profiling";
}
synchronized (statisticsMonitor) {
Counters counters = new Counters();
Iterator<Statistics> it = statisticsMap.values().iterator();
while (it.hasNext()) {
Statistics queryPointStatistics = it.next();
if (!queryPointStatistics.hasUsage()){
// no usage statistics collected yet...
counters.incrementNoUsage();
} else {
updateTunedQueryFromUsage(counters, queryPointStatistics);
}
}
String summaryInfo = counters.toString();
if (counters.isInteresting()){
// only log it if its interesting
logging.logSummary(summaryInfo);
}
return summaryInfo;
}
}
private static class Counters {
int newPlan;
int modified;
int unchanged;
int noUsage;
void incrementNoUsage(){
noUsage++;
}
void incrementNew(){
newPlan++;
}
void incrementModified(){
modified++;
}
void incrementUnchanged(){
unchanged++;
}
boolean isInteresting() {
return newPlan > 0 || modified > 0;
}
public String toString() {
return "new["+newPlan+"] modified["+modified+"] unchanged["+unchanged+"] nousage["+noUsage+"]";
}
}
private void updateTunedQueryFromUsage(Counters counters, Statistics statistics) {
ObjectGraphOrigin queryPoint = statistics.getOrigin();
String beanType = queryPoint.getBeanType();
try {
Class<?> beanClass = ClassUtil.forName(beanType, this.getClass());
BeanDescriptor<?> beanDescriptor = server.getBeanDescriptor(beanClass);
if (beanDescriptor == null){
// previously was an entity but not longer
} else {
// Determine the fetch plan from the latest statistics.
// Use this to compare with current "tuned fetch plan".
OrmQueryDetail newFetchDetail = statistics.buildTunedFetch(beanDescriptor);
// get the current tuned fetch info...
TunedQueryInfo currentFetch = tunedQueryInfoMap.get(queryPoint.getKey());
if (currentFetch == null) {
// its a new fetch plan, add it.
counters.incrementNew();
currentFetch = statistics.createTunedFetch(newFetchDetail);
logging.logNew(currentFetch);
tunedQueryInfoMap.put(queryPoint.getKey(), currentFetch);
} else if (!currentFetch.isSame(newFetchDetail)) {
// the fetch plan has changed, update it.
counters.incrementModified();
logging.logChanged(currentFetch, newFetchDetail);
currentFetch.setTunedDetail(newFetchDetail);
} else {
// the fetch plan has not changed...
counters.incrementUnchanged();
}
currentFetch.setProfileCount(statistics.getCounter());
}
} catch (ClassNotFoundException e) {
// expected after renaming/moving an entity bean
String msg = e.toString()+" updating autoFetch tuned query for " + beanType
+". It isLikely this bean has been renamed or moved";
logging.logError(Level.INFO, msg, null);
statisticsMap.remove(statistics.getOrigin().getKey());
}
}
/**
* Return true if we should try to use autoFetch for this query.
*/
private boolean useAutoFetch(SpiQuery<?> query) {
if (query.isLoadBeanCache()){
// when loading the cache don't tune the query
// as we want full objects loaded into the cache
return false;
}
Boolean autoFetch = query.isAutofetch();
if (autoFetch != null) {
// explicitly set...
return autoFetch.booleanValue();
} else {
// determine using implicit mode...
switch (mode) {
case DEFAULT_ON:
return true;
case DEFAULT_OFF:
return false;
case DEFAULT_ONIFEMPTY:
return query.isDetailEmpty();
default:
throw new PersistenceException("Invalid autoFetchMode " + mode);
}
}
}
/**
* Auto tune the query and enable profiling.
*/
public boolean tuneQuery(SpiQuery<?> query) {
if (!queryTuning && !profiling) {
return false;
}
if (!useAutoFetch(query)) {
// not using autoFetch for this query
return false;
}
ObjectGraphNode parentAutoFetchNode = query.getParentNode();
if (parentAutoFetchNode != null) {
// This is a +lazy/+query query with profiling on.
// We continue to collect the profiling information.
query.setAutoFetchManager(this);
return true;
}
// create a query point to identify the query
CallStack stack = server.createCallStack();
ObjectGraphNode origin = query.setOrigin(stack);
// get current "tuned fetch" for this query point
TunedQueryInfo tunedFetch = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey());
// get the number of times we have collected profiling information
int profileCount = tunedFetch == null ? 0 : tunedFetch.getProfileCount();
if (profiling) {
// we want more profiling information?
if (tunedFetch == null) {
query.setAutoFetchManager(this);
} else if (profileCount < profilingBase) {
query.setAutoFetchManager(this);
} else if (tunedFetch.isPercentageProfile(profilingRate)) {
query.setAutoFetchManager(this);
}
}
if (queryTuning) {
if (tunedFetch != null && profileCount >= profilingMin) {
// deemed to have enough profiling
// information for automatic tuning
if (tunedFetch.autoFetchTune(query)){
// tunedQueryCount++ not thread-safe, could use AtomicInteger.
// But I'm happy if this statistic is a little wrong
// and this is a VERY HOT method
tunedQueryCount++;
}
return true;
}
}
return false;
}
/**
* Gather query execution statistics. This could either be the originating
* query in which case the parentNode will be null, or a lazy loading query
* resulting from traversal of the object graph.
*/
public void collectQueryInfo(ObjectGraphNode node, int beans, int micros) {
if (node != null){
ObjectGraphOrigin origin = node.getOriginQueryPoint();
if (origin != null){
Statistics stats = getQueryPointStats(origin);
stats.collectQueryInfo(node, beans, micros);
}
}
}
/**
* Collect usage statistics from a node in the object graph.
* <p>
* This is sent to use from a EntityBeanIntercept when the finalise method
* is called on the bean.
* </p>
*/
public void collectNodeUsage(NodeUsageCollector usageCollector) {
ObjectGraphOrigin origin = usageCollector.getNode().getOriginQueryPoint();
Statistics stats = getQueryPointStats(origin);
if (logging.isTraceUsageCollection()){
System.out.println("... NodeUsageCollector "+usageCollector);
}
stats.collectUsageInfo(usageCollector);
if (logging.isTraceUsageCollection()){
System.out.println("stats\n"+stats);
}
}
private Statistics getQueryPointStats(ObjectGraphOrigin originQueryPoint) {
synchronized (statisticsMonitor) {
Statistics stats = statisticsMap.get(originQueryPoint.getKey());
if (stats == null) {
stats = new Statistics(originQueryPoint, queryTuningAddVersion);
statisticsMap.put(originQueryPoint.getKey(), stats);
}
return stats;
}
}
public String toString() {
synchronized (statisticsMonitor) {
return statisticsMap.values().toString();
}
}
}
@@ -1,112 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebean.config.AutofetchConfig;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.lib.BackgroundThread;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.transaction.log.SimpleLogger;
/**
* Handles the logging aspects for the DefaultAutoFetchListener.
* <p>
* Note that java util logging loggers generally should not be serialised and
* that is one of the main reasons for pulling out the logging to this class.
* </p>
*/
public class DefaultAutoFetchManagerLogging {
private static final Logger logger = Logger.getLogger(DefaultAutoFetchManagerLogging.class.getName());
private final SimpleLogger fileLogger;
private final DefaultAutoFetchManager manager;
private final boolean useFileLogger;
private final boolean traceUsageCollection;
public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, DefaultAutoFetchManager profileListener) {
this.manager = profileListener;
AutofetchConfig autofetchConfig = serverConfig.getAutofetchConfig();
traceUsageCollection = GlobalProperties.getBoolean("ebean.autofetch.traceUsageCollection", false);
useFileLogger = autofetchConfig.isUseFileLogging();
if (!useFileLogger) {
fileLogger = null;
} else {
// a separate log file just like the transaction logging
// for putting the profiling log messages. The benefit is that
// this doesn't pollute the main log with heaps of messages.
String baseDir = serverConfig.getLoggingDirectoryWithEval();
fileLogger = new SimpleLogger(baseDir, "autofetch", true, "csv");
}
int updateFreqInSecs = autofetchConfig.getProfileUpdateFrequency();
BackgroundThread.add(updateFreqInSecs, new UpdateProfile());
}
private final class UpdateProfile implements Runnable {
public void run() {
manager.updateTunedQueryInfo();
}
}
public void logError(Level level, String msg, Throwable e) {
if (useFileLogger) {
String errMsg = e == null ? "" : e.getMessage();
fileLogger.log("\"Error\",\"" + msg+" "+errMsg+"\",,,,");
}
logger.log(level, msg, e);
}
public void logToJavaLogger(String msg) {
logger.info(msg);
}
public void logSummary(String summaryInfo) {
String msg = "\"Summary\",\""+summaryInfo+"\",,,,";
if (useFileLogger) {
fileLogger.log(msg);
}
logger.fine(msg);
}
public void logChanged(TunedQueryInfo tunedFetch, OrmQueryDetail newQueryDetail) {
String msg = tunedFetch.getLogOutput(newQueryDetail);
if (useFileLogger) {
fileLogger.log(msg);
} else {
logger.fine(msg);
}
}
public void logNew(TunedQueryInfo tunedFetch) {
String msg = tunedFetch.getLogOutput(null);
if (useFileLogger) {
fileLogger.log(msg);
} else {
logger.fine(msg);
}
}
public boolean isTraceUsageCollection() {
return traceUsageCollection;
}
}
@@ -1,207 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.meta.MetaAutoFetchStatistic;
import com.avaje.ebean.meta.MetaAutoFetchStatistic.NodeUsageStats;
import com.avaje.ebean.meta.MetaAutoFetchStatistic.QueryStats;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.text.PathProperties.Props;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
public class Statistics implements Serializable {
private static final long serialVersionUID = -5586783791097230766L;
private final ObjectGraphOrigin origin;
private final boolean queryTuningAddVersion;
private int counter;
private Map<String, StatisticsQuery> queryStatsMap = new LinkedHashMap<String, StatisticsQuery>();
private Map<String, StatisticsNodeUsage> nodeUsageMap = new LinkedHashMap<String, StatisticsNodeUsage>();
private final String monitor = new String();
public Statistics(ObjectGraphOrigin origin, boolean queryTuningAddVersion) {
this.origin = origin;
this.queryTuningAddVersion = queryTuningAddVersion;
}
public ObjectGraphOrigin getOrigin() {
return origin;
}
public TunedQueryInfo createTunedFetch(OrmQueryDetail newFetchDetail) {
synchronized (monitor) {
// NB: create a copy of queryPoint allowing garbage
// collection of source...
return new TunedQueryInfo(origin, newFetchDetail, counter);
}
}
public MetaAutoFetchStatistic createPublicMeta() {
synchronized (monitor) {
StatisticsQuery[] sourceQueryStats = queryStatsMap.values().toArray(new StatisticsQuery[queryStatsMap.size()]);
List<QueryStats> destQueryStats = new ArrayList<QueryStats>(sourceQueryStats.length);
// copy the query statistics
for (int i = 0; i < sourceQueryStats.length; i++) {
destQueryStats.add(sourceQueryStats[i].createPublicMeta());
}
StatisticsNodeUsage[] sourceNodeUsage = nodeUsageMap.values().toArray(new StatisticsNodeUsage[nodeUsageMap.size()]);
List<NodeUsageStats> destNodeUsage = new ArrayList<NodeUsageStats>(sourceNodeUsage.length);
// copy the node usage statistics
for (int i = 0; i < sourceNodeUsage.length; i++) {
destNodeUsage.add(sourceNodeUsage[i].createPublicMeta());
}
return new MetaAutoFetchStatistic(origin, counter, destQueryStats, destNodeUsage);
}
}
/**
* Return the number of times the root query has executed.
* <p>
* This tells us how much profiling we have done for this query.
* For example, after 100 times we may stop collecting more profiling info.
* </p>
*/
public int getCounter() {
return counter;
}
/**
* Return true if this has usage statistics.
*/
public boolean hasUsage() {
synchronized (monitor) {
return !nodeUsageMap.isEmpty();
}
}
public OrmQueryDetail buildTunedFetch(BeanDescriptor<?> rootDesc){
synchronized (monitor) {
if (nodeUsageMap.isEmpty()){
return null;
}
PathProperties pathProps = new PathProperties();
Iterator<StatisticsNodeUsage> it = nodeUsageMap.values().iterator();
while (it.hasNext()) {
StatisticsNodeUsage statsNode = it.next();
statsNode.buildTunedFetch(pathProps, rootDesc);
}
OrmQueryDetail detail = new OrmQueryDetail();
Collection<Props> pathProperties = pathProps.getPathProps();
for (Props props : pathProperties) {
if (!props.isEmpty()){
detail.addFetch(props.getPath(), props.getPropertiesAsString(), null);
}
}
detail.sortFetchPaths(rootDesc);
return detail;
}
}
public void collectQueryInfo(ObjectGraphNode node, int beansLoaded, int micros) {
synchronized (monitor) {
String key = node.getPath();
if (key == null){
key = "";
// this is basically the number of times the root query
// has executed which gives us an indication of how
// much profiling information we have gathered.
counter++;
}
StatisticsQuery stats = queryStatsMap.get(key);
if (stats == null){
stats = new StatisticsQuery(key);
queryStatsMap.put(key, stats);
}
stats.add(beansLoaded, micros);
}
}
/**
* Collect the usage information for from a instance for this node.
*/
public void collectUsageInfo(NodeUsageCollector profile) {
if (profile.isEmpty()){
// no usage was collected
} else {
ObjectGraphNode node = profile.getNode();
StatisticsNodeUsage nodeStats = getNodeStats(node.getPath());
nodeStats.publish(profile);
}
}
private StatisticsNodeUsage getNodeStats(String path) {
synchronized (monitor) {
StatisticsNodeUsage nodeStats = nodeUsageMap.get(path);
if (nodeStats == null) {
nodeStats = new StatisticsNodeUsage(path, queryTuningAddVersion);
nodeUsageMap.put(path, nodeStats);
}
return nodeStats;
}
}
public String getUsageDebug() {
synchronized (monitor) {
StringBuilder sb = new StringBuilder();
sb.append("root[").append(origin.getBeanType()).append("] ");
for (StatisticsNodeUsage node : nodeUsageMap.values()) {
sb.append(node.toString()).append("\n");
}
return sb.toString();
}
}
public String getQueryStatDebug() {
synchronized (monitor) {
StringBuilder sb = new StringBuilder();
for (StatisticsQuery queryStat : queryStatsMap.values()) {
sb.append(queryStat.toString()).append("\n");
}
return sb.toString();
}
}
public String toString() {
synchronized (monitor) {
return getUsageDebug();
}
}
}
@@ -1,123 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.logging.Logger;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.meta.MetaAutoFetchStatistic.NodeUsageStats;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
/**
* Collects usages statistics for a given node in the object graph.
*/
public class StatisticsNodeUsage implements Serializable {
private static final long serialVersionUID = -1663951463963779547L;
private static final Logger logger = Logger.getLogger(StatisticsNodeUsage.class.getName());
private final String monitor = new String();
private final String path;
private final boolean queryTuningAddVersion;
private int profileCount;
private int profileUsedCount;
private boolean modified;
private Set<String> aggregateUsed = new LinkedHashSet<String>();
public StatisticsNodeUsage(String path, boolean queryTuningAddVersion) {
this.path = path;
this.queryTuningAddVersion = queryTuningAddVersion;
}
public NodeUsageStats createPublicMeta() {
synchronized(monitor){
String[] usedProps = aggregateUsed.toArray(new String[aggregateUsed.size()]);
return new NodeUsageStats(path, profileCount, profileUsedCount, usedProps);
}
}
public void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc) {
synchronized(monitor){
BeanDescriptor<?> desc = rootDesc;
if (path != null){
ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
if (elGetValue == null){
desc = null;
logger.warning("Autofetch: Can't find join for path["+path+"] for "+rootDesc.getName());
} else {
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>){
desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
}
}
}
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
if (beanProp == null){
logger.warning("Autofetch: Can't find property["+propName+"] for "+desc.getName());
} else {
if (beanProp instanceof BeanPropertyAssoc<?>){
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>)beanProp;
String targetIdProp = assocProp.getTargetIdProperty();
String manyPath = SplitName.add(path, assocProp.getName());
pathProps.addToPath(manyPath, targetIdProp);
} else {
if (beanProp.isLob() && !beanProp.isFetchEager()) {
// AutoFetch will not include Lob's marked FetchLazy
// (which is the default for Lob's so typical).
} else {
pathProps.addToPath(path, beanProp.getName());
}
}
}
}
if ((modified || queryTuningAddVersion) && desc != null) {
BeanProperty[] versionProps = desc.propertiesVersion();
if (versionProps.length > 0) {
pathProps.addToPath(path, versionProps[0].getName());
}
}
}
}
public void publish(NodeUsageCollector profile) {
synchronized(monitor){
HashSet<String> used = profile.getUsed();
profileCount++;
if (!used.isEmpty()){
profileUsedCount++;
aggregateUsed.addAll(used);
}
if (profile.isModified()){
modified = true;
}
}
}
public String toString() {
return "path["+path+"] profileCount["+profileCount+"] used["+profileUsedCount+"] props"+aggregateUsed;
}
}
@@ -1,42 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import com.avaje.ebean.meta.MetaAutoFetchStatistic.QueryStats;
/**
* Used to accumulate query execution statistics.
*/
public class StatisticsQuery implements Serializable {
private static final long serialVersionUID = -1133958958072778811L;
private final String path;
private int exeCount;
private int totalBeanLoaded;
private int totalMicros;
public StatisticsQuery(String path){
this.path = path;
}
public QueryStats createPublicMeta() {
return new QueryStats(path, exeCount, totalBeanLoaded, totalMicros);
}
public void add(int beansLoaded, int micros) {
exeCount++;
totalBeanLoaded += beansLoaded;
totalMicros += micros;
}
public String toString() {
long avgMicros = exeCount == 0 ? 0 : totalMicros / exeCount;
return "queryExe path["+path+"] count[" + exeCount + "] totalBeansLoaded[" + totalBeanLoaded + "] avgMicros["
+ avgMicros + "] totalMicros[" + totalMicros + "]";
}
}
@@ -1,193 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.meta.MetaAutoFetchTunedQueryInfo;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
/**
* Holds tuned query information. Is immutable so this represents the tuning at
* a given point in time.
*/
public class TunedQueryInfo implements Serializable {
private static final long serialVersionUID = 7381493228797997282L;
private final ObjectGraphOrigin origin;
/**
* The tuned query details with joins and properties.
*/
private OrmQueryDetail tunedDetail;
/**
* The number of times profiling has been collected for this query point.
*/
private int profileCount;
private Long lastTuneTime = Long.valueOf(0);
private final String rateMonitor = new String();
/**
* The number of queries tuned by this object.
* Could use AtomicInteger perhaps.
*/
private transient int tunedCount;
private transient int rateTotal;
private transient int rateHits;
private transient double lastRate;
public TunedQueryInfo(ObjectGraphOrigin queryPoint, OrmQueryDetail tunedDetail, int profileCount) {
this.origin = queryPoint;
this.tunedDetail = tunedDetail;
this.profileCount = profileCount;
}
/**
* Return true if this query should be profiled based on a percentage rate.
*/
public boolean isPercentageProfile(double rate) {
synchronized (rateMonitor) {
if (lastRate != rate) {
// the rate has changed so resetting
lastRate = rate;
rateTotal = 0;
rateHits = 0;
}
rateTotal++;
if (rate > (double) rateHits / rateTotal) {
rateHits++;
return true;
} else {
return false;
}
}
}
/**
* Create a copy of this tuned fetch data for public consumption.
*/
public MetaAutoFetchTunedQueryInfo createPublicMeta() {
return new MetaAutoFetchTunedQueryInfo(origin, tunedDetail.toString(), profileCount, tunedCount, lastTuneTime);
}
/**
* Set the number of times profiling has been collected for this query
* point.
*/
public void setProfileCount(int profileCount) {
// int assignment is atomic
this.profileCount = profileCount;
}
/**
* Set the tuned query detail.
*/
public void setTunedDetail(OrmQueryDetail tunedDetail) {
// assignment is atomic
this.tunedDetail = tunedDetail;
this.lastTuneTime = Long.valueOf(System.currentTimeMillis());
}
/**
* Return true if the fetches are essentially the same.
*/
public boolean isSame(OrmQueryDetail newQueryDetail) {
if (tunedDetail == null) {
return false;
}
return tunedDetail.isAutoFetchEqual(newQueryDetail);
}
/**
* Tune the query by replacing its OrmQueryDetail with a tuned one.
*
* @return true if the query was tuned, otherwise false.
*/
public boolean autoFetchTune(SpiQuery<?> query) {
if (tunedDetail == null) {
return false;
}
boolean tuned = false;
//Note: tunedDetail is immutable by convention
if (query.isDetailEmpty()) {
tuned = true;
// tune by 'replacement'
query.setDetail(tunedDetail.copy());
} else {
// tune by 'addition'
tuned = query.tuneFetchProperties(tunedDetail);
}
if (tuned){
query.setAutoFetchTuned(true);
// a case for AtomicInteger but good enough for statistics
tunedCount++;
}
return tuned;
}
/**
* Return the time of the last tune.
*/
public Long getLastTuneTime() {
return lastTuneTime;
}
/**
* Return the number of queries tuned by this object.
*/
public int getTunedCount() {
return tunedCount;
}
/**
* Return the number of times profiling has been collected for this query
* point.
*/
public int getProfileCount() {
return profileCount;
}
public OrmQueryDetail getTunedDetail() {
return tunedDetail;
}
public ObjectGraphOrigin getOrigin() {
return origin;
}
public String getLogOutput(OrmQueryDetail newQueryDetail) {
boolean changed = newQueryDetail != null;
StringBuilder sb = new StringBuilder(150);
sb.append( changed ? "\"Changed\",":"\"New\",");
sb.append("\"").append(origin.getBeanType()).append("\",");
sb.append("\"").append(origin.getKey()).append("\",");
if (changed){
sb.append("\"to: ").append(newQueryDetail.toString()).append("\",");
sb.append("\"from: ").append(tunedDetail.toString()).append("\",");
} else {
sb.append("\"to: ").append(tunedDetail.toString()).append("\",");
sb.append("\"\",");
}
sb.append("\"").append(origin.getFirstStackElement()).append("\"");
return sb.toString();
}
public String toString() {
return origin.getBeanType()+" "+origin.getKey()+" " + tunedDetail;
}
}
@@ -1,8 +0,0 @@
<html>
<head>
<title>AutoFetch Implementation</title>
</head>
<body>
AutoFetch Implementation
</body>
</html>
@@ -1,79 +0,0 @@
package com.avaje.ebeaninternal.server.bean;
import java.util.Iterator;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.common.BeanList;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaAutoFetchStatistic;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.autofetch.Statistics;
/**
* Bean Finder for MetaAutoFetchStatistic.
* <p>
* This gets the meta data from the AutoFetchManager and creates a copy of that
* data to give back to the caller in the form of MetaAutoFetchStatistic beans.
* </p>
*/
public class BFAutoFetchStatisticFinder implements BeanFinder<MetaAutoFetchStatistic> {
public MetaAutoFetchStatistic find(BeanQueryRequest<MetaAutoFetchStatistic> request) {
SpiQuery<MetaAutoFetchStatistic> query = (SpiQuery<MetaAutoFetchStatistic>)request.getQuery();
try {
String queryPointKey = (String) query.getId();
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
Statistics stats = manager.getStatistics(queryPointKey);
if (stats != null) {
return stats.createPublicMeta();
} else {
return null;
}
} catch (Exception e) {
throw new PersistenceException(e);
}
}
/**
* Only returns Lists at this stage.
*/
public BeanCollection<MetaAutoFetchStatistic> findMany(BeanQueryRequest<MetaAutoFetchStatistic> request) {
SpiQuery.Type queryType = ((SpiQuery<?>)request.getQuery()).getType();
if (!queryType.equals(SpiQuery.Type.LIST)) {
throw new PersistenceException("Only findList() supported at this stage.");
}
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
BeanList<MetaAutoFetchStatistic> list = new BeanList<MetaAutoFetchStatistic>();
Iterator<Statistics> it = manager.iterateStatistics();
while (it.hasNext()) {
Statistics stats = it.next();
// create a copy for public use
list.add(stats.createPublicMeta());
}
String orderBy = request.getQuery().order().toStringFormat();
if (orderBy == null){
orderBy = "beanType";
}
server.sort(list, orderBy);
return list;
}
}
@@ -1,76 +0,0 @@
package com.avaje.ebeaninternal.server.bean;
import java.util.Iterator;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.common.BeanList;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaAutoFetchTunedQueryInfo;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.autofetch.TunedQueryInfo;
/**
* BeanFinder for MetaAutoFetchTunedFetch.
*/
public class BFAutoFetchTunedFetchFinder implements BeanFinder<MetaAutoFetchTunedQueryInfo> {
public MetaAutoFetchTunedQueryInfo find(BeanQueryRequest<MetaAutoFetchTunedQueryInfo> request) {
SpiQuery<?> query = (SpiQuery<?>)request.getQuery();
try {
String queryPointKey = (String)query.getId();
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
TunedQueryInfo tunedFetch = manager.getTunedQueryInfo(queryPointKey);
if (tunedFetch != null){
return tunedFetch.createPublicMeta();
} else {
return null;
}
} catch (Exception e){
throw new PersistenceException(e);
}
}
/**
* Only returns Lists at this stage.
*/
public BeanCollection<MetaAutoFetchTunedQueryInfo> findMany(BeanQueryRequest<MetaAutoFetchTunedQueryInfo> request) {
SpiQuery.Type queryType = ((SpiQuery<?>)request.getQuery()).getType();
if (!queryType.equals(SpiQuery.Type.LIST)){
throw new PersistenceException("Only findList() supported at this stage.");
}
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
AutoFetchManager manager = server.getAutoFetchManager();
BeanList<MetaAutoFetchTunedQueryInfo> list = new BeanList<MetaAutoFetchTunedQueryInfo>();
Iterator<TunedQueryInfo> it = manager.iterateTunedQueryInfo();
while (it.hasNext()) {
TunedQueryInfo tunedFetch = it.next();
// create a copy for public use
list.add(tunedFetch.createPublicMeta());
}
String orderBy = request.getQuery().order().toStringFormat();
if (orderBy == null){
orderBy = "beanType, origQueryPlanHash";
}
server.sort(list, orderBy);
return list;
}
}
@@ -1,69 +0,0 @@
package com.avaje.ebeaninternal.server.bean;
import java.util.Iterator;
import java.util.List;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.common.BeanList;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.meta.MetaQueryStatistic;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQueryPlan;
/**
* BeanFinder for MetaQueryStatistic.
*/
public class BFQueryStatisticFinder implements BeanFinder<MetaQueryStatistic> {
public MetaQueryStatistic find(BeanQueryRequest<MetaQueryStatistic> request) {
throw new RuntimeException("Not Supported yet");
}
/**
* Only returns Lists at this stage.
*/
public BeanCollection<MetaQueryStatistic> findMany(BeanQueryRequest<MetaQueryStatistic> request) {
SpiQuery.Type queryType = ((SpiQuery<?>)request.getQuery()).getType();
if (!queryType.equals(SpiQuery.Type.LIST)){
throw new PersistenceException("Only findList() supported at this stage.");
}
BeanList<MetaQueryStatistic> list = new BeanList<MetaQueryStatistic>();
SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer();
build(list, server);
String orderBy = request.getQuery().order().toStringFormat();
if (orderBy == null){
orderBy = "beanType, origQueryPlanHash, autofetchTuned";
}
server.sort(list, orderBy);
return list;
}
private void build(List<MetaQueryStatistic> list, SpiEbeanServer server) {
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
desc.clearQueryStatistics();
build(list, desc);
}
}
private void build(List<MetaQueryStatistic> list, BeanDescriptor<?> desc) {
Iterator<CQueryPlan> it = desc.queryPlans();
while (it.hasNext()) {
CQueryPlan queryPlan = (CQueryPlan) it.next();
list.add(queryPlan.createMetaQueryStatistic(desc.getFullName()));
}
}
}
@@ -1,8 +0,0 @@
<html>
<head>
<title>BeanFinders, BeanControllers etc for "meta" beans</title>
</head>
<body>
BeanFinders, BeanControllers etc for "meta" beans
</body>
</html>
@@ -1,50 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.Set;
public class CachedBeanData {
private final Object sharableBean;
private final Set<String> loadedProperties;
private final Object[] data;
private final int naturalKeyUpdate;
public CachedBeanData(Object sharableBean, Set<String> loadedProperties, Object[] data, int naturalKeyUpdate) {
this.sharableBean = sharableBean;
this.loadedProperties= loadedProperties;
this.data = data;
this.naturalKeyUpdate = naturalKeyUpdate;
}
public Object getSharableBean() {
return sharableBean;
}
public boolean isNaturalKeyUpdate() {
return naturalKeyUpdate > -1;
}
public Object getNaturalKey() {
return data[naturalKeyUpdate];
}
public boolean containsProperty(String propName) {
return loadedProperties == null || loadedProperties.contains(propName);
}
public Object getData(int i){
return data[i];
}
public Set<String> getLoadedProperties() {
return loadedProperties;
}
public Object[] copyData() {
Object[] dest = new Object[data.length];
System.arraycopy(data, 0, dest, 0, data.length);
return dest;
}
}
@@ -1,103 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.HashSet;
import java.util.Set;
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;
public class CachedBeanDataFromBean {
private final BeanDescriptor<?> desc;
private final Object bean;
private final EntityBeanIntercept ebi;
private final Set<String> loadedProps;
private final Set<String> extractProps;
public static CachedBeanData extract(BeanDescriptor<?> desc, Object bean){
if (bean instanceof EntityBean){
return new CachedBeanDataFromBean(desc, bean, ((EntityBean)bean)._ebean_getIntercept()).extract();
} else {
return new CachedBeanDataFromBean(desc, bean, null).extract();
}
}
public static CachedBeanData extract(BeanDescriptor<?> desc, Object bean, EntityBeanIntercept ebi){
return new CachedBeanDataFromBean(desc, bean, ebi).extract();
}
private CachedBeanDataFromBean(BeanDescriptor<?> desc, Object bean, EntityBeanIntercept ebi) {
this.desc = desc;
this.bean = bean;
this.ebi = ebi;
if (ebi != null){
this.loadedProps = ebi.getLoadedProps();
this.extractProps = (loadedProps == null) ? null : new HashSet<String>();
} else {
this.extractProps = new HashSet<String>();
this.loadedProps = null;
}
}
private CachedBeanData extract(){
BeanProperty[] props = desc.propertiesNonMany();
Object[] data = new Object[props.length];
int naturalKeyUpdate = -1;
for (int i = 0; i < props.length; i++) {
BeanProperty prop = props[i];
if (includeNonManyProperty(prop.getName())){
data[i] = prop.getCacheDataValue(bean);
if (prop.isNaturalKey()) {
naturalKeyUpdate = i;
}
if (ebi != null){
if (extractProps != null){
extractProps.add(prop.getName());
}
} else if (data[i] != null){
if (extractProps != null){
extractProps.add(prop.getName());
}
}
}
}
Object sharableBean = null;
if (desc.isCacheSharableBeans() && ebi != null && loadedProps == null){
if (ebi.isReadOnly()){
sharableBean = bean;
} else {
// create a readOnly sharable instance by copying the data
sharableBean = desc.createBean(false);
BeanProperty[] propertiesId = desc.propertiesId();
for (int i = 0; i < propertiesId.length; i++) {
Object v = propertiesId[i].getValue(bean);
propertiesId[i].setValue(sharableBean, v);
}
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
for (int i = 0; i < propertiesNonTransient.length; i++) {
Object v = propertiesNonTransient[i].getValue(bean);
propertiesNonTransient[i].setValue(sharableBean, v);
}
EntityBeanIntercept ebi = ((EntityBean)sharableBean)._ebean_intercept();
ebi.setReadOnly(true);
ebi.setLoaded();
}
}
return new CachedBeanData(sharableBean, extractProps, data, naturalKeyUpdate);
}
private boolean includeNonManyProperty(String name) {
return loadedProps == null || loadedProps.contains(name);
}
}
@@ -1,117 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.HashSet;
import java.util.Set;
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;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
public class CachedBeanDataToBean {
private final BeanDescriptor<?> desc;
private final Object bean;
private final EntityBeanIntercept ebi;
private final CachedBeanData cacheBeandata;
private final Set<String> cacheLoadedProperties;
private final Set<String> loadedProps;
private final Set<String> excludeProps;
private final Object oldValuesBean;
private final boolean readOnly;
public static void load(BeanDescriptor<?> desc, Object bean, CachedBeanData cacheBeandata) {
if (bean instanceof EntityBean){
load(desc, bean, ((EntityBean)bean)._ebean_getIntercept(), cacheBeandata);
} else {
load(desc, bean, null, cacheBeandata);
}
}
public static void load(BeanDescriptor<?> desc, Object bean, EntityBeanIntercept ebi, CachedBeanData cacheBeandata) {
new CachedBeanDataToBean(desc, bean, ebi, cacheBeandata).load();
}
private CachedBeanDataToBean(BeanDescriptor<?> desc, Object bean, EntityBeanIntercept ebi, CachedBeanData cacheBeandata) {
this.desc = desc;
this.bean = bean;
this.ebi = ebi;
this.cacheBeandata = cacheBeandata;
this.cacheLoadedProperties = cacheBeandata.getLoadedProperties();
this.loadedProps = (cacheLoadedProperties == null) ? null : new HashSet<String>();
if (ebi != null){
this.excludeProps = ebi.getLoadedProps();
this.oldValuesBean = ebi.getOldValues();
this.readOnly = ebi.isReadOnly();
} else {
this.excludeProps = null;
this.oldValuesBean = null;
this.readOnly = false;
}
}
private boolean load(){
BeanProperty[] propertiesNonTransient = desc.propertiesNonMany();
for (int i = 0; i < propertiesNonTransient.length; i++) {
BeanProperty prop = propertiesNonTransient[i];
if (includeNonManyProperty(prop.getName())){
Object data = cacheBeandata.getData(i);
prop.setCacheDataValue(bean, data, oldValuesBean, readOnly);
}
}
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
for (int i = 0; i < manys.length; i++) {
BeanPropertyAssocMany<?> prop = manys[i];
if (includeManyProperty(prop.getName())){
// set a lazy loading proxy
prop.createReference(bean);
}
}
if (ebi != null){
if (loadedProps == null){
ebi.setLoadedProps(null);
} else {
HashSet<String> mergeProps = new HashSet<String>();
if (excludeProps != null) {
mergeProps.addAll(excludeProps);
}
mergeProps.addAll(loadedProps);
ebi.setLoadedProps(mergeProps);
}
ebi.setLoadedLazy();
}
return true;
}
private boolean includeManyProperty(String name) {
if (excludeProps != null && excludeProps.contains(name)){
// ignore this property (partial bean lazy loading)
return false;
}
if (loadedProps != null){
loadedProps.add(name);
}
return true;
}
private boolean includeNonManyProperty(String name) {
if (excludeProps != null && excludeProps.contains(name)){
// ignore this property (partial bean lazy loading)
return false;
}
if (cacheLoadedProperties != null && !cacheLoadedProperties.contains(name)){
return false;
}
if (loadedProps != null){
loadedProps.add(name);
}
return true;
}
}
@@ -1,49 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.HashSet;
import java.util.Set;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
public class CachedBeanDataUpdate {
public static CachedBeanData update(BeanDescriptor<?> desc, CachedBeanData data, PersistRequestBean<?> updateRequest){
Set<String> loadedProperties = data.getLoadedProperties();
Object[] copyOfData = data.copyData();
Object updateBean = updateRequest.getBean();
Set<String> updatedProperties = updateRequest.getUpdatedProperties();
int naturalKeyUpdate = -1;
boolean mergeProperties = false;
BeanProperty[] props = desc.propertiesNonMany();
for (int i = 0; i < props.length; i++) {
if (updatedProperties.contains(props[i].getName())){
if (props[i].isNaturalKey()){
naturalKeyUpdate = i;
}
copyOfData[i] = props[i].getCacheDataValue(updateBean);
if (loadedProperties != null && !mergeProperties && !loadedProperties.contains(props[i].getName())){
mergeProperties = true;
}
}
}
if (mergeProperties){
HashSet<String> mergeProps = new HashSet<String>();
mergeProps.addAll(loadedProperties);
mergeProps.addAll(updatedProperties);
loadedProperties = mergeProps;
}
return new CachedBeanData(null, loadedProperties, copyOfData, naturalKeyUpdate);
}
}
@@ -1,17 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.List;
public class CachedManyIds {
private final List<Object> idList;
public CachedManyIds(List<Object> idList) {
this.idList = idList;
}
public List<Object> getIdList() {
return idList;
}
}
@@ -1,123 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheOptions;
/**
* Manages the construction of caches.
*/
public class DefaultCacheHolder {
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
private final Object monitor = new Object();
private final ServerCacheFactory cacheFactory;
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
* @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) {
this.cacheFactory = cacheFactory;
this.defaultOptions = defaultOptions;
this.useBeanTuning = useBeanTuning;
}
/**
* Return the default cache options.
*/
public ServerCacheOptions getDefaultOptions() {
return defaultOptions;
}
/**
* Return the cache for a given bean type.
*/
public ServerCache getCache(String cacheKey) {
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;
}
}
public void clearCache(String cacheKey) {
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);
}
public void clearAll() {
Iterator<ServerCache> it = concMap.values().iterator();
while (it.hasNext()) {
ServerCache serverCache = it.next();
serverCache.clear();
}
}
/**
* Return the cache options for a given bean type.
*/
private ServerCacheOptions getCacheOptions(String beanType) {
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
}
}
return defaultOptions.copy();
}
}
@@ -1,407 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCacheStatistics;
/**
* The default cache implementation.
* <p>
* It is base on ConcurrentHashMap with periodic trimming using a TimerTask.
* The periodic trimming means that an LRU list does not have to be maintained.
* </p>
*/
public class DefaultServerCache implements ServerCache {
private static final Logger logger = Logger.getLogger(DefaultServerCache.class.getName());
private static final CacheEntryComparator comparator = new CacheEntryComparator();
private final ConcurrentHashMap<Object, CacheEntry> map = new ConcurrentHashMap<Object, CacheEntry>();
private final AtomicInteger missCount = new AtomicInteger();
private final AtomicInteger removedHitCount = new AtomicInteger();
private final Object monitor = new Object();
private final String name;
private int maxSize;
private long trimFrequency;
private int maxIdleSecs;
private int maxSecsToLive;
public DefaultServerCache(String name, ServerCacheOptions options) {
this(name, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive());
}
public DefaultServerCache(String name, int maxSize, int maxIdleSecs, int maxSecsToLive) {
this.name = name;
this.maxSize = maxSize;
this.maxIdleSecs = maxIdleSecs;
this.maxSecsToLive = maxSecsToLive;
this.trimFrequency = 60;
}
public void init(EbeanServer server) {
TrimTask trim = new TrimTask();
BackgroundExecutor executor = server.getBackgroundExecutor();
executor.executePeriodically(trim, trimFrequency, TimeUnit.SECONDS);
}
public ServerCacheStatistics getStatistics(boolean reset) {
ServerCacheStatistics s = new ServerCacheStatistics();
s.setCacheName(name);
s.setMaxSize(maxSize);
// these counters won't necessarily be consistent with
// respect to each other as activity can occur while
// they are being calculated
int mc = reset ? missCount.getAndSet(0) : missCount.get();
int hc = getHitCount(reset);
int size = size();
s.setSize(size);
s.setHitCount(hc);
s.setMissCount(mc);
return s;
}
public int getHitRatio() {
int mc = missCount.get();
int hc = getHitCount(false);
int totalCount = hc + mc;
if (totalCount == 0){
return 0;
} else {
return hc * 100 / totalCount;
}
}
private int getHitCount(boolean reset) {
int hc = reset ? removedHitCount.getAndSet(0) : removedHitCount.get();
Iterator<CacheEntry> it = map.values().iterator();
while (it.hasNext()) {
CacheEntry cacheEntry = it.next();
hc += cacheEntry.getHitCount(reset);
}
return hc;
}
public ServerCacheOptions getOptions() {
synchronized (monitor) {
ServerCacheOptions o = new ServerCacheOptions();
o.setMaxIdleSecs(maxIdleSecs);
o.setMaxSize(maxSize);
o.setMaxSecsToLive(maxSecsToLive);
return o;
}
}
public void setOptions(ServerCacheOptions o) {
synchronized (monitor) {
maxIdleSecs = o.getMaxIdleSecs();
maxSize = o.getMaxSize();
maxSecsToLive = o.getMaxSecsToLive();
}
}
/**
* Return the max cache size.
*/
public int getMaxSize() {
return maxSize;
}
/**
* Set the max cache size.
*/
public void setMaxSize(int maxSize) {
synchronized (monitor) {
this.maxSize = maxSize;
}
}
/**
* Return the max idle time.
*/
public long getMaxIdleSecs() {
return maxIdleSecs;
}
/**
* Set the max idle time.
*/
public void setMaxIdleSecs(int maxIdleSecs) {
synchronized (monitor) {
this.maxIdleSecs = maxIdleSecs;
}
}
/**
* Return the maximum time to live.
*/
public long getMaxSecsToLive() {
return maxSecsToLive;
}
/**
* Set the maximum time to live.
*/
public void setMaxSecsToLive(int maxSecsToLive) {
synchronized (monitor) {
this.maxSecsToLive = maxSecsToLive;
}
}
/**
* Return the name of the cache.
*/
public String getName() {
return name;
}
/**
* Clear the cache.
*/
public void clear() {
map.clear();
}
/**
* Return a value from the cache.
*/
public Object get(Object key) {
CacheEntry entry = map.get(key);
if (entry == null){
missCount.incrementAndGet();
return null;
} else {
// get value incrementing last
// access time and hitCount
return entry.getValue();
}
}
/**
* Put a value into the cache.
*/
public Object put(Object key, Object value) {
// put new entry with create time
CacheEntry entry = map.put(key, new CacheEntry(key, value));
if (entry == null){
return null;
} else {
int removedHits = entry.getHitCount(true);
removedHitCount.addAndGet(removedHits);
return entry.getValue();
}
}
/**
* Put a value into the cache but only if absent.
*/
public Object putIfAbsent(Object key, Object value) {
CacheEntry entry = map.putIfAbsent(key, new CacheEntry(key, value));
if (entry == null){
return null;
} else {
return entry.getValue();
}
}
/**
* Remove an entry from the cache.
*/
public Object remove(Object key) {
CacheEntry entry = map.remove(key);
if (entry == null){
return null;
} else {
int removedHits = entry.getHitCount(true);
removedHitCount.addAndGet(removedHits);
return entry.getValue();
}
}
/**
* Return the number of elements in the cache.
*/
public int size() {
return map.size();
}
private Iterator<CacheEntry> cacheEntries() {
return map.values().iterator();
}
/**
* The task used to periodically trim the cache.
*/
private class TrimTask implements Runnable {
public void run() {
long startTime = System.currentTimeMillis();
if (logger.isLoggable(Level.FINER)){
logger.finer("trimming cache " + name);
}
int trimmedByIdle = 0;
int trimmedByTTL = 0;
int trimmedByLRU = 0;
boolean trimMaxSize = maxSize > 0 && maxSize < size();
ArrayList<CacheEntry> activeList = new ArrayList<CacheEntry>();
long idleExpire = System.currentTimeMillis() - (maxIdleSecs*1000);
long ttlExpire = System.currentTimeMillis() - (maxSecsToLive*1000);
Iterator<CacheEntry> it = cacheEntries();
while (it.hasNext()) {
CacheEntry cacheEntry = it.next();
if (maxIdleSecs > 0 && idleExpire > cacheEntry.getLastAccessTime()) {
it.remove();
trimmedByIdle++;
} else if (maxSecsToLive > 0 && ttlExpire > cacheEntry.getCreateTime()) {
it.remove();
trimmedByTTL++;
} else if (trimMaxSize) {
activeList.add(cacheEntry);
}
}
if (trimMaxSize) {
trimmedByLRU = activeList.size() - maxSize;
if (trimmedByLRU > 0) {
// sort into last access time ascending
Collections.sort(activeList, comparator);
for (int i = maxSize; i < activeList.size(); i++) {
// remove if still in the cache
map.remove(activeList.get(i).getKey());
}
}
}
long exeTime = System.currentTimeMillis() - startTime;
if (logger.isLoggable(Level.FINE)){
logger.fine("Executed trim of cache " + name + " in ["+exeTime
+"]millis idle[" + trimmedByIdle + "] timeToLive["
+ trimmedByTTL + "] accessTime["
+ trimmedByLRU + "]");
}
}
}
/**
* Comparator for sorting by last access time.
*/
private static class CacheEntryComparator implements Comparator<CacheEntry>, Serializable {
private static final long serialVersionUID = 1L;
public int compare(CacheEntry o1, CacheEntry o2) {
return o1.getLastAccessLong().compareTo(o2.getLastAccessLong());
}
}
/**
* Wraps the values to additionally hold createTime and lastAccessTime.
*/
public static class CacheEntry {
private final Object key;
private final Object value;
private final long createTime;
private final AtomicInteger hitCount = new AtomicInteger();
private Long lastAccessTime;
public CacheEntry(Object key, Object value) {
this.key = key;
this.value = value;
this.createTime = System.currentTimeMillis();
this.lastAccessTime = Long.valueOf(createTime);
}
public Object getKey() {
return key;
}
public Object getValue() {
// object assignment is atomic
hitCount.incrementAndGet();
this.lastAccessTime = Long.valueOf(System.currentTimeMillis());
return value;
}
public long getCreateTime() {
return createTime;
}
public long getLastAccessTime() {
return lastAccessTime.longValue();
}
public Long getLastAccessLong() {
return lastAccessTime;
}
public int getHitCount(boolean reset) {
if (reset){
return hitCount.getAndSet(0);
} else {
return hitCount.get();
}
}
public int getHitCount() {
return hitCount.get();
}
}
}
@@ -1,27 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheOptions;
/**
* Default implementation of ServerCacheFactory.
*/
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;
}
}
@@ -1,112 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.EbeanServer;
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.ebeaninternal.api.SpiEbeanServer;
/**
* Manages the bean and query caches.
*/
public class DefaultServerCacheManager implements ServerCacheManager {
private final DefaultCacheHolder beanCache;
private final DefaultCacheHolder queryCache;
private final DefaultCacheHolder naturalKeyCache;
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;
}
public void setCaching(Class<?> beanType, boolean useCache) {
ebeanServer.getBeanDescriptor(beanType).getCacheOptions().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);
}
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 boolean isQueryCaching(Class<?> beanType) {
return queryCache.isCaching(beanType.getName());
}
}
@@ -1 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
@@ -1,64 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
/**
* Represents a relatively small independent message.
* <p>
* In general terms we break up a potentially large object like
* RemoteTransactionEvent into many smaller BinaryMessages. This is so that if
* they don't all fit on a single Packet we can easily break them up and put
* them on multiple packets.
* </p>
* <p>
* Also note that for the Multicast approach a Packet will generally contain
* many messages each directed to different members of the cluster. So it would
* be common for many Ack, Resend and Control messages to all be contained in a
* single packet.
* </p>
*
* @author rbygrave
*
*/
public class BinaryMessage {
public static final int TYPE_MSGCONTROL = 0;
public static final int TYPE_BEANIUD = 1;
public static final int TYPE_TABLEIUD = 2;
public static final int TYPE_BEANDELTA = 3;
public static final int TYPE_BEANPATHUPDATE = 4;
public static final int TYPE_INDEX_INVALIDATE = 6;
public static final int TYPE_INDEX = 7;
public static final int TYPE_MSGACK = 8;
public static final int TYPE_MSGRESEND = 9;
private final ByteArrayOutputStream buffer;
private final DataOutputStream os;
private byte[] bytes;
/**
* Create with an estimated buffer size.
*/
public BinaryMessage(int bufSize) {
this.buffer = new ByteArrayOutputStream(bufSize);
this.os = new DataOutputStream(buffer);
}
/**
* Return the DataOutputStream to write content to.
*/
public DataOutputStream getOs() {
return os;
}
/**
* Return all the content as a byte array.
*/
public byte[] getByteArray() {
if (bytes == null) {
bytes = buffer.toByteArray();
}
return bytes;
}
}
@@ -1,23 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.util.ArrayList;
import java.util.List;
/**
* Holds a List of BinaryMessage's.
*
* @author rbygrave
*/
public class BinaryMessageList {
ArrayList<BinaryMessage> list = new ArrayList<BinaryMessage>();
public void add(BinaryMessage msg) {
list.add(msg);
}
public List<BinaryMessage> getList() {
return list;
}
}
@@ -1,28 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* Sends messages to the cluster members.
*/
public interface ClusterBroadcast {
/**
* Inform the other cluster members that this instance has come online and
* start any listeners etc.
*/
public void startup(ClusterManager clusterManager);
/**
* Inform the other cluster members that this instance is leaving and
* shutdown any listeners.
*/
public void shutdown();
/**
* Send a transaction event to all the members of the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent);
}
@@ -1,105 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.server.cluster.mcast.McastClusterManager;
import com.avaje.ebeaninternal.server.cluster.socket.SocketClusterBroadcast;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* Manages the cluster service.
*/
public class ClusterManager {
private static final Logger logger = Logger.getLogger(ClusterManager.class.getName());
private final ConcurrentHashMap<String, EbeanServer> serverMap = new ConcurrentHashMap<String, EbeanServer>();
private final Object monitor = new Object();
private final ClusterBroadcast broadcast;
private boolean started;
public ClusterManager() {
String clusterType = GlobalProperties.get("ebean.cluster.type", null);
if (clusterType == null || clusterType.trim().length() == 0) {
// not clustering this instance
this.broadcast = null;
} else {
try {
if ("mcast".equalsIgnoreCase(clusterType)) {
this.broadcast = new McastClusterManager();
} else if ("socket".equalsIgnoreCase(clusterType)) {
this.broadcast = new SocketClusterBroadcast();
} else {
logger.info("Clustering using [" + clusterType + "]");
this.broadcast = (ClusterBroadcast) ClassUtil.newInstance(clusterType);
}
} catch (Exception e) {
String msg = "Error initialising ClusterManager type [" + clusterType + "]";
logger.log(Level.SEVERE, msg, e);
throw new RuntimeException(e);
}
}
}
public void registerServer(EbeanServer server) {
synchronized (monitor) {
if (!started) {
startup();
}
serverMap.put(server.getName(), server);
}
}
public EbeanServer getServer(String name) {
synchronized (monitor) {
return serverMap.get(name);
}
}
private void startup() {
started = true;
if (broadcast != null) {
broadcast.startup(this);
}
}
/**
* Return true if clustering is on.
*/
public boolean isClustering() {
return broadcast != null;
}
/**
* Send the message headers and payload to every server in the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent) {
if (broadcast != null) {
broadcast.broadcast(remoteTransEvent);
}
}
/**
* Shutdown the service and Deregister from the cluster.
*/
public void shutdown() {
if (broadcast != null) {
logger.info("ClusterManager shutdown ");
broadcast.shutdown();
}
}
}
@@ -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 short packetType;
/**
* The PacketId.
*/
protected long packetId;
/**
* The timestamp the Packet was created.
*/
protected long timestamp;
/**
* The EbeanServer name this relates to if relevant.
*/
protected 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 java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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;
/**
* 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,75 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.DataInput;
import java.io.IOException;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.transaction.BeanDelta;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIds;
import com.avaje.ebeaninternal.server.transaction.IndexEvent;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* 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>
* @author rbygrave
*/
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;
case BinaryMessage.TYPE_BEANDELTA:
event.addBeanDelta(BeanDelta.readBinaryMessage(server, dataInput));
break;
case BinaryMessage.TYPE_INDEX:
event.addIndexEvent(IndexEvent.readBinaryMessage(dataInput));
break;
default:
throw new RuntimeException("Invalid Transaction msgType "+msgType);
}
}
}
@@ -1,160 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebeaninternal.server.cluster.mcast.Message;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* 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 {
public 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,55 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.List;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* Mechanism to convert RemoteTransactionEvent to/from byte[] content.
*/
public abstract class SerialiseTransactionHelper {
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());
PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server);
tranEventPacket.read(dataInput);
return tranEventPacket.getEvent();
}
}
@@ -1,43 +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.
*
* @author rbygrave
*/
public class AckResendMessages {
ArrayList<Message> messages = new ArrayList<Message>();
public String toString() {
return messages.toString();
}
public int size() {
return messages.size();
}
/**
* Add a ACK message to send.
*/
public void add(MessageAck ack){
messages.add(ack);
}
/**
* Add a RESEND message to send.
*/
public void add(MessageResend resend){
messages.add(resend);
}
/**
* Return all the messages to be sent out.
*/
public List<Message> getMessages() {
return messages;
}
}
@@ -1,53 +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.
*
* @author rbygrave
*/
public class IncomingPacketsLastAck {
private HashMap<String,MessageAck> lastAckMap = new HashMap<String, MessageAck>();
public String toString() {
return lastAckMap.values().toString();
}
/**
* Remove a member of the cluster who has left.
*/
public void remove(String memberHostPort) {
lastAckMap.remove(memberHostPort);
}
/**
* Get the last Ack point for a given member of the cluster.
*/
public MessageAck getLastAck(String memberHostPort) {
return lastAckMap.get(memberHostPort);
}
/**
* For the ACK messages in AckResendMessages update the
* last Ack packetId.
*/
public void updateLastAck(AckResendMessages ackResendMessages) {
List<Message> messages = ackResendMessages.getMessages();
for (int i = 0; i < messages.size(); i++) {
Message msg = messages.get(i);
if (msg instanceof MessageAck){
MessageAck lastAck = (MessageAck)msg;
lastAckMap.put(lastAck.getToHostPort(), lastAck);
}
}
}
}
@@ -1,273 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
* 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>
*
* @author rbygrave
*
*/
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 = Logger.getLogger(GotAllPoint.class.getName());
private final String memberKey;
private final int maxResendIncoming;
private long gotAllPoint;
private long gotMaxPoint;
/**
* Packets received out of order.
*/
private ArrayList<Long> outOfOrderList = new ArrayList<Long>();
private 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 = Long.valueOf(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.intValue() + 1;
if (i > maxResendIncoming){
// we are going to give up trying to get this packet now
logger.warning("Exceeded maxResendIncoming["+maxResendIncoming+"] for packet["+packetId+"]. Giving up on requesting it.");
resendCountMap.remove(packetId);
outOfOrderList.add(packetId);
return false;
}
resendCount = Integer.valueOf(i);
resendCountMap.put(packetId, resendCount);
} else {
resendCountMap.put(packetId, ONE);
}
return true;
}
private static final Integer ONE = Integer.valueOf(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(Long.valueOf(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.longValue() == nextPoint) {
// we found the next one in the outOfOrderList
it.remove();
gotAllPoint = nextPoint;
continueCheck = true;
break;
}
}
} while (continueCheck);
}
}
}
@@ -1,595 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebean.config.GlobalProperties;
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;
/**
* 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>
*
* @author rbygrave
*/
public class McastClusterManager implements ClusterBroadcast, Runnable {
private static final Logger logger = Logger.getLogger(McastClusterManager.class.getName());
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 long managerSleepMillis;
/**
* When true then packets are still sent out even when the cluster has no other online members.
*/
private 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 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 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() {
this.managerSleepMillis = GlobalProperties.getInt("ebean.cluster.mcast.managerSleepMillis", 80);
this.lastSendTimeFreqMillis = 1000*GlobalProperties.getInt("ebean.cluster.mcast.pingFrequencySecs", 300);//5mins
this.lastStatusTimeFreqMillis = 1000*GlobalProperties.getInt("ebean.cluster.mcast.statusFrequencySecs", 600);//10mins
// the maximum number of times we will try to re-send a given packet before giving up sending
this.maxResendOutgoing = GlobalProperties.getInt("ebean.cluster.mcast.maxResendOutgoing", 200);
// the maximum number of times we will ask for a packet to be resent to us before giving up asking
int maxResendIncoming = GlobalProperties.getInt("ebean.cluster.mcast.maxResendIncoming", 50);
int port = GlobalProperties.getInt("ebean.cluster.mcast.listen.port", 0);
String addr = GlobalProperties.get("ebean.cluster.mcast.listen.address", null);
int sendPort = GlobalProperties.getInt("ebean.cluster.mcast.send.port", 0);
String sendAddr = GlobalProperties.get("ebean.cluster.mcast.send.address", null);
// Sender options
// Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507
int maxSendPacketSize = GlobalProperties.getInt("ebean.cluster.mcast.send.maxPacketSize", 1500);
// Whether to send packets even when there are no other members online
this.sendWithNoMembers = GlobalProperties.getBoolean("ebean.cluster.mcast.send.sendWithNoMembers", true);
// Listener options
// When multiple instances are on same box you need to broadcast back locally
boolean disableLoopback = GlobalProperties.getBoolean("ebean.cluster.mcast.listen.disableLoopback", false);
int ttl = GlobalProperties.getInt("ebean.cluster.mcast.listen.ttl", -1);
int timeout = GlobalProperties.getInt("ebean.cluster.mcast.listen.timeout", 1000);
int bufferSize = GlobalProperties.getInt("ebean.cluster.mcast.listen.bufferSize", 65500);
// For multihomed environment the address the listener should bind to
String mcastAddr = GlobalProperties.get("ebean.cluster.mcast.listen.mcastAddress", null);
InetAddress mcastAddress = null;
if (mcastAddr != null) {
try {
mcastAddress = 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, mcastAddress);
}
/**
* 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(boolean reset) {
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.fine("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(false);
logger.info("Cluster Status: "+status.getSummary());
lastStatusTime = System.currentTimeMillis();
}
}
}
} catch (Exception e){
String msg = "Error with Cluster Mcast Manager thread";
logger.log(Level.SEVERE, msg, 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();
Iterator<Long> it = s.iterator();
while (it.hasNext()) {
Long resendPacketId = it.next();
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.log(Level.SEVERE, 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.log(Level.SEVERE, 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.log(Level.SEVERE, 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.log(Level.SEVERE, 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.log(Level.SEVERE, msg, e);
}
}
}
/**
* Effectively set the frequency by which this manager will send out ACKs.
*/
public void setManagerSleepMillis(long managerSleepMillis) {
synchronized (managerThread) {
this.managerSleepMillis = managerSleepMillis;
}
}
/**
* Return the frequency by which this manager will send out ACKs.
*/
public long getManagerSleepMillis() {
synchronized (managerThread) {
return managerSleepMillis;
}
}
}
@@ -1,263 +0,0 @@
/**
* Copyright (C) 2009 Authors
*
* This file is part of Ebean.
*
* Ebean is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Ebean is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Ebean; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.avaje.ebeaninternal.server.cluster.mcast;
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;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.avaje.ebeaninternal.server.cluster.PacketTransactionEvent;
/**
* Listens for Incoming packets.
*
* @author rbygrave
*/
public class McastListener implements Runnable {
private static final Logger logger = Logger.getLogger(McastListener.class.getName());
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 final boolean debugIgnore;
private DatagramPacket pack;
private 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.debugIgnore = GlobalProperties.getBoolean("ebean.debug.mcast.ignore", false);
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(disableLoopback);
}
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.warning(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.log(Level.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.log(Level.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 (debugIgnore || logger.isLoggable(Level.FINE)){
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 (debugIgnore || logger.isLoggable(Level.FINE)){
logger.info("Already processed packet: "+header.getPacketId()+" type:"+header.getPacketType()+" len:"+data.length);
}
} else {
if (logger.isLoggable(Level.FINER)){
logger.info("Incoming packet:"+header.getPacketId()+" type:"+header.getPacketType()+" len:"+data.length);
}
processPacket(senderHostPort, header, dataInput);
}
}
} catch (java.net.SocketTimeoutException e) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "timeout", e);
}
packetControl.onListenerTimeout();
} catch (IOException e) {
logger.log(Level.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.log(Level.SEVERE, msg);
break;
}
} catch (IOException e) {
// need to ask to get this packet resent...
String msg = "Error reading Packet " + header.getPacketId() + " type:" + header.getPacketType();
logger.log(Level.SEVERE, 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 java.io.DataInput;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.avaje.ebeaninternal.server.cluster.PacketMessages;
/**
* Helps co-ordinate Packet information between the McastListener and the
* McastClusterManager.
*
* @author rbygrave
*/
public class McastPacketControl {
private static final Logger logger = Logger.getLogger(McastPacketControl.class.getName());
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.isLoggable(Level.FINER)) {
logger.finer("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.log(Level.SEVERE, "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,116 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebeaninternal.server.cluster.Packet;
/**
* Handles the sending of Packets via DatagramPacket.
*
* @author rbygrave
*/
public class McastSender {
private static final Logger logger = Logger.getLogger(McastSender.class.getName());
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 = null;
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.isLoggable(Level.FINE)){
logger.fine("OUTGOING packet: " + packet.getPacketId() + " size:" + pktBytes.length);
}
if (pktBytes.length > 65507){
logger.warning("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,136 +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>
*
* @author rbygrave
*
*/
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() {
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 java.io.IOException;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
public interface Message {
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException;
public boolean isControlMessage();
public String getToHostPort();
}
@@ -1,57 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
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,73 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
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 java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebeaninternal.server.cluster.BinaryMessage;
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
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(Long.valueOf(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,106 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.util.HashMap;
import java.util.Map;
public class OutgoingPacketsAcked {
private long minimumGotAllPacketId;
private 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;
if (recievedByMap.isEmpty()){
//System.out.println(" -- -- -- -- "+recievedByMap.isEmpty());
tempMin = Long.MAX_VALUE;
} else {
tempMin = Long.MAX_VALUE;
}
for (GroupMemberAck groupMemAck : recievedByMap.values()) {
long memberMin = groupMemAck.getGotAllPacketId();
if (memberMin < tempMin){
//System.out.println(" -- new tmpMin "+memberMin);
tempMin = memberMin;
}
}
if (tempMin != minimumGotAllPacketId) {
minimumGotAllPacketId = tempMin;
return true;
} else {
return false;
}
}
public long receivedAck(String groupMember, MessageAck ack) {
synchronized (this) {
boolean checkMin = false;
GroupMemberAck groupMemberAck = recievedByMap.get(groupMember);
if (groupMemberAck == null) {
//System.out.println(" -- new groupMemberAck");
groupMemberAck = new GroupMemberAck();
groupMemberAck.setIfBigger(ack.getGotAllPacketId());
recievedByMap.put(groupMember, groupMemberAck);
checkMin = true;
} else {
checkMin = groupMemberAck.getGotAllPacketId() == minimumGotAllPacketId;
//System.out.println(" -- existing groupMemberAck, checkMin:"+checkMin+" "+groupMemberAck.getGotAllPacketId());
groupMemberAck.setIfBigger(ack.getGotAllPacketId());
}
boolean minChanged = false;
//System.out.println(" -- checkMin:"+checkMin+" minimumGotAllPacketId:"+minimumGotAllPacketId);
if (checkMin || minimumGotAllPacketId == 0){
minChanged = resetGotAllMin();
//System.out.println(" -- minChanged:"+minChanged+" minimumGotAllPacketId:"+minimumGotAllPacketId);
}
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,66 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.mcast;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.avaje.ebeaninternal.server.cluster.Packet;
/**
* 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>
*
* @author rbygrave
*
*/
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.longValue()) {
it.remove();
}
}
}
}
@@ -1,12 +0,0 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>AvajeLib</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Clustering service for an application.
<P>
A framework for supporting clustering of servers.
</P>
</Body>
</HTML>
@@ -1,59 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 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 = Logger.getLogger(RequestProcessor.class.getName());
private final Socket clientSocket;
private final SocketClusterBroadcast owner;
/**
* 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 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 {
SocketConnection sc = new SocketConnection(clientSocket);
while(true){
if (owner.process(sc)) {
// got the offline message or timeout
break;
}
}
sc.disconnect();
} catch (IOException e) {
logger.log(Level.SEVERE, null, e);
} catch (ClassNotFoundException e) {
logger.log(Level.SEVERE, null, e);
}
}
};
@@ -1,134 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The client side of the socket clustering.
*/
class SocketClient {
private static final Logger logger = Logger.getLogger(SocketClient.class.getName());
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 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.log(Level.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 boolean send(SocketClusterMessage msg) throws IOException {
if (online){
writeObject(msg);
return true;
} else {
return false;
}
}
private void writeObject(Object object) throws IOException {
if (oos == null){
this.oos = new ObjectOutputStream(os);
}
oos.writeObject(object);
oos.flush();
}
}
@@ -1,248 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.GlobalProperties;
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.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
/**
* Broadcast messages across the cluster using sockets.
*/
public class SocketClusterBroadcast implements ClusterBroadcast {
private static final Logger logger = Logger.getLogger(SocketClusterBroadcast.class.getName());
private final SocketClient local;
private final HashMap<String,SocketClient> clientMap;
private final SocketClusterListener listener;
private 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( ){
String localHostPort = GlobalProperties.get("ebean.cluster.local", null);
String members = GlobalProperties.get("ebean.cluster.members", null);
logger.info("Clustering using Sockets local["+localHostPort+"] members["+members+"]");
this.local = new SocketClient(parseFullName(localHostPort));
this.clientMap = new HashMap<String, SocketClient>();
String[] memArray = StringHelper.delimitedToArray(members, ",", false);
for (int i = 0; i < memArray.length; i++) {
InetSocketAddress member = parseFullName(memArray[i]);
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());
}
/**
* 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;
try {
listener.startListening();
register();
} catch (IOException e) {
throw new PersistenceException(e);
}
}
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);
String msg = "Cluster Member ["+members[i].getHostPort()+"] online["+online+"]";
logger.info(msg);
}
}
protected void setMemberOnline(String fullName, boolean online) throws IOException {
synchronized (clientMap) {
String msg = "Cluster Member ["+fullName+"] online["+online+"]";
logger.info(msg);
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
client.send(msg);
} catch (Exception ex){
logger.log(Level.SEVERE, "Error sending message", ex);
try {
client.reconnect();
} catch (IOException e) {
logger.log(Level.SEVERE, "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){
String msg = "Error sending RemoteTransactionEvent "+remoteTransEvent+" to cluster members.";
logger.log(Level.SEVERE, msg, e);
}
}
protected void broadcast(SocketClusterMessage 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 a Cluster message.
*/
protected boolean process(SocketConnection request) throws IOException, ClassNotFoundException {
try {
SocketClusterMessage h = (SocketClusterMessage)request.readObject();
if (h.isRegisterEvent()){
setMemberOnline(h.getRegisterHost(), h.isRegister());
} else {
txnIncoming.incrementAndGet();
DataHolder dataHolder = h.getDataHolder();
RemoteTransactionEvent transEvent = txnSerialiseHelper.read(dataHolder);
transEvent.run();
}
if (h.isRegisterEvent() && !h.isRegister()){
// instance shutting down
return true;
} else {
return false;
}
} catch (InterruptedIOException e) {
String msg = "Timeout waiting for message";
logger.log(Level.INFO, msg, e);
try {
request.disconnect();
} catch (IOException ex){
logger.log(Level.INFO, "Error disconnecting after timeout", ex);
}
return true;
}
}
/**
* Parse a host:port into a InetSocketAddress.
*/
private InetSocketAddress parseFullName(String hostAndPort) {
try {
hostAndPort = hostAndPort.trim();
int colonPos = hostAndPort.indexOf(":");
if (colonPos == -1) {
String msg = "No colon \":\" in "+hostAndPort;
throw new IllegalArgumentException(msg);
}
String host = hostAndPort.substring(0, colonPos);
String sPort = hostAndPort.substring(colonPos + 1, hostAndPort.length());
int port = Integer.parseInt(sPort);
return new InetSocketAddress(host, port);
} catch (Exception ex){
throw new RuntimeException("Error parsing ["+hostAndPort+"] for the form [host:port]", ex);
}
}
class TxnSerialiseHelper extends SerialiseTransactionHelper {
@Override
public SpiEbeanServer getEbeanServer(String serverName) {
return (SpiEbeanServer)clusterManager.getServer(serverName);
}
}
}
@@ -1,164 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.avaje.ebeaninternal.server.lib.thread.ThreadPool;
import com.avaje.ebeaninternal.server.lib.thread.ThreadPoolManager;
/**
* 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 = Logger.getLogger(SocketClusterListener.class.getName());
/**
* The port the SocketListener uses.
*/
private final int port;
/**
* The length of the socket accept timeout.
*/
private final int listenTimeout = 60000;
/**
* 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 ThreadPool 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) {
this.owner = owner;
this.threadPool = ThreadPoolManager.getThreadPool("EbeanClusterMember");
this.port = port;
try {
this.serverListenSocket = new ServerSocket(port);
this.serverListenSocket.setSoTimeout(listenTimeout);
this.listenerThread = new Thread(this, "EbeanClusterListener");
} catch (IOException e){
String msg = "Error starting cluster socket listener on port "+port;
throw new RuntimeException(msg,e);
}
}
/**
* Returns the port the listener is using.
*/
public int getPort() {
return port;
}
/**
* Start listening for requests.
*/
public void startListening() throws IOException {
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.log(Level.SEVERE, null, e);
}
}
/**
* 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.assign(request, true);
isActive = false;
}
} catch (SocketException e) {
if (doingShutdown) {
String msg = "doingShutdown and accept threw:"+ e.getMessage();
logger.info(msg);
} else {
logger.log(Level.SEVERE, null, e);
}
} catch (InterruptedIOException e) {
// this will happen when the server is very quiet.
// that is, no requests
logger.fine("Possibly expected due to accept timeout?" + e.getMessage());
} catch (IOException e) {
// log it and continue in the loop...
logger.log(Level.SEVERE, null, e);
}
}
}
}
@@ -1,78 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.Serializable;
import com.avaje.ebeaninternal.server.cluster.DataHolder;
import com.avaje.ebeaninternal.server.cluster.Packet;
/**
* 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,41 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
/**
* The current state of this cluster member.
*
* @author rbygrave
*/
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,129 +0,0 @@
package com.avaje.ebeaninternal.server.cluster.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* The client side of a TCP Sockect connection.
*/
class SocketConnection {
/**
* The object underlying objectOutputStream.
*/
ObjectOutputStream oos;
/**
* 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();
}
/**
* Write an object to the object output stream.
*/
public ObjectOutputStream writeObject(Object object) throws IOException {
ObjectOutputStream oos = getObjectOutputStream();
oos.writeObject(object);
return oos;
}
/**
* Get the object output stream.
*/
public ObjectOutputStream getObjectOutputStream() throws IOException {
if (oos == null){
oos = new ObjectOutputStream(os);
}
return oos;
}
/**
* Get the object input stream.
*/
public ObjectInputStream getObjectInputStream() throws IOException {
if (ois == null){
ois = new ObjectInputStream(is);
}
return ois;
}
/**
* Set the ObjectInputStream to use.
*/
public void setObjectInputStream(ObjectInputStream ois) {
this.ois = ois;
}
/**
* Set the ObjectOutputStream to use.
*/
public void setObjectOutputStream(ObjectOutputStream oos) {
this.oos = oos;
}
/**
* Return the underlying input stream.
*/
public InputStream getInputStream() throws IOException {
return is;
}
/**
* Return the underlying output stream.
*/
public OutputStream getOutputStream() throws IOException {
return os;
}
}
@@ -1,474 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.UUID;
/**
* Default implementation of TypeConverter.
* <p>
* Converts objects to the required type if required.
* </p>
*/
public final class BasicTypeConverter implements Serializable {
private static final long serialVersionUID = 7691463236204070311L;
/**
* Type code for java.util.Calendar.
*/
public static final int UTIL_CALENDAR = -999998986;
/**
* Type code for java.util.Date.
*/
public static final int UTIL_DATE = -999998988;
/**
* Type code for java.math.BigInteger.
*/
public static final int MATH_BIGINTEGER = -999998987;
/**
* Type code for an Enum type.
*/
public static final int ENUM = -999998989;
private BasicTypeConverter() {
}
/**
* Convert the Object to the required data type.
*
* @param value
* the Object value
* @param toDataType
* the dataType as per java.sql.Types.
*/
public static Object convert(Object value, int toDataType) {
try {
switch (toDataType) {
case UTIL_DATE: {
return toUtilDate(value);
}
case UTIL_CALENDAR: {
return toCalendar(value);
}
case Types.BIGINT: {
return toLong(value);
}
case Types.INTEGER: {
return toInteger(value);
}
case Types.BIT: {
return toBoolean(value);
}
case Types.TINYINT: {
return toByte(value);
}
case Types.SMALLINT: {
return toShort(value);
}
case Types.NUMERIC: {
return toBigDecimal(value);
}
case Types.DECIMAL: {
return toBigDecimal(value);
}
case Types.REAL: {
return toFloat(value);
}
case Types.DOUBLE: {
return toDouble(value);
}
case Types.FLOAT: {
return toDouble(value);
}
case Types.BOOLEAN: {
return toBoolean(value);
}
case Types.TIMESTAMP: {
return toTimestamp(value);
}
case Types.DATE: {
return toDate(value);
}
case Types.VARCHAR: {
return toString(value);
}
case Types.CHAR: {
return toString(value);
}
case Types.OTHER: {
return value;
}
case Types.JAVA_OBJECT: {
return value;
}
case Types.BINARY:
case Types.LONGVARBINARY:
case Types.BLOB: {
return value;
}
case Types.LONGVARCHAR:
case Types.CLOB: {
return value;
}
default: {
String msg = "Unhandled data type [" + toDataType + "] converting [" + value + "]";
throw new RuntimeException(msg);
}
}
} catch (ClassCastException e) {
String m = "ClassCastException converting to data type [" + toDataType + "] value [" + value + "]";
throw new RuntimeException(m);
}
}
/**
* Convert the value to a String.
*/
public static String toString(Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
return (String) value;
}
if (value instanceof char[]) {
return String.valueOf((char[]) value);
}
return value.toString();
}
/**
* Convert the value to a Boolean with an explicit String true value.
*/
public static Boolean toBoolean(Object value, String dbTrueValue) {
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
String s = value.toString();
return s.equalsIgnoreCase(dbTrueValue);
}
/**
* Convert the value to a Boolean. Can be a Boolean or the string values
* "true" or "false".
*/
public static Boolean toBoolean(Object value) {
if (value == null) {
return null;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
return Boolean.valueOf(value.toString());
}
/**
* Convert the value to a UUID.
*/
public static UUID toUUID(Object value) {
if (value == null) {
return null;
}
if (value instanceof String) {
return UUID.fromString((String) value);
}
return (UUID) value;
}
/**
* convert the passed in object to a BigDecimal. It should be another
* numeric type.
*/
public static BigDecimal toBigDecimal(Object value) {
if (value == null) {
return null;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
return new BigDecimal(value.toString());
}
public static Float toFloat(Object value) {
if (value == null) {
return null;
}
if (value instanceof Float) {
return (Float) value;
}
if (value instanceof Number) {
return Float.valueOf(((Number) value).floatValue());
}
return Float.valueOf(value.toString());
}
public static Short toShort(Object value) {
if (value == null) {
return null;
}
if (value instanceof Short) {
return (Short) value;
}
if (value instanceof Number) {
return Short.valueOf(((Number) value).shortValue());
}
return Short.valueOf(value.toString());
}
public static Byte toByte(Object value) {
if (value == null) {
return null;
}
if (value instanceof Byte) {
return (Byte) value;
}
return Byte.valueOf(value.toString());
}
/**
* convert the passed in object to a Integer. It should be another numeric
* type.
*/
public static Integer toInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof Number) {
return Integer.valueOf(((Number) value).intValue());
}
return Integer.valueOf(value.toString());
}
/**
* Convert the object to a Long. It should be another numeric type.
*/
public static Long toLong(Object value) {
if (value == null) {
return null;
}
if (value instanceof Long) {
return (Long) value;
}
if (value instanceof String) {
return Long.valueOf((String) value);
}
if (value instanceof Number) {
return Long.valueOf(((Number) value).longValue());
}
if (value instanceof java.util.Date) {
return Long.valueOf(((java.util.Date) value).getTime());
}
if (value instanceof Calendar) {
return Long.valueOf(((Calendar) value).getTime().getTime());
}
return Long.valueOf(value.toString());
}
public static BigInteger toMathBigInteger(Object value) {
if (value == null) {
return null;
}
if (value instanceof BigInteger) {
return (BigInteger) value;
}
return new BigInteger(value.toString());
}
/**
* Convert the object to a Double. It should be another numberic type.
*/
public static Double toDouble(Object value) {
if (value == null) {
return null;
}
if (value instanceof Double) {
return (Double) value;
}
if (value instanceof Number) {
return Double.valueOf(((Number) value).doubleValue());
}
return Double.valueOf(value.toString());
}
/**
* convert the passed in object to a Timestamp. It is expected to be a
* java.sql.Date really.
*/
public static Timestamp toTimestamp(Object value) {
if (value == null) {
return null;
}
if (value instanceof Timestamp) {
return (Timestamp) value;
} else if (value instanceof java.util.Date) {
// no nanos here... so hopefully ok
return new Timestamp(((java.util.Date) value).getTime());
} else if (value instanceof Calendar) {
return new Timestamp(((Calendar) value).getTime().getTime());
} else if (value instanceof String) {
return Timestamp.valueOf((String) value);
} else if (value instanceof Number) {
return new Timestamp(((Number) value).longValue());
} else {
String msg = "Unable to convert [" + value.getClass().getName() + "] into a Timestamp.";
throw new RuntimeException(msg);
}
}
public static java.sql.Time toTime(Object value) {
if (value == null) {
return null;
}
if (value instanceof java.sql.Time) {
return (java.sql.Time) value;
} else if (value instanceof String) {
return java.sql.Time.valueOf((String) value);
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
}
}
/**
* convert the passed in object to a java sql Date.
*/
public static java.sql.Date toDate(Object value) {
if (value == null) {
return null;
}
if (value instanceof java.sql.Date) {
return (java.sql.Date) value;
} else if (value instanceof java.util.Date) {
return new java.sql.Date(((java.util.Date) value).getTime());
} else if (value instanceof Calendar) {
return new java.sql.Date(((Calendar) value).getTime().getTime());
} else if (value instanceof String) {
return java.sql.Date.valueOf((String) value);
} else if (value instanceof Number) {
return new java.sql.Date(((Number) value).longValue());
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
}
}
/**
* convert the passed in object to a java sql Date.
*/
public static java.util.Date toUtilDate(Object value) {
if (value == null) {
return null;
}
if (value instanceof java.sql.Timestamp) {
// loss of nanos precision
return new java.util.Date(((java.sql.Timestamp) value).getTime());
}
// DEVNOTE: strictly speaking do I need to convert a java.sql.Date to
// java.util.Date? equals() is symmetrical so perhaps this is not
// really required?
if (value instanceof java.sql.Date) {
return new java.util.Date(((java.sql.Date) value).getTime());
}
if (value instanceof java.util.Date) {
return (java.util.Date) value;
} else if (value instanceof Calendar) {
return ((Calendar) value).getTime();
} else if (value instanceof String) {
return new java.util.Date(Timestamp.valueOf((String) value).getTime());
} else if (value instanceof Number) {
return new java.util.Date(((Number) value).longValue());
} else {
throw new RuntimeException("Unable to convert [" + value.getClass().getName() + "] into a java.util.Date");
}
}
/**
* convert the passed in object to a java sql Date.
*/
public static Calendar toCalendar(Object value) {
if (value == null) {
return null;
}
if (value instanceof Calendar) {
return (Calendar) value;
} else if (value instanceof java.util.Date) {
java.util.Date date = ((java.util.Date) value);
return toCalendarFromDate(date);
} else if (value instanceof String) {
java.util.Date date = toUtilDate(value);
return toCalendarFromDate(date);
} else if (value instanceof Number) {
long timeMillis = ((Number) value).longValue();
java.util.Date date = new java.util.Date(timeMillis);
return toCalendarFromDate(date);
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.util.Date";
throw new RuntimeException(m);
}
}
private static Calendar toCalendarFromDate(java.util.Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
}
@@ -1,127 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.sql.Connection;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.LogLevel;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiTransaction;
/**
* Base class for find and persist requests.
*/
public abstract class BeanRequest {
/**
* The server processing the request.
*/
final SpiEbeanServer ebeanServer;
final String serverName;
/**
* The transaction this is part of.
*/
SpiTransaction transaction;
boolean createdTransaction;
boolean readOnly;
public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) {
this.ebeanServer = ebeanServer;
this.serverName = ebeanServer.getName();
this.transaction = t;
}
/**
* initialise an implicit transaction if one is not currently supplied.
* <p>
* A transaction may have been passed in or active in the thread local. If
* not then create one implicitly to handle the request.
* </p>
*/
public abstract void initTransIfRequired();
/**
* A helper method for creating an implicit transaction is it is required.
* <p>
* A transaction may have been passed in or active in the thread local. If
* not then create one implicitly to handle the request.
* </p>
*/
public void createImplicitTransIfRequired(boolean readOnlyTransaction) {
if (transaction == null) {
transaction = ebeanServer.getCurrentServerTransaction();
if (transaction == null || !transaction.isActive()) {
// create an implicit transaction to execute this query
transaction = ebeanServer.createServerTransaction(false, -1);
// commented out for performance reasons...
// TODO: review performance of trans.setReadOnly(true)
//if (readOnlyTransaction) {
// readOnly = true;
// transaction.setReadOnly(true);
//}
createdTransaction = true;
}
}
}
/**
* Commit this transaction if it was created for this request.
*/
public void commitTransIfRequired() {
if (createdTransaction) {
if (readOnly) {
transaction.rollback();
} else {
transaction.commit();
}
}
}
/**
* Rollback the transaction if it was created for this request.
*/
public void rollbackTransIfRequired() {
if (createdTransaction) {
transaction.rollback();
}
}
/**
* Return the server processing the request. Made available for
* BeanController and BeanFinder.
*/
public EbeanServer getEbeanServer() {
return ebeanServer;
}
/**
* Return the Transaction associated with this request.
*/
public SpiTransaction getTransaction() {
return transaction;
}
/**
* Returns the connection from the Transaction.
*/
public Connection getConnection() {
return transaction.getInternalConnection();
}
/**
* Return true if SQL should be logged for this transaction.
*/
public boolean isLogSql() {
return transaction.getLogLevel().ordinal() >= LogLevel.SQL.ordinal();
}
/**
* Return true if SUMMARY information should be logged for this transaction.
*/
public boolean isLogSummary() {
return transaction.getLogLevel().ordinal() >= LogLevel.SUMMARY.ordinal();
}
}
@@ -1,98 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import com.avaje.ebeaninternal.server.util.ClassPathSearch;
import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter;
/**
* Searches for interesting classes such as Entities, Embedded and ScalarTypes.
*/
public class BootupClassPathSearch {
private static final Logger logger = Logger.getLogger(BootupClassPathSearch.class.getName());
private final Object monitor = new Object();
private final ClassLoader classLoader;
private final List<String> packages;
private final List<String> jars;
private BootupClasses bootupClasses;
/**
* Construct and search for interesting classes.
*/
public BootupClassPathSearch(ClassLoader classLoader, List<String> packages, List<String> jars) {
this.classLoader = (classLoader == null) ? getClass().getClassLoader() : classLoader;
this.packages = packages;
this.jars = jars;
}
public BootupClasses getBootupClasses() {
synchronized (monitor) {
if (bootupClasses == null){
bootupClasses = search();
}
return bootupClasses;
}
}
/**
* Search the classPath for the classes we are interested in.
*/
private BootupClasses search() {
synchronized (monitor) {
try {
BootupClasses bc = new BootupClasses();
long st = System.currentTimeMillis();
ClassPathSearchFilter filter = createFilter();
ClassPathSearch finder = new ClassPathSearch(classLoader, filter, bc);
finder.findClasses();
Set<String> jars = finder.getJarHits();
Set<String> pkgs = finder.getPackageHits();
long searchTime = System.currentTimeMillis() - st;
String msg = "Classpath search hits in jars" + jars + " pkgs" + pkgs + " searchTime[" + searchTime+ "]";
logger.info(msg);
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) {
for (String packageName : packages) {
filter.includePackage(packageName);
}
}
if (jars != null) {
for (String jarName : jars) {
filter.includeJar(jarName);
}
}
return filter;
}
}
@@ -1,441 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import com.avaje.ebean.annotation.LdapDomain;
import com.avaje.ebean.config.CompoundType;
import com.avaje.ebean.config.ScalarTypeConverter;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.ServerConfigStartup;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher;
/**
* Interesting classes for a EbeanServer such as Embeddable, Entity,
* ScalarTypes, Finders, Listeners and Controllers.
*/
public class BootupClasses implements ClassPathSearchMatcher {
private static final Logger logger = Logger.getLogger(BootupClasses.class.getName());
private ArrayList<Class<?>> xmlBeanList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> embeddableList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> entityList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> scalarTypeList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> scalarConverterList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> compoundTypeList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> beanControllerList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> transactionEventListenerList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> beanFinderList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> beanListenerList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> beanQueryAdapterList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> luceneIndexList = new ArrayList<Class<?>>();
private ArrayList<Class<?>> serverConfigStartupList = new ArrayList<Class<?>>();
private ArrayList<ServerConfigStartup> serverConfigStartupInstances = new ArrayList<ServerConfigStartup>();
private List<BeanPersistController> persistControllerInstances = new ArrayList<BeanPersistController>();
private List<BeanPersistListener<?>> persistListenerInstances = new ArrayList<BeanPersistListener<?>>();
private List<BeanQueryAdapter> queryAdapterInstances = new ArrayList<BeanQueryAdapter>();
private List<TransactionEventListener> transactionEventListenerInstances = new ArrayList<TransactionEventListener>();
public BootupClasses() {
}
public BootupClasses(List<Class<?>> list) {
if (list != null) {
process(list.iterator());
}
}
private BootupClasses(BootupClasses parent) {
this.xmlBeanList.addAll(parent.xmlBeanList);
this.embeddableList.addAll(parent.embeddableList);
this.entityList.addAll(parent.entityList);
this.scalarTypeList.addAll(parent.scalarTypeList);
this.scalarConverterList.addAll(parent.scalarConverterList);
this.compoundTypeList.addAll(parent.compoundTypeList);
this.beanControllerList.addAll(parent.beanControllerList);
this.transactionEventListenerList.addAll(parent.transactionEventListenerList);
this.beanFinderList.addAll(parent.beanFinderList);
this.beanListenerList.addAll(parent.beanListenerList);
this.beanQueryAdapterList.addAll(parent.beanQueryAdapterList);
this.luceneIndexList.addAll(parent.luceneIndexList);
this.serverConfigStartupList.addAll(parent.serverConfigStartupList);
}
private void process(Iterator<Class<?>> it) {
while (it.hasNext()) {
Class<?> cls = it.next();
isMatch(cls);
}
}
/**
* Create a copy of this object so that classes can be added to it.
*/
public BootupClasses createCopy() {
return new BootupClasses(this);
}
/**
* Run any ServerConfigStartup listeners.
*/
public void runServerConfigStartup(ServerConfig serverConfig) {
for (Class<?> cls : serverConfigStartupList) {
try {
ServerConfigStartup newInstance = (ServerConfigStartup) cls.newInstance();
newInstance.onStart(serverConfig);
} catch (Exception e) {
String msg = "Error creating BeanQueryAdapter " + cls;
logger.log(Level.SEVERE, msg, e);
}
}
}
public void addQueryAdapters(List<BeanQueryAdapter> queryAdapterInstances) {
if (queryAdapterInstances != null) {
for (BeanQueryAdapter a : queryAdapterInstances) {
this.queryAdapterInstances.add(a);
// don't automatically instantiate
this.beanQueryAdapterList.remove(a.getClass());
}
}
}
/**
* Add BeanPersistController instances.
*/
public void addPersistControllers(List<BeanPersistController> beanControllerInstances) {
if (beanControllerInstances != null) {
for (BeanPersistController c : beanControllerInstances) {
this.persistControllerInstances.add(c);
// don't automatically instantiate
this.beanControllerList.remove(c.getClass());
}
}
}
/**
* Add TransactionEventListeners instances.
*/
public void addTransactionEventListeners(List<TransactionEventListener> transactionEventListeners) {
if (transactionEventListeners != null) {
for (TransactionEventListener c : transactionEventListeners) {
this.transactionEventListenerInstances.add(c);
// don't automatically instantiate
this.transactionEventListenerList.remove(c.getClass());
}
}
}
public void addPersistListeners(List<BeanPersistListener<?>> listenerInstances) {
if (listenerInstances != null) {
for (BeanPersistListener<?> l : listenerInstances) {
this.persistListenerInstances.add(l);
// don't automatically instantiate
this.beanListenerList.remove(l.getClass());
}
}
}
public void addServerConfigStartup(List<ServerConfigStartup> startupInstances) {
if (startupInstances != null) {
for (ServerConfigStartup l : startupInstances) {
this.serverConfigStartupInstances.add(l);
// don't automatically instantiate
this.serverConfigStartupList.remove(l.getClass());
}
}
}
public List<BeanQueryAdapter> getBeanQueryAdapters() {
// add class registered BeanQueryAdapter to the
// already created instances
for (Class<?> cls : beanQueryAdapterList) {
try {
BeanQueryAdapter newInstance = (BeanQueryAdapter) cls.newInstance();
queryAdapterInstances.add(newInstance);
} catch (Exception e) {
String msg = "Error creating BeanQueryAdapter " + cls;
logger.log(Level.SEVERE, msg, e);
}
}
return queryAdapterInstances;
}
public List<BeanPersistListener<?>> getBeanPersistListeners() {
// add class registered BeanPersistController to the
// already created instances
for (Class<?> cls : beanListenerList) {
try {
BeanPersistListener<?> newInstance = (BeanPersistListener<?>) cls.newInstance();
persistListenerInstances.add(newInstance);
} catch (Exception e) {
String msg = "Error creating BeanPersistController " + cls;
logger.log(Level.SEVERE, msg, e);
}
}
return persistListenerInstances;
}
public List<BeanPersistController> getBeanPersistControllers() {
// add class registered BeanPersistController to the
// already created instances
for (Class<?> cls : beanControllerList) {
try {
BeanPersistController newInstance = (BeanPersistController) cls.newInstance();
persistControllerInstances.add(newInstance);
} catch (Exception e) {
String msg = "Error creating BeanPersistController " + cls;
logger.log(Level.SEVERE, msg, e);
}
}
return persistControllerInstances;
}
public List<TransactionEventListener> getTransactionEventListeners() {
// add class registered TransactionEventListener to the
// already created instances
for (Class<?> cls : transactionEventListenerList) {
try {
TransactionEventListener newInstance = (TransactionEventListener) cls.newInstance();
transactionEventListenerInstances.add(newInstance);
} catch (Exception e) {
String msg = "Error creating TransactionEventListener " + cls;
logger.log(Level.SEVERE, msg, e);
}
}
return transactionEventListenerInstances;
}
/**
* Return the list of Embeddable classes.
*/
public ArrayList<Class<?>> getEmbeddables() {
return embeddableList;
}
/**
* Return the list of entity classes.
*/
public ArrayList<Class<?>> getEntities() {
return entityList;
}
/**
* Return the list of ScalarTypes found.
*/
public ArrayList<Class<?>> getScalarTypes() {
return scalarTypeList;
}
/**
* Return the list of ScalarConverters found.
*/
public ArrayList<Class<?>> getScalarConverters() {
return scalarConverterList;
}
/**
* Return the list of ScalarConverters found.
*/
public ArrayList<Class<?>> getCompoundTypes() {
return compoundTypeList;
}
/**
* Return the list of BeanControllers found.
*/
public ArrayList<Class<?>> getBeanControllers() {
return beanControllerList;
}
/**
* Return the list of TransactionEventListeners found
*/
public ArrayList<Class<?>> getTransactionEventListenerList() {
return transactionEventListenerList;
}
/**
* Return the list of BeanFinders found.
*/
public ArrayList<Class<?>> getBeanFinders() {
return beanFinderList;
}
/**
* Return the list of BeanListeners found.
*/
public ArrayList<Class<?>> getBeanListeners() {
return beanListenerList;
}
/**
* Return the list of XML Beans.
*/
public ArrayList<Class<?>> getXmlBeanList() {
return xmlBeanList;
}
public void add(Iterator<Class<?>> it) {
while (it.hasNext()) {
Class<?> clazz = it.next();
isMatch(clazz);
}
}
public boolean isMatch(Class<?> cls) {
if (isEmbeddable(cls)) {
embeddableList.add(cls);
} else if (isEntity(cls)) {
entityList.add(cls);
} else if (isXmlBean(cls)){
entityList.add(cls);
//xmlBeanList.add(cls);
} else if (isInterestingInterface(cls)) {
return true;
} else {
return false;
}
return true;
}
/**
* Look for interesting interfaces.
* <p>
* This includes ScalarType, BeanController, BeanFinder and BeanListener.
* </p>
*/
private boolean isInterestingInterface(Class<?> cls) {
boolean interesting = false;
if (BeanPersistController.class.isAssignableFrom(cls)) {
beanControllerList.add(cls);
interesting = true;
}
if (TransactionEventListener.class.isAssignableFrom(cls)) {
transactionEventListenerList.add(cls);
interesting = true;
}
if (ScalarType.class.isAssignableFrom(cls)) {
scalarTypeList.add(cls);
interesting = true;
}
if (ScalarTypeConverter.class.isAssignableFrom(cls)) {
scalarConverterList.add(cls);
interesting = true;
}
if (CompoundType.class.isAssignableFrom(cls)) {
compoundTypeList.add(cls);
interesting = true;
}
if (BeanFinder.class.isAssignableFrom(cls)) {
beanFinderList.add(cls);
interesting = true;
}
if (BeanPersistListener.class.isAssignableFrom(cls)) {
beanListenerList.add(cls);
interesting = true;
}
if (BeanQueryAdapter.class.isAssignableFrom(cls)) {
beanQueryAdapterList.add(cls);
interesting = true;
}
if (ServerConfigStartup.class.isAssignableFrom(cls)){
serverConfigStartupList.add(cls);
interesting = true;
}
return interesting;
}
private boolean isEntity(Class<?> cls) {
Annotation ann = cls.getAnnotation(Entity.class);
if (ann != null) {
return true;
}
ann = cls.getAnnotation(Table.class);
if (ann != null) {
return true;
}
ann = cls.getAnnotation(LdapDomain.class);
if (ann != null) {
return true;
}
return false;
}
private boolean isEmbeddable(Class<?> cls) {
Annotation ann = cls.getAnnotation(Embeddable.class);
if (ann != null) {
return true;
}
return false;
}
private boolean isXmlBean(Class<?> cls) {
Annotation ann = cls.getAnnotation(XmlRootElement.class);
if (ann != null) {
return true;
}
ann = cls.getAnnotation(XmlType.class);
if (ann != null) {
// Only looking for Beans and not Enums
return !cls.isEnum();
}
return false;
}
}
@@ -1,90 +0,0 @@
package com.avaje.ebeaninternal.server.core;
/**
* Options for controlling cache behaviour for a given type.
*/
public class CacheOptions {
private boolean useCache;
private boolean readOnly;
private String naturalKey;
private String warmingQuery;
/**
* Construct with options.
*/
public CacheOptions() {
}
/**
* Return true if this should use a cache for lazy loading.
*/
public boolean isUseCache() {
return useCache;
}
/**
* Set whether to use the bean cache for the associated type.
*/
public void setUseCache(boolean useCache) {
this.useCache = useCache;
}
/**
* Return the readOnly default setting.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set read Only default setting.
*/
public void setReadOnly(boolean readOnly) {
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.
*/
public boolean isUseNaturalKeyCache() {
return naturalKey != null;
}
/**
* Return the natural key property.
*/
public String getNaturalKey() {
return naturalKey;
}
/**
* Set the natural key property.
*/
public void setNaturalKey(String naturalKey) {
if (naturalKey == null || naturalKey.length() == 0){
naturalKey = null;
} else {
this.naturalKey = naturalKey.trim();
}
}
}

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