mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
initial add of EbeanORM server based on v2.8.1
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
*.sql
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
target/
|
||||
logs/
|
||||
@@ -0,0 +1,219 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-javaparent</artifactId>
|
||||
<version>1.1</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-server</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm-server</name>
|
||||
<url>http://www.avaje.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.transaction</groupId>
|
||||
<artifactId>jta</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-api</artifactId>
|
||||
<version>[3.1,4)</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-agent</artifactId>
|
||||
<version>[3.1,4)</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>1.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.scala-lang</groupId>
|
||||
<artifactId>scala-library</artifactId>
|
||||
<version>2.10.0-M6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.3.153</version>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<!-- Enhance the test classes -->
|
||||
<build>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
|
||||
<execution>
|
||||
<id>enhance-test-classes</id>
|
||||
<phase>process-test-classes</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<property name="compile_classpath" refid="maven.compile.classpath" />
|
||||
<echo message="Ebean enhancing test classes debug level -----------------------------------" />
|
||||
<echo message="Classpath: ${compile_classpath}" />
|
||||
<taskdef name="ebeanEnhance" classname="com.avaje.ebean.enhance.ant.AntEnhanceTask" classpath="${compile_classpath}" />
|
||||
<ebeanEnhance classSource="${project.build.testOutputDirectory}" packages="com.avaje.tests.**" transformArgs="debug=1" />
|
||||
</tasks>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<configuration>
|
||||
<useSystemClassLoader>false</useSystemClassLoader>
|
||||
<failIfNoTests>false</failIfNoTests>
|
||||
<includes>
|
||||
<include>**/Test*.java</include>
|
||||
<include>**/*Test.java</include>
|
||||
<include>**/*Tests.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
</plugins>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. -->
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<versionRange>[1.7,)</versionRange>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<ignore></ignore>
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,19 @@
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||
<id>agent</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/classes</directory>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
<includes>
|
||||
<include>com/avaje/ebean/enhance/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package com.avaje.ebeaninternal.jdbc;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLWarning;
|
||||
import java.sql.Savepoint;
|
||||
import java.sql.Statement;
|
||||
import java.util.Map;
|
||||
|
||||
public class ConnectionDelegator implements Connection
|
||||
{
|
||||
private final Connection delegate;
|
||||
|
||||
public ConnectionDelegator(Connection delegate)
|
||||
{
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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.ParameterMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Ref;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLWarning;
|
||||
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;
|
||||
}
|
||||
|
||||
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 i, Object o, int i1, int i2) throws SQLException
|
||||
{
|
||||
delegate.setObject(i, o, i1, i2);
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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;
|
||||
|
||||
public class ConnectionDelegator implements Connection
|
||||
{
|
||||
private final Connection delegate;
|
||||
|
||||
public ConnectionDelegator(Connection delegate)
|
||||
{
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Copyright (C) 2010 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (C) 2010 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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.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();
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (C) 2009 Robin Bygrave
|
||||
*
|
||||
* 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.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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebean.CallableSql;
|
||||
|
||||
public interface SpiCallableSql extends CallableSql {
|
||||
|
||||
public BindParams getBindParams();
|
||||
|
||||
public TransactionEventTable getTransactionEventTable();
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
/**
|
||||
* Copyright (C) 2009 Robin Bygrave
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import com.avaje.ebean.SqlUpdate;
|
||||
|
||||
public interface SpiSqlUpdate extends SqlUpdate {
|
||||
|
||||
public BindParams getBindParams();
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
public interface SpiTransactionScopeManager {
|
||||
|
||||
public void replace(SpiTransaction t);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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.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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Internal service API.
|
||||
*/
|
||||
package com.avaje.ebeaninternal.api;
|
||||
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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;
|
||||
|
||||
public class ConnectionDelegator implements Connection
|
||||
{
|
||||
private final Connection delegate;
|
||||
|
||||
public ConnectionDelegator(Connection delegate)
|
||||
{
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,582 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>AutoFetch Implementation</title>
|
||||
</head>
|
||||
<body>
|
||||
AutoFetch Implementation
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>BeanFinders, BeanControllers etc for "meta" beans</title>
|
||||
</head>
|
||||
<body>
|
||||
BeanFinders, BeanControllers etc for "meta" beans
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Default L2 server cache implementation.
|
||||
*/
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* 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.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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.IOException;
|
||||
|
||||
import com.avaje.ebeaninternal.server.cluster.BinaryMessageList;
|
||||
|
||||
public interface Message {
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException;
|
||||
|
||||
public boolean isControlMessage();
|
||||
|
||||
public String getToHostPort();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<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>
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* This library 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.
|
||||
*
|
||||
* This library 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 this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (C) 2006 Robin Bygrave
|
||||
*
|
||||
* 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.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();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user