Compare commits

...
Author SHA1 Message Date
rbygrave 34be7cc1f1 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.2 2014-12-18 00:46:32 +13:00
rbygrave 54f21836b0 Javadoc update for RawSql 2014-12-18 00:44:58 +13:00
rbygrave 4adf5a9eba Fix for #224 - ENH: Add tableAliasMapping() to RawSqlBuilder ... for better mapping of complex objects with raw sql 2014-12-17 23:58:27 +13:00
rbygrave b6a0653a3c Fix for #223 - RawSql that includes a OneToMany not working 2014-12-17 23:05:53 +13:00
rbygrave c0810a375e No effective change - code formatting 2014-12-17 23:04:59 +13:00
rbygrave 90fe19eb8d cleanup of ebean.properties 2014-12-17 21:04:21 +13:00
rbygrave 43ecfe7c19 Fix for #185 - RawSql parse not working with "order siblings by NAME" 2014-12-17 21:03:53 +13:00
rbygrave 15be5dd4c7 Small tweak to example ebean.properties 2014-12-17 00:54:34 +13:00
rbygrave d54cffacea #222 - SQL Server - OnetoMany with compound key using IN is not legal in mssql 2014-12-17 00:53:42 +13:00
rbygrave 965de6e84d #101 - SQL SERVER - drop.sql does not disable referential integrity 2014-12-17 00:52:31 +13:00
rbygrave 56fd6b90be Rename db columns that clash with sql server keywords etc 2014-12-17 00:49:23 +13:00
rbygrave dd85eba3f0 Rename db columns that clash with sql server keywords etc 2014-12-17 00:32:43 +13:00
rbygrave d1a40d5676 Enable disallowBatchOnCascade per DatabasePlatform (added for SQL Server) 2014-12-17 00:31:29 +13:00
rbygrave b0462bcda6 Enable disallowBatchOnCascade per DatabasePlatform (added for SQL Server) 2014-12-17 00:28:28 +13:00
rbygrave 59428bb214 Rename db columns that clash with sql server keywords etc 2014-12-17 00:20:48 +13:00
rbygrave 73cf3e8511 [maven-release-plugin] prepare for next development iteration 2014-12-13 01:14:36 +13:00
rbygrave beff4465b0 [maven-release-plugin] prepare release avaje-ebeanorm-4.5.1 2014-12-13 01:14:14 +13:00
rbygrave 563cdd3b3b Fix for #221 - ebean 4.4.1 does no longer respects ebean.namingConvention.useForeignKeyPrefix 2014-12-13 01:12:28 +13:00
rbygrave 1bd7b99235 Added @Transactional support for PersistBatch #220 2014-12-12 23:32:41 +13:00
rbygrave bb9d9f7e17 Bump pom to 4.5.1-SNAPSHOT 2014-12-10 03:30:56 +13:00
rbygrave 6bb38f2674 #220 - Refactor Persist batch, add effectively add PersistBatch.INSERT (to ALL and NONE) and allow batching per request (save(), insert(), update(), delete()) 2014-12-10 03:29:44 +13:00
rbygrave bb6571f426 javadoc update, eol characters 2014-12-04 01:47:11 +13:00
rbygrave 1ee679c5ae javadoc update 2014-12-04 01:20:35 +13:00
rbygrave e5264ca69e Update version in README to 4.4.1 2014-12-03 22:35:09 +13:00
rbygrave 3a86be5f3d #219 - Update PagedList javadoc - enable foreground query for getTotalRowCount() when loadRowCount() has not been used 2014-12-03 22:33:35 +13:00
rbygrave 9f71b0df22 [maven-release-plugin] prepare for next development iteration 2014-12-01 23:39:03 +13:00
rbygrave f271ea8a0d [maven-release-plugin] prepare release avaje-ebeanorm-4.4.1 2014-12-01 23:38:42 +13:00
rbygrave 1ced574cd8 bump pom version to 4.4.1-SNAPSHOT 2014-12-01 23:36:41 +13:00
rbygrave 9dd412cd19 #218 - Internal change - modify SequenceIdGenerator to use Long instead of Integer 2014-12-01 23:35:25 +13:00
rbygrave 8237072f5b #217 - ENH: Restore findFutureList() removing its previous deprecated status. Change implementation to use its own PersistenceContext (aka PersistenceContextScope.QUERY) 2014-12-01 23:09:25 +13:00
rbygrave dd30b43d59 #216 - ENH: Add Model.Finder deleteById(id) method 2014-12-01 23:02:58 +13:00
rbygrave 09f8967459 #215 - Remove findPagingList() ... it has been deprecated for a while, please migrate to findPagedList() 2014-12-01 21:52:40 +13:00
rbygrave e3bf3629ee Remove extraneous ServerConfig.enhanceLogLevel which is no longer used 2014-12-01 21:27:27 +13:00
rbygrave b6edd33f9a Fix for #214 - Change default lazyLoadBatchSize from 1 to 10 (in ServerConfig) 2014-12-01 21:16:30 +13:00
rbygrave 3a11a23e01 Fix for #213 - Make PathProperties easier to use - deprecate JsonWriteOptions 2014-12-01 21:10:51 +13:00
rbygrave 370267ab6f Refactor properties bootup, remove GlobalProperties and allow ServerConfig.loadFromProperties() 2014-11-30 17:08:02 +13:00
rbygrave fe65a0ccb9 Change Test - make test more explicit wrt batch lazy loading 2014-11-30 12:55:46 +13:00
rbygrave 2837324a03 Change Test - make test more explicit wrt batch lazy loading 2014-11-30 12:55:16 +13:00
rbygrave a6bd614f87 bump version in readme 2014-11-27 01:20:57 +13:00
rbygrave dfaf4b2f31 [maven-release-plugin] prepare for next development iteration 2014-11-27 01:01:41 +13:00
201 changed files with 9824 additions and 7943 deletions
+1 -1
View File
@@ -7,6 +7,6 @@ Maven Dependency
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.3.0</version>
<version>4.4.1</version>
</dependency>
+31 -5
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.3.1</version>
<version>4.5.2</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -89,6 +89,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -99,14 +106,14 @@
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>4.1.9</version>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.153</version>
<version>1.4.182</version>
<scope>test</scope>
</dependency>
@@ -191,7 +198,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.1.9</version>
<version>4.5.1</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -241,8 +248,27 @@
<doctitle>Ebean 4</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<!-- <excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames> -->
<additionalparam>-Xdoclint:none</additionalparam>
<!--<additionalparam>-Xdoclint:none</additionalparam>-->
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
<version>1.0.0</version>
</docletArtifact>
<additionalparam>
-Xdoclint:none
-include-basedir ${project.basedir}
-attributes "idseparator=-; project_name=${project.name}; \
project_version=${project.version}; \
project_desc=${project.description}"
</additionalparam>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -81,10 +81,6 @@ public interface BeanState {
* {@link EbeanServer#createEntityBean(Class)}, then populate its properties
* and then call this method specifying which properties where loaded or null
* for a fully loaded entity bean.
*
* @param loadedProperties
* the properties that where loaded or null for a fully loaded entity
* bean.
*/
public void setLoaded();
}
@@ -16,8 +16,6 @@ class DRawSqlParser {
public static final String $_WHERE = "${where}";
private static final String ORDER_BY = "order by";
private final SimpleTextParser textParser;
private String sql;
@@ -35,6 +33,7 @@ class DRawSqlParser {
private int groupByPos = -1;
private int havingPos = -1;
private int orderByPos = -1;
private int orderByStmtPos = -1;
private boolean whereExprAnd;
private int whereExprPos = -1;
@@ -66,12 +65,12 @@ class DRawSqlParser {
String preFrom = removeWhitespace(findPreFromSql());
String preWhere = removeWhitespace(findPreWhereSql());
String preHaving = removeWhitespace(findPreHavingSql());
String orderByPrefix = findOrderByPrefixSql();
String orderBySql = findOrderBySql();
preFrom = trimSelectKeyword(preFrom);
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd,
orderBySql, (distinctPos > -1));
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
}
/**
@@ -142,12 +141,12 @@ class DRawSqlParser {
return preWhereExprSql;
}
private String findOrderByPrefixSql() {
return (orderByPos < 1) ? null : sql.substring(orderByPos, orderByStmtPos);
}
private String findOrderBySql() {
if (orderByPos > -1) {
int pos = orderByPos + ORDER_BY.length();
return sql.substring(pos).trim();
}
return null;
return (orderByStmtPos < 1) ? null : sql.substring(orderByStmtPos).trim();
}
private String findPreHavingSql() {
@@ -226,6 +225,11 @@ class DRawSqlParser {
}
orderByPos = textParser.findWordLower("order", startOrderBy);
if (orderByPos > 1) {
// there might be keywords like siblings in between the order
// and by so search for the by keyword explicitly
orderByStmtPos = 2 + textParser.findWordLower("by", orderByPos);
}
}
private int findWhereExprPosition() {
+6 -13
View File
@@ -15,7 +15,6 @@ import org.slf4j.LoggerFactory;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
@@ -155,14 +154,14 @@ public final class Ebean {
// skipDefaultServer is set by EbeanServerFactory
// ... when it is creating the primaryServer
if (GlobalProperties.isSkipPrimaryServer()) {
if (PrimaryServer.isSkip()) {
// primary server being created by EbeanServerFactory
// ... so we should not try and create it here
logger.debug("GlobalProperties.isSkipPrimaryServer()");
logger.debug("PrimaryServer.isSkip()");
} else {
// look to see if there is a default server defined
String primaryName = getPrimaryServerName();
String primaryName = PrimaryServer.getPrimaryServerName();
logger.debug("primaryName:" + primaryName);
if (primaryName != null && primaryName.trim().length() > 0) {
primaryServer = getWithCreate(primaryName.trim());
@@ -170,12 +169,6 @@ public final class Ebean {
}
}
private String getPrimaryServerName() {
String serverName = GlobalProperties.get("ebean.default.datasource", null);
return GlobalProperties.get("datasource.default", serverName);
}
private EbeanServer getPrimaryServer() {
if (primaryServer == null) {
String msg = "The default EbeanServer has not been defined?";
@@ -678,7 +671,8 @@ public final class Ebean {
/**
* Refresh the values of a bean.
* <p>
* Note that this does not refresh any OneToMany or ManyToMany properties.
* Note that this resets OneToMany and ManyToMany properties so that if they
* are accessed a lazy load will refresh the many property.
* </p>
*/
public static void refresh(Object bean) {
@@ -1383,8 +1377,7 @@ public final class Ebean {
/**
* Return the BeanState for a given entity bean.
* <p>
* This will return null if the bean is not an enhanced (or subclassed) entity
* bean.
* This will return null if the bean is not an enhanced entity bean.
* </p>
*/
public static BeanState getBeanState(Object bean) {
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,12 @@
package com.avaje.ebean;
import javax.persistence.PersistenceException;
import com.avaje.ebean.common.BootupEbeanManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.util.ClassUtil;
import javax.persistence.PersistenceException;
import java.lang.reflect.Constructor;
import java.util.Properties;
/**
* Creates EbeanServer instances.
@@ -27,31 +28,43 @@ import com.avaje.ebean.util.ClassUtil;
*/
public class EbeanServerFactory {
private static BootupEbeanManager serverFactory = createServerFactory();
private static BootupEbeanManager bootupEbeanManager;
/**
* Initialise the container with clustering configuration.
*
* Call this prior to creating any EbeanServer instances or alternatively set the
* ContainerConfig on the ServerConfig when creating the first EbeanServer instance.
*/
public static synchronized void initialiseContainer(ContainerConfig containerConfig) {
getServerFactory(containerConfig);
}
/**
* Create using ebean.properties to configure the server.
*/
public static EbeanServer create(String name) {
public static synchronized EbeanServer create(String name) {
EbeanServer server = serverFactory.createServer(name);
return server;
// construct based on loading properties files
// and if invoked by Ebean then it handles registration
BootupEbeanManager serverFactory = getServerFactory(null);
return serverFactory.createServer(name);
}
/**
* Create using the ServerConfig object to configure the server.
*/
public static EbeanServer create(ServerConfig config) {
public static synchronized EbeanServer create(ServerConfig config) {
if (config.getName() == null) {
throw new PersistenceException("The name is null (it is required)");
}
EbeanServer server = serverFactory.createServer(config);
EbeanServer server = createInternal(config);
if (config.isDefaultServer()) {
GlobalProperties.setSkipPrimaryServer(true);
PrimaryServer.setSkip(true);
}
if (config.isRegister()) {
Ebean.register(server, config.isDefaultServer());
@@ -60,13 +73,45 @@ public class EbeanServerFactory {
return server;
}
private static BootupEbeanManager createServerFactory() {
private static EbeanServer createInternal(ServerConfig config) {
return getServerFactory(config.getContainerConfig()).createServer(config);
}
/**
* Get the BootupEbeanManager initialising it if necessary.
*
* @param containerConfig the configuration controlling clustering communication
*/
private static BootupEbeanManager getServerFactory(ContainerConfig containerConfig) {
if (bootupEbeanManager != null) {
return bootupEbeanManager;
}
if (containerConfig == null) {
// effectively load configuration from ebean.properties
Properties properties = PrimaryServer.getProperties();
containerConfig = new ContainerConfig();
containerConfig.loadFromProperties(properties);
}
bootupEbeanManager = createServerFactory(containerConfig);
return bootupEbeanManager;
}
/**
* Create the container instance using the configuration.
*/
private static BootupEbeanManager createServerFactory(ContainerConfig containerConfig) {
String dflt = "com.avaje.ebeaninternal.server.core.DefaultServerFactory";
String implClassName = System.getProperty("ebean.serverfactory", dflt);
try {
return (BootupEbeanManager) ClassUtil.newInstance(implClassName);
Class<?> cls = Class.forName(implClassName);
Constructor<?> constructor = cls.getConstructor(ContainerConfig.class);
return (BootupEbeanManager) constructor.newInstance(containerConfig);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@@ -208,25 +208,6 @@ public interface ExpressionList<T> extends Serializable {
*/
public FutureList<T> findFutureList();
/**
* Return a PagingList for this query.
* <p>
* This can be used to break up a query into multiple queries to fetch the
* data a page at a time.
* </p>
* <p>
* This typically works by using a query per page and setting
* {@link Query#setFirstRow(int)} and and {@link Query#setMaxRows(int)} on the
* query. This usually would translate into SQL that uses limit offset, rownum
* or row_number function to limit the result set.
* </p>
*
* @param pageSize
* the number of beans fetched per Page
* @deprecated
*/
public PagingList<T> findPagingList(int pageSize);
/**
* Return a PagedList for this query.
* <p>
+30 -7
View File
@@ -1,7 +1,11 @@
package com.avaje.ebean;
import javax.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* FutureList represents the result of a background query execution that will
@@ -15,13 +19,13 @@ import java.util.concurrent.Future;
* A simple example:
* </p>
*
* <pre class="code">
* <pre>{@code
* // create a query to find all orders
* Query&lt;Order&gt; query = Ebean.find(Order.class);
* Query<Order> query = Ebean.find(Order.class);
*
* // execute the query in a background thread
* // immediately returning the futureList
* FutureList&lt;Order&gt; futureList = query.findFutureList();
* FutureList<Order> futureList = query.findFutureList();
*
* // do something else ...
*
@@ -35,13 +39,11 @@ import java.util.concurrent.Future;
*
* if (!futureList.isCancelled()){
* // wait for the query to finish and return the list
* List&lt;Order&gt; list = futureList.get();
* List<Order> list = futureList.get();
* ...
* }
*
* </pre>
*
* @author rbygrave
* }</pre>
*/
public interface FutureList<T> extends Future<List<T>> {
@@ -50,4 +52,25 @@ public interface FutureList<T> extends Future<List<T>> {
*/
public Query<T> getQuery();
/**
* Same as {@link #get()} but wraps InterruptedException and ExecutionException in the
* unchecked PersistenceException.
*
* @return The query list result
*
* @throws PersistenceException when a InterruptedException or ExecutionException occurs.
*/
public List<T> getUnchecked();
/**
* Same as {@link #get(long, java.util.concurrent.TimeUnit)} but wraps InterruptedException
* and ExecutionException in the unchecked PersistenceException.
*
* @return The query list result
*
* @throws TimeoutException if the wait timed out
* @throws PersistenceException if a InterruptedException or ExecutionException occurs.
*/
public List<T> getUnchecked(long timeout, TimeUnit unit) throws TimeoutException;
}
+109 -6
View File
@@ -24,11 +24,94 @@ import javax.persistence.MappedSuperclass;
* <p>
* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
* or if you believe it 'pollutes' your entity beans.
*
*
* <p>
* You can use Dependency Injection like Guice or Spring to construct and wire a EbeanServer instance
* and have that same instance used with this Model and Finder. The way that works is that when the
* DI container creates the EbeanServer instance it can be registered with the Ebean singleton. In this
* way the EbeanServer instance can be injected as per normal Guice / Spring dependency injection and
* that same instance also used to support the Model and Finder active record style.
*
* <p>
* If you choose to use the Model mapped superclass you will probably also chose to additionally add
* a {@link Finder} as a public static field to complete the active record pattern and provide a
* relatively nice clean way to write queries.
*
* <h3>Typical common @MappedSuperclass</h3>
* <pre>{@code
*
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* @MappedSuperclass
* public class BaseModel extends Model {
*
* @Id Long id;
*
* @Version Long version;
*
* @CreatedTimestamp Timestamp whenCreated;
*
* @UpdatedTimestamp Timestamp whenUpdated;
*
* ...
*
* }</pre>
*
* <h3>Extend the Model</h3>
* <pre>{@code
*
* // Extend the mappedSuperclass
*
* @Entity @Table(name="oto_account")
* public class Account extends BaseModel {
*
* // add a static Finder
* // ... with Long being the type of our ID property ...
*
* public static final Finder<Long,Account> find =
* new Finder<Long,Account>(Long.class, Account.class);
*
* String name;
*
* @OneToOne(mappedBy = "account",optional = true)
* User user;
*
* ...
* }
*
* }</pre>
*
* <h3>Modal: save()</h3>
* <pre>{@code
*
* // Active record style ... save(), delete() etc
* Account account = new Account();
* account.setName("AC234");
*
* // save() method inherited from Model
* account.save();
*
* }</pre>
*
* <h3>Finder: find byId</h3>
* <pre>{@code
*
* // find byId
* Account account = Account.find.byId(42);
*
* }</pre>
*
* <h3>Finder: find where</h3>
* <pre>{@code
*
* // find where ...
* List<Account> accounts =
* Account.find
* .where().gt("startDate", lastMonth)
* .findList();
*
* }</pre>
*/
@MappedSuperclass
public abstract class Model {
@@ -105,7 +188,9 @@ public abstract class Model {
* customer.markAsDirty();
* customer.save();
*
* </pre>
* </pre>
*
* @see EbeanServer#markAsDirty(Object)
*/
public void markAsDirty() {
db().markAsDirty(this);
@@ -117,6 +202,8 @@ public abstract class Model {
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#save(Object)
*/
public void save() {
db().save(this);
@@ -124,6 +211,8 @@ public abstract class Model {
/**
* Update this entity.
*
* @see EbeanServer#update(Object)
*/
public void update() {
db().update(this);
@@ -131,6 +220,8 @@ public abstract class Model {
/**
* Insert this entity.
*
* @see EbeanServer#insert(Object)
*/
public void insert() {
db().insert(this);
@@ -138,6 +229,8 @@ public abstract class Model {
/**
* Delete this entity.
*
* @see EbeanServer#delete(Object)
*/
public void delete() {
db().delete(this);
@@ -166,6 +259,8 @@ public abstract class Model {
/**
* Refreshes this entity from the database.
*
* @see EbeanServer#refresh(Object)
*/
public void refresh() {
db().refresh(this);
@@ -248,10 +343,14 @@ public abstract class Model {
return new Finder<I, T>(server, idType, type);
}
// Does not exist yet but I think should
// public void deleteById(I id) {
// return db().deleteById(type, id);
// }
/**
* Delete a bean by Id.
* <p>
* Equivalent to {@link EbeanServer#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
@@ -304,6 +403,8 @@ public abstract class Model {
/**
* Creates a query applying the path properties to set the select and fetch clauses.
* <p>
* Equivalent to {@link Query#apply(com.avaje.ebean.text.PathProperties)}
*/
public Query<T> apply(PathProperties pathProperties) {
return db().find(type).apply(pathProperties);
@@ -344,6 +445,8 @@ public abstract class Model {
/**
* Execute the query consuming each bean one at a time.
* <p>
* Equivalent to {@link Query#findEachWhile(QueryEachWhileConsumer)}
* <p>
* This is similar to #findEach except that you return boolean
* true to continue processing beans and return false to stop
* processing early.
-74
View File
@@ -1,74 +0,0 @@
package com.avaje.ebean;
import java.util.List;
/**
* Represents a Page of results that is part of a PagingList.
* <p>
* Typically a Page represents the data that is shown to the user at a single
* time - and the user 'pages' through a large list.
* </p>
*
* @author rbygrave
*
* @param <T>
* the entity bean type
*
* @see Query#findPagingList(int)
* @see PagingList
*/
public interface Page<T> {
/**
* Return the list of entities for this page.
*/
public List<T> getList();
/**
* Return the total row count for all pages.
*/
public int getTotalRowCount();
/**
* Return the total number of pages.
*/
public int getTotalPageCount();
/**
* Return the index position of this page.
*/
public int getPageIndex();
/**
* Return true if there is a next page.
*/
public boolean hasNext();
/**
* Return true if there is a previous page.
*/
public boolean hasPrev();
/**
* Return the next page.
*/
public Page<T> next();
/**
* Return the previous page.
*/
public Page<T> prev();
/**
* Helper method to return a "X to Y of Z" string for this page where X is the
* first row, Y the last row and Z the total row count.
*
* @param to
* String to put between the first and last row
* @param of
* String to put between the last row and the total row count
*
* @return String of the format XtoYofZ.
*/
public String getDisplayXtoYofZ(String to, String of);
}
+115 -3
View File
@@ -16,7 +16,44 @@ import java.util.concurrent.Future;
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> pagedList
* = ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findPagedList(0, 100);
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <h4>Example: No total row count required</h4>
* <pre>{@code
*
* // If you are not getting the 'first page' often
* // you do not bother getting the total row count again
* // so instead just get the page list of data
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* }</pre>
*
* @param <T>
* the entity bean type
*
@@ -26,12 +63,53 @@ public interface PagedList<T> {
/**
* Initiate the loading of the total row count in the background.
* <pre>{@code
*
* // initiate the loading of the total row count
* // in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <p>
* Also note that using loadRowCount() and getTotalRowCount() rather than getFutureRowCount()
* means that exceptions ExecutionException, InterruptedException, TimeoutException are instead
* wrapped in the unchecked PersistenceException (which might be preferrable).
* </p>
*/
public void loadRowCount();
/**
* Return the Future row count. You might get this if you wish to cancel the total row count query
* or specify a timeout for that query.
* or specify a timeout for the row count query.
* <p>
* The loadRowCount() & getTotalRowCount() methods internally make use of this getFutureRowCount() method.
* Generally I expect people to prefer loadRowCount() & getTotalRowCount() over getFutureRowCount().
* </p>
* <pre>{@code
*
* // initiate the row count query in the background thread
* Future<Integer> rowCount = pagedList.getFutureRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // now get the total count with a timeout
* Integer totalRowCount = rowCount.get(30, TimeUnit.SECONDS);
*
* // or ge the total count without a timeout
* Integer totalRowCountViaFuture = rowCount.get();
*
* // which is actually the same as ...
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public Future<Integer> getFutureRowCount();
@@ -42,11 +120,37 @@ public interface PagedList<T> {
/**
* Return the total row count for all pages.
* <p>
* If loadRowCount() has already been called then the row count query is already executing in a background thread
* and this gets the associated Future and gets the value waiting for the future to finish.
* </p>
* <p>
* If loadRowCount() has not been called then this executes the find row count query and returns the result and this
* will just occur in the current thread and not use a background thread.
* </p>
* <pre>{@code
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (which was being executed
* // in a background thread if loadRowCount() was used)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public int getTotalRowCount();
/**
* Return the total number of pages based on the page size and total row count.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
public int getTotalPageCount();
@@ -57,6 +161,10 @@ public interface PagedList<T> {
/**
* Return true if there is a next page.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
public boolean hasNext();
@@ -68,7 +176,11 @@ public interface PagedList<T> {
/**
* Helper method to return a "X to Y of Z" string for this page where X is the first row, Y the
* last row and Z the total row count.
*
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*
* @param to
* String to put between the first and last row
* @param of
@@ -1,127 +0,0 @@
package com.avaje.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* Used to page through a query result rather than fetching all the results in a
* single query.
* <p>
* Has the ability to use background threads to 'fetch ahead' the next page and
* get the total row count.
* </p>
* <p>
* If you are building a stateless web application and not keeping the
* PagingList over multiple requests then there is not much to be gained in
* using PagingList. Instead you can just use {@link Query#setFirstRow(int)} and
* {@link Query#setMaxRows(int)}.
* </p>
*
* <p>
* If you are using PagingList is a stateful web application where the
* PagingList is held over multiple requests then PagingList provides the extra
* benefits of
* <ul>
* <li>Fetch ahead - automatically fetching the next page via background query
* execution</li>
* <li>Automatic propagation of the persistence context</li>
* </ul>
* </p>
* <p>
* So with PagingList when you use Page 2 it can automatically fetch Page 3 data
* in the background (using a findFutureList() query). It also automatically
* propagates the persistence context so that all the queries executed by the
* PagingList all use the same persistence context.
* </p>
*
* <pre>
* PagingList&lt;TOne&gt; pagingList =
* Ebean.find(TOne.class)
* .where().gt(&quot;name&quot;, &quot;2&quot;)
* .findPagingList(10);
*
* // get the row count in the background...
* // ... otherwise it is fetched on demand
* // ... when getRowCount() or getPageCount()
* // ... is called
* pagingList.getFutureRowCount();
*
* // get the first page
* Page&lt;TOne&gt; page = pagingList.getPage(0);
*
* // get the beans from the page as a list
* List&lt;TOne&gt; list = page.getList();
* </pre>
*
* @author rbygrave
*
* @param <T>
* the entity bean type
*/
public interface PagingList<T> {
/**
* Refresh will clear all the pages and row count forcing them to be
* re-fetched when next required.
*/
public void refresh();
// public void fetchAll();
// public String? getOrderBy();
// public void setOrderBy(String?);
/**
* By default fetchAhead is true so use this to turn off fetchAhead.
* <p>
* Set this to false if you don't want to fetch ahead using background
* fetching.
* <p>
* If set to true (or left as to default) then the next page is fetched in the
* background as soon as the list is accessed.
* </p>
*/
public PagingList<T> setFetchAhead(boolean fetchAhead);
/**
* Return the Future for getting the total row count.
*/
public Future<Integer> getFutureRowCount();
/**
* Return the data for all the pages in the form of a single List.
* <p>
* Iterating through this list will automatically fire the paging queries as
* required.
* </p>
*/
public List<T> getAsList();
/**
* Return the page size. This is the number of rows per page.
*/
public int getPageSize();
/**
* Return the total row count.
* <p>
* This gets the result from getFutureRowCount and will wait until that query
* has completed.
* </p>
*/
public int getTotalRowCount();
/**
* Return the total page count.
* <p>
* This is based on the total row count. This will wait until the row count
* has returned if it has not already.
* </p>
*/
public int getTotalPageCount();
/**
* Return the page for a given page position (starting at 0).
*/
public Page<T> getPage(int i);
}
@@ -0,0 +1,55 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PropertyMap;
import java.util.Properties;
/**
* Provides singleton state for the default server.
* <p/>
* Intended for internal use as part of bootup, construction, registration of the default server.
*/
class PrimaryServer {
private static Properties globalProperties;
private static String defaultServerName;
private static boolean skip;
/**
* Set whether to skip automatically creating the primary server.
*/
static synchronized void setSkip(boolean skip) {
PrimaryServer.skip = skip;
}
/**
* Return true to skip automatically creating the primary server.
*/
static synchronized boolean isSkip() {
return skip;
}
/**
* Return the default server name.
*/
static synchronized String getPrimaryServerName() {
getProperties();
return defaultServerName;
}
/**
* Return the default configuration Properties.
*/
static synchronized Properties getProperties() {
if (globalProperties == null) {
globalProperties = PropertyMap.defaultProperties();
}
defaultServerName = globalProperties.getProperty("datasource.default");
if (defaultServerName == null) {
defaultServerName = globalProperties.getProperty("ebean.default.datasource");
}
return globalProperties;
}
}
+294 -184
View File
@@ -13,54 +13,57 @@ import java.util.Set;
* Example: Create the query using the API.
* </p>
*
* <pre class="code">
* List&lt;Order&gt; orderList =
* Ebean.find(Order.class)
* .fetch(&quot;customer&quot;)
* .fetch(&quot;details&quot;)
* <pre>{@code
*
* List<Order> orderList =
* ebeanServer.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .where()
* .like(&quot;customer.name&quot;,&quot;rob%&quot;)
* .gt(&quot;orderDate&quot;,lastWeek)
* .orderBy(&quot;customer.id, id desc&quot;)
* .like("customer.name","rob%")
* .gt("orderDate",lastWeek)
* .orderBy("customer.id, id desc")
* .setMaxRows(50)
* .findList();
*
* ...
* </pre>
* }</pre>
*
* <p>
* Example: The same query using the query language
* </p>
*
* <pre class="code">
* <pre>{@code
*
* String oql =
* &quot; find order &quot;
* +&quot; fetch customer &quot;
* +&quot; fetch details &quot;
* +&quot; where customer.name like :custName and orderDate &gt; :minOrderDate &quot;
* +&quot; order by customer.id, id desc &quot;
* +&quot; limit 50 &quot;;
* " find order "
* +" fetch customer "
* +" fetch details "
* +" where customer.name like :custName and orderDate > :minOrderDate "
* +" order by customer.id, id desc "
* +" limit 50 ";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* query.setParameter(&quot;custName&quot;, &quot;Rob%&quot;);
* query.setParameter(&quot;minOrderDate&quot;, lastWeek);
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
* query.setParameter("custName", "Rob%");
* query.setParameter("minOrderDate", lastWeek);
*
* List&lt;Order&gt; orderList = query.findList();
* List<Order> orderList = query.findList();
* ...
* </pre>
* }</pre>
*
* <p>
* Example: Using a named query called "with.cust.and.details"
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createNamedQuery(Order.class,&quot;with.cust.and.details&quot;);
* query.setParameter(&quot;custName&quot;, &quot;Rob%&quot;);
* query.setParameter(&quot;minOrderDate&quot;, lastWeek);
* <pre>{@code
*
* Query<Order> query = ebeanServer.createNamedQuery(Order.class,"with.cust.and.details");
* query.setParameter("custName", "Rob%");
* query.setParameter("minOrderDate", lastWeek);
*
* List&lt;Order&gt; orderList = query.findList();
* List<Order> orderList = query.findList();
* ...
* </pre>
* }</pre>
*
* <h3>Autofetch</h3>
* <p>
@@ -98,13 +101,13 @@ import java.util.Set;
* Refer to "ALL Properties/Columns" mode of Optimistic Concurrency checking.
* </p>
*
* <pre class="code">
* <pre>{@code
* [ find {bean type} [ ( * | {fetch properties} ) ] ]
* [ fetch {associated bean} [ ( * | {fetch properties} ) ] ]
* [ where {predicates} ]
* [ order by {order by properties} ]
* [ limit {max rows} [ offset {first row} ] ]
* </pre>
* }</pre>
*
* <p>
* <b>FIND</b> <b>{bean type}</b> [ ( <i>*</i> | <i>{fetch properties}</i> ) ]
@@ -160,17 +163,17 @@ import java.util.Set;
* Find orders fetching all its properties
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* </pre>
* }</pre>
*
* <p>
* Find orders fetching all its properties
* </p>
*
* <pre class="code">
* <pre>{@code
* find order (*)
* </pre>
* }</pre>
*
* <p>
* Find orders fetching its id, shipDate and status properties. Note that the id
@@ -178,30 +181,30 @@ import java.util.Set;
* properties.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order (shipDate, status)
* </pre>
* }</pre>
*
* <p>
* Find orders with a named bind variable (that will need to be bound via
* {@link Query#setParameter(String, Object)}).
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* where customer.name like :custLike
* </pre>
* }</pre>
*
* <p>
* Find orders and also fetch the customer with a named bind parameter. This
* will fetch and populate both the order and customer objects.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* fetch customer
* where customer.id = :custId
* </pre>
* }</pre>
*
* <p>
* Find orders and also fetch the customer, customer shippingAddress, order
@@ -212,13 +215,13 @@ import java.util.Set;
* populated.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* fetch customer (name)
* fetch customer.shippingAddress
* fetch details
* fetch details.product (sku, name)
* </pre>
* }</pre>
*
* <h3>Early parsing of the Query</h3>
* <p>
@@ -352,19 +355,24 @@ public interface Query<T> extends Serializable {
/**
* Explicitly set a comma delimited list of the properties to fetch on the
* 'main' entity bean (aka partial object). Note that '*' means all
* 'main' root level entity bean (aka partial object). Note that '*' means all
* properties.
* <p>
* You use {@link #fetch(String, String)} to specify specific properties to fetch
* on other non-root level paths of the object graph.
* </p>
*
* <pre class="code">
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
* <pre>{@code
*
* // Only fetch the customer id, name and status.
* // This is described as a &quot;Partial Object&quot;
* query.select(&quot;name, status&quot;);
* query.where(&quot;lower(name) like :custname&quot;).setParameter(&quot;custname&quot;, &quot;rob%&quot;);
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // Only fetch the customer id, name and status.
* // This is described as a "Partial Object"
* .select("name, status")
* .where.ilike("name", "rob%")
* .findList();
*
* List&lt;Customer&gt; customerList = query.findList();
* </pre>
* }</pre>
*
* @param fetchProperties
* the properties to fetch for this bean (* = all properties).
@@ -383,31 +391,35 @@ public interface Query<T> extends Serializable {
* "Partial Object" - a bean that only has some of its properties populated.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // query orders...
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* List<Order> orders =
* ebeanserver.find(Order.class)
* // fetch the customer...
* // ... getting the customers name and phone number
* .fetch("customer", "name, phoneNumber")
*
* // fetch the customer...
* // ... getting the customer's name and phone number
* query.fetch(&quot;customer&quot;, &quot;name, phNumber&quot;);
*
* // ... also fetch the customers billing address (* = all properties)
* query.fetch(&quot;customer.billingAddress&quot;, &quot;*&quot;);
* </pre>
* // ... also fetch the customers billing address (* = all properties)
* .fetch("customer.billingAddress", "*")
* .findList();
* }</pre>
*
* <p>
* If columns is null or "*" then all columns/properties for that path are
* fetched.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // fetch customers (their id, name and status)
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
*
* // only fetch some of the properties of the customers
* query.select(&quot;name, status&quot;);
* List&lt;Customer&gt; list = query.findList();
* </pre>
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email")
* .findList();
*
* }</pre>
*
* @param path
* the path of an associated (1-1,1-M,M-1,M-M) bean.
@@ -420,6 +432,17 @@ public interface Query<T> extends Serializable {
/**
* Additionally specify a FetchConfig to use a separate query or lazy loading
* to load this path.
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
* .findList();
*
* }</pre>
*/
public Query<T> fetch(String assocProperty, String fetchProperties, FetchConfig fetchConfig);
@@ -428,7 +451,17 @@ public interface Query<T> extends Serializable {
* <p>
* The same as {@link #fetch(String, String)} with the fetchProperties as "*".
* </p>
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // eager fetch the contacts
* .fetch("contacts")
* .findList();
*
* }</pre>
*
* @param path
* the property of an associated (1-1,1-M,M-1,M-M) bean.
*/
@@ -437,6 +470,18 @@ public interface Query<T> extends Serializable {
/**
* Additionally specify a JoinConfig to specify a "query join" and or define
* the lazy loading query.
*
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // lazy fetch contacts with a batch size of 100
* .fetch("contacts", new FetchConfig().lazy(100))
* .findList();
*
* }</pre>
*/
public Query<T> fetch(String path, FetchConfig joinConfig);
@@ -466,6 +511,10 @@ public interface Query<T> extends Serializable {
* (typically in a finally block).
* </p>
* <p>
* findEach() and findEachWhile() are preferred to findIterate() as they ensure
* the jdbc statement and resultSet are closed at the end of the iteration.
* </p>
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*/
@@ -493,6 +542,12 @@ public interface Query<T> extends Serializable {
* (unlike #findList #findSet etc)
* </p>
* <p>
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
* generally preferable.
* </p>
* <p>
* Compared with #findEachWhile this will always process all the beans where as
* #findEachWhile provides a way to stop processing the query result early before
* all the beans have been read.
@@ -503,19 +558,18 @@ public interface Query<T> extends Serializable {
* with Java8 closures.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
* ebeanServer.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .findEach((Customer customer) -> {
*
* query.findVisit((Customer customer) -> {
* // do something with customer
* System.out.println("-- visit " + customer);
* });
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* });
* </pre>
* }</pre>
*
* @param consumer
* the consumer used to process the queried beans.
@@ -532,23 +586,23 @@ public interface Query<T> extends Serializable {
* </p>
*
* <pre class="code">
* <pre>{@code
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .fetch(&quot;contacts&quot;, new FetchConfig().query(2))
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
* ebeanServer.find(Customer.class)
* .fetch("contacts", new FetchConfig().query(2))
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .setMaxRows(2000)
* .findEachWhile((Customer customer) -> {
*
* query.findEachWhile((Customer customer) -> {
* // do something with customer
* System.out.println("-- visit " + customer);
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
*
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
* </pre>
* }</pre>
*
* @param consumer
* the consumer used to process the queried beans.
@@ -560,7 +614,16 @@ public interface Query<T> extends Serializable {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
*
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
public List<T> findList();
@@ -570,7 +633,16 @@ public interface Query<T> extends Serializable {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
*
* <pre>{@code
*
* Set<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
public Set<T> findSet();
@@ -585,11 +657,14 @@ public interface Query<T> extends Serializable {
* on the map. If one is not specified then the id property is used.
* </p>
*
* <pre class="code">
* Query&lt;Product&gt; query = Ebean.createQuery(Product.class);
* query.setMapKey(&quot;sku&quot;);
* Map&lt;?, Product&gt; map = query.findMap();
* </pre>
* <pre>{@code
*
* Map<?, Product> map =
* ebeanServer.find(Product.class)
* .setMapKey("sku")
* .findMap();
*
* }</pre>
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@@ -612,32 +687,34 @@ public interface Query<T> extends Serializable {
* return 0 or 1 results.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // assuming the sku of products is unique...
* Product product =
* Ebean.find(Product.class)
* .where(&quot;sku = ?&quot;)
* .set(1, &quot;aa113&quot;)
* ebeanServer.find(Product.class)
* .where().eq("sku", "aa113")
* .findUnique();
* ...
* </pre>
* }</pre>
*
* <p>
* It is also useful with finding objects by their id when you want to specify
* further join information.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // Fetch order 1 and additionally fetch join its order details...
* Order order =
* Ebean.find(Order.class)
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch(&quot;details&quot;)
* .fetch("details")
* .findUnique();
*
* List&lt;OrderDetail&gt; details = order.getDetails();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
* ...
* </pre>
* }</pre>
*/
public T findUnique();
@@ -676,21 +753,14 @@ public interface Query<T> extends Serializable {
/**
* Execute find list query in a background thread.
* <p>
* Deprecated with a view to simplifying internals.
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
* </p>
*
*
* @return a Future object for the list result of the query
* @deprecated
*/
public FutureList<T> findFutureList();
/**
* This is being deprecated in favour of the simplier {@link Query#findPagedList(int, int)}.
*
* @deprecated
*/
public PagingList<T> findPagingList(int pageSize);
/**
* Return a PagedList for this query.
* <p>
@@ -703,7 +773,32 @@ public interface Query<T> extends Serializable {
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> pagedList
* = ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findPagedList(0, 100);
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @param pageIndex
* The zero based index of the page.
* @param pageSize
@@ -713,19 +808,20 @@ public interface Query<T> extends Serializable {
public PagedList<T> findPagedList(int pageIndex, int pageSize);
/**
* Set a named bind parameter. Named parameters have a colon to prefix the
* name.
* Set a named bind parameter. Named parameters have a colon to prefix the name.
*
* <pre class="code">
* <pre>{@code
*
* // a query with a named parameter
* String oql = &quot;find order where status = :orderStatus&quot;;
* String oql = "find order where status = :orderStatus";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* Query<Order> query = ebeanServer.find(Order.class, oql);
*
* // bind the named parameter
* query.bind(&quot;orderStatus&quot;, OrderStatus.NEW);
* List&lt;Order&gt; list = query.findList();
* </pre>
* query.bind("orderStatus", OrderStatus.NEW);
* List<Order> list = query.findList();
*
* }</pre>
*
* @param name
* the parameter name
@@ -739,17 +835,19 @@ public interface Query<T> extends Serializable {
* position starts at 1 to be consistent with JDBC PreparedStatement. You need
* to set a parameter value for each ? you have in the query.
*
* <pre class="code">
* <pre>{@code
*
* // a query with a positioned parameter
* String oql = &quot;where status = ? order by id desc&quot;;
* String oql = "where status = ? order by id desc";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
*
* // bind the parameter
* query.setParameter(1, OrderStatus.NEW);
*
* List&lt;Order&gt; list = query.findList();
* </pre>
* List<Order> list = query.findList();
*
* }</pre>
*
* @param position
* the parameter bind position starting from 1 (not 0)
@@ -765,12 +863,18 @@ public interface Query<T> extends Serializable {
* fetch joins.
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* Order order = query.setId(1).join(&quot;details&quot;).findUnique();
* List&lt;OrderDetail&gt; details = order.getDetails();
* ...
* </pre>
* <pre>{@code
*
* Order order =
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
*
* // the order details were eagerly fetched
* List<OrderDetail> details = order.getDetails();
*
* }</pre>
*/
public Query<T> setId(Object id);
@@ -781,15 +885,17 @@ public interface Query<T> extends Serializable {
* {@link #setParameter(String, Object)}.
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;top&quot;);
* <pre>{@code
*
* Query<Order> query = ebeanServer.createQuery(Order.class, "top");
* ...
* if (...) {
* query.where(&quot;status = :status and lower(customer.name) like :custName&quot;);
* query.setParameter(&quot;status&quot;, Order.NEW);
* query.setParameter(&quot;custName&quot;, &quot;rob%&quot;);
* query.where("status = :status and lower(customer.name) like :custName");
* query.setParameter("status", Order.NEW);
* query.setParameter("custName", "rob%");
* }
* </pre>
*
* }</pre>
*
* <p>
* Internally the addToWhereClause string is processed by removing named
@@ -809,13 +915,15 @@ public interface Query<T> extends Serializable {
/**
* Add a single Expression to the where clause returning the query.
*
* <pre class="code">
* List&lt;Order&gt; newOrders =
* Ebean.find(Order.class)
* .where().eq(&quot;status&quot;, Order.NEW)
* <pre>{@code
*
* List<Order> newOrders =
* ebeanServer.find(Order.class)
* .where().eq("status", Order.NEW)
* .findList();
* ...
* </pre>
*
* }</pre>
*/
public Query<T> where(Expression expression);
@@ -824,15 +932,16 @@ public interface Query<T> extends Serializable {
* ExpressionList. You can use this for adding multiple expressions to the
* where clause.
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;top&quot;);
* ...
* if (...) {
* query.where()
* .eq(&quot;status&quot;, Order.NEW)
* .ilike(&quot;customer.name&quot;,&quot;rob%&quot;);
* }
* </pre>
* <pre>{@code
*
* List<Order> orders =
* ebeanServer.find(Order.class)
* .where()
* .eq("status", Order.NEW)
* .ilike("customer.name","rob%")
* .findList();
*
* }</pre>
*
* @see Expr
* @return The ExpressionList for adding expressions to.
@@ -850,17 +959,18 @@ public interface Query<T> extends Serializable {
* week. In this case you can use filterMany() to filter the orders.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* List&lt;Customer&gt; list = Ebean
* .find(Customer.class)
* // .fetch(&quot;orders&quot;, new FetchConfig().lazy())
* // .fetch(&quot;orders&quot;, new FetchConfig().query())
* .fetch(&quot;orders&quot;).where().ilike(&quot;name&quot;, &quot;rob%&quot;).filterMany(&quot;orders&quot;)
* .eq(&quot;status&quot;, Order.Status.NEW).gt(
* &quot;orderDate&quot;, lastWeek).findList();
* List<Customer> list =
* ebeanServer.find(Customer.class)
* // .fetch("orders", new FetchConfig().lazy())
* // .fetch("orders", new FetchConfig().query())
* .fetch("orders")
* .where().ilike("name", "rob%")
* .filterMany("orders").eq("status", Order.Status.NEW).gt("orderDate", lastWeek)
* .findList();
*
* </pre>
* }</pre>
*
* <p>
* Please note you have to be careful that you add expressions to the correct
@@ -898,14 +1008,14 @@ public interface Query<T> extends Serializable {
* {@link #setParameter(String, Object)}.
* </p>
*
* <pre class="code">
* Query&lt;ReportOrder&gt; query = Ebean.createQuery(ReportOrder.class);
* ...
* if (...) {
* query.having(&quot;score &gt; :min&quot;);
* query.setParameter(&quot;min&quot;, 1);
* }
* </pre>
* <pre>{@code
*
* List<ReportOrder> query =
* ebeanServer.find(ReportOrder.class)
* .having("score > :min").setParameter("min", 1)
* .findList();
*
* }</pre>
*
* @param addToHavingClause
* the clause to append to the having clause which typically contains
@@ -1037,17 +1147,17 @@ public interface Query<T> extends Serializable {
* If no property is set then the id property is used.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // Assuming sku is unique for products...
*
* Query&lt;Product&gt; query = Ebean.createQuery(Product.class);
*
* // use sku for keys...
* query.setMapKey(&quot;sku&quot;);
*
* Map&lt;?,Product&gt; productMap = query.findMap();
* ...
* </pre>
* Map<?,Product> productMap =
* ebeanServer.find(Product.class)
* // use sku for keys...
* .setMapKey("sku")
* .findMap();
*
* }</pre>
*
* @param mapKey
* the property to use as keys for a map.
@@ -12,19 +12,19 @@ package com.avaje.ebean;
* QueryResultVisitor useful for processing large queries.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
* Query<Customer> query = server.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id");
*
* query.findVisit((Customer customer) -> {
* query.findEach((Customer customer) -> {
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* System.out.println("-- visit " + customer);
* });
* </pre>
*
* }</pre>
*
* @param <T>
* the type of entity bean being queried.
+133 -62
View File
@@ -2,12 +2,7 @@ package com.avaje.ebean;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import com.avaje.ebean.util.CamelCaseHelper;
@@ -22,25 +17,25 @@ import com.avaje.ebean.util.CamelCaseHelper;
* <b>Unparsed RawSql:</b>
* </p>
* <p>
* When RawSql is created via RawSqlBuilder.unparsed(sql) then Ebean can not
* When RawSql is created via {@link RawSqlBuilder#unparsed(String)} then Ebean can not
* modify the SQL at all. It can't add any extra expressions into the SQL.
* </p>
* <p>
* <b>Parsed RawSql:</b>
* </p>
* <p>
* When RawSql is created via RawSqlBuilder.parse(sql) then Ebean will parse the
* When RawSql is created via {@link RawSqlBuilder#parse(String)} then Ebean will parse the
* SQL and find places in the SQL where it can add extra where expressions, add
* extra having expressions or replace the order by clause. If you want to
* explicitly tell Ebean where these insertion points are you can place special
* strings into your SQL (${where} or ${andWhere} and ${having} or
* ${andHaving}).
* strings into your SQL ({@code ${where}} or {@code ${andWhere}} and {@code ${having}} or
* {@code ${andHaving})}.
* </p>
* <p>
* If the SQL already includes a WHERE clause put in ${andWhere} in the location
* If the SQL already includes a WHERE clause put in {@code ${andWhere}} in the location
* you want Ebean to add any extra where expressions. If the SQL doesn't have a
* WHERE clause put ${where} in instead. Similarly you can put in ${having} or
* ${andHaving} where you want Ebean put add extra having expressions.
* WHERE clause put {@code ${where}} in instead. Similarly you can put in {@code ${having}} or
* {@code ${andHaving}} where you want Ebean put add extra having expressions.
* </p>
* <p>
* <b>Aggregates:</b>
@@ -56,23 +51,19 @@ import com.avaje.ebean.util.CamelCaseHelper;
* to hold the values for the aggregate functions (sum etc) and a &#064;OneToOne
* to Order.
* </p>
* <p>
* &nbsp;
* </p>
* <p>
* <b>Example OrderAggregate</b>
* </p>
*
* <h3>Example OrderAggregate</h3>
*
* <pre class="code">
* <pre>{@code
* ...
* // &#064;Sql indicates to that this bean
* // @Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* &#064;Entity
* &#064;Sql
* @Entity
* @Sql
* public class OrderAggregate {
*
* &#064;OneToOne
* @OneToOne
* Order order;
*
* Double totalAmount;
@@ -81,35 +72,38 @@ import com.avaje.ebean.util.CamelCaseHelper;
*
* // getters and setters
* ...
* </pre>
* <p>
* <b>Example 1:</b>
* </p>
*
* }</pre>
*
* <h3>Example 1:</h3>
*
* <pre class="code">
* String sql = &quot; select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount&quot;
* + &quot; from o_order o&quot;
* + &quot; join o_customer c on c.id = o.kcustomer_id &quot;
* + &quot; join o_order_detail d on d.order_id = o.id &quot; + &quot; group by order_id, o.status &quot;;
* <pre>{@code
*
* String sql = " select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount"
* + " from o_order o"
* + " join o_customer c on c.id = o.kcustomer_id "
* + " join o_order_detail d on d.order_id = o.id " + " group by order_id, o.status ";
*
* RawSql rawSql = RawSqlBuilder.parse(sql)
* RawSql rawSql = RawSqlBuilder.parse(sql)
* // map the sql result columns to bean properties
* .columnMapping(&quot;order_id&quot;, &quot;order.id&quot;).columnMapping(&quot;o.status&quot;, &quot;order.status&quot;)
* .columnMapping(&quot;c.id&quot;, &quot;order.customer.id&quot;)
* .columnMapping(&quot;c.name&quot;, &quot;order.customer.name&quot;)
* .columnMapping("order_id", "order.id")
* .columnMapping("o.status", "order.status")
* .columnMapping("c.id", "order.customer.id")
* .columnMapping("c.name", "order.customer.name")
* // we don't need to map this one due to the sql column alias
* // .columnMapping(&quot;sum(d.order_qty*d.unit_price)&quot;, &quot;totalAmount&quot;)
* // .columnMapping("sum(d.order_qty*d.unit_price)", "totalAmount")
* .create();
*
* Query&lt;OrderAggregate&gt; query = Ebean.find(OrderAggregate.class);
* query.setRawSql(rawSql).where().gt(&quot;order.id&quot;, 0).having().gt(&quot;totalAmount&quot;, 20);
* List<OrderAggregate> list = Ebean.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
* .findList();
*
* List&lt;OrderAggregate&gt; list = query.findList();
* </pre>
*
* }</pre>
*
* <p>
* <b>Example 2:</b>
* </p>
* <h3>Example 2:</h3>
*
* <p>
* The following example uses a FetchConfig().query() so that after the initial
@@ -117,21 +111,59 @@ import com.avaje.ebean.util.CamelCaseHelper;
* associated order status, orderDate along with the customer name.
* </p>
*
* <pre class="code">
* String sql = &quot; select order_id, 'ignoreMe', sum(d.order_qty*d.unit_price) as totalAmount &quot;
* + &quot; from o_order_detail d&quot;
* + &quot; group by order_id &quot;;
* <pre>{@code
*
* String sql = " select order_id, 'ignoreMe', sum(d.order_qty*d.unit_price) as totalAmount "
* + " from o_order_detail d"
* + " group by order_id ";
*
* RawSql rawSql = RawSqlBuilder.parse(sql).columnMapping(&quot;order_id&quot;, &quot;order.id&quot;)
* .columnMappingIgnore(&quot;'ignoreMe'&quot;).create();
* RawSql rawSql = RawSqlBuilder.parse(sql)
* .columnMapping("order_id", "order.id")
* .columnMappingIgnore("'ignoreMe'")
* .create();
*
* Query&lt;OrderAggregate&gt; query = Ebean.find(OrderAggregate.class);
* query.setRawSql(rawSql).fetch(&quot;order&quot;, &quot;status,orderDate&quot;, new FetchConfig().query())
* .fetch(&quot;order.customer&quot;, &quot;name&quot;).where()
* .gt(&quot;order.id&quot;, 0).having().gt(&quot;totalAmount&quot;, 20).order().desc(&quot;totalAmount&quot;).setMaxRows(10);
*
* </pre>
* List<OrderAggregate> orders = Ebean.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .fetch("order", "status,orderDate", new FetchConfig().query())
* .fetch("order.customer", "name")
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
* .order().desc("totalAmount")
* .setMaxRows(10)
* .findList();
*
* }</pre>
*
*
* <h3>Example 3: tableAliasMapping</h3>
* <p>
* Instead of mapping each column you can map each table alias to a path using tableAliasMapping().
* </p>
* <pre>{@code
*
* String rs = "select o.id, o.status, c.id, c.name, "+
* " d.id, d.order_qty, p.id, p.name " +
* "from o_order o join o_customer c on c.id = o.kcustomer_id " +
* "join o_order_detail d on d.order_id = o.id " +
* "join o_product p on p.id = d.product_id " +
* "where o.id <= :maxOrderId and p.id = :productId "+
* "order by o.id, d.id asc";
*
* RawSql rawSql = RawSqlBuilder.parse(rs)
* .tableAliasMapping("c", "customer")
* .tableAliasMapping("d", "details")
* .tableAliasMapping("p", "details.product")
* .create();
*
* List<Order> ordersFromRaw = Ebean.find(Order.class)
* .setRawSql(rawSql)
* .setParameter("maxOrderId", 2)
* .setParameter("productId", 1)
* .findList();
*
* }</pre>
*
*
* <p>
* Note that lazy loading also works with object graphs built with RawSql.
* </p>
@@ -222,6 +254,8 @@ public final class RawSql implements Serializable {
private final boolean andHavingExpr;
private final String orderByPrefix;
private final String orderBy;
private final boolean distinct;
@@ -240,6 +274,7 @@ public final class RawSql implements Serializable {
this.preWhere = null;
this.andHavingExpr = false;
this.andWhereExpr = false;
this.orderByPrefix = null;
this.orderBy = null;
this.distinct = false;
}
@@ -248,8 +283,7 @@ public final class RawSql implements Serializable {
* Construct for parsed SQL.
*/
protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr,
String preHaving, boolean andHavingExpr,
String orderBy, boolean distinct) {
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
this.queryHashCode = queryHashCode;
this.parsed = true;
@@ -259,6 +293,7 @@ public final class RawSql implements Serializable {
this.preWhere = preWhere;
this.andHavingExpr = andHavingExpr;
this.andWhereExpr = andWhereExpr;
this.orderByPrefix = orderByPrefix;
this.orderBy = orderBy;
this.distinct = distinct;
}
@@ -337,6 +372,14 @@ public final class RawSql implements Serializable {
return andHavingExpr;
}
/**
* Return the 'order by' keywords.
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
*/
public String getOrderByPrefix() {
return (orderByPrefix == null) ? "order by" : orderByPrefix;
}
/**
* Return the SQL ORDER BY clause.
*/
@@ -429,9 +472,8 @@ public final class RawSql implements Serializable {
for (Column c : dbColumnMap.values()) {
pMap.put(c.getPropertyName(), c.getDbColumn());
pcMap.put(c.getPropertyName(), c);
hc = 31 * hc + c.getPropertyName() == null ? 0 : c.getPropertyName().hashCode();
hc = 31 * hc + c.getDbColumn() == null ? 0 : c.getDbColumn().hashCode();
hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode());
hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode());
}
this.propertyMap = Collections.unmodifiableMap(pMap);
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
@@ -531,6 +573,22 @@ public final class RawSql implements Serializable {
return dbColumnMap.values().iterator();
}
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
*/
public void tableAliasMapping(String tableAlias, String path) {
String startMatch = tableAlias+".";
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
if (entry.getKey().startsWith(startMatch)) {
entry.getValue().tableAliasMapping(path);
}
}
}
/**
* A Column of the RawSql that is mapped to a bean property (or ignored).
*/
@@ -612,10 +670,23 @@ public final class RawSql implements Serializable {
return propertyName;
}
/**
* Set the property name mapped to this db column.
*/
private void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* Prepend the path to the property name.
* <p/>
* For example if path is "customer" then "name" becomes "customer.name".
*/
public void tableAliasMapping(String path) {
if (path != null) {
propertyName = path + "." + propertyName;
}
}
}
}
}
@@ -70,13 +70,6 @@ public class RawSqlBuilder {
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
return new RawSqlBuilder(sql2, mapping);
}
private RawSqlBuilder(ResultSet resultSet, ColumnMapping columnMapping) {
this.resultSet = resultSet;
this.columnMapping = columnMapping;
this.sql = null;
}
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
@@ -108,6 +101,17 @@ public class RawSqlBuilder {
return columnMapping(dbColumn, IGNORE_COLUMN);
}
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
*/
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
columnMapping.tableAliasMapping(tableAlias, path);
return this;
}
/**
* Create the immutable RawSql object. Do this after all the column mapping
* has been defined.
@@ -122,6 +126,7 @@ public class RawSqlBuilder {
protected Sql getSql() {
return sql;
}
}
+64 -25
View File
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PersistBatch;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
@@ -128,47 +130,93 @@ public interface Transaction extends Closeable {
* Example: batch processing executing every 3 rows
* </p>
*
* <pre class="code">
* String data = &quot;This is a simple test of the batch processing&quot;
* + &quot; mode and the transaction execute batch method&quot;;
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
* + " mode and the transaction execute batch method";
*
* String[] da = data.split(&quot; &quot;);
* String[] da = data.split(" ");
*
* String sql = &quot;{call sp_t3(?,?)}&quot;;
* String sql = "{call sp_t3(?,?)}";
*
* CallableSql cs = new CallableSql(sql);
* cs.registerOut(2, Types.INTEGER);
*
* // (optional) inform eBean this stored procedure
* // inserts into a table called sp_test
* cs.addModification(&quot;sp_test&quot;, true, false, false);
* cs.addModification("sp_test", true, false, false);
*
* Transaction t = Ebean.beginTransaction();
* t.setBatchMode(true);
* t.setBatchSize(3);
* Transaction txn = ebeanServer.beginTransaction();
* txn.setBatchMode(true);
* txn.setBatchSize(3);
* try {
* for (int i = 0; i &lt; da.length;) {
*
* for (int i = 0; i < da.length;) {
* cs.setParameter(1, da[i]);
* Ebean.execute(cs);
* ebeanServer.execute(cs);
* }
*
* // NB: commit implicitly flushes
* Ebean.commitTransaction();
* txn.commit();
*
* } finally {
* Ebean.endTransaction();
* txn.end();
* }
* </pre>
*
* }</pre>
*
*/
public void setBatchMode(boolean useBatch);
/**
* The JDBC batch mode to use for this transaction.
* <p>
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
* and this would be useful if the request cascades to detail beans.
* </p>
*
* @param persistBatchMode the batch mode to use for this transaction
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatch(com.avaje.ebean.config.PersistBatch)
*/
public void setBatch(PersistBatch persistBatchMode);
/**
* Return the batch mode at the transaction level.
*/
public PersistBatch getBatch();
/**
* Set the JDBC batch mode to use for a save() or delete() request.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
* </p>
* <p>
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
*
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(com.avaje.ebean.config.PersistBatch)
*/
public void setBatchOnCascade(PersistBatch batchOnCascadeMode);
/**
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
*/
public PersistBatch getBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
*/
public void setBatchSize(int batchSize);
/**
* Return the current batch size.
*/
public int getBatchSize();
/**
* Specify if you want batched inserts to use getGeneratedKeys.
* <p>
@@ -228,20 +276,11 @@ public interface Transaction extends Closeable {
* <li>the batch size is reached</li>
* <li>A query is executed on the same transaction</li>
* <li>UpdateSql or CallableSql are mixed with bean save and delete</li>
* <li>Transaction commit occurs</li>
* </ul>
*/
public void flushBatch() throws PersistenceException, OptimisticLockException;
/**
* Deprecated in favour of {@link #flushBatch()}.
* <p>
* Exactly the same as flushBatch. Deprecated as a name change.
* </p>
*
* @deprecated Please use flushBatch
*/
public void batchFlush() throws PersistenceException, OptimisticLockException;
/**
* Return the underlying Connection object.
* <p>
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PersistBatch;
import java.util.ArrayList;
/**
@@ -28,12 +30,39 @@ public final class TxScope {
TxIsolation isolation;
PersistBatch batch;
PersistBatch batchOnCascade;
int batchSize;
boolean readOnly;
ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Return true if PersistBatch has been set.
*/
public boolean isBatchSet() {
return batch != null && batch != PersistBatch.INHERIT;
}
/**
* Return true if batch on cascade has been set.
*/
public boolean isBatchOnCascadeSet() {
return batchOnCascade != null && batchOnCascade != PersistBatch.INHERIT;
}
/**
* Return true if batch size has been set.
*/
public boolean isBatchSizeSet() {
return batchSize > 0;
}
/**
* Helper method to create a TxScope with REQUIRES.
*/
@@ -114,6 +143,51 @@ public final class TxScope {
return this;
}
/**
* Return the batch mode.
*/
public PersistBatch getBatch() {
return batch;
}
/**
* Set the batch mode to use.
*/
public TxScope setBatch(PersistBatch batch) {
this.batch = batch;
return this;
}
/**
* Return the batch on cascade mode.
*/
public PersistBatch getBatchOnCascade() {
return batchOnCascade;
}
/**
* Set the batch on cascade mode.
*/
public TxScope setBatchOnCascade(PersistBatch batchOnCascade) {
this.batchOnCascade = batchOnCascade;
return this;
}
/**
* Return the batch size. 0 means use the default value.
*/
public int getBatchSize() {
return batchSize;
}
/**
* Set the batch size to use.
*/
public TxScope setBatchSize(int batchSize) {
this.batchSize = batchSize;
return this;
}
/**
* Return if the transaction should be treated as read only.
*/
@@ -20,7 +20,7 @@ import com.avaje.ebean.Query;
* You may also put use the Transient annotation with the Formula annotation.
* The effect of the Transient annotation in this case is that the formula will
* <b>NOT</b> be included in queries by default - you have to explicitly include
* it via {@link Query#select(String)} or {@link Query#join(String, String)}.
* it via {@link Query#select(String)} or {@link Query#fetch(String, String, com.avaje.ebean.FetchConfig)}.
* You may want to do this if the Formula is relatively expensive and only want
* it included in the query when you explicitly state it.
* </p>
@@ -1,103 +1,128 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.TxIsolation;
import com.avaje.ebean.TxType;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own com.avaje.ebean.annotation.Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
*
* <pre class="code">
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* &#064;Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = Ebean.find(Customer.class, 1);
*
* Order order = ...;
* ...
* // save some objects
* Ebean.save(customer);
* Ebean.save(order);
* }
* </pre>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
};
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.TxIsolation;
import com.avaje.ebean.TxType;
import com.avaje.ebean.config.PersistBatch;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own com.avaje.ebean.annotation.Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
*
* <pre>{@code
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* @Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = ebeanServer.find(Customer.class, 42);
*
* Order order = ...;
* ...
* // save some objects
* ebeanServer.save(customer);
* ebeanServer.save(order);
* }
*
* }</pre>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* Persist batch mode for the transaction.
*/
PersistBatch batch() default PersistBatch.INHERIT;
/**
* Persist batch mode for the request if not set on the transaction.
* <p>
* If batch is set to NONE then batchOnCascade can be set to INSERT or ALL
* and then each save(), delete(), insert(), update() request that cascades
* to child beans can use JDBC batch.
* </p>
*/
PersistBatch batchOnCascade() default PersistBatch.INHERIT;
/**
* The batch size to use when using JDBC batch mode.
* <p>
* If unset this defaults to the value set in ServerConfig.
* </p>
*/
int batchSize() default 0;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The Throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The Throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
};
@@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory;
/**
* Provides some base implementation for NamingConventions.
*
*
* @author emcgreal
*/
public abstract class AbstractNamingConvention implements NamingConvention {
@@ -41,7 +41,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
/** Used to trim off extra prefix for M2M. */
protected int rhsPrefixLength = 3;
protected boolean useForeignKeyPrefix = true;
protected boolean useForeignKeyPrefix;
/**
* Construct with a sequence format and useForeignKeyPrefix setting.
@@ -53,12 +53,12 @@ public abstract class AbstractNamingConvention implements NamingConvention {
/**
* Construct with a sequence format.
*
* @param sequenceFormat
* the sequence format
*
* @param sequenceFormat the sequence format
*/
public AbstractNamingConvention(String sequenceFormat) {
this.sequenceFormat = sequenceFormat;
this.useForeignKeyPrefix = true;
}
/**
@@ -125,10 +125,9 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* The format should include "{table}". When generating the sequence name
* {table} is replaced with the actual table name.
* </p>
*
* @param sequenceFormat
* string containing "{table}" which is replaced with the actual
* table name to generate the sequence name.
*
* @param sequenceFormat string containing "{table}" which is replaced with the actual
* table name to generate the sequence name.
*/
public void setSequenceFormat(String sequenceFormat) {
this.sequenceFormat = sequenceFormat;
@@ -173,7 +172,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* This first checks for the @Table annotation and if not present uses the
* naming convention to define the table name.
* </p>
*
*
* @see #getTableNameFromAnnotation(Class)
* @see #getTableNameByConvention(Class)
*/
@@ -240,8 +239,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
if (t != null && !isEmpty(t.name())) {
// Note: empty catalog and schema are converted to null
// Only need to convert quoted identifiers from annotations
return new TableName(quoteIdentifiers(t.catalog()), quoteIdentifiers(t.schema()),
quoteIdentifiers(t.name()));
return new TableName(quoteIdentifiers(t.catalog()), quoteIdentifiers(t.schema()), quoteIdentifiers(t.name()));
}
// No annotation
@@ -279,4 +277,16 @@ public abstract class AbstractNamingConvention implements NamingConvention {
}
return false;
}
/**
* Load settings from properties.
*/
@Override
public void loadFromProperties(PropertiesWrapper properties) {
useForeignKeyPrefix = properties.getBoolean("namingConvention.useForeignKeyPrefix", useForeignKeyPrefix);
sequenceFormat = properties.get("namingConvention.sequenceFormat", sequenceFormat);
schema = properties.get("namingConvention.schema", schema);
}
}
@@ -155,14 +155,6 @@ public class AutofetchConfig {
return logDirectory;
}
/**
* Return the log directory substituting any expressions such as
* ${catalina.base} etc.
*/
public String getLogDirectoryWithEval() {
return GlobalProperties.evaluateExpressions(logDirectory);
}
/**
* Set the directory to put the autofetch log in.
*/
@@ -234,22 +226,20 @@ public class AutofetchConfig {
/**
* Load the settings from the properties file.
*/
public void loadSettings(GlobalProperties.PropertySource p) {
public void loadSettings(PropertiesWrapper p) {
logDirectory = p.get("autofetch.logDirectory", null);
queryTuning = p.getBoolean("autofetch.querytuning", false);
queryTuningAddVersion = p.getBoolean("autofetch.queryTuningAddVersion", false);
garbageCollectionOnShutdown = p.getBoolean("autofetch.garbageCollectionOnShutdown", false);
logDirectory = p.get("autofetch.logDirectory", logDirectory);
queryTuning = p.getBoolean("autofetch.querytuning", queryTuning);
queryTuningAddVersion = p.getBoolean("autofetch.queryTuningAddVersion", queryTuningAddVersion);
garbageCollectionOnShutdown = p.getBoolean("autofetch.garbageCollectionOnShutdown", garbageCollectionOnShutdown);
profiling = p.getBoolean("autofetch.profiling", false);
mode = p.getEnum(AutofetchMode.class, "autofetch.implicitmode", AutofetchMode.DEFAULT_ONIFEMPTY);
profiling = p.getBoolean("autofetch.profiling", profiling);
mode = p.getEnum(AutofetchMode.class, "autofetch.implicitmode", mode);
profilingMin = p.getInt("autofetch.profiling.min", 1);
profilingBase = p.getInt("autofetch.profiling.base", 10);
profilingMin = p.getInt("autofetch.profiling.min", profilingMin);
profilingBase = p.getInt("autofetch.profiling.base", profilingBase);
String rate = p.get("autofetch.profiling.rate", "0.05");
profilingRate = Double.parseDouble(rate);
profileUpdateFrequency = p.getInt("autofetch.profiling.updatefrequency", 60);
profilingRate = p.getDouble("autofetch.profiling.rate", profilingRate);
profileUpdateFrequency = p.getInt("autofetch.profiling.updatefrequency", profileUpdateFrequency);
}
}
@@ -1,50 +0,0 @@
package com.avaje.ebean.config;
import com.avaje.ebean.config.GlobalProperties.PropertySource;
/**
* Helper to read server specific properties from ebean.properties.
*/
class ConfigPropertyMap implements PropertySource {
private final String serverName;
public ConfigPropertyMap(String serverName) {
this.serverName = serverName;
}
public String getServerName() {
return serverName;
}
public String get(String key, String defaultValue) {
String namedKey = "ebean." + serverName + "." + key;
String inheritKey = "ebean." + key;
String value = GlobalProperties.get(namedKey, null);
if (value == null) {
value = GlobalProperties.get(inheritKey, null);
}
if (value == null) {
return defaultValue;
} else {
return value;
}
}
public int getInt(String key, int defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Integer.parseInt(value);
}
public boolean getBoolean(String key, boolean defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Boolean.parseBoolean(value);
}
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
}
}
@@ -0,0 +1,464 @@
package com.avaje.ebean.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* Configuration for the container that holds the EbeanServer instances.
* <p>
* Provides configuration for cluster communication (if clustering is used). The cluster communication is
* used to invalidate appropriate parts of the L2 cache across the cluster.
*/
public class ContainerConfig {
/**
* Communication mode used for clustering.
*/
public enum ClusterMode {
/**
* No clustering.
*/
NONE,
/**
* Use Multicast networking for cluster wide communication.
*/
MULTICAST,
/**
* Use TCP Sockets for cluster wide communication.
*/
SOCKET
}
/**
* The cluster mode to use.
*/
ClusterMode mode = ClusterMode.NONE;
/**
* Configuration if using TCP sockets for clustering communication.
*/
SocketConfig socketConfig = new SocketConfig();
/**
* Configuration if using Multicast for clustering communication.
*/
MulticastConfig multicastConfig = new MulticastConfig();
// -------------------------------------------------------------------------------------------
// MulticastConfig
/**
* The configuration for clustering using Multicast networking.
*/
public static class MulticastConfig {
int managerSleepMillis = 80;
int lastSendTimeFreqSecs = 300;//5mins
int lastStatusTimeFreqSecs = 600;//10mins
int maxResendOutgoingAttempts = 200;
int maxResendIncomingRequests = 50;
int listenPort;
String listenAddress;
int sendPort;
String sendAddress;
// Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507
int maxSendPacketSize = 1500;
// Whether to send packets even when there are no other members online
boolean sendWithNoMembers = true;
// When multiple instances are on same box you need to broadcast back locally
boolean disableLoopback;
int listenTimeToLive = -1;
int listenTimeout = 1000;
int listenBufferSize = 65500;
// For multihomed environment the address the listener should bind to
String listenBindAddress;
/**
* Return the manager sleep millis.
*/
public int getManagerSleepMillis() {
return managerSleepMillis;
}
/**
* Set the manager sleep millis.
*/
public void setManagerSleepMillis(int managerSleepMillis) {
this.managerSleepMillis = managerSleepMillis;
}
/**
* Return the last send time frequency.
*/
public int getLastSendTimeFreqSecs() {
return lastSendTimeFreqSecs;
}
/**
* Set the last send time frequency.
*/
public void setLastSendTimeFreqSecs(int lastSendTimeFreqSecs) {
this.lastSendTimeFreqSecs = lastSendTimeFreqSecs;
}
/**
* Return the last status time frequency.
*/
public int getLastStatusTimeFreqSecs() {
return lastStatusTimeFreqSecs;
}
/**
* Set the last status time frequency.
*/
public void setLastStatusTimeFreqSecs(int lastStatusTimeFreqSecs) {
this.lastStatusTimeFreqSecs = lastStatusTimeFreqSecs;
}
/**
* Return the maximum number of times we will try to re-send a given packet before giving up sending
*/
public int getMaxResendOutgoingAttempts() {
return maxResendOutgoingAttempts;
}
/**
* Set the maximum retry attempts for outgoing messages.
*/
public void setMaxResendOutgoingAttempts(int maxResendOutgoingAttempts) {
this.maxResendOutgoingAttempts = maxResendOutgoingAttempts;
}
/**
* Return the maximum number of times we will ask for a packet to be resent to us before giving up asking.
*/
public int getMaxResendIncomingRequests() {
return maxResendIncomingRequests;
}
/**
* Set the maximum retry attempts for incoming messages.
*/
public void setMaxResendIncomingRequests(int maxResendIncomingRequests) {
this.maxResendIncomingRequests = maxResendIncomingRequests;
}
/**
* Return the listen port.
*/
public int getListenPort() {
return listenPort;
}
/**
* Set the listen port.
*/
public void setListenPort(int port) {
this.listenPort = port;
}
/**
* Return the listen address.
*/
public String getListenAddress() {
return listenAddress;
}
/**
* Set the listen address.
*/
public void setListenAddress(String listenAddress) {
this.listenAddress = listenAddress;
}
/**
* Return the send port.
*/
public int getSendPort() {
return sendPort;
}
/**
* Set the send port.
*/
public void setSendPort(int sendPort) {
this.sendPort = sendPort;
}
/**
* Return the send address.
*/
public String getSendAddress() {
return sendAddress;
}
/**
* Set the send address.
*/
public void setSendAddress(String sendAddress) {
this.sendAddress = sendAddress;
}
/**
* Return the maximum send packet size.
*/
public int getMaxSendPacketSize() {
return maxSendPacketSize;
}
/**
* Set the maximum send packet size. Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507.
*/
public void setMaxSendPacketSize(int maxSendPacketSize) {
this.maxSendPacketSize = maxSendPacketSize;
}
/**
* Return true if send messages when no other members in the cluster are up.
*/
public boolean isSendWithNoMembers() {
return sendWithNoMembers;
}
/**
* Set true if send messages when no other members in the cluster are up.
*/
public void setSendWithNoMembers(boolean sendWithNoMembers) {
this.sendWithNoMembers = sendWithNoMembers;
}
/**
* Return true if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public boolean isDisableLoopback() {
return disableLoopback;
}
/**
* Set if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public void setDisableLoopback(boolean disableLoopback) {
this.disableLoopback = disableLoopback;
}
/**
* Return the listen time to live.
*/
public int getListenTimeToLive() {
return listenTimeToLive;
}
/**
* Set the listen time to live.
*/
public void setListenTimeToLive(int listenTimeToLive) {
this.listenTimeToLive = listenTimeToLive;
}
/**
* Return the listen timeout.
*/
public int getListenTimeout() {
return listenTimeout;
}
/**
* set the listen timeout.
*/
public void setListenTimeout(int listenTimeout) {
this.listenTimeout = listenTimeout;
}
/**
* Return the listen buffer size.
*/
public int getListenBufferSize() {
return listenBufferSize;
}
/**
* Set the listen buffer size.
*/
public void setListenBufferSize(int listenBufferSize) {
this.listenBufferSize = listenBufferSize;
}
/**
* Return the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public String getListenBindAddress() {
return listenBindAddress;
}
/**
* Set the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public void setListenBindAddress(String listenBindAddress) {
this.listenBindAddress = listenBindAddress;
}
}
// -------------------------------------------------------------------------------------------
// SocketConfig
/**
* Configuration for clustering using TCP sockets.
* <p>
* This is good for when there are relatively small number of cluster members.
*/
public static class SocketConfig {
/**
* This local server in host:port format.
*/
String localHostPort;
/**
* All the cluster members in host:port format.
*/
List<String> members = new ArrayList<String>();
/**
* core threads for the associated thread pool.
*/
int coreThreads = 2;
/**
* Max threads for the associated thread pool.
*/
int maxThreads = 16;
String threadPoolName = "EbeanCluster";
/**
* Return the host and port for this server instance.
*/
public String getLocalHostPort() {
return localHostPort;
}
/**
* Set the host and port for this server instance.
*/
public void setLocalHostPort(String localHostPort) {
this.localHostPort = localHostPort;
}
/**
* Return all the host and port for all the members of the cluster.
*/
public List<String> getMembers() {
return members;
}
/**
* Set all the host and port for all the members of the cluster.
*/
public void setMembers(List<String> members) {
this.members = members;
}
/**
* Return the number of core threads to use.
*/
public int getCoreThreads() {
return coreThreads;
}
/**
* Set the number of core threads to use.
*/
public void setCoreThreads(int coreThreads) {
this.coreThreads = coreThreads;
}
/**
* Return the number of max threads to use.
*/
public int getMaxThreads() {
return maxThreads;
}
/**
* Set the number of max threads to use.
*/
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* Return the thread pool name.
*/
public String getThreadPoolName() {
return threadPoolName;
}
/**
* Set the thread pool name.
*/
public void setThreadPoolName(String threadPoolName) {
this.threadPoolName = threadPoolName;
}
}
// -------------------------------------------------------------------------------------------
// Members
/**
* Load the settings from properties.
*/
public void loadFromProperties(Properties properties) {
//TODO
}
/**
* Return the cluster mode.
*/
public ClusterMode getMode() {
return mode;
}
/**
* Set the cluster mode.
*/
public void setMode(ClusterMode mode) {
this.mode = mode;
}
/**
* Return the socket communication configuration.
*/
public SocketConfig getSocketConfig() {
return socketConfig;
}
/**
* Set the socket communication configuration.
*/
public void setSocketConfig(SocketConfig socketConfig) {
this.socketConfig = socketConfig;
}
/**
* Return the multicast communication configuration.
*/
public MulticastConfig getMulticastConfig() {
return multicastConfig;
}
/**
* Set the multicast communication configuration.
*/
public void setMulticastConfig(MulticastConfig multicastConfig) {
this.multicastConfig = multicastConfig;
}
}
@@ -2,6 +2,7 @@ package com.avaje.ebean.config;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.util.StringHelper;
@@ -448,48 +449,63 @@ public class DataSourceConfig {
this.customProperties = customProperties;
}
/**
* Load the settings by reading the ebean.properties file.
*
* @param serverName name of the server
*/
public void loadSettings(String serverName) {
loadSettingsCustomPrefix("datasource." + serverName + ".", new GlobalProperties.DelegatedGlobalPropertySource(serverName));
loadSettings(new PropertiesWrapper("datasource", serverName, PropertyMap.defaultProperties()));
}
/**
* Load the settings from ebean.properties.
* Load the settings from the properties supplied.
* <p>
* You can use this when you have your own properties to use for configuration.
* </p>
*
* @param properties the properties to configure the datasource
* @param serverName the name of the specific datasource (optional)
*/
public void loadSettingsCustomPrefix(String prefix, GlobalProperties.PropertySource properties) {
public void loadSettings(Properties properties, String serverName) {
PropertiesWrapper dbProps = new PropertiesWrapper("datasource", serverName, properties);
loadSettings(dbProps);
}
this.username = properties.get(prefix + "username", null);
this.password = properties.get(prefix + "password", null);
/**
* Load the settings from the PropertiesWrapper.
*/
public void loadSettings(PropertiesWrapper properties) {
String dbDriver = properties.get(prefix + "databaseDriver", null);
this.driver = properties.get(prefix + "driver", dbDriver);
username = properties.get("username", username);
password = properties.get("password", password);
driver = properties.get("driver", properties.get("databaseDriver", driver));
url = properties.get("url", properties.get("databaseUrl", url));
String dbUrl = properties.get(prefix + "databaseUrl", null);
this.url = properties.get(prefix + "url", dbUrl);
autoCommit = properties.getBoolean("autoCommit", autoCommit);
captureStackTrace = properties.getBoolean("captureStackTrace", captureStackTrace);
maxStackTraceSize = properties.getInt("maxStackTraceSize", maxStackTraceSize);
leakTimeMinutes = properties.getInt("leakTimeMinutes", leakTimeMinutes);
maxInactiveTimeSecs = properties.getInt("maxInactiveTimeSecs", maxInactiveTimeSecs);
trimPoolFreqSecs = properties.getInt("trimPoolFreqSecs", trimPoolFreqSecs);
maxAgeMinutes = properties.getInt("maxAgeMinutes", maxAgeMinutes);
this.autoCommit = properties.getBoolean(prefix + "autoCommit", false);
this.captureStackTrace = properties.getBoolean(prefix + "captureStackTrace", false);
this.maxStackTraceSize = properties.getInt(prefix + "maxStackTraceSize", 5);
this.leakTimeMinutes = properties.getInt(prefix + "leakTimeMinutes", 30);
this.maxInactiveTimeSecs = properties.getInt(prefix + "maxInactiveTimeSecs", 720);
this.trimPoolFreqSecs = properties.getInt(prefix + "trimPoolFreqSecs", 59);
this.maxAgeMinutes = properties.getInt(prefix + "maxAgeMinutes", 0);
minConnections = properties.getInt("minConnections", minConnections);
maxConnections = properties.getInt("maxConnections", maxConnections);
pstmtCacheSize = properties.getInt("pstmtCacheSize", pstmtCacheSize);
cstmtCacheSize = properties.getInt("cstmtCacheSize", cstmtCacheSize);
this.minConnections = properties.getInt(prefix + "minConnections", 0);
this.maxConnections = properties.getInt(prefix + "maxConnections", 20);
this.pstmtCacheSize = properties.getInt(prefix + "pstmtCacheSize", 20);
this.cstmtCacheSize = properties.getInt(prefix + "cstmtCacheSize", 20);
waitTimeoutMillis = properties.getInt("waitTimeout", waitTimeoutMillis);
this.waitTimeoutMillis = properties.getInt(prefix + "waitTimeout", 1000);
heartbeatSql = properties.get("heartbeatSql", heartbeatSql);
heartbeatTimeoutSeconds = properties.getInt("heartbeatTimeoutSeconds", heartbeatTimeoutSeconds);
poolListener = properties.get("poolListener", poolListener);
offline = properties.getBoolean("offline", offline);
this.heartbeatSql = properties.get(prefix + "heartbeatSql", null);
this.heartbeatTimeoutSeconds = properties.getInt(prefix + "heartbeatTimeoutSeconds", 3);
this.poolListener = properties.get(prefix + "poolListener", null);
this.offline = properties.getBoolean(prefix + "offline", false);
String isoLevel = properties.get(prefix + "isolationlevel", "READ_COMMITTED");
String isoLevel = properties.get("isolationlevel", getTransactionIsolationLevel(isolationLevel));
this.isolationLevel = getTransactionIsolationLevel(isoLevel);
String customProperties = properties.get(prefix + "customProperties", null);
String customProperties = properties.get("customProperties", null);
if (customProperties != null && customProperties.length() > 0) {
Map<String, String> custProps = StringHelper.delimitedToMap(customProperties, ";", "=");
this.customProperties = custProps;
@@ -498,7 +514,21 @@ public class DataSourceConfig {
}
/**
* return the isolation level for a given string description.
* Return the isolation level description from the associated Connection int value.
*/
public String getTransactionIsolationLevel(int level) {
switch (level) {
case Connection.TRANSACTION_NONE : return "NONE";
case Connection.TRANSACTION_READ_COMMITTED : return "READ_COMMITTED";
case Connection.TRANSACTION_READ_UNCOMMITTED : return "READ_UNCOMMITTED";
case Connection.TRANSACTION_REPEATABLE_READ : return "REPEATABLE_READ";
case Connection.TRANSACTION_SERIALIZABLE : return "SERIALIZABLE";
default: throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
}
}
/**
* Return the isolation level for a given string description.
*/
public int getTransactionIsolationLevel(String level) {
level = level.toUpperCase();
@@ -522,6 +552,6 @@ public class DataSourceConfig {
return Connection.TRANSACTION_SERIALIZABLE;
}
throw new RuntimeException("Transaction Isolaction level [" + level + "] is not known.");
throw new RuntimeException("Transaction Isolation level [" + level + "] is not known.");
}
}
@@ -1,211 +0,0 @@
package com.avaje.ebean.config;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletContext;
import com.avaje.ebean.util.ClassUtil;
/**
* Provides access to properties loaded from the ebean.properties file.
*/
public final class GlobalProperties {
private static volatile PropertyMap globalMap;
private static boolean skipPrimaryServer;
/**
* Set whether to skip automatically creating the primary server.
*/
public static synchronized void setSkipPrimaryServer(boolean skip) {
skipPrimaryServer = skip;
}
/**
* Return true to skip automatically creating the primary server.
*/
public static synchronized boolean isSkipPrimaryServer() {
return skipPrimaryServer;
}
/**
* Parse the string replacing any expressions like ${catalina.base}.
* <p>
* This will evaluate expressions using first environment variables, than java
* system variables and lastly properties in ebean.properties - in that order.
* </p>
* <p>
* Expressions start with "${" and end with "}".
* </p>
*/
public static String evaluateExpressions(String val) {
return getPropertyMap().eval(val);
}
/**
* Parse and evaluate any expressions that have not already been evaluated.
*/
public static synchronized void evaluateExpressions() {
getPropertyMap().evaluateProperties();
}
/**
* In a servlet container environment this will additionally look in WEB-INF
* for the ebean.properties file.
*/
public static synchronized void setServletContext(ServletContext servletContext) {
PropertyMapLoader.setServletContext(servletContext);
}
/**
* Return the ServletContext (if setup in a servlet container environment).
*/
public static synchronized ServletContext getServletContext() {
return PropertyMapLoader.getServletContext();
}
private static void initPropertyMap() {
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
fileName = "ebean.properties";
}
}
globalMap = PropertyMapLoader.load(null, fileName);
if (globalMap == null) {
// ebean.properties file was not found... but that
// is ok because we are likely doing programmatic config
globalMap = new PropertyMap();
}
String loaderCn = globalMap.get("ebean.properties.loader");
if (loaderCn != null) {
// a Runnable that can be used to customise the initialisation
// of the GlobalProperties
try {
Runnable r = (Runnable) ClassUtil.newInstance(loaderCn);
r.run();
} catch (Exception e) {
String m = "Error creating or running properties loader " + loaderCn;
throw new RuntimeException(m, e);
}
}
}
/**
* Return the property map loading it if required.
*/
private static synchronized PropertyMap getPropertyMap() {
if (globalMap == null) {
initPropertyMap();
}
return globalMap;
}
/**
* Return a String property with a default value.
*/
public static synchronized String get(String key, String defaultValue) {
return getPropertyMap().get(key, defaultValue);
}
/**
* Return a int property with a default value.
*/
public static synchronized int getInt(String key, int defaultValue) {
return getPropertyMap().getInt(key, defaultValue);
}
/**
* Return a boolean property with a default value.
*/
public static synchronized boolean getBoolean(String key, boolean defaultValue) {
return getPropertyMap().getBoolean(key, defaultValue);
}
/**
* Set a property return the previous value. This will evaluate any
* expressions in the value.
*/
public static synchronized String put(String key, String value) {
return getPropertyMap().putEval(key, value);
}
/**
* Set a Map of key value properties.
*/
public static synchronized void putAll(Map<String, String> keyValueMap) {
for (Entry<String, String> e : keyValueMap.entrySet()) {
getPropertyMap().putEval(e.getKey(), e.getValue());
}
}
public static PropertySource getPropertySource(String name) {
return new ConfigPropertyMap(name);
}
public static interface PropertySource {
/**
* Return the name of the server. This is also the dataSource name.
*/
public String getServerName();
/**
* Get a property. This will prepend "ebean" and the server name to lookup
* the value.
*/
public String get(String key, String defaultValue);
public int getInt(String key, int defaultValue);
public boolean getBoolean(String key, boolean defaultValue);
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue);
}
public static class DelegatedGlobalPropertySource implements PropertySource {
private String serverName;
public DelegatedGlobalPropertySource(String serverName) {
this.serverName = serverName;
}
@Override
public String getServerName() {
return serverName;
}
@Override
public String get(String key, String defaultValue) {
return GlobalProperties.get(key, defaultValue);
}
@Override
public int getInt(String key, int defaultValue) {
return GlobalProperties.getInt(key, defaultValue);
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
return GlobalProperties.getBoolean(key, defaultValue);
}
@Override
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
}
}
}
@@ -112,4 +112,9 @@ public interface NamingConvention {
*/
public boolean isUseForeignKeyPrefix();
/**
* Load setting from properties.
*/
public void loadFromProperties(PropertiesWrapper properties);
}
@@ -0,0 +1,52 @@
package com.avaje.ebean.config;
/**
* Defines the mode for JDBC batch processing.
* <p>
* Used both at a per transaction basis and per request basis.
* </p>
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
* @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
*
* @see com.avaje.ebean.Transaction#setBatch(PersistBatch)
* @see com.avaje.ebean.Transaction#setBatchOnCascade(PersistBatch)
*/
public enum PersistBatch {
/**
* Do not use JDBC Batch mode.
*/
NONE(false),
/**
* Use JDBC Batch mode on Inserts.
*/
INSERT(true),
/**
* Use JDBC Batch mode on Inserts, Updates and Deletes.
*/
ALL(true),
/**
* You should not use this value explicitly. It should only used on the Transactional annotation
* to indicate that the value should inherit from the ServerConfig setting.
*/
INHERIT(false);
boolean forInsert;
PersistBatch(boolean forInsert) {
this.forInsert = forInsert;
}
/**
* Return true if persist cascade should use JDBC batch for inserts.
*/
public boolean forInsert() {
return forInsert;
}
}
@@ -0,0 +1,148 @@
package com.avaje.ebean.config;
import java.util.Properties;
public class PropertiesWrapper {
protected final Properties properties;
protected final String prefix;
protected final String serverName;
protected final PropertyMap propertyMap;
/**
* Construct with a prefix, serverName and properties.
*/
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
this.serverName = serverName;
this.prefix = prefix;
this.propertyMap = PropertyMapLoader.load(null, properties);
this.properties = propertyMap.asProperties();
}
/**
* Construct without prefix of serverName.
*/
public PropertiesWrapper(Properties properties) {
this(null, null, properties);
}
/**
* Internal copy constructor when changing prefix.
*/
protected PropertiesWrapper(String prefix, String serverName, PropertyMap propertyMap, Properties properties) {
this.serverName = serverName;
this.prefix = prefix;
this.propertyMap = propertyMap;
this.properties = properties;
}
/**
* Return a PropertiesWrapper instance with a different prefix but same underlying properties.
* <p/>
* Used when wanting to use "datasource" as the prefix rather than "ebean".
* <p/>
* The returning instance should only be used in a read only fashion.
*/
public PropertiesWrapper withPrefix(String prefix) {
return new PropertiesWrapper(prefix, serverName, propertyMap, properties);
}
/**
* Return the serverName (optional).
*/
public String getServerName() {
return serverName;
}
/**
* Return as Properties with lower case keys and after evaluation and additional properties loading has occurred.
* <p>
* Ebean has historically ignored the case of keys hence returning the Properties with all the keys lower cased.
* </p>
*/
public Properties asPropertiesLowerCase() {
return properties;
}
/**
* Get a property with no default value.
*/
public String get(String key) {
return get(key, null);
}
/**
* Get a property with a default value.
* <p>
* This performs a search using the prefix and server name (if supplied) to search for the property
* value in order based on:
* <pre>{@code
* prefix.serverName.key
* prefix.key
* key
* }</pre>
* </p>
*/
public String get(String key, String defaultValue) {
String value = null;
if (serverName != null && prefix != null) {
value = propertyMap.get(prefix + "." + serverName + "." + key, null);
}
if (value == null && prefix != null) {
value = propertyMap.get(prefix + "." + key, null);
}
if (value == null) {
value = propertyMap.get(key, null);
}
return value == null ? defaultValue : value;
}
/**
* Return a double property value.
*/
public double getDouble(String key, double defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Double.parseDouble(value);
}
/**
* Return an int property value.
*/
public int getInt(String key, int defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Integer.parseInt(value);
}
/**
* Return a long property value.
*/
public long getLong(String key, long defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Long.parseLong(value);
}
/**
* Return a boolean property value.
*/
public boolean getBoolean(String key, boolean defaultValue) {
String value = get(key, String.valueOf(defaultValue));
return Boolean.parseBoolean(value);
}
/**
* Return a Enum property value.
*/
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
}
}
@@ -4,21 +4,40 @@ import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
/**
* A map like structure of properties.
* <p/>
* Handles evaluation of expressions like ${home} and provides convenience methods for int, long and boolean.
*/
final class PropertyMap implements Serializable {
public final class PropertyMap implements Serializable {
private static final long serialVersionUID = 1L;
private LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
public static Properties defaultProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public String toString() {
return map.toString();
}
/**
* Return as standard Properties.
*/
public Properties asProperties() {
Properties properties = new Properties();
for (Entry<String, String> e : entrySet()) {
properties.put(e.getKey(), e.getValue());
}
return properties;
}
/**
* Go through all the properties and evaluate any expressions that have not
* been resolved.
@@ -35,10 +54,16 @@ final class PropertyMap implements Serializable {
}
}
/**
* Returns the value with expressions like ${home} evaluated using system properties and environment variables.
*/
public synchronized String eval(String val) {
return PropertyExpression.eval(val, this);
}
/**
* Return the boolean property value with a given default.
*/
public synchronized boolean getBoolean(String key, boolean defaultValue) {
String value = get(key);
if (value == null) {
@@ -48,6 +73,9 @@ final class PropertyMap implements Serializable {
}
}
/**
* Return the int property value with a given default.
*/
public synchronized int getInt(String key, int defaultValue) {
String value = get(key);
if (value == null) {
@@ -57,36 +85,69 @@ final class PropertyMap implements Serializable {
}
}
/**
* Return the long property value with a given default.
*/
public synchronized long getLong(String key, long defaultValue) {
String value = get(key);
if (value == null) {
return defaultValue;
} else {
return Long.parseLong(value);
}
}
/**
* Return the string property value with a given default.
*/
public synchronized String get(String key, String defaultValue) {
String value = map.get(key.toLowerCase());
return value == null ? defaultValue : value;
}
/**
* Return the property value returning null if there is no value defined.
*/
public synchronized String get(String key) {
return map.get(key.toLowerCase());
}
synchronized void putAll(Map<String, String> keyValueMap) {
/**
* Put all evaluating any expressions in the values.
*/
public synchronized void putEvalAll(Map<String, String> keyValueMap) {
for (Map.Entry<String, String> entry : keyValueMap.entrySet()) {
put(entry.getKey(), entry.getValue());
putEval(entry.getKey(), entry.getValue());
}
}
synchronized String putEval(String key, String value) {
/**
* Put a single key value evaluating any expressions in the value.
*/
public synchronized String putEval(String key, String value) {
value = PropertyExpression.eval(value, this);
return map.put(key.toLowerCase(), value);
}
synchronized String put(String key, String value) {
/**
* Put a single key value with no expression evaluation.
*/
public synchronized String put(String key, String value) {
return map.put(key.toLowerCase(), value);
}
synchronized String remove(String key) {
/**
* Remove an entry.
*/
public synchronized String remove(String key) {
return map.remove(key.toLowerCase());
}
synchronized Set<Entry<String, String>> entrySet() {
/**
* Return the entries.
*/
public synchronized Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
@@ -1,40 +1,30 @@
package com.avaje.ebean.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Map;
import java.util.Properties;
/**
* Helper used to load the PropertyMap.
* Helper used to load the ebean.properties into a PropertyMap.
*/
final class PropertyMapLoader {
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
private static ServletContext servletContext;
public static PropertyMap loadGlobalProperties() {
/**
* Return the servlet context when in a web environment.
*/
public static ServletContext getServletContext() {
return servletContext;
}
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
fileName = "ebean.properties";
}
}
/**
* Set the ServletContext for when ebean.properties is in WEB-INF in a web
* application environment.
*/
public static void setServletContext(ServletContext servletContext) {
PropertyMapLoader.servletContext = servletContext;
return load(null, fileName);
}
/**
@@ -64,15 +54,20 @@ final class PropertyMapLoader {
* @param in
* the InputStream of the properties file to load.
*/
private static PropertyMap load(PropertyMap p, InputStream in) {
public static PropertyMap load(PropertyMap p, InputStream in) {
Properties props = new Properties();
try {
props.load(in);
in.close();
return load(p, props);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static PropertyMap load(PropertyMap p, Properties props) {
if (p == null) {
p = new PropertyMap();
@@ -117,18 +112,6 @@ final class PropertyMapLoader {
throw new NullPointerException("fileName is null?");
}
if (servletContext == null) {
logger.debug("No servletContext so not looking in WEB-INF for " + fileName);
} else {
// first look in WEB-INF ...
InputStream in = servletContext.getResourceAsStream("/WEB-INF/" + fileName);
if (in != null) {
logger.debug(fileName + " found in WEB-INF");
return in;
}
}
try {
File f = new File(fileName);
@@ -5,7 +5,6 @@ import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.annotation.Encrypted;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.GlobalProperties.PropertySource;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.event.*;
@@ -16,6 +15,7 @@ import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* The configuration used for creating a EbeanServer.
@@ -30,9 +30,9 @@ import java.util.List;
* classes and listeners etc.
* </p>
*
* <pre class="code">
* <pre>{@code
* ServerConfig c = new ServerConfig();
* c.setName(&quot;ordh2&quot;);
* c.setName("ordh2");
*
* // read the ebean.properties and load
* // those settings into this serverConfig object
@@ -43,18 +43,18 @@ import java.util.List;
* c.setDdlRun(true);
*
* // add any classes found in the app.data package
* c.addPackage(&quot;app.data&quot;);
* c.addPackage("app.data");
*
* // add the names of Jars that contain entities
* c.addJar(&quot;myJarContainingEntities.jar&quot;);
* c.addJar(&quot;someOtherJarContainingEntities.jar&quot;);
* c.addJar("myJarContainingEntities.jar");
* c.addJar("someOtherJarContainingEntities.jar");
*
* // register as the 'Default' server
* c.setDefaultServer(true);
*
* EbeanServer server = EbeanServerFactory.create(c);
*
* </pre>
* }</pre>
*
* @see EbeanServerFactory
*
@@ -62,27 +62,19 @@ import java.util.List;
* @author rbygrave
*/
public class ServerConfig {
/**
* The Constant DEFAULT_QUERY_BATCH_SIZE. Default: 100
*/
private final static int DEFAULT_QUERY_BATCH_SIZE = 100;
/**
* The EbeanServer name.
*/
private String name;
private ContainerConfig containerConfig;
/**
* The resource directory.
*/
private String resourceDirectory;
/**
* The enhance log level. Used with subclass generation.
*/
private int enhanceLogLevel;
/**
* Set to true to register this EbeanServer with the Ebean singleton.
*/
@@ -111,6 +103,11 @@ public class ServerConfig {
*/
private List<String> searchJars = new ArrayList<String>();
/**
* Class name of a classPathReader implementation.
*/
private String classPathReaderClassName;
/**
* Config controlling the autofetch behaviour.
*/
@@ -136,19 +133,29 @@ public class ServerConfig {
*/
private int databaseSequenceBatchSize = 20;
private boolean persistBatching;
/**
* Use for transaction scoped batch mode.
*/
private PersistBatch persistBatch = PersistBatch.NONE;
/**
* Use for per request batch mode.
*/
private PersistBatch persistBatchOnCascade = PersistBatch.NONE;
private int persistBatchSize = 20;
/**
* The default batch size for lazy loading
*/
private int lazyLoadBatchSize = 1;
private int lazyLoadBatchSize = 10;
/**
* The query batch size.
* The default batch size for 'query joins'.
*/
private int queryBatchSize = -1;
private int queryBatchSize = 100;
private boolean eagerFetchLobs;
private boolean ddlGenerate;
@@ -201,7 +208,7 @@ public class ServerConfig {
/**
* The naming convention.
*/
private NamingConvention namingConvention;
private NamingConvention namingConvention = new UnderscoreNamingConvention();
/**
* Behaviour of update to include on the change properties.
@@ -238,9 +245,9 @@ public class ServerConfig {
private ServerCacheManager serverCacheManager;
private boolean collectQueryStatsByNode;
private boolean collectQueryStatsByNode = true;
private boolean collectQueryOrigins;
private boolean collectQueryOrigins = true;
/**
* The default PersistenceContextScope used if one is not explicitly set on a query.
@@ -253,6 +260,33 @@ public class ServerConfig {
private boolean durationWithNanos;
private int maxCallStack = 5;
private boolean transactionRollbackOnChecked = true;
private boolean registerJmxMBeans = true;
// configuration for the background executor service (thread pool)
private int backgroundExecutorSchedulePoolSize = 1;
private int backgroundExecutorCorePoolSize = 1;
private int backgroundExecutorMaxPoolSize = 8;
private int backgroundExecutorIdleSecs = 60;
private int backgroundExecutorShutdownSecs = 30;
// defaults for the L2 bean caching
private int cacheWarmingDelay = 30;
private int cacheMaxSize = 10000;
private int cacheMaxIdleTime = 600;
private int cacheMaxTimeToLive = 60*60*6;
// defaults for the L2 query caching
private int queryCacheMaxSize = 1000;
private int queryCacheMaxIdleTime = 600;
private int queryCacheMaxTimeToLive = 60*60*6;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -306,6 +340,26 @@ public class ServerConfig {
this.name = name;
}
/**
* Return the container / clustering configuration.
* <p/>
* The container holds all the EbeanServer instances and provides clustering communication
* services to all the EbeanServer instances.
*/
public ContainerConfig getContainerConfig() {
return containerConfig;
}
/**
* Set the container / clustering configuration.
* <p/>
* The container holds all the EbeanServer instances and provides clustering communication
* services to all the EbeanServer instances.
*/
public void setContainerConfig(ContainerConfig containerConfig) {
this.containerConfig = containerConfig;
}
/**
* Return true if this server should be registered with the Ebean singleton
* when it is created.
@@ -351,45 +405,74 @@ public class ServerConfig {
}
/**
* Returns true if by default JDBC batching is used for persisting or deleting
* Return the PersistBatch mode to use by default at the transaction level.
* <p>
* When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
* a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
* or the batch size is meet.
* </p>
*/
public PersistBatch getPersistBatch() {
return persistBatch;
}
/**
* Set the JDBC batch mode to use at the transaction level.
* <p>
* When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
* a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
* or the batch size is meet.
* </p>
*/
public void setPersistBatch(PersistBatch persistBatch) {
this.persistBatch = persistBatch;
}
/**
* Return the JDBC batch mode to use per save(), delete(), insert() or update() request.
* <p>
* This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
* </p>
* <p>
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
* </p>
*/
public PersistBatch getPersistBatchOnCascade() {
return persistBatchOnCascade;
}
/**
* Set the JDBC batch mode to use per save(), delete(), insert() or update() request.
* <p>
* This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
* </p>
* <p>
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
* </p>
*/
public void setPersistBatchOnCascade(PersistBatch persistBatchOnCascade) {
this.persistBatchOnCascade = persistBatchOnCascade;
}
/**
* Deprecated, please migrate to using setPersistBatch().
* <p>
* Set to true if you what to use JDBC batching for persisting and deleting
* beans.
* </p>
* <p>
* With this Ebean will batch up persist requests and use the JDBC batch api.
* This is a performance optimisation designed to reduce the network chatter.
* </p>
*/
public boolean isPersistBatching() {
return persistBatching;
}
/**
* Use isPersistBatching() instead.
*
* @deprecated
*/
public boolean isUsePersistBatching() {
return persistBatching;
}
/**
* Set to true if you what to use JDBC batching for persisting and deleting
* beans.
* <p>
* With this Ebean will batch up persist requests and use the JDBC batch api.
* This is a performance optimisation designed to reduce the network chatter.
* When true this is equivalent to {@code setPersistBatch(PersistBatch.ALL)} or
* when false to {@code setPersistBatch(PersistBatch.NONE)}
* </p>
*/
public void setPersistBatching(boolean persistBatching) {
this.persistBatching = persistBatching;
}
/**
* Use setPersistBatching() instead.
*
* @deprecated
*/
public void setUsePersistBatching(boolean persistBatching) {
this.persistBatching = persistBatching;
this.persistBatch = (persistBatching) ? PersistBatch.ALL : PersistBatch.NONE;
}
/**
@@ -401,11 +484,34 @@ public class ServerConfig {
/**
* Set the batch size used for JDBC batching. If unset this defaults to 20.
* <p>
* You can also set the batch size on the transaction.
* </p>
* @see com.avaje.ebean.Transaction#setBatchSize(int)
*/
public void setPersistBatchSize(int persistBatchSize) {
this.persistBatchSize = persistBatchSize;
}
/**
* Gets the query batch size. This defaults to 100.
*
* @return the query batch size
*/
public int getQueryBatchSize() {
return queryBatchSize;
}
/**
* Sets the query batch size. This defaults to 100.
*
* @param queryBatchSize
* the new query batch size
*/
public void setQueryBatchSize(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
}
/**
* Return the default batch size for lazy loading of beans and collections.
*/
@@ -413,25 +519,6 @@ public class ServerConfig {
return lazyLoadBatchSize;
}
/**
* Gets the query batch size.
*
* @return the query batch size
*/
public int getQueryBatchSize() {
return queryBatchSize;
}
/**
* Sets the query batch size.
*
* @param queryBatchSize
* the new query batch size
*/
public void setQueryBatchSize(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
}
/**
* Set the default batch size for lazy loading.
* <p>
@@ -439,7 +526,7 @@ public class ServerConfig {
* invoked by default.
* </p>
* <p>
* The default value is for this is 1 (load 1 bean or collection).
* The default value is for this is 10 (load 10 beans or collections).
* </p>
* <p>
* You can explicitly control the lazy loading batch size for a given join on
@@ -519,17 +606,231 @@ public class ServerConfig {
}
/**
* Return the log level used for "subclassing" enhancement.
* Return true if LOB's should default to fetch eager.
* By default this is set to false and LOB's must be explicitly fetched.
*/
public int getEnhanceLogLevel() {
return enhanceLogLevel;
public boolean isEagerFetchLobs() {
return eagerFetchLobs;
}
/**
* Set the log level used for "subclassing" enhancement.
* Set to true if you want LOB's to be fetch eager by default.
* By default this is set to false and LOB's must be explicitly fetched.
*/
public void setEnhanceLogLevel(int enhanceLogLevel) {
this.enhanceLogLevel = enhanceLogLevel;
public void setEagerFetchLobs(boolean eagerFetchLobs) {
this.eagerFetchLobs = eagerFetchLobs;
}
/**
* Return the max call stack to use for origin location.
*/
public int getMaxCallStack() {
return maxCallStack;
}
/**
* Set the max call stack to use for origin location.
*/
public void setMaxCallStack(int maxCallStack) {
this.maxCallStack = maxCallStack;
}
/**
* Return true if transactions should rollback on checked exceptions.
*/
public boolean isTransactionRollbackOnChecked() {
return transactionRollbackOnChecked;
}
/**
* Set to true if transactions should by default rollback on checked exceptions.
*/
public void setTransactionRollbackOnChecked(boolean transactionRollbackOnChecked) {
this.transactionRollbackOnChecked = transactionRollbackOnChecked;
}
/**
* Return true if the server should register JMX MBeans.
*/
public boolean isRegisterJmxMBeans() {
return registerJmxMBeans;
}
/**
* Set if the server should register JMX MBeans.
*/
public void setRegisterJmxMBeans(boolean registerJmxMBeans) {
this.registerJmxMBeans = registerJmxMBeans;
}
/**
* Return the Background executor schedule pool size. Defaults to 1.
*/
public int getBackgroundExecutorSchedulePoolSize() {
return backgroundExecutorSchedulePoolSize;
}
/**
* Set the Background executor schedule pool size.
*/
public void setBackgroundExecutorSchedulePoolSize(int backgroundExecutorSchedulePoolSize) {
this.backgroundExecutorSchedulePoolSize = backgroundExecutorSchedulePoolSize;
}
/**
* Return the Background executor core pool size.
*/
public int getBackgroundExecutorCorePoolSize() {
return backgroundExecutorCorePoolSize;
}
/**
* Set the Background executor core pool size.
*/
public void setBackgroundExecutorCorePoolSize(int backgroundExecutorCorePoolSize) {
this.backgroundExecutorCorePoolSize = backgroundExecutorCorePoolSize;
}
/**
* Return the Background executor max pool size.
*/
public int getBackgroundExecutorMaxPoolSize() {
return backgroundExecutorMaxPoolSize;
}
/**
* Set the Background executor max pool size.
*/
public void setBackgroundExecutorMaxPoolSize(int backgroundExecutorMaxPoolSize) {
this.backgroundExecutorMaxPoolSize = backgroundExecutorMaxPoolSize;
}
/**
* Return the Background executor idle seconds.
*/
public int getBackgroundExecutorIdleSecs() {
return backgroundExecutorIdleSecs;
}
/**
* Set the Background executor idle seconds.
*/
public void setBackgroundExecutorIdleSecs(int backgroundExecutorIdleSecs) {
this.backgroundExecutorIdleSecs = backgroundExecutorIdleSecs;
}
/**
* Return the Background executor shutdown seconds. This is the time allowed for the pool to shutdown nicely
* before it is forced shutdown.
*/
public int getBackgroundExecutorShutdownSecs() {
return backgroundExecutorShutdownSecs;
}
/**
* Set the Background executor shutdown seconds. This is the time allowed for the pool to shutdown nicely
* before it is forced shutdown.
*/
public void setBackgroundExecutorShutdownSecs(int backgroundExecutorShutdownSecs) {
this.backgroundExecutorShutdownSecs = backgroundExecutorShutdownSecs;
}
/**
* Return the cache warming delay in seconds.
*/
public int getCacheWarmingDelay() {
return cacheWarmingDelay;
}
/**
* Set the cache warming delay in seconds.
*/
public void setCacheWarmingDelay(int cacheWarmingDelay) {
this.cacheWarmingDelay = cacheWarmingDelay;
}
/**
* Return the L2 cache default max size.
*/
public int getCacheMaxSize() {
return cacheMaxSize;
}
/**
* Set the L2 cache default max size.
*/
public void setCacheMaxSize(int cacheMaxSize) {
this.cacheMaxSize = cacheMaxSize;
}
/**
* Return the L2 cache default max idle time in seconds.
*/
public int getCacheMaxIdleTime() {
return cacheMaxIdleTime;
}
/**
* Set the L2 cache default max idle time in seconds.
*/
public void setCacheMaxIdleTime(int cacheMaxIdleTime) {
this.cacheMaxIdleTime = cacheMaxIdleTime;
}
/**
* Return the L2 cache default max time to live in seconds.
*/
public int getCacheMaxTimeToLive() {
return cacheMaxTimeToLive;
}
/**
* Set the L2 cache default max time to live in seconds.
*/
public void setCacheMaxTimeToLive(int cacheMaxTimeToLive) {
this.cacheMaxTimeToLive = cacheMaxTimeToLive;
}
/**
* Return the L2 query cache default max size.
*/
public int getQueryCacheMaxSize() {
return queryCacheMaxSize;
}
/**
* Set the L2 query cache default max size.
*/
public void setQueryCacheMaxSize(int queryCacheMaxSize) {
this.queryCacheMaxSize = queryCacheMaxSize;
}
/**
* Return the L2 query cache default max idle time in seconds.
*/
public int getQueryCacheMaxIdleTime() {
return queryCacheMaxIdleTime;
}
/**
* Set the L2 query cache default max idle time in seconds.
*/
public void setQueryCacheMaxIdleTime(int queryCacheMaxIdleTime) {
this.queryCacheMaxIdleTime = queryCacheMaxIdleTime;
}
/**
* Return the L2 query cache default max time to live in seconds.
*/
public int getQueryCacheMaxTimeToLive() {
return queryCacheMaxTimeToLive;
}
/**
* Set the L2 query cache default max time to live in seconds.
*/
public void setQueryCacheMaxTimeToLive(int queryCacheMaxTimeToLive) {
this.queryCacheMaxTimeToLive = queryCacheMaxTimeToLive;
}
/**
@@ -1049,6 +1350,23 @@ public class ServerConfig {
this.searchJars = searchJars;
}
/**
* Return the class name of a classPathReader implementation.
*/
public String getClassPathReaderClassName() {
return classPathReaderClassName;
}
/**
* Set the class name of a classPathReader implementation.
*
* Refer to server.util.ClassPathReader, this should really by a plugin but doing this for now
* to be relatively compatible with current implementation.
*/
public void setClassPathReaderClassName(String classPathReaderClassName) {
this.classPathReaderClassName = classPathReaderClassName;
}
/**
* Set the list of classes (entities, listeners, scalarTypes etc) that should
* be used for this server.
@@ -1334,44 +1652,26 @@ public class ServerConfig {
}
/**
* Load the settings from the ebean.properties file.
* Load settings from ebean.properties.
*/
public void loadFromProperties() {
ConfigPropertyMap p = new ConfigPropertyMap(name);
loadFromProperties(PropertyMap.defaultProperties());
}
/**
* Load the settings from the given properties
*/
public void loadFromProperties(Properties properties) {
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
loadSettings(p);
}
/**
* Return a PropertySource for this server.
*/
public PropertySource getPropertySource() {
return GlobalProperties.getPropertySource(name);
}
/**
* Return a configuration property using a default value.
*/
public String getProperty(String propertyName, String defaultValue) {
PropertySource p = new ConfigPropertyMap(name);
return p.get(propertyName, defaultValue);
}
/**
* Return a configuration property.
*/
public String getProperty(String propertyName) {
return getProperty(propertyName, null);
}
@SuppressWarnings("unchecked")
private <T> T createInstance(PropertySource p, Class<T> type, String key) {
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key) {
String classname = p.get(key, null);
if (classname == null) {
return null;
}
return (T) ClassUtil.newInstance(classname);
return classname == null ? null : (T) ClassUtil.newInstance(classname);
}
/**
@@ -1381,37 +1681,39 @@ public class ServerConfig {
*
* @param p - The defined property source passed to load settings
*/
protected void loadDataSourceSettings(PropertySource p) {
dataSourceConfig.loadSettings(p.getServerName());
protected void loadDataSourceSettings(PropertiesWrapper p) {
dataSourceConfig.loadSettings(p.withPrefix("datasource"));
}
/**
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
*/
protected void loadAutofetchConfig(PropertySource p) {
protected void loadAutofetchSettings(PropertiesWrapper p) {
autofetchConfig.loadSettings(p);
}
/**
* Load the configuration settings from the properties file.
*/
protected void loadSettings(PropertySource p) {
protected void loadSettings(PropertiesWrapper p) {
namingConvention = createNamingConvention(p, namingConvention);
if (namingConvention != null) {
namingConvention.loadFromProperties(p);
}
if (autofetchConfig == null) {
autofetchConfig = new AutofetchConfig();
}
loadAutofetchConfig(p);
loadAutofetchSettings(p);
if (dataSourceConfig == null) {
dataSourceConfig = new DataSourceConfig();
}
loadDataSourceSettings(p);
autoCommitMode = p.getBoolean("autoCommitMode", false);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", false);
namingConvention = createNamingConvention(p);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager");
@@ -1425,37 +1727,41 @@ public class ServerConfig {
searchJars = getSearchJarsPackages(jarsProp);
}
String packagesProp = p.get("search.packages", p.get("packages", null));
if (packages != null) {
String packagesProp = p.get("search.packages", p.get("packages", null));
packages = getSearchJarsPackages(packagesProp);
}
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", true);
collectQueryOrigins = p.getBoolean("collectQueryOrigins", true);
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", collectQueryStatsByNode);
collectQueryOrigins = p.getBoolean("collectQueryOrigins", collectQueryOrigins);
updateChangesOnly = p.getBoolean("updateChangesOnly", true);
updateChangesOnly = p.getBoolean("updateChangesOnly", updateChangesOnly);
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", true);
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren);
updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren);
boolean batchMode = p.getBoolean("batch.mode", false);
persistBatching = p.getBoolean("persistBatching", batchMode);
if (p.get("batch.mode") != null || p.get("persistBatching") != null) {
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
}
int batchSize = p.getInt("batch.size", 20);
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
int batchSize = p.getInt("batch.size", persistBatchSize);
persistBatchSize = p.getInt("persistBatchSize", batchSize);
persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope","TRANSACTION"));
dataSourceJndiName = p.get("dataSourceJndiName", null);
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", 20);
databaseBooleanTrue = p.get("databaseBooleanTrue", null);
databaseBooleanFalse = p.get("databaseBooleanFalse", null);
databasePlatformName = p.get("databasePlatformName", null);
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", false);
localTimeWithNanos = p.getBoolean("localTimeWithNanos", false);
dataSourceJndiName = p.get("dataSourceJndiName", dataSourceJndiName);
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", uuidStoreAsBinary);
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", 1);
queryBatchSize = p.getInt("queryBatchSize", DEFAULT_QUERY_BATCH_SIZE);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
String jsonDateTimeFormat = p.get("jsonDateTime", null);
if (jsonDateTimeFormat != null) {
@@ -1464,51 +1770,35 @@ public class ServerConfig {
jsonDateTime = JsonConfig.DateTime.MILLIS;
}
ddlGenerate = p.getBoolean("ddl.generate", false);
ddlRun = p.getBoolean("ddl.run", false);
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
ddlRun = p.getBoolean("ddl.run", ddlRun);
classes = getClasses(p);
}
private NamingConvention createNamingConvention(PropertySource p) {
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = createInstance(p, NamingConvention.class, "namingconvention");
if (nc == null) {
return null;
}
if (nc instanceof AbstractNamingConvention) {
AbstractNamingConvention anc = (AbstractNamingConvention) nc;
String v = p.get("namingConvention.useForeignKeyPrefix", null);
if (v != null) {
boolean useForeignKeyPrefix = Boolean.valueOf(v);
anc.setUseForeignKeyPrefix(useForeignKeyPrefix);
}
String sequenceFormat = p.get("namingConvention.sequenceFormat", null);
if (sequenceFormat != null) {
anc.setSequenceFormat(sequenceFormat);
}
}
return nc;
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention");
return (nc != null) ? nc : namingConvention;
}
/**
* Build the list of classes from the comma delimited string.
*
* @param p
* the p
* @param properties
* the properties
*
* @return the classes
*/
private ArrayList<Class<?>> getClasses(PropertySource p) {
private List<Class<?>> getClasses(PropertiesWrapper properties) {
String classNames = p.get("classes", null);
String classNames = properties.get("classes", null);
if (classNames == null) {
return null;
}
ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
List<Class<?>> classes = new ArrayList<Class<?>>();
String[] split = classNames.split("[ ,;]");
for (int i = 0; i < split.length; i++) {
@@ -1539,4 +1829,15 @@ public class ServerConfig {
return hitList;
}
/**
* Return the PersistBatch mode to use for 'batchOnCascade' taking into account if the database
* platform supports getGeneratedKeys in batch mode.
* <p>
* Used to effectively turn off batchOnCascade for SQL Server - still allows explicit batch mode.
* </p>
*/
public PersistBatch appliedPersistBatchOnCascade() {
return databasePlatform.isDisallowBatchOnCascade() ? PersistBatch.NONE : persistBatchOnCascade;
}
}
@@ -72,6 +72,10 @@ public class DatabasePlatform {
*/
protected String name = "generic";
protected String columnAliasPrefix = "c";
protected String tableAliasPlaceHolder = "${ta}";
/**
* Use a BackTick ` at the beginning and end of table or column names that you
* want to use quoted identifiers for. The backticks get converted to the
@@ -95,7 +99,14 @@ public class DatabasePlatform {
* findIterate() and findVisit().
*/
protected boolean forwardOnlyHintOnFindIterate;
/**
* Flag set for SQL Server due to lack of support of getGeneratedKeys in
* batch mode (meaning for batch inserts you should explicitly turn off
* getGeneratedKeys - joy).
*/
protected boolean disallowBatchOnCascade;
/**
* Instantiates a new database platform.
*/
@@ -166,6 +177,34 @@ public class DatabasePlatform {
return dbDdlSyntax;
}
/**
* Return the column alias prefix.
*/
public String getColumnAliasPrefix() {
return columnAliasPrefix;
}
/**
* Set the column alias prefix.
*/
public void setColumnAliasPrefix(String columnAliasPrefix) {
this.columnAliasPrefix = columnAliasPrefix;
}
/**
* Return the table alias placeholder.
*/
public String getTableAliasPlaceHolder() {
return tableAliasPlaceHolder;
}
/**
* Set the table alias placeholder.
*/
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
}
/**
* Return the close quote for quoted identifiers.
*
@@ -335,4 +374,15 @@ public class DatabasePlatform {
public String getLikeClause() {
return likeClause;
}
/**
* Return true if the persistBatchOnCascade setting should be ignored.
* <p>
* This is primarily for SQL Server which does not support getGeneratedKeys with jdbc batch mode
* so can't really be transparently used.
* </p>
*/
public boolean isDisallowBatchOnCascade() {
return disallowBatchOnCascade;
}
}
@@ -7,30 +7,35 @@ import com.avaje.ebean.config.TableName;
*/
public class DbDdlSyntax {
private boolean renderIndexForFkey = true;
protected boolean renderIndexForFkey = true;
private boolean inlinePrimaryKeyConstraint = false;
protected boolean inlinePrimaryKeyConstraint = false;
private boolean addOneToOneUniqueContraint = true;
protected boolean addOneToOneUniqueContraint = true;
private int maxConstraintNameLength = 32;
protected int maxConstraintNameLength = 32;
private int columnNameWidth = 25;
protected int columnNameWidth = 25;
private String dropTableCascade;
private String dropIfExists;
/**
* Flag set when we want to disable constraints on each table (rather than globally).
*/
protected boolean dropKeyConstraints;
private String newLine = "\n";
protected String dropTableCascade;
protected String dropIfExists;
private String identity = "auto_increment";
private String identitySuffix = "";
protected String newLine = "\n";
private String pkPrefix = "pk_";
protected String identity = "auto_increment";
protected String identitySuffix = "";
private String disableReferentialIntegrity;
private String enableReferentialIntegrity;
protected String pkPrefix = "pk_";
private String foreignKeySuffix;
protected String disableReferentialIntegrity;
protected String enableReferentialIntegrity;
protected String foreignKeySuffix;
/**
* Return the primary key name for a given bean descriptor.
@@ -158,6 +163,20 @@ public class DbDdlSyntax {
this.enableReferentialIntegrity = enableReferentialIntegrity;
}
/**
* Return true to is constraints are disabled on each table.
*/
public boolean isDropKeyConstraints() {
return dropKeyConstraints;
}
/**
* Return some DDL to disable constraints on the given table.
*/
public String dropKeyConstraintPrefix(String tableName, String fkName) {
return null;
}
/**
* Return true if indexes should be created for the foreign keys.
*/
@@ -188,6 +207,13 @@ public class DbDdlSyntax {
this.dropIfExists = dropIfExists;
}
/**
* Return prefix text that goes before drop table.
*/
public String dropTablePrefix(String tableName) {
return "";
}
/**
* Return the cascade option for the drop table command.
*/
@@ -1,7 +1,6 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import javax.sql.DataSource;
@@ -14,15 +13,13 @@ public class H2Platform extends DatabasePlatform {
super();
this.name = "h2";
this.dbEncrypt = new H2DbEncrypt();
this.likeClause = "like ? escape''";
// like ? escape'' not working in the latest version H2 so just using no
// escape clause for now noting that backslash is an escape char for like in H2
this.likeClause = "like ?";
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
boolean useIdentity = GlobalProperties.getBoolean("ebean.h2platform.useIdentity", false);
IdType idType = useIdentity ? IdType.IDENTITY : IdType.SEQUENCE;
this.dbIdentity.setIdType(idType);
this.dbIdentity.setIdType(IdType.SEQUENCE);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -1,11 +1,9 @@
package com.avaje.ebean.config.dbplatform;
import java.sql.Types;
import com.avaje.ebean.BackgroundExecutor;
import javax.sql.DataSource;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import java.sql.Types;
/**
* H2 specific platform.
@@ -16,14 +14,7 @@ public class HsqldbPlatform extends DatabasePlatform {
super();
this.name = "hsqldb";
this.dbEncrypt = new H2DbEncrypt();
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
boolean useIdentity = GlobalProperties.getBoolean("ebean.hsqldb.useIdentity", true);
IdType idType = useIdentity ? IdType.IDENTITY : IdType.SEQUENCE;
this.dbIdentity.setIdType(idType);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -17,8 +17,12 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
public MsSqlServer2005Platform() {
super();
this.name = "mssqlserver2005";
// effectively disable persistBatchOnCascade mode for SQL Server
// due to lack of support for getGeneratedKeys in batch mode
this.disallowBatchOnCascade = true;
this.idInExpandedForm = true;
this.sqlLimiter = new MsSqlServer2005SqlLimiter();
this.dbDdlSyntax.setIdentity("identity(1,1)");
this.dbDdlSyntax = new MsDdlSyntax();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -28,6 +32,7 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
dbTypeMap.put(Types.BIGINT, new DbType("numeric", 19));
dbTypeMap.put(Types.REAL, new DbType("float(16)"));
dbTypeMap.put(Types.DOUBLE, new DbType("float(32)"));
@@ -45,4 +50,30 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
}
/**
* MS SQL Server specific DDL Syntax.
*/
public class MsDdlSyntax extends DbDdlSyntax {
MsDdlSyntax() {
this.identity = "identity(1,1)";
this.dropKeyConstraints = true;
}
/**
* Return some DDL to disable constraints on the given table.
*/
public String dropKeyConstraintPrefix(String tableName, String fkName) {
return "IF OBJECT_ID('"+fkName+"', 'F') IS NOT NULL";
}
/**
* Return prefix text that goes before drop table.
*/
public String dropTablePrefix(String tableName) {
return "IF OBJECT_ID('"+tableName+"', 'U') IS NOT NULL ";
}
}
}
@@ -1,7 +1,5 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.config.GlobalProperties;
/**
* Oracle encryption support.
*
@@ -48,12 +46,19 @@ import com.avaje.ebean.config.GlobalProperties;
public class Oracle10DbEncrypt extends AbstractDbEncrypt {
/**
* Constructs the Oracle10DbEncrypt.
* Constructs the Oracle10DbEncrypt with default encrypt and decrypt stored procedures.
*/
public Oracle10DbEncrypt() {
this("eb_encrypt", "eb_decrypt");
}
String encryptfunction = GlobalProperties.get("ebean.oracle.encryptfunction", "eb_encrypt");
String decryptfunction = GlobalProperties.get("ebean.oracle.decryptfunction", "eb_decrypt");
/**
* Constructs the Oracle10DbEncrypt specifying encrypt and decrypt stored procedures.
*
* @param encryptfunction the encrypt stored procedure
* @param decryptfunction the decrypt stored procedure
*/
public Oracle10DbEncrypt(String encryptfunction, String decryptfunction) {
this.varcharEncryptFunction = new OraVarcharFunction(encryptfunction, decryptfunction);
this.dateEncryptFunction = new OraDateFunction(encryptfunction, decryptfunction);
@@ -1,7 +1,6 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import javax.sql.DataSource;
import java.sql.Types;
@@ -27,12 +26,7 @@ public class Postgres8Platform extends DatabasePlatform {
this.dbIdentity.setIdType(IdType.SEQUENCE);
this.dbIdentity.setSupportsSequence(true);
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
if (colAlias == null) {
// Postgres requires the "as" keyword for column alias
GlobalProperties.put("ebean.columnAliasPrefix", "as c");
}
this.columnAliasPrefix = "as c";
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -1,10 +1,8 @@
package com.avaje.ebean.config.dbplatform;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.GlobalProperties;
import javax.sql.DataSource;
import java.sql.Types;
/**
@@ -38,11 +36,7 @@ public class PostgresPlatform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
if (colAlias == null) {
// Postgres requires the "as" keyword for column alias
GlobalProperties.put("ebean.columnAliasPrefix", "as c");
}
this.columnAliasPrefix = "as c";
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -40,7 +40,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
protected final BackgroundExecutor backgroundExecutor;
protected final ArrayList<Integer> idList = new ArrayList<Integer>(50);
protected final ArrayList<Long> idList = new ArrayList<Long>(50);
protected int batchSize;
@@ -117,7 +117,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
if (idList.size() == 0) {
loadMoreIds(batchSize, t);
}
Integer nextId = idList.remove(0);
Long nextId = idList.remove(0);
if (batchSize > 1) {
if (idList.size() <= batchSize / 2) {
@@ -158,7 +158,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
protected void loadMoreIds(final int numberToLoad, Transaction t) {
ArrayList<Integer> newIds = getMoreIds(numberToLoad, t);
ArrayList<Long> newIds = getMoreIds(numberToLoad, t);
if (logger.isDebugEnabled()) {
logger.debug("... seq:" + seqName + " loaded:" + numberToLoad + " ids:" + newIds);
@@ -174,11 +174,11 @@ public abstract class SequenceIdGenerator implements IdGenerator {
/**
* Get more Id's by executing a query and reading the Id's returned.
*/
protected ArrayList<Integer> getMoreIds(int loadSize, Transaction t) {
protected ArrayList<Long> getMoreIds(int loadSize, Transaction t) {
String sql = getSql(loadSize);
ArrayList<Integer> newIds = new ArrayList<Integer>(loadSize);
ArrayList<Long> newIds = new ArrayList<Long>(loadSize);
boolean useTxnConnection = t != null;
@@ -191,12 +191,10 @@ public abstract class SequenceIdGenerator implements IdGenerator {
pstmt = c.prepareStatement(sql);
rset = pstmt.executeQuery();
while (rset.next()) {
int val = rset.getInt(1);
newIds.add(Integer.valueOf(val));
newIds.add(rset.getLong(1));
}
if (newIds.size() == 0) {
String m = "Always expecting more than 1 row from " + sql;
throw new PersistenceException(m);
throw new PersistenceException("Always expecting more than 1 row from " + sql);
}
return newIds;
@@ -33,7 +33,7 @@ import com.avaje.ebean.config.ServerConfig;
* </p>
* <p>
* A BeanPersistListener is either found automatically via class path search or
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener<?>)}}.
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener)}}.
* </p>
* @see ServerConfig#add(BeanPersistListener)
*/
+15 -15
View File
@@ -3,8 +3,8 @@
<title>Ebean API</title>
</head>
<body BGCOLOR="#ffffff">
Ebean Object Relational Mapping (start at <a href='com/avaje/ebean/Ebean.html'>Ebean</a>
or <a href='com/avaje/ebean/EbeanServer.html'>EbeanServer</a>).
Ebean Object Relational Mapping (start at
<a href='com/avaje/ebean/EbeanServer.html'>EbeanServer</a> or <a href='com/avaje/ebean/Ebean.html'>Ebean</a>).
<h3>Ebean</h3>
@@ -21,15 +21,15 @@ For a full description of the query language refer to <a href="com/avaje/ebean/Q
<h3>
EXAMPLE 1: Simple fetch
</h3>
<pre class="code">
<pre>{@code
// fetch order 10
Order order = Ebean.find(Order.class, 10);
</pre>
}</pre>
<h3>
EXAMPLE 2: Fetch an Object with associations
</h3>
<pre class="code">
<pre>{@code
// fetch Customer 7 including their billing and shipping addresses
Customer customer = Ebean.find(Customer.class)
.fetch("billingAddress");
@@ -40,19 +40,19 @@ Customer customer = Ebean.find(Customer.class)
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
</pre>
}</pre>
<h3>
EXAMPLE 3: Fetch a list of Objects with associations
</h3>
<pre class="code">
<pre>{@code
// Note: This example shows a "Partial Object".
// For the product objects associated with the
// order details only the product id and name is
// fetched (the product objects are partially populated).
// fetch orders for customer.id = 2
List&lt;Order&gt; orderList = Ebean.find(Order.class);
List<Order> orderList = Ebean.find(Order.class);
.fetch("customer")
.fetch("customer.shippingAddress")
.fetch("details")
@@ -72,17 +72,17 @@ Order order = orderList.get(0);
Customer customer = order.getCustomer();
Address shipAddr = customer.getShippingAddress();
List&lt;OrderDetail&gt; details = order.getDetails();
List<OrderDetail> details = order.getDetails();
OrderDetail detail = details.get(0);
Product product = detail.getProduct();
String productName = product.getName();
</pre>
}</pre>
<h3>
EXAMPLE 4: Create and save an Order
</h3>
<pre class="code">
<pre>{@code
// get a Customer reference so we don't hit the database
Customer custRef = Ebean.getReference(Customer.class, 7);
@@ -107,14 +107,14 @@ orderLines.add(line);
// NB: assumes CascadeType.PERSIST is set on the order lines association
Ebean.save(newOrder);
</pre>
}</pre>
<h3>
EXAMPLE 5: Use another database
</h3>
<pre class="code">
<pre>{@code
// Get access to the Human Resources EbeanServer/Database
EbeanServer hrServer = Ebean.getServer(&quot;HR&quot;);
EbeanServer hrServer = Ebean.getServer("HR");
// fetch contact 3 from the HR database
@@ -125,7 +125,7 @@ contact.setStatus(Contact.Status.INACTIVE);
// save the contact back to the HR database
hrServer.save(contact);
</pre>
}</pre>
</div>
@@ -19,9 +19,6 @@ import com.avaje.ebean.Query;
* properties and applying that to both what to fetch (ORM query) and what to
* render (JAX-RS JSON / XML).
* </p>
*
* @author rbygrave
*
*/
public class PathProperties {
@@ -1,5 +1,6 @@
package com.avaje.ebean.text.json;
import com.avaje.ebean.text.PathProperties;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -58,37 +59,68 @@ public interface JsonContext {
public Object toObject(Type genericType, String json) throws JsonIOException;
/**
* Write the bean or collection in JSON format to the writer with default
* options.
*
* @param value the bean or collection of beans to write
* @param writer used to write the json output to
* @throws JsonIOException When IOException occurs
*/
public void toJson(Object value, Writer writer) throws JsonIOException;
/**
* With additional options to specify JsonValueAdapter and
* JsonWriteBeanVisitor's.
*
* @param value the bean or collection of beans to write
* @param writer used to write the json output to
* @param options additional options to control the JSON output
* @throws JsonIOException When IOException occurs
*/
public void toJson(Object value, Writer writer, JsonWriteOptions options) throws JsonIOException;
/**
* Convert a bean or collection to json string using default options.
* Return the bean or collection as JSON string.
*
* @throws JsonIOException When IOException occurs
*/
public String toJson(Object value) throws JsonIOException;
/**
* Write the bean or collection in JSON format to the writer.
*
* @throws JsonIOException When IOException occurs
*/
public void toJson(Object value, Writer writer) throws JsonIOException;
/**
* Write the bean or collection to the JsonGenerator.
*
* @throws JsonIOException When IOException occurs
*/
public void toJson(Object value, JsonGenerator generator) throws JsonIOException;
/**
* Return the bean or collection as JSON string using PathProperties.
*
* @throws JsonIOException When IOException occurs
*/
public String toJson(Object value, PathProperties pathProperties) throws JsonIOException;
/**
* Write the bean or collection as json to the writer using the PathProperties.
*/
public void toJson(Object value, Writer writer, PathProperties pathProperties) throws JsonIOException;
/**
* Write the bean or collection to the JsonGenerator using the PathProperties.
*/
public void toJson(Object value, JsonGenerator generator, PathProperties pathProperties) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
* Write json to the JsonGenerator using the JsonWriteOptions.
*
* @deprecated
*/
public void toJson(Object value, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
* With additional options.
*
* @throws JsonIOException When IOException occurs
*
* @deprecated
*/
public void toJson(Object value, Writer writer, JsonWriteOptions options) throws JsonIOException;
/**
* Deprecated in favour of using PathProperties by itself.
* Convert a bean or collection to json string.
*
* @throws JsonIOException When IOException occurs
*
* @deprecated
*/
public String toJson(Object value, JsonWriteOptions options) throws JsonIOException;
@@ -3,18 +3,14 @@ package com.avaje.ebean.text.json;
import com.avaje.ebean.text.PathProperties;
/**
* Deprecated in favour of just using PathProperties.
*
* Provides options for customising the JSON write process.
* <p>
* You can explicitly state which properties to include in the JSON output for
* the root level and each path.
* </p>
*
* <pre class="code">
* // output as a JSON string with pretty formatting
* String s = json.toJson(list, true, writeOptions);
*
* </pre>
* @deprecated
*/
public class JsonWriteOptions {
@@ -24,12 +20,20 @@ public class JsonWriteOptions {
* Parse and return a PathProperties from nested string format like
* (a,b,c(d,e),f(g)) where "c" is a path containing "d" and "e" and "f" is a
* path containing "g" and the root path contains "a","b","c" and "f".
*
* @see com.avaje.ebean.text.PathProperties#parse(String)
*/
public static JsonWriteOptions parsePath(String pathProperties) {
PathProperties p = PathProperties.parse(pathProperties);
return pathProperties(PathProperties.parse(pathProperties));
}
/**
* Construct JsonWriteOptions with the given pathProperties.
*/
public static JsonWriteOptions pathProperties(PathProperties pathProperties) {
JsonWriteOptions o = new JsonWriteOptions();
o.setPathProperties(p);
o.setPathProperties(pathProperties);
return o;
}
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import com.avaje.ebean.TxScope;
import com.avaje.ebean.config.PersistBatch;
/**
* Used internally to handle the scoping of transactions for methods.
@@ -10,14 +11,14 @@ import com.avaje.ebean.TxScope;
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).
*/
@@ -44,7 +45,13 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
*/
private final ArrayList<Class<? extends Throwable>> rollbackFor;
/**
private PersistBatch restoreBatch;
private PersistBatch restoreBatchOnCascade;
private int restoreBatchSize;
/**
* Flag set when a rollback has occurred.
*/
private boolean rolledBack;
@@ -61,6 +68,24 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
this.noRollbackFor = txScope.getNoRollbackFor();
this.rollbackFor = txScope.getRollbackFor();
if (transaction != null) {
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
restoreBatch = transaction.getBatch();
restoreBatchOnCascade = transaction.getBatchOnCascade();
restoreBatchSize = transaction.getBatchSize();
}
if (txScope.isBatchSet()) {
transaction.setBatch(txScope.getBatch());
}
if (txScope.isBatchOnCascadeSet()) {
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
}
if (txScope.isBatchSizeSet()) {
transaction.setBatchSize(txScope.getBatchSize());
}
}
}
/**
@@ -97,10 +122,22 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
*/
public void onFinally() {
try {
if (!rolledBack && created) {
transaction.commit();
if (!rolledBack) {
if (created) {
transaction.commit();
} else {
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
}
if (restoreBatchOnCascade != null) {
transaction.setBatchOnCascade(restoreBatchOnCascade);
}
if (restoreBatchSize > 0) {
transaction.setBatchSize(restoreBatchSize);
}
}
}
} finally {
if (suspendedTransaction != null){
// put the previously suspended transaction
@@ -5,6 +5,8 @@ import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.persist.BatchControl;
/**
@@ -119,6 +121,11 @@ public interface SpiTransaction extends Transaction {
*/
public int depth(int diff);
/**
* Return the current depth.
*/
public int depth();
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
@@ -144,7 +151,7 @@ public interface SpiTransaction extends Transaction {
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
public boolean isBatchThisRequest();
public boolean isBatchThisRequest(PersistRequest.Type type);
/**
* Return the queue used to batch up persist requests.
@@ -194,4 +201,25 @@ public interface SpiTransaction extends Transaction {
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
*/
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName);
/**
* Return true if batch mode got escalated for this request (and associated cascades).
*/
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request);
/**
* If batch mode was turned on for the request then flush the batch.
*/
public void flushBatchOnCascade();
/**
* Potentially escalate batch mode on saving or deleting a collection.
*/
public void checkBatchEscalationOnCollection();
/**
* Flush batch if we escalated batch mode on saving or deleting a collection.
*/
public void flushBatchOnCollection();
}
@@ -1,19 +1,11 @@
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 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.List;
/**
* Holds information for a transaction. There is one TransactionEvent instance
@@ -25,180 +17,107 @@ import org.slf4j.LoggerFactory;
*/
public class TransactionEvent implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(TransactionEvent.class);
private static final long serialVersionUID = 7230903304106097120L;
private static final long serialVersionUID = 7230903304106097120L;
/**
* Flag indicating this is a local transaction (not from another server in
* the cluster).
*/
private transient boolean local;
/**
* 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 TransactionEventTable eventTables;
private transient TransactionEventBeans eventBeans;
private transient TransactionEventBeans eventBeans;
private transient List<BeanDelta> beanDeltas;
private transient DeleteByIdMap deleteByIdMap;
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;
}
/**
* Create the TransactionEvent, one per Transaction.
*/
public TransactionEvent() {
this.local = true;
}
/**
* 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());
}
public void addDeleteById(BeanDescriptor<?> desc, Object id) {
if (deleteByIdMap == null) {
deleteByIdMap = new DeleteByIdMap();
}
/**
* Add an IndexInvalidation notices to the transaction.
*/
public void addIndexInvalidate(IndexInvalidate indexEvent){
if (pauseIndexInvalidate != null && pauseIndexInvalidate.contains(indexEvent.getIndexName())){
logger.debug("--- 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);
deleteByIdMap.add(desc, id);
}
public void addDeleteByIdList(BeanDescriptor<?> desc, List<Object> idList) {
if (deleteByIdMap == null) {
deleteByIdMap = new DeleteByIdMap();
}
public DeleteByIdMap getDeleteByIdMap() {
return deleteByIdMap;
deleteByIdMap.addList(desc, idList);
}
public DeleteByIdMap getDeleteByIdMap() {
return deleteByIdMap;
}
/**
* 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 void add(String tableName, boolean inserts, boolean updates, boolean deletes) {
if (eventTables == null) {
eventTables = new TransactionEventTable();
}
eventTables.add(tableName, inserts, updates, deletes);
}
public void addBeanDelta(BeanDelta delta) {
if (beanDeltas == null) {
beanDeltas = new ArrayList<BeanDelta>();
}
beanDeltas.add(delta);
public void add(TransactionEventTable table) {
if (eventTables == null) {
eventTables = new TransactionEventTable();
}
eventTables.add(table);
}
public List<BeanDelta> getBeanDeltas() {
return beanDeltas;
/**
* Add a inserted updated or deleted bean to the event.
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify()) {
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
}
eventBeans.add(request);
}
/**
* 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;
/**
* 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();
}
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();
}
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache();
}
}
}
@@ -75,7 +75,7 @@ public final class TransactionEventTable implements Serializable {
private boolean update;
private boolean delete;
private TableIUD(String table, boolean insert, boolean update, boolean delete){
public TableIUD(String table, boolean insert, boolean update, boolean delete){
this.table = table;
this.insert = insert;
this.update = update;
@@ -1,18 +1,16 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.resource.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class AutoFetchManagerFactory {
private static final Logger logger = LoggerFactory.getLogger(AutoFetchManagerFactory.class);
@@ -37,7 +35,7 @@ public class AutoFetchManagerFactory {
AutoFetchManager autoFetchManager = null;
boolean readFile = GlobalProperties.getBoolean("autofetch.readfromfile", true);
boolean readFile = !"false".equalsIgnoreCase(System.getProperty("autofetch.readfromfile"));
if (readFile) {
autoFetchManager = deserializeAutoFetch(autoFetchFile);
}
@@ -22,6 +22,8 @@ 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The manager of all the usage/query statistics as well as the tuned fetch
@@ -29,6 +31,8 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
*/
public class DefaultAutoFetchManager implements AutoFetchManager, Serializable {
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoFetchManager.class);
private static final long serialVersionUID = -6826119882781771722L;
private final String statisticsMonitor = new String();
@@ -551,16 +555,16 @@ public class DefaultAutoFetchManager implements AutoFetchManager, Serializable {
Statistics stats = getQueryPointStats(origin);
if (logging.isTraceUsageCollection()){
System.out.println("... NodeUsageCollector "+usageCollector);
}
stats.collectUsageInfo(usageCollector);
if (logging.isTraceUsageCollection()){
System.out.println("stats\n"+stats);
}
}
if (logger.isTraceEnabled()) {
logger.trace("... NodeUsageCollector " + usageCollector);
}
stats.collectUsageInfo(usageCollector);
if (logger.isTraceEnabled()) {
logger.trace("stats\n" + stats);
}
}
private Statistics getQueryPointStats(ObjectGraphOrigin originQueryPoint) {
synchronized (statisticsMonitor) {
@@ -1,14 +1,12 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
* Handles the logging aspects for the DefaultAutoFetchListener.
@@ -23,15 +21,12 @@ public class DefaultAutoFetchManagerLogging {
private final DefaultAutoFetchManager manager;
private final boolean traceUsageCollection;
private final int updateFreqInSecs;
public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, DefaultAutoFetchManager profileListener) {
this.manager = profileListener;
this.traceUsageCollection = GlobalProperties.getBoolean("ebean.autofetch.traceUsageCollection", false);
this.updateFreqInSecs = serverConfig.getAutofetchConfig().getProfileUpdateFrequency();
this.updateFreqInSecs = serverConfig.getAutofetchConfig().getProfileUpdateFrequency();
}
public void init(SpiEbeanServer ebeanServer) {
@@ -69,9 +64,4 @@ public class DefaultAutoFetchManagerLogging {
String msg = tunedFetch.getLogOutput(null);
logger.debug(msg);
}
public boolean isTraceUsageCollection() {
return traceUsageCollection;
}
}
@@ -17,9 +17,6 @@ import java.io.DataOutputStream;
* be common for many Ack, Resend and Control messages to all be contained in a
* single packet.
* </p>
*
* @author rbygrave
*
*/
public class BinaryMessage {
@@ -28,8 +25,7 @@ public class BinaryMessage {
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;
@@ -3,8 +3,7 @@ package com.avaje.ebeaninternal.server.cluster;
import java.util.concurrent.ConcurrentHashMap;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebeaninternal.server.cluster.mcast.McastClusterManager;
import com.avaje.ebeaninternal.server.cluster.socket.SocketClusterBroadcast;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
@@ -26,41 +25,36 @@ public class ClusterManager {
private boolean started;
public ClusterManager() {
public ClusterManager(ContainerConfig containerConfig) {
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);
ContainerConfig.ClusterMode mode = containerConfig.getMode();
try {
switch (mode) {
case SOCKET: {
this.broadcast = new SocketClusterBroadcast(containerConfig);
break;
}
case MULTICAST: {
this.broadcast = new McastClusterManager(containerConfig);
break;
}
default: {
this.broadcast = null;
}
} catch (Exception e) {
String msg = "Error initialising ClusterManager type [" + clusterType + "]";
logger.error(msg, e);
throw new RuntimeException(e);
}
} catch (Exception e) {
logger.error("Error initialising ClusterManager type [" + mode + "]", e);
throw new RuntimeException(e);
}
}
public void registerServer(EbeanServer server) {
synchronized (monitor) {
serverMap.put(server.getName(), server);
if (!started) {
startup();
}
serverMap.put(server.getName(), server);
}
}
@@ -7,7 +7,6 @@ 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;
/**
@@ -16,7 +15,6 @@ import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
* 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 {
@@ -63,10 +61,6 @@ public class PacketTransactionEvent extends Packet {
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);
}
@@ -7,49 +7,54 @@ import java.util.List;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Mechanism to convert RemoteTransactionEvent to/from byte[] content.
*/
public abstract class SerialiseTransactionHelper {
private final PacketWriter packetWriter;
private static final Logger logger = LoggerFactory.getLogger(SerialiseTransactionHelper.class);
public SerialiseTransactionHelper() {
packetWriter = new PacketWriter(Integer.MAX_VALUE);
private final PacketWriter packetWriter;
public SerialiseTransactionHelper() {
packetWriter = new PacketWriter(Integer.MAX_VALUE);
}
public abstract SpiEbeanServer getEbeanServer(String serverName);
/**
* Convert the RemoteTransactionEvent to byte[] content.
*/
public DataHolder createDataHolder(RemoteTransactionEvent transEvent) throws IOException {
List<Packet> packetList = packetWriter.write(transEvent);
if (packetList.size() != 1) {
throw new RuntimeException("Always expecting 1 Packet but got " + packetList.size());
}
byte[] data = packetList.get(0).getBytes();
return new DataHolder(data);
}
/**
* Convert the byte[] content to RemoteTransactionEvent.
*/
public RemoteTransactionEvent read(DataHolder dataHolder) throws IOException {
ByteArrayInputStream bi = new ByteArrayInputStream(dataHolder.getData());
DataInputStream dataInput = new DataInputStream(bi);
Packet header = Packet.readHeader(dataInput);
SpiEbeanServer server = getEbeanServer(header.getServerName());
if (server == null) {
logger.error("server [{}] not found/registered?", header.getServerName());
}
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();
}
PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server);
tranEventPacket.read(dataInput);
return tranEventPacket.getEvent();
}
}
File diff suppressed because it is too large Load Diff
@@ -19,6 +19,12 @@
*/
package com.avaje.ebeaninternal.server.cluster.mcast;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.avaje.ebeaninternal.server.cluster.PacketTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
@@ -28,13 +34,6 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listens for Incoming packets.
*
@@ -56,8 +55,6 @@ public class McastListener implements Runnable {
private final InetAddress group;
private final boolean debugIgnore;
private DatagramPacket pack;
private byte[] receiveBuffer;
@@ -73,8 +70,6 @@ public class McastListener implements Runnable {
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;
@@ -96,7 +91,7 @@ public class McastListener implements Runnable {
this.sock.setSoTimeout(timeout);
if (disableLoopback){
sock.setLoopbackMode(disableLoopback);
sock.setLoopbackMode(true);
}
if (mcastBindAddress != null) {
@@ -173,7 +168,7 @@ public class McastListener implements Runnable {
String senderHostPort = senderAddr.getAddress().getHostAddress()+":"+senderAddr.getPort();
if (senderHostPort.equals(localSenderHostPort)){
if (debugIgnore || logger.isDebugEnabled()){
if (logger.isTraceEnabled()){
logger.info("Ignoring message as sent by localSender: "+localSenderHostPort);
}
} else {
@@ -195,7 +190,7 @@ public class McastListener implements Runnable {
boolean processThisPacket = ackMsg || packetControl.isProcessPacket(senderHostPort, header.getPacketId());
if (!processThisPacket){
if (debugIgnore || logger.isDebugEnabled()){
if (logger.isTraceEnabled()){
logger.info("Already processed packet: "+header.getPacketId()+" type:"+header.getPacketType()+" len:"+data.length);
}
} else {
@@ -21,15 +21,18 @@ class RequestProcessor implements Runnable {
private final Socket clientSocket;
private final SocketClusterBroadcast owner;
/**
private final String hostPort;
/**
* Create including the Listener (used to lookup the Request Handler) and
* the socket itself.
*/
public RequestProcessor(SocketClusterBroadcast owner, Socket clientSocket) {
this.clientSocket = clientSocket;
this.owner = owner;
}
this.hostPort = owner.getHostPort();
}
/**
* This will parse out the command. Lookup the appropriate Handler and
@@ -39,22 +42,20 @@ class RequestProcessor implements Runnable {
*/
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.error(null, e);
} catch (ClassNotFoundException e) {
logger.error(null, e);
logger.trace("start listening for cluster messages");
SocketConnection sc = new SocketConnection(clientSocket);
while (true) {
if (owner.process(sc)) {
// got the offline message or timeout
break;
}
}
}
logger.trace("disconnecting: {}", hostPort);
sc.disconnect();
} catch (Exception e) {
logger.error("Error listening for messages - "+owner.getHostPort(), e);
}
}
};
}
@@ -35,6 +35,10 @@ class SocketClient {
this.hostPort = address.getHostName()+":"+address.getPort();
}
public String toString() {
return address.toString();
}
public String getHostPort() {
return hostPort;
}
@@ -1,248 +1,256 @@
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 javax.persistence.PersistenceException;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cluster.ClusterBroadcast;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.cluster.DataHolder;
import com.avaje.ebeaninternal.server.cluster.SerialiseTransactionHelper;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Broadcast messages across the cluster using sockets.
* Broadcast messages across the cluster using sockets.
*/
public class SocketClusterBroadcast implements ClusterBroadcast {
private static final Logger logger = LoggerFactory.getLogger(SocketClusterBroadcast.class);
private final SocketClient local;
private final HashMap<String,SocketClient> clientMap;
private final SocketClusterListener listener;
private SocketClient[] members;
private static final Logger logger = LoggerFactory.getLogger(SocketClusterBroadcast.class);
private ClusterManager clusterManager;
private final SocketClient local;
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);
private final HashMap<String, SocketClient> clientMap;
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();
private final SocketClusterListener listener;
} catch (IOException e) {
throw new PersistenceException(e);
}
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(ContainerConfig containerConfig) {
ContainerConfig.SocketConfig socketConfig = containerConfig.getSocketConfig();
String localHostPort = socketConfig.getLocalHostPort();
List<String> members = socketConfig.getMembers();
logger.info("Clustering using Sockets local[" + localHostPort + "] members[" + members + "]");
this.local = new SocketClient(parseFullName(localHostPort));
this.clientMap = new HashMap<String, SocketClient>();
for (String memberHostPort : members) {
InetSocketAddress member = parseFullName(memberHostPort);
SocketClient client = new SocketClient(member);
if (!local.getHostPort().equalsIgnoreCase(client.getHostPort())) {
// don't add the local one ...
clientMap.put(client.getHostPort(), client);
}
}
public void shutdown() {
deregister();
listener.shutdown();
}
/**
* Register with all the other members of the Cluster.
*/
private void register() {
this.members = clientMap.values().toArray(new SocketClient[clientMap.size()]);
this.listener = new SocketClusterListener(this, local.getPort(), socketConfig.getCoreThreads(), socketConfig.getMaxThreads(), socketConfig.getThreadPoolName());
}
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);
}
public String getHostPort() {
return local.getHostPort();
}
/**
* Return the current status of this instance.
*/
public SocketClusterStatus getStatus() {
// count of online members
int currentGroupSize = 0;
for (int i = 0; i < members.length; i++) {
if (members[i].isOnline()) {
++currentGroupSize;
}
}
int txnIn = txnIncoming.get();
int txnOut = txnOutgoing.get();
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);
}
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);
}
}
private void send(SocketClient client, SocketClusterMessage msg) {
public void shutdown() {
deregister();
listener.shutdown();
}
try {
// alternative would be to connect/disconnect here
// but prefer to use keepalive
client.send(msg);
} catch (Exception ex){
logger.error("Error sending message", ex);
try {
client.reconnect();
} catch (IOException e) {
logger.error("Error trying to reconnect", ex);
}
}
/**
* Register with all the other members of the Cluster.
*/
private void register() {
SocketClusterMessage h = SocketClusterMessage.register(local.getHostPort(), true);
for (int i = 0; i < members.length; i++) {
boolean online = members[i].register(h);
logger.info("Cluster Member [{}] online[{}]", members[i].getHostPort(), online);
}
}
/**
* 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.error(msg, e);
}
protected void setMemberOnline(String fullName, boolean online) throws IOException {
synchronized (clientMap) {
logger.info("Cluster Member [{}] online[{}]", fullName, online);
SocketClient member = clientMap.get(fullName);
member.setOnline(online);
}
}
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();
}
}
private void send(SocketClient client, SocketClusterMessage msg) {
/**
* Process a Cluster message.
*/
protected boolean process(SocketConnection request) throws IOException, ClassNotFoundException {
try {
// alternative would be to connect/disconnect here but prefer to use keepalive
if (logger.isTraceEnabled()) {
logger.trace("... send to member {} broadcast msg: {}", client, msg);
}
client.send(msg);
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.info(msg, e);
try {
request.disconnect();
} catch (IOException ex){
logger.info("Error disconnecting after timeout", ex);
}
return true;
}
} catch (Exception ex) {
logger.error("Error sending message", ex);
try {
client.reconnect();
} catch (IOException e) {
logger.error("Error trying to reconnect", ex);
}
}
}
/**
* 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);
}
/**
* Send the payload to all the members of the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent) {
try {
txnOutgoing.incrementAndGet();
DataHolder dataHolder = txnSerialiseHelper.createDataHolder(remoteTransEvent);
SocketClusterMessage msg = SocketClusterMessage.transEvent(dataHolder);
broadcast(msg);
} catch (Exception e) {
logger.error("Error sending RemoteTransactionEvent " + remoteTransEvent + " to cluster members.", e);
}
class TxnSerialiseHelper extends SerialiseTransactionHelper {
}
@Override
public SpiEbeanServer getEbeanServer(String serverName) {
return (SpiEbeanServer)clusterManager.getServer(serverName);
}
protected void broadcast(SocketClusterMessage msg) {
if (logger.isTraceEnabled()) {
logger.trace("... broadcast msg: "+msg);
}
for (int i = 0; i < members.length; i++) {
send(members[i], msg);
}
}
/**
* Leave the cluster.
*/
private void deregister() {
SocketClusterMessage h = SocketClusterMessage.register(local.getHostPort(), false);
broadcast(h);
for (int i = 0; i < members.length; i++) {
members[i].disconnect();
}
}
/**
* Process an incoming Cluster message.
*/
protected boolean process(SocketConnection request) throws IOException, ClassNotFoundException {
try {
SocketClusterMessage h = (SocketClusterMessage) request.readObject();
if (logger.isTraceEnabled()) {
logger.trace("... received msg: {}", h);
}
if (h.isRegisterEvent()) {
setMemberOnline(h.getRegisterHost(), h.isRegister());
} else {
txnIncoming.incrementAndGet();
DataHolder dataHolder = h.getDataHolder();
RemoteTransactionEvent transEvent = txnSerialiseHelper.read(dataHolder);
transEvent.run();
}
// instance shutting down
return h.isRegisterEvent() && !h.isRegister();
} catch (InterruptedIOException e) {
logger.info("Timeout waiting for message", e);
try {
request.disconnect();
} catch (IOException ex) {
logger.info("Error disconnecting after timeout", ex);
}
return true;
} catch (EOFException e) {
logger.info("EOF disconnecting");
return true;
} catch (IOException e) {
logger.info("IO Error waiting/reading message", e);
return true;
}
}
/**
* Parse a host:port into a InetSocketAddress.
*/
private InetSocketAddress parseFullName(String hostAndPort) {
try {
hostAndPort = hostAndPort.trim();
int colonPos = hostAndPort.indexOf(":");
if (colonPos == -1) {
String msg = "No colon \":\" in " + hostAndPort;
throw new IllegalArgumentException(msg);
}
String host = hostAndPort.substring(0, colonPos);
String sPort = hostAndPort.substring(colonPos + 1, hostAndPort.length());
int port = Integer.parseInt(sPort);
return new InetSocketAddress(host, port);
} catch (Exception ex) {
throw new RuntimeException("Error parsing [" + hostAndPort + "] for the form [host:port]", ex);
}
}
class TxnSerialiseHelper extends SerialiseTransactionHelper {
@Override
public SpiEbeanServer getEbeanServer(String serverName) {
return (SpiEbeanServer) clusterManager.getServer(serverName);
}
}
}
@@ -6,10 +6,10 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import com.avaje.ebeaninternal.server.lib.DaemonThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebeaninternal.server.lib.thread.ThreadPool;
/**
* Serverside multithreaded socket listener. Accepts connections and dispatches
@@ -26,141 +26,118 @@ import com.avaje.ebeaninternal.server.lib.thread.ThreadPool;
*/
class SocketClusterListener implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(SocketClusterListener.class);
/**
* The port the SocketListener uses.
*/
private final int port;
private static final Logger logger = LoggerFactory.getLogger(SocketClusterListener.class);
/**
* 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 server socket used to listen for requests.
*/
private final ServerSocket serverListenSocket;
/**
* The listening thread.
*/
private final Thread listenerThread;
/**
* The listening thread.
*/
private final Thread listenerThread;
/**
* The pool of threads that actually do the parsing execution of requests.
*/
private final DaemonThreadPool threadPool;
/**
* The pool of threads that actually do the parsing execution of requests.
*/
private final ThreadPool threadPool;
private final SocketClusterBroadcast owner;
private final SocketClusterBroadcast owner;
/**
* shutting down flag.
*/
boolean doingShutdown;
/**
* shutting down flag.
*/
boolean doingShutdown;
/**
* Whether the listening thread is busy assigning a request to a thread.
*/
boolean isActive;
/**
* Whether the listening thread is busy assigning a request to a thread.
*/
boolean isActive;
/**
* Construct with a given thread pool name.
*/
public SocketClusterListener(SocketClusterBroadcast owner, int port, int coreThreads, int maxThreads, String poolName) {
this.owner = owner;
this.threadPool = new DaemonThreadPool(coreThreads, maxThreads, 60, 30, poolName);
try {
this.serverListenSocket = new ServerSocket(port);
this.serverListenSocket.setSoTimeout(60000);
this.listenerThread = new Thread(this, "EbeanClusterListener");
/**
* Construct with a given thread pool name.
*/
public SocketClusterListener(SocketClusterBroadcast owner, int port) {
this.owner = owner;
this.threadPool = ThreadPool.createThreadPool("EbeanCluster");
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);
} catch (IOException e) {
String msg = "Error starting cluster socket listener on port " + port;
throw new RuntimeException(msg, e);
}
}
/**
* Start listening for requests.
*/
public void startListening() throws IOException {
logger.trace("... startListening()");
this.listenerThread.setDaemon(true);
this.listenerThread.start();
}
/**
* Shutdown this listener.
*/
public void shutdown() {
doingShutdown = true;
try {
if (isActive) {
synchronized (listenerThread) {
try {
listenerThread.wait(1000);
} catch (InterruptedException e) {
// OK to ignore as expected to Interrupt for shutdown.
}
}
}
listenerThread.interrupt();
serverListenSocket.close();
} catch (IOException e) {
logger.error("Error shutting down listener", e);
}
/**
* Returns the port the listener is using.
*/
public int getPort() {
return port;
}
threadPool.shutdown();
}
/**
* 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.error("Error shutting down listener", 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.execute(request);
isActive = false;
}
threadPool.shutdown();
}
/**
* This is a runnable and so this must be public. Don't call this externally
* but rather call the startListening() method.
*/
public void run() {
// run in loop until doingShutdown is true...
while (!doingShutdown) {
try {
synchronized (listenerThread) {
Socket clientSocket = serverListenSocket.accept();
isActive = true;
Runnable request = new RequestProcessor(owner, clientSocket);
threadPool.assign(request, true);
isActive = false;
}
} catch (SocketException e) {
if (doingShutdown) {
String msg = "doingShutdown and accept threw:"+ e.getMessage();
logger.info(msg);
} else {
logger.error(null, e);
}
} catch (InterruptedIOException e) {
// this will happen when the server is very quiet.
// that is, no requests
logger.debug("Possibly expected due to accept timeout?" + e.getMessage());
} catch (IOException e) {
// log it and continue in the loop...
logger.error(null, e);
}
} catch (SocketException e) {
if (doingShutdown) {
logger.info("doingShutdown and accept threw:" + e.getMessage());
} else {
logger.error("Error while listening", e);
}
} catch (InterruptedIOException e) {
// this will happen when the server is very quiet.
// that is, no requests
logger.debug("Possibly expected due to accept timeout? {}", e.getMessage());
} catch (IOException e) {
// log it and continue in the loop...
logger.error("IOException processing cluster message", e);
}
}
}
}
@@ -1,13 +1,13 @@
package com.avaje.ebeaninternal.server.core;
import java.util.List;
import java.util.Set;
import com.avaje.ebeaninternal.server.util.ClassPathSearch;
import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Set;
/**
* Searches for interesting classes such as Entities, Embedded and ScalarTypes.
*/
@@ -20,17 +20,21 @@ public class BootupClassPathSearch {
private final ClassLoader classLoader;
private final List<String> packages;
private final List<String> jars;
private final List<String> jars;
private BootupClasses bootupClasses;
private final String classPathReaderClassName;
/**
* Construct and search for interesting classes.
*/
public BootupClassPathSearch(ClassLoader classLoader, List<String> packages, List<String> jars) {
public BootupClassPathSearch(ClassLoader classLoader, List<String> packages, List<String> jars, String classPathReaderClassName) {
this.classLoader = (classLoader == null) ? getClass().getClassLoader() : classLoader;
this.packages = packages;
this.jars = jars;
this.classPathReaderClassName = classPathReaderClassName;
}
public BootupClasses getBootupClasses() {
@@ -57,7 +61,7 @@ public class BootupClassPathSearch {
ClassPathSearchFilter filter = createFilter();
ClassPathSearch finder = new ClassPathSearch(classLoader, filter, bc);
ClassPathSearch finder = new ClassPathSearch(classLoader, filter, bc, classPathReaderClassName);
finder.findClasses();
Set<String> jars = finder.getJarHits();
@@ -1,25 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.config.ServerConfig;
/**
* Build a ServerConfig from ebean.properties.
*/
public class ConfigBuilder {
/**
* Create a ServerConfig and load it from ebean.properties.
*/
public ServerConfig build(String serverName) {
ServerConfig config = new ServerConfig();
config.setName(serverName);
config.loadFromProperties();
return config;
}
}
@@ -15,19 +15,21 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
private final DaemonScheduleThreadPool schedulePool;
/**
* Construct the default implementation of BackgroundExecutor.
*
* @param corePoolSize
* the core size of the thread pool.
* @param keepAliveSecs
* the time in seconds idle threads are keep alive
* @param shutdownWaitSeconds
* the time in seconds allowed for the pool to shutdown nicely.
* After this the pool is forced to shutdown.
*/
public DefaultBackgroundExecutor(int schedulePoolSize, int corePoolSize, long keepAliveSecs,int shutdownWaitSeconds, String namePrefix) {
this.pool = new DaemonThreadPool(corePoolSize, keepAliveSecs, shutdownWaitSeconds, namePrefix);
/**
* Construct the default implementation of BackgroundExecutor.
*
* @param corePoolSize
* the core size of the thread pool.
* @param maximumPoolSize
* the maximum pool size before jobs are queued
* @param keepAliveSecs
* the time in seconds idle threads are keep alive
* @param shutdownWaitSeconds
* the time in seconds allowed for the pool to shutdown nicely.
* After this the pool is forced to shutdown.
*/
public DefaultBackgroundExecutor(int schedulePoolSize, int corePoolSize, int maximumPoolSize, long keepAliveSecs,int shutdownWaitSeconds, String namePrefix) {
this.pool = new DaemonThreadPool(corePoolSize, maximumPoolSize, keepAliveSecs, shutdownWaitSeconds, namePrefix);
this.schedulePool = new DaemonScheduleThreadPool(schedulePoolSize, shutdownWaitSeconds, namePrefix+"-periodic-");
}
@@ -1,81 +1,27 @@
package com.avaje.ebeaninternal.server.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.persistence.PersistenceException;
import com.avaje.ebean.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.bean.*;
import com.avaje.ebean.bean.PersistenceContext.WithOption;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.LoadBeanRequest;
import com.avaje.ebeaninternal.api.LoadManyRequest;
import com.avaje.ebeaninternal.api.ScopeTrans;
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.*;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.api.SpiQuery.Type;
import com.avaje.ebeaninternal.api.SpiSqlQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEventTable;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.ddl.DdlGenerator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.BeanManager;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.DNativeQuery;
import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.deploy.*;
import com.avaje.ebeaninternal.server.el.ElFilter;
import com.avaje.ebeaninternal.server.jmx.MAdminAutofetch;
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
import com.avaje.ebeaninternal.server.query.CallableQueryIds;
import com.avaje.ebeaninternal.server.query.CallableQueryList;
import com.avaje.ebeaninternal.server.query.CallableQueryRowCount;
import com.avaje.ebeaninternal.server.query.CallableSqlQueryList;
import com.avaje.ebeaninternal.server.query.LimitOffsetPagedList;
import com.avaje.ebeaninternal.server.query.LimitOffsetPagingQuery;
import com.avaje.ebeaninternal.server.query.QueryFutureIds;
import com.avaje.ebeaninternal.server.query.QueryFutureList;
import com.avaje.ebeaninternal.server.query.QueryFutureRowCount;
import com.avaje.ebeaninternal.server.query.SqlQueryFutureList;
import com.avaje.ebeaninternal.server.query.*;
import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery;
import com.avaje.ebeaninternal.server.querydefn.DefaultOrmUpdate;
import com.avaje.ebeaninternal.server.querydefn.DefaultRelationalQuery;
@@ -86,6 +32,16 @@ import com.avaje.ebeaninternal.server.transaction.TransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.util.ParamTypeHelper;
import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.persistence.PersistenceException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
/**
* The default server side implementation of EbeanServer.
@@ -226,9 +182,9 @@ public final class DefaultServer implements SpiEbeanServer {
this.collectQueryOrigins = serverConfig.isCollectQueryOrigins();
this.collectQueryStatsByNode = serverConfig.isCollectQueryStatsByNode();
this.maxCallStack = GlobalProperties.getInt("ebean.maxCallStack", 5);
this.maxCallStack = serverConfig.getMaxCallStack();
this.rollbackOnChecked = GlobalProperties.getBoolean("ebean.transaction.rollbackOnChecked", true);
this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked();
this.transactionManager = config.getTransactionManager();
this.transactionScopeManager = config.getTransactionScopeManager();
@@ -482,20 +438,6 @@ public final class DefaultServer implements SpiEbeanServer {
return serverCacheManager;
}
/**
* Return the Profile Listener.
*/
public AutoFetchManager getProfileListener() {
return autoFetchManager;
}
/**
* Return the Relational query engine.
*/
public RelationalQueryEngine getRelationalQueryEngine() {
return relationalQueryEngine;
}
public void refreshMany(Object parentBean, String propertyName, Transaction t) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName, t);
@@ -1072,15 +1014,10 @@ public final class DefaultServer implements SpiEbeanServer {
public <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> query, Transaction t) {
if (desc.isAutoFetchTunable() && !query.isSqlSelect()) {
// its a tunable query
if (autoFetchManager.tuneQuery(query)) {
// was automatically tuned by Autofetch
} else {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
}
if (desc.isAutoFetchTunable() && !query.isSqlSelect() && !autoFetchManager.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
}
if (query.selectAllForLazyLoadProperty()) {
@@ -1091,12 +1028,9 @@ public final class DefaultServer implements SpiEbeanServer {
}
}
if (true) {
// if determine cost and no origin for Autofetch
if (query.getParentNode() == null) {
CallStack callStack = createCallStack();
query.setOrigin(callStack);
}
// if determine cost and no origin for Autofetch
if (query.getParentNode() == null) {
query.setOrigin(createCallStack());
}
// determine extra joins required to support where clause
@@ -1360,49 +1294,17 @@ public final class DefaultServer implements SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setFutureFetch(true);
// transfer the persistence content from the transaction
if (spiQuery.getPersistenceContext() == null) {
if (t != null) {
spiQuery.setPersistenceContext(((SpiTransaction) t).getPersistenceContext());
} else {
SpiTransaction st = getCurrentServerTransaction();
if (st != null) {
spiQuery.setPersistenceContext(st.getPersistenceContext());
}
}
}
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
// Create a new transaction solely to execute the findList() at some future time
Transaction newTxn = createTransaction();
CallableQueryList<T> call = new CallableQueryList<T>(this, spiQuery, newTxn);
QueryFutureList<T> queryFuture = new QueryFutureList<T>(call);
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
public <T> PagingList<T> findPagingList(Query<T> query, Transaction t, int pageSize) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
// we want to use a single PersistenceContext to be used
// for all the paging queries so we make sure there is a
// PersistenceContext on the query
PersistenceContext pc = spiQuery.getPersistenceContext();
if (pc == null) {
SpiTransaction currentTransaction = getCurrentServerTransaction();
if (currentTransaction != null) {
pc = currentTransaction.getPersistenceContext();
}
if (pc == null) {
pc = new DefaultPersistenceContext();
}
spiQuery.setPersistenceContext(pc);
}
return new LimitOffsetPagingQuery<T>(this, spiQuery, pageSize);
}
@Override
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize) {
@@ -1786,6 +1688,7 @@ public final class DefaultServer implements SpiEbeanServer {
TransWrapper wrap = initTransIfRequired(t);
try {
wrap.batchEscalateOnCollection();
SpiTransaction trans = wrap.transaction;
int saveCount = 0;
while (it.hasNext()) {
@@ -1795,7 +1698,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
wrap.commitIfCreated();
wrap.flushBatchOnCollection();
return saveCount;
} catch (RuntimeException e) {
@@ -1879,6 +1782,7 @@ public final class DefaultServer implements SpiEbeanServer {
TransWrapper wrap = initTransIfRequired(t);
try {
wrap.batchEscalateOnCollection();
SpiTransaction trans = wrap.transaction;
int deleteCount = 0;
while (it.hasNext()) {
@@ -1888,7 +1792,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
wrap.commitIfCreated();
wrap.flushBatchOnCollection();
return deleteCount;
} catch (RuntimeException e) {
@@ -1939,10 +1843,6 @@ public final class DefaultServer implements SpiEbeanServer {
return execute(update, null);
}
public <T> BeanManager<T> getBeanManager(Class<T> beanClass) {
return beanDescriptorManager.getBeanManager(beanClass);
}
/**
* Return all the BeanDescriptors.
*/
@@ -1950,13 +1850,6 @@ public final class DefaultServer implements SpiEbeanServer {
return beanDescriptorManager.getBeanDescriptorList();
}
public List<MetaBeanInfo> getMetaBeanInfoList() {
List<MetaBeanInfo> list = new ArrayList<MetaBeanInfo>();
list.addAll(getBeanDescriptors());
return list;
}
public void register(BeanPersistController c) {
List<BeanDescriptor<?>> list = beanDescriptorManager.getBeanDescriptorList();
for (int i = 0; i < list.size(); i++) {
@@ -2081,9 +1974,7 @@ public final class DefaultServer implements SpiEbeanServer {
// create the 'interesting' part of the stackTrace
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
for (int i = 0; i < stackLength; i++) {
finalTrace[i] = stackTrace[i + startIndex];
}
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
if (stackLength < 1) {
// this should not really happen
@@ -2,10 +2,7 @@ package com.avaje.ebeaninternal.server.core;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.MBeanServer;
@@ -13,6 +10,7 @@ import javax.management.MBeanServerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import com.avaje.ebean.config.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,11 +19,6 @@ import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.common.BootupEbeanManager;
import com.avaje.ebean.config.DataSourceConfig;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.PstmtDelegate;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.UnderscoreNamingConvention;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
@@ -38,7 +31,6 @@ import com.avaje.ebeaninternal.server.lib.ShutdownManager;
import com.avaje.ebeaninternal.server.lib.sql.DataSourceAlert;
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
import com.avaje.ebeaninternal.server.lib.sql.SimpleDataSourceAlert;
import com.avaje.ebeaninternal.server.lib.thread.ThreadPool;
/**
* Default Server side implementation of ServerFactory.
@@ -51,46 +43,18 @@ public class DefaultServerFactory implements BootupEbeanManager {
private final JndiDataSourceLookup jndiDataSourceFactory;
private final BootupClassPathSearch bootupClassSearch;
private final AtomicInteger serverId = new AtomicInteger(1);
private final XmlConfigLoader xmlConfigLoader;
public DefaultServerFactory(ContainerConfig containerConfig) {
private final XmlConfig xmlConfig;
public DefaultServerFactory() {
this.clusterManager = new ClusterManager();
this.clusterManager = new ClusterManager(containerConfig);
this.jndiDataSourceFactory = new JndiDataSourceLookup();
List<String> packages = getSearchJarsPackages(GlobalProperties.get("ebean.search.packages", null));
List<String> jars = getSearchJarsPackages(GlobalProperties.get("ebean.search.jars", null));
this.bootupClassSearch = new BootupClassPathSearch(null, packages, jars);
this.xmlConfigLoader = new XmlConfigLoader(null);
this.xmlConfig = xmlConfigLoader.load();
// register so that we can shutdown any Ebean wide
// resources such as clustering
ShutdownManager.registerServerFactory(this);
}
private List<String> getSearchJarsPackages(String searchPackages) {
List<String> hitList = new ArrayList<String>();
if (searchPackages != null) {
String[] entries = searchPackages.split("[ ,;]");
for (int i = 0; i < entries.length; i++) {
hitList.add(entries[i].trim());
}
}
return hitList;
}
public void shutdown() {
clusterManager.shutdown();
}
@@ -100,35 +64,26 @@ public class DefaultServerFactory implements BootupEbeanManager {
*/
public SpiEbeanServer createServer(String name) {
ConfigBuilder b = new ConfigBuilder();
ServerConfig config = b.build(name);
ServerConfig config = new ServerConfig();
config.setName(name);
Properties prop = PropertyMap.defaultProperties();
config.loadFromProperties(prop);
return createServer(config);
}
private SpiBackgroundExecutor createBackgroundExecutor(ServerConfig serverConfig, int uniqueServerId) {
private SpiBackgroundExecutor createBackgroundExecutor(ServerConfig serverConfig) {
String namePrefix = "Ebean-" + serverConfig.getName();
String namePrefix = "ebean-" + serverConfig.getName();
// the size of the pool for executing periodic tasks (such as cache flushing)
int schedulePoolSize = GlobalProperties.getInt("backgroundExecutor.schedulePoolsize", 1);
int schedulePoolSize = serverConfig.getBackgroundExecutorSchedulePoolSize();
int corePoolSize = serverConfig.getBackgroundExecutorCorePoolSize();
int maxPoolSize = serverConfig.getBackgroundExecutorMaxPoolSize();
int idleSecs = serverConfig.getBackgroundExecutorIdleSecs();
int shutdownSecs = serverConfig.getBackgroundExecutorShutdownSecs();
// the side of the main pool for immediate background task execution
int minPoolSize = GlobalProperties.getInt("backgroundExecutor.minPoolSize", 0);
int poolSize = GlobalProperties.getInt("backgroundExecutor.poolsize", 20);
int maxPoolSize = GlobalProperties.getInt("backgroundExecutor.maxPoolSize", poolSize);
int idleSecs = GlobalProperties.getInt("backgroundExecutor.idlesecs", 120);
int shutdownSecs = GlobalProperties.getInt("backgroundExecutor.shutdownSecs", 30);
boolean useTrad = GlobalProperties.getBoolean("backgroundExecutor.traditional", true);
if (useTrad) {
// this pool will use Idle seconds to maintain the thread count between min and max
ThreadPool pool = new ThreadPool(namePrefix, true, null, minPoolSize, maxPoolSize, idleSecs*1000);
return new TraditionalBackgroundExecutor(pool, schedulePoolSize, shutdownSecs, namePrefix);
} else {
return new DefaultBackgroundExecutor(schedulePoolSize, maxPoolSize, idleSecs, shutdownSecs, namePrefix);
}
return new DefaultBackgroundExecutor(schedulePoolSize, corePoolSize, maxPoolSize, idleSecs, shutdownSecs, namePrefix);
}
/**
@@ -163,8 +118,7 @@ public class DefaultServerFactory implements BootupEbeanManager {
pstmtDelegate = getOraclePstmtDelegate(serverConfig.getDataSource());
}
if (pstmtDelegate != null) {
// We can support JDBC batching with Oracle
// via OraclePreparedStatement
// We can support JDBC batching with Oracle via OraclePreparedStatement
pstmtBatch = new OraclePstmtBatch(pstmtDelegate);
}
if (pstmtBatch == null) {
@@ -180,27 +134,30 @@ public class DefaultServerFactory implements BootupEbeanManager {
ServerCacheManager cacheManager = getCacheManager(serverConfig);
int uniqueServerId = serverId.incrementAndGet();
SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig, uniqueServerId);
SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig);
InternalConfiguration c = new InternalConfiguration(xmlConfig, clusterManager, cacheManager, bgExecutor, serverConfig, bootupClasses,
pstmtBatch);
XmlConfigLoader xmlConfigLoader = new XmlConfigLoader(null);
XmlConfig xmlConfig = xmlConfigLoader.load();
InternalConfiguration c = new InternalConfiguration(xmlConfig, clusterManager, cacheManager, bgExecutor, serverConfig, bootupClasses, pstmtBatch);
DefaultServer server = new DefaultServer(c, cacheManager);
cacheManager.init(server);
MBeanServer mbeanServer;
ArrayList<?> list = MBeanServerFactory.findMBeanServer(null);
if (list.size() == 0) {
// probably not running in a server
mbeanServer = MBeanServerFactory.createMBeanServer();
} else {
// use the first MBeanServer
mbeanServer = (MBeanServer) list.get(0);
if (serverConfig.isRegisterJmxMBeans()) {
MBeanServer mbeanServer;
ArrayList<?> list = MBeanServerFactory.findMBeanServer(null);
if (list.size() == 0) {
// probably not running in a server
mbeanServer = MBeanServerFactory.createMBeanServer();
} else {
// use the first MBeanServer
mbeanServer = (MBeanServer) list.get(0);
}
server.registerMBeans(mbeanServer, uniqueServerId);
}
server.registerMBeans(mbeanServer, uniqueServerId);
// generate and run DDL if required
// if there are any other tasks requiring action in their plugins, do them as well
server.executePlugins(online);
@@ -215,9 +172,7 @@ public class DefaultServerFactory implements BootupEbeanManager {
}
// warm the cache in 30 seconds
int delaySecs = GlobalProperties.getInt("ebean.cacheWarmingDelay", 30);
long sleepMillis = 1000 * delaySecs;
long sleepMillis = 1000 * serverConfig.getCacheWarmingDelay();
if (sleepMillis > 0) {
Timer t = new Timer("EbeanCacheWarmer", true);
t.schedule(new CacheWarmer(server), sleepMillis);
@@ -252,19 +207,15 @@ public class DefaultServerFactory implements BootupEbeanManager {
// reasonable default settings are for a cache per bean type
ServerCacheOptions beanOptions = new ServerCacheOptions();
beanOptions.setMaxSize(GlobalProperties.getInt("cache.maxSize", 1000));
// maxIdleTime 10 minutes
beanOptions.setMaxIdleSecs(GlobalProperties.getInt("cache.maxIdleTime", 60 * 10));
// maxTimeToLive 6 hrs
beanOptions.setMaxSecsToLive(GlobalProperties.getInt("cache.maxTimeToLive", 60 * 60 * 6));
beanOptions.setMaxSize(serverConfig.getCacheMaxSize());
beanOptions.setMaxIdleSecs(serverConfig.getCacheMaxIdleTime());
beanOptions.setMaxSecsToLive(serverConfig.getCacheMaxTimeToLive());
// reasonable default settings for the query cache per bean type
ServerCacheOptions queryOptions = new ServerCacheOptions();
queryOptions.setMaxSize(GlobalProperties.getInt("querycache.maxSize", 100));
// maxIdleTime 10 minutes
queryOptions.setMaxIdleSecs(GlobalProperties.getInt("querycache.maxIdleTime", 60 * 10));
// maxTimeToLive 6 hours
queryOptions.setMaxSecsToLive(GlobalProperties.getInt("querycache.maxTimeToLive", 60 * 60 * 6));
queryOptions.setMaxSize(serverConfig.getQueryCacheMaxSize());
queryOptions.setMaxIdleSecs(serverConfig.getQueryCacheMaxIdleTime());
queryOptions.setMaxSecsToLive(serverConfig.getQueryCacheMaxTimeToLive());
ServerCacheFactory cacheFactory = serverConfig.getServerCacheFactory();
if (cacheFactory == null) {
@@ -303,17 +254,8 @@ public class DefaultServerFactory implements BootupEbeanManager {
return new BootupClasses(serverConfig.getClasses());
}
List<String> jars = serverConfig.getJars();
List<String> packages = serverConfig.getPackages();
if ((packages != null && !packages.isEmpty()) || (jars != null && !jars.isEmpty())) {
// filter by package name
BootupClassPathSearch search = new BootupClassPathSearch(null, packages, jars);
return search.getBootupClasses();
}
// just use classes we can find via class path search
return bootupClassSearch.getBootupClasses().createCopy();
BootupClassPathSearch search = new BootupClassPathSearch(null, serverConfig.getPackages(), serverConfig.getJars(), serverConfig.getClassPathReaderClassName());
return search.getBootupClasses();
}
/**
@@ -323,22 +265,6 @@ public class DefaultServerFactory implements BootupEbeanManager {
if (config.getNamingConvention() == null) {
UnderscoreNamingConvention nc = new UnderscoreNamingConvention();
config.setNamingConvention(nc);
String v = config.getProperty("namingConvention.useForeignKeyPrefix");
if (v != null) {
boolean useForeignKeyPrefix = Boolean.valueOf(v);
nc.setUseForeignKeyPrefix(useForeignKeyPrefix);
}
String sequenceFormat = config.getProperty("namingConvention.sequenceFormat");
if (sequenceFormat != null) {
nc.setSequenceFormat(sequenceFormat);
}
String schema = config.getProperty("namingConvention.schema");
if (schema != null) {
nc.setSchema(schema);
}
}
}
@@ -351,7 +277,6 @@ public class DefaultServerFactory implements BootupEbeanManager {
if (dbPlatform == null) {
DatabasePlatformFactory factory = new DatabasePlatformFactory();
DatabasePlatform db = factory.create(config);
config.setDatabasePlatform(db);
logger.info("DatabasePlatform name:" + config.getName() + " platform:" + db.getName());
@@ -370,7 +295,7 @@ public class DefaultServerFactory implements BootupEbeanManager {
private DataSource getDataSourceFromConfig(ServerConfig config) {
DataSource ds = null;
DataSource ds;
if (config.getDataSourceJndiName() != null) {
ds = jndiDataSourceFactory.lookup(config.getDataSourceJndiName());
@@ -1,266 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ExternalTransactionManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManagerFactory;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.DeployOrmXml;
import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit;
import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionFactory;
import com.avaje.ebeaninternal.server.lib.sql.DataSourcePool;
import com.avaje.ebeaninternal.server.persist.Binder;
import com.avaje.ebeaninternal.server.persist.DefaultPersister;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
import com.avaje.ebeaninternal.server.query.DefaultOrmQueryEngine;
import com.avaje.ebeaninternal.server.query.DefaultRelationalQueryEngine;
import com.avaje.ebeaninternal.server.resource.ResourceManager;
import com.avaje.ebeaninternal.server.resource.ResourceManagerFactory;
import com.avaje.ebeaninternal.server.text.json.DJsonContext;
<<<<<<< HEAD
import com.avaje.ebeaninternal.server.text.json.DefaultJsonValueAdapter;
import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager;
=======
>>>>>>> json-refactor
import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
import com.avaje.ebeaninternal.server.type.TypeManager;
/**
* Used to extend the ServerConfig with additional objects used to configure and
* construct an EbeanServer.
*/
public class InternalConfiguration {
private static final Logger logger = LoggerFactory.getLogger(InternalConfiguration.class);
private final ServerConfig serverConfig;
private final BootupClasses bootupClasses;
private final DeployInherit deployInherit;
private final ResourceManager resourceManager;
private final DeployOrmXml deployOrmXml;
private final TypeManager typeManager;
private final Binder binder;
private final DeployCreateProperties deployCreateProperties;
private final DeployUtil deployUtil;
private final BeanDescriptorManager beanDescriptorManager;
private final TransactionManager transactionManager;
private final TransactionScopeManager transactionScopeManager;
private final CQueryEngine cQueryEngine;
private final ClusterManager clusterManager;
private final ServerCacheManager cacheManager;
private final ExpressionFactory expressionFactory;
private final SpiBackgroundExecutor backgroundExecutor;
private final PstmtBatch pstmtBatch;
private final XmlConfig xmlConfig;
public InternalConfiguration(XmlConfig xmlConfig, ClusterManager clusterManager,
ServerCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses, PstmtBatch pstmtBatch) {
this.xmlConfig = xmlConfig;
this.pstmtBatch = pstmtBatch;
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.cacheManager = cacheManager;
this.serverConfig = serverConfig;
this.bootupClasses = bootupClasses;
this.expressionFactory = new DefaultExpressionFactory();
this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
this.binder = new Binder(typeManager);
this.resourceManager = ResourceManagerFactory.createResourceManager(serverConfig);
this.deployOrmXml = new DeployOrmXml(resourceManager.getResourceSource());
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, serverConfig);
this.beanDescriptorManager = new BeanDescriptorManager(this);
beanDescriptorManager.deploy();
this.transactionManager = createTransactionManager();
this.cQueryEngine = new CQueryEngine(serverConfig.getDatabasePlatform(), binder);
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
externalTransactionManager = new JtaTransactionManager();
}
if (externalTransactionManager != null) {
externalTransactionManager.setTransactionManager(transactionManager);
this.transactionScopeManager = new ExternalTransactionScopeManager(transactionManager, externalTransactionManager);
logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]");
} else {
this.transactionScopeManager = new DefaultTransactionScopeManager(transactionManager);
}
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
private TransactionManager createTransactionManager() {
if (isAutoCommitMode()) {
return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
return new TransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
/**
* Return true if autoCommit mode is on.
*/
private boolean isAutoCommitMode() {
if (serverConfig.isAutoCommitMode()) {
// explicitly set
return true;
}
DataSource dataSource = serverConfig.getDataSource();
if (dataSource instanceof DataSourcePool && ((DataSourcePool)dataSource).getAutoCommit()) {
// We know the DataSourcePool is using autoCommit
return true;
}
return false;
}
public JsonContext createJsonContext(SpiEbeanServer server) {
return new DJsonContext(server);
}
public XmlConfig getXmlConfig() {
return xmlConfig;
}
public AutoFetchManager createAutoFetchManager(SpiEbeanServer server) {
return AutoFetchManagerFactory.create(server, serverConfig, resourceManager);
}
public RelationalQueryEngine createRelationalQueryEngine() {
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue());
}
public OrmQueryEngine createOrmQueryEngine() {
return new DefaultOrmQueryEngine(beanDescriptorManager, cQueryEngine);
}
public Persister createPersister(SpiEbeanServer server) {
return new DefaultPersister(server, binder, beanDescriptorManager, pstmtBatch);
}
public PstmtBatch getPstmtBatch() {
return pstmtBatch;
}
public ServerCacheManager getCacheManager() {
return cacheManager;
}
public BootupClasses getBootupClasses() {
return bootupClasses;
}
public DatabasePlatform getDatabasePlatform() {
return serverConfig.getDatabasePlatform();
}
public ServerConfig getServerConfig() {
return serverConfig;
}
public ExpressionFactory getExpressionFactory() {
return expressionFactory;
}
public TypeManager getTypeManager() {
return typeManager;
}
public Binder getBinder() {
return binder;
}
public BeanDescriptorManager getBeanDescriptorManager() {
return beanDescriptorManager;
}
public DeployInherit getDeployInherit() {
return deployInherit;
}
public ResourceManager getResourceManager() {
return resourceManager;
}
public DeployOrmXml getDeployOrmXml() {
return deployOrmXml;
}
public DeployCreateProperties getDeployCreateProperties() {
return deployCreateProperties;
}
public DeployUtil getDeployUtil() {
return deployUtil;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public TransactionScopeManager getTransactionScopeManager() {
return transactionScopeManager;
}
public CQueryEngine getCQueryEngine() {
return cQueryEngine;
}
public ClusterManager getClusterManager() {
return clusterManager;
}
public SpiBackgroundExecutor getBackgroundExecutor() {
return backgroundExecutor;
}
}
@@ -6,9 +6,6 @@ import javax.naming.NamingException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import com.avaje.ebean.config.GlobalProperties;
/**
* Helper to lookup a DataSource from JNDI.
*/
@@ -16,8 +13,6 @@ public class JndiDataSourceLookup {
private static final String DEFAULT_PREFIX = "java:comp/env/jdbc/";
String jndiPrefix = GlobalProperties.get("ebean.datasource.jndi.prefix", DEFAULT_PREFIX);
public JndiDataSourceLookup() {
}
@@ -32,7 +27,7 @@ public class JndiDataSourceLookup {
try {
if (!jndiName.startsWith("java:")){
jndiName = jndiPrefix + jndiName;
jndiName = DEFAULT_PREFIX + jndiName;
}
Context ctx = new InitialContext();
@@ -13,7 +13,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
public enum Type {
DETERMINE, INSERT, UPDATE, DELETE, ORMUPDATE, UPDATESQL, CALLABLESQL
};
}
protected boolean persistCascade;
@@ -24,7 +24,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
protected final PersistExecute persistExecute;
/**
/**
* Used by CallableSqlRequest and UpdateSqlRequest.
*/
public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
@@ -41,25 +41,33 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
* Execute the request right now.
*/
public abstract int executeNow();
public PstmtBatch getPstmtBatch() {
return ebeanServer.getPstmtBatch();
}
public boolean isLogSql() {
return transaction.isLogSql();
}
public boolean isLogSummary() {
return transaction.isLogSummary();
}
public PstmtBatch getPstmtBatch() {
return ebeanServer.getPstmtBatch();
}
public boolean isLogSql() {
return transaction.isLogSql();
}
public boolean isLogSummary() {
return transaction.isLogSummary();
}
/**
* Return true if this persist request should use JDBC batch.
*/
public boolean isBatchThisRequest() {
return transaction.isBatchThisRequest(type);
}
/**
* Execute the Callable statement.
* Execute the statement.
*/
public int executeStatement() {
boolean batch = transaction.isBatchThisRequest();
boolean batch = isBatchThisRequest();
int rows;
BatchControl control = transaction.getBatchControl();
@@ -69,15 +77,15 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
} else if (batch) {
// need to create the BatchControl
control = persistExecute.createBatchControl(transaction);
rows = control.executeStatementOrBatch(this, batch);
rows = control.executeStatementOrBatch(this, true);
} else {
rows = executeNow();
}
return rows;
}
public void initTransIfRequired() {
public void initTransIfRequired() {
createImplicitTransIfRequired(false);
persistCascade = transaction.isPersistCascade();
}
@@ -97,6 +97,21 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private Set<String> updatedProperties;
/**
* Flag set when request is added to JDBC batch.
*/
private boolean batched;
/**
* Flag set when batchOnCascade to avoid using batch on the top bean.
*/
private boolean skipBatchForTopLevel;
/**
* Flag set when batch mode is turned on for a persist cascade.
*/
private boolean batchOnCascadeSet;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
@@ -127,6 +142,54 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.dirty = intercept.isDirty();
}
/**
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
createImplicitTransIfRequired(false);
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
batchOnCascadeSet = !createdTransaction;
}
persistCascade = transaction.isPersistCascade();
}
/**
* If using batch on cascade flush if required.
*/
public void flushBatchOnCascade() {
if (batchOnCascadeSet) {
// we escalated to batch mode for request so flush
transaction.flushBatchOnCascade();
batchOnCascadeSet = false;
}
}
/**
* Return true is this request was added to the JDBC batch.
*/
public boolean isBatched() {
return batched;
}
/**
* Set when request is added to the JDBC batch.
*/
public void setBatched() {
batched = true;
}
public void setSkipBatchForTopLevel() {
skipBatchForTopLevel = true;
}
@Override
public boolean isBatchThisRequest() {
return !skipBatchForTopLevel && super.isBatchThisRequest();
}
/**
* Return true if this is an insert request.
*/
@@ -149,7 +212,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.getDirtyValues();
}
public boolean isNotify(TransactionEvent txnEvent) {
public boolean isNotify() {
this.notifyCache = beanDescriptor.isCacheNotify();
return notifyCache || isNotifyPersistListener();
}
@@ -234,7 +297,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (id != null) {
hc += id.hashCode();
}
beanHash = Integer.valueOf(hc);
beanHash = new Integer(hc);
}
return beanHash;
}
@@ -397,7 +460,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
@Override
public int executeOrQueue() {
boolean batch = transaction.isBatchThisRequest();
boolean batch = isBatchThisRequest();
BatchControl control = transaction.getBatchControl();
if (control != null) {
@@ -405,7 +468,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
if (batch) {
control = persistExecute.createBatchControl(transaction);
return control.executeOrQueue(this, batch);
return control.executeOrQueue(this, true);
} else {
return executeNow();
@@ -438,10 +501,15 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
String m = Message.msg("persist.conc2", "" + rowCount);
throw new OptimisticLockException(m, null, bean);
}
if (type == Type.DELETE) {
postDelete();
}
}
public void postDelete() {
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
private void postDelete() {
// Delete the bean from the PersistenceContent
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
// Delete from cache early even if transaction fails
@@ -457,10 +525,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controllerPost();
}
if (intercept != null) {
// if bean persisted again then should result in an update
intercept.setLoaded();
}
// if bean persisted again then should result in an update
intercept.setLoaded();
addEvent();
@@ -528,9 +594,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (concurrencyMode.equals(ConcurrencyMode.VERSION)) {
// check the version property was loaded
BeanProperty prop = beanDescriptor.getVersionProperty();
if (prop != null && intercept.isLoadedProperty(prop.getPropertyIndex())) {
// OK to use version property
} else {
if (prop == null || !intercept.isLoadedProperty(prop.getPropertyIndex())) {
concurrencyMode = ConcurrencyMode.NONE;
}
}
@@ -1,13 +1,8 @@
package com.avaje.ebeaninternal.server.core;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContextEvent;
/**
* Listens for webserver server starting and stopping events.
@@ -19,8 +14,6 @@ import org.slf4j.LoggerFactory;
*/
public class ServletContextListener implements javax.servlet.ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(ServletContextListener.class);
/**
* The servlet container is stopping.
*/
@@ -29,29 +22,10 @@ public class ServletContextListener implements javax.servlet.ServletContextListe
}
/**
* The servlet container is starting.
* <p>
* Initialise the properties file using SystemProperties.initWebapp();
* and start Ebean.
* </p>
* Do nothing on startup.
*/
public void contextInitialized(ServletContextEvent event) {
try {
ServletContext servletContext = event.getServletContext();
GlobalProperties.setServletContext(servletContext);
if (servletContext != null) {
String servletRealPath = servletContext.getRealPath("");
GlobalProperties.put("servlet.realpath", servletRealPath);
logger.info("servlet.realpath=[" + servletRealPath + "]");
}
Ebean.getServer(null);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
@@ -1,55 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.server.lib.DaemonScheduleThreadPool;
import com.avaje.ebeaninternal.server.lib.thread.ThreadPool;
/**
* BackgroundExecutor using my traditional ThreadPool that will grow and trim.
*
* @author rbygrave
*/
public class TraditionalBackgroundExecutor implements SpiBackgroundExecutor {
private static Logger logger = LoggerFactory.getLogger(TraditionalBackgroundExecutor.class);
private final ThreadPool pool;
private final DaemonScheduleThreadPool schedulePool;
/**
* Construct the default implementation of BackgroundExecutor.
*/
public TraditionalBackgroundExecutor(ThreadPool pool, int schedulePoolSize, int shutdownWaitSeconds, String namePrefix) {
this.pool = pool;
this.schedulePool = new DaemonScheduleThreadPool(schedulePoolSize, shutdownWaitSeconds, namePrefix+"-periodic-");
}
/**
* Execute a Runnable using a background thread.
*/
public void execute(Runnable r) {
pool.assign(r, true);
}
public void executePeriodically(Runnable r, long delay, TimeUnit unit) {
if (logger.isDebugEnabled()) {
logger.debug("Registering for executePeriodically {} delay:{} {}",r, delay, unit);
}
schedulePool.scheduleWithFixedDelay(r, delay, delay, unit);
}
public void shutdown() {
if (logger.isDebugEnabled()) {
logger.debug("Shutting down");
}
pool.shutdown();
schedulePool.shutdown();
}
}
@@ -23,6 +23,16 @@ final class TransWrapper {
wasCreated = created;
}
void batchEscalateOnCollection() {
transaction.checkBatchEscalationOnCollection();
}
void flushBatchOnCollection() {
if (!wasCreated) {
transaction.flushBatchOnCollection();
}
}
void commitIfCreated() {
if (wasCreated){
transaction.commit();
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.ddl;
import com.avaje.ebean.config.dbplatform.DbDdlSyntax;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
@@ -16,23 +17,23 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
final FkeyPropertyVisitor pv;
public AddForeignKeysVisitor(DdlGenContext ctx) {
public AddForeignKeysVisitor(boolean addMode, DdlGenContext ctx) {
this.ctx = ctx;
this.pv = new FkeyPropertyVisitor(this, ctx);
this.pv = new FkeyPropertyVisitor(addMode, this, ctx);
}
public boolean visitBean(BeanDescriptor<?> descriptor) {
if (!descriptor.isInheritanceRoot()){
// ignore/skip if not a top level BeanDescriptor
return false;
}
return true;
}
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
visitInheritanceProperties(descriptor, pv);
public boolean visitBean(BeanDescriptor<?> descriptor) {
if (!descriptor.isInheritanceRoot()) {
// ignore/skip if not a top level BeanDescriptor
return false;
}
return true;
}
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
visitInheritanceProperties(descriptor, pv);
}
public void visitBegin() {
}
@@ -48,11 +49,17 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
public static class FkeyPropertyVisitor extends BaseTablePropertyVisitor {
/**
* Set to false when generating drop foreign key statements.
*/
final boolean addMode;
final DdlGenContext ctx;
final AddForeignKeysVisitor parent;
public FkeyPropertyVisitor(AddForeignKeysVisitor parent, DdlGenContext ctx) {
public FkeyPropertyVisitor(boolean addMode, AddForeignKeysVisitor parent, DdlGenContext ctx) {
this.addMode = addMode;
this.parent = parent;
this.ctx = ctx;
}
@@ -69,51 +76,59 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
// Alter table o_address add Foreign Key (country_code) references o_country (code) on delete restrict on update restrict;
String baseTable = p.getBeanDescriptor().getBaseTable();
TableJoin tableJoin = p.getTableJoin();
TableJoinColumn[] columns = tableJoin.columns();
String tableName = p.getBeanDescriptor().getBaseTable();
String fkName = ctx.getDdlSyntax().getForeignKeyName(tableName, p.getName(), ctx.incrementFkCount());
ctx.write("alter table ").write(baseTable).write(" add ");
DbDdlSyntax ddlSyntax = ctx.getDdlSyntax();
String fkName = ddlSyntax.getForeignKeyName(tableName, p.getName(), ctx.incrementFkCount());
if (!addMode) {
// look to generate drop foreign key statement
String dropKeyConstraintPrefix = ddlSyntax.dropKeyConstraintPrefix(tableName, fkName);
if (dropKeyConstraintPrefix != null && !dropKeyConstraintPrefix.isEmpty()) {
ctx.write(dropKeyConstraintPrefix).write(" ");
}
}
ctx.write("alter table ").write(baseTable).write(addOrDrop());
if (fkName != null) {
ctx.write("constraint ").write(fkName).write(" ");
}
ctx.write("foreign key (");
for (int i = 0; i < columns.length; i++) {
if (i > 0){
ctx.write(",");
}
ctx.write(columns[i].getLocalDbColumn());
}
ctx.write(")");
if (addMode) {
ctx.write("foreign key (");
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
ctx.write(",");
}
ctx.write(columns[i].getLocalDbColumn());
}
ctx.write(")");
ctx.write(" references ");
ctx.write(tableJoin.getTable());
ctx.write(" (");
for (int i = 0; i < columns.length; i++) {
if (i > 0){
ctx.write(",");
}
ctx.write(columns[i].getForeignDbColumn());
}
ctx.write(")");
ctx.write(" references ");
ctx.write(tableJoin.getTable());
ctx.write(" (");
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
ctx.write(",");
}
ctx.write(columns[i].getForeignDbColumn());
}
ctx.write(")");
String fkeySuffix = ctx.getDdlSyntax().getForeignKeySuffix();
if (fkeySuffix != null){
ctx.write(" ").write(fkeySuffix);
}
String fkeySuffix = ctx.getDdlSyntax().getForeignKeySuffix();
if (fkeySuffix != null) {
ctx.write(" ").write(fkeySuffix);
}
}
ctx.write(";").writeNewLine();
if (ctx.getDdlSyntax().isRenderIndexForFkey()){
if (addMode && ddlSyntax.isRenderIndexForFkey()){
//create index idx_fk_o_address_ctry on o_address(country_code);
ctx.write("create index ");
String idxName = ctx.getDdlSyntax().getIndexName(tableName, p.getName(), ctx.incrementIxCount());
String idxName = ddlSyntax.getIndexName(tableName, p.getName(), ctx.incrementIxCount());
if (idxName != null){
ctx.write(idxName);
}
@@ -129,21 +144,27 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
}
}
@Override
public void visitScalar(BeanProperty p) {
// not interested
}
/**
* Return 'add' or 'drop' of foreign key.
*/
protected String addOrDrop() {
return addMode ? " add " : " drop " ;
}
@Override
public void visitCompound(BeanPropertyCompound p) {
// not interested
}
@Override
public void visitScalar(BeanProperty p) {
// not interested
}
@Override
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
// not interested
}
}
@Override
public void visitCompound(BeanPropertyCompound p) {
// not interested
}
@Override
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
// not interested
}
}
}
@@ -126,6 +126,13 @@ public class DdlGenerator implements SpiEbeanPlugin {
DdlGenContext ctx = createContext();
if (ctx.getDdlSyntax().isDropKeyConstraints()) {
// generate drop foreign key constraint statements (sql server joy)
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(false, ctx);
VisitorUtil.visit(server, fkeys);
ctx.writeNewLine();
}
DropTableVisitor drop = new DropTableVisitor(ctx);
VisitorUtil.visit(server, drop);
@@ -146,7 +153,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
CreateSequenceVisitor createSequence = new CreateSequenceVisitor(ctx);
VisitorUtil.visit(server, createSequence);
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(ctx);
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(true, ctx);
VisitorUtil.visit(server, fkeys);
CreateIndexVisitor indexes = new CreateIndexVisitor(ctx);
@@ -275,7 +282,6 @@ public class DdlGenerator implements SpiEbeanPlugin {
} catch (Exception e) {
if (expectErrors) {
logger.info(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
e.printStackTrace();
} else {
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
throw new RuntimeException(msg, e);
@@ -24,6 +24,7 @@ public class DropTableVisitor implements BeanVisitor {
}
protected void writeDropTable(String tableName){
ctx.write(ddlSyntax.dropTablePrefix(tableName));
ctx.write("drop table ");
if (ddlSyntax.getDropIfExists() != null){
ctx.write(ddlSyntax.getDropIfExists()).write(" ");
@@ -1,24 +1,5 @@
package com.avaje.ebeaninternal.server.deploy;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.PersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.annotation.ConcurrencyMode;
@@ -35,11 +16,7 @@ import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
import com.avaje.ebeaninternal.api.*;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
@@ -50,13 +27,7 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
import com.avaje.ebeaninternal.server.el.ElComparator;
import com.avaje.ebeaninternal.server.el.ElComparatorCompound;
import com.avaje.ebeaninternal.server.el.ElComparatorProperty;
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.persist.DmlUtil;
import com.avaje.ebeaninternal.server.el.*;
import com.avaje.ebeaninternal.server.query.CQueryPlan;
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import com.avaje.ebeaninternal.server.query.SplitName;
@@ -68,6 +39,15 @@ import com.avaje.ebeaninternal.util.SortByClause;
import com.avaje.ebeaninternal.util.SortByClause.Property;
import com.avaje.ebeaninternal.util.SortByClauseParser;
import com.fasterxml.jackson.core.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Describes Beans including their deployment information.
@@ -1036,6 +1016,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return null;
}
/**
* Return a raw expression for 'where parent id in ...' clause.
*/
public String getParentIdInExpr(int parentIdSize, String rawWhere) {
String inClause = idBinder.getIdInValueExpr(parentIdSize);
return idBinder.isIdInExpandedForm() ? inClause : rawWhere + inClause;
}
/**
* Return the IdBinder which is helpful for handling the various types of Id.
*/
@@ -1,25 +1,5 @@
package com.avaje.ebeaninternal.server.deploy;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.Model;
import com.avaje.ebean.RawSql;
@@ -29,7 +9,6 @@ import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.NamingConvention;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbIdentity;
@@ -38,35 +17,31 @@ import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.event.BeanFinder;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.TransactionEventTable;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.core.InternalConfiguration;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.core.XmlConfig;
import com.avaje.ebeaninternal.server.core.*;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.deploy.id.IdBinderEmbedded;
import com.avaje.ebeaninternal.server.deploy.id.IdBinderFactory;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable;
import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin;
import com.avaje.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import com.avaje.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import com.avaje.ebeaninternal.server.deploy.parse.DeployInherit;
import com.avaje.ebeaninternal.server.deploy.parse.DeployUtil;
import com.avaje.ebeaninternal.server.deploy.parse.ReadAnnotations;
import com.avaje.ebeaninternal.server.deploy.parse.TransientProperties;
import com.avaje.ebeaninternal.server.deploy.meta.*;
import com.avaje.ebeaninternal.server.deploy.parse.*;
import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator;
import com.avaje.ebeaninternal.server.lib.util.Dnode;
import com.avaje.ebeaninternal.server.properties.BeanPropertiesReader;
import com.avaje.ebeaninternal.server.properties.BeanPropertyInfo;
import com.avaje.ebeaninternal.server.properties.BeanPropertyInfoFactory;
import com.avaje.ebeaninternal.server.properties.BeanPropertiesReader;
import com.avaje.ebeaninternal.server.properties.EnhanceBeanPropertyInfoFactory;
import com.avaje.ebeaninternal.server.type.TypeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import javax.sql.DataSource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* Creates BeanDescriptors.
@@ -153,6 +128,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory;
private final boolean eagerFetchLobs;
/**
* Create for a given database dbConfig.
*/
@@ -167,6 +144,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.encryptKeyManager = config.getServerConfig().getEncryptKeyManager();
this.databasePlatform = config.getServerConfig().getDatabasePlatform();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm());
this.eagerFetchLobs = config.getServerConfig().isEagerFetchLobs();
this.bootupClasses = config.getBootupClasses();
this.createProperties = config.getDeployCreateProperties();
@@ -397,12 +375,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
String msg = "SERIOUS ERROR: The hashCode() and equals() methods *MUST* be implemented ";
msg += "on Embedded bean " + idType + " as it is used as an Id for " + beanType;
if (GlobalProperties.getBoolean("ebean.strict", true)) {
throw new PersistenceException(msg, source);
} else {
logger.error(msg, source);
}
throw new PersistenceException(msg, source);
}
/**
@@ -1017,7 +990,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DeployBeanInfo<T> info = new DeployBeanInfo<T>(deployUtil, desc);
readAnnotations.readInitial(info);
readAnnotations.readInitial(info, eagerFetchLobs);
return info;
}
@@ -225,6 +225,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return createElPropertyValue(propName, remainder, chain, propertyDeploy);
}
public void buildSelectExpressionChain(String prefix, List<String> selectChain) {
// do not add to the selectChain at the top level of the Many bean
}
public SqlUpdate deleteByParentId(Object parentId, List<Object> parentIdist) {
if (parentId != null) {
return deleteByParentId(parentId);
@@ -278,9 +282,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
query.setIncludeTableJoin(inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String inClause = descriptor.getIdBinder().getIdInValueExpr(parentIds.size());
String expr = rawWhere + inClause;
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
// Flatten the bind values if needed (embeddedId)
List<Object> bindValues = getBindParentIds(parentIds);
@@ -287,7 +287,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
/**
* Return true if this a OneToOne property. Otherwise assumed ManyToOne.
*/
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.deploy;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable;
import com.avaje.ebeaninternal.server.deploy.meta.DeployTableJoin;
@@ -99,15 +98,11 @@ public class BeanTable {
}
if (complexKey){
// check to see if we want prefixes by default with complex keys
boolean usePrefixOnComplex = GlobalProperties.getBoolean("ebean.prefixComplexKeys", false);
if (!usePrefixOnComplex){
// just to copy the column name rather than prefix with the foreignKeyPrefix.
// I think that with complex keys this is the more common approach.
String msg = "On table["+baseTable+"] foreign key column ["+lc+"]";
logger.debug(msg);
fk = lc;
}
// just to copy the column name rather than prefix with the foreignKeyPrefix.
// I think that with complex keys this is the more common approach.
String msg = "On table["+baseTable+"] foreign key column ["+lc+"]";
logger.debug(msg);
fk = lc;
}
DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk);
@@ -25,6 +25,11 @@ public interface IdBinder {
*/
public void initialise();
/**
* Return true if this is a compound key and must use expanded and or form.
*/
public boolean isIdInExpandedForm();
/**
* Write the Id value to binary DataOuput.
*/
@@ -43,17 +43,21 @@ public final class IdBinderEmbedded implements IdBinder {
this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed();
}
public boolean isIdInExpandedForm() {
return idInExpandedForm;
}
private String idInExpanded() {
StringBuilder sb = new StringBuilder();
StringBuilder sb = new StringBuilder(30);
sb.append("(");
for (int i = 0; i < props.length; i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append(embIdProperty.getName());
sb.append(idDesc.getBaseTableAlias());
sb.append(".");
sb.append(props[i].getName());
sb.append(props[i].getDbColumn());
sb.append("=?");
}
sb.append(")");
@@ -30,6 +30,10 @@ public final class IdBinderEmpty implements IdBinder {
}
public boolean isIdInExpandedForm() {
return false;
}
public String getOrderBy(String pathPrefix, boolean ascending) {
return pathPrefix;
}
@@ -41,7 +41,11 @@ public final class IdBinderSimple implements IdBinder {
public void initialise(){
// do nothing
}
public boolean isIdInExpandedForm() {
return false;
}
public String getOrderBy(String pathPrefix, boolean ascending) {
StringBuilder sb = new StringBuilder();
@@ -1,21 +1,11 @@
package com.avaje.ebeaninternal.server.deploy.parse;
import java.sql.Types;
import java.util.Map;
import java.util.UUID;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.avaje.ebean.annotation.*;
import com.avaje.ebean.config.EncryptDeploy;
import com.avaje.ebean.config.EncryptDeploy.Mode;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.config.dbplatform.DbEncryptFunction;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
@@ -23,12 +13,14 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.type.CtCompoundType;
import com.avaje.ebeaninternal.server.type.DataEncryptSupport;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.ScalarTypeBytesBase;
import com.avaje.ebeaninternal.server.type.ScalarTypeBytesEncrypted;
import com.avaje.ebeaninternal.server.type.ScalarTypeEncryptedWrapper;
import com.avaje.ebeaninternal.server.type.*;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.sql.Types;
import java.util.Map;
import java.util.UUID;
/**
* Read the field level deployment annotations.
@@ -42,10 +34,10 @@ public class AnnotationFields extends AnnotationParser {
private GeneratedPropertyFactory generatedPropFactory = new GeneratedPropertyFactory();
public AnnotationFields(DeployBeanInfo<?> info) {
public AnnotationFields(DeployBeanInfo<?> info, boolean eagerFetchLobs) {
super(info);
if (GlobalProperties.getBoolean("ebean.lobEagerFetch", false)) {
if (eagerFetchLobs) {
defaultLobFetchType = FetchType.EAGER;
}
}
@@ -15,11 +15,11 @@ public class ReadAnnotations {
* to resolve the relationships etc.
* </p>
*/
public void readInitial(DeployBeanInfo<?> info){
public void readInitial(DeployBeanInfo<?> info, boolean eagerFetchLobs){
try {
new AnnotationClass(info).parse();
new AnnotationFields(info).parse();
new AnnotationFields(info, eagerFetchLobs).parse();
} catch (RuntimeException e){
String msg = "Error reading annotations for "+info;
@@ -59,6 +59,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
this.exprList = new DefaultExpressionList<T>(query, parent);
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
List<SpiExpression> list = exprList.internalList();
@@ -79,17 +80,20 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public Junction<T> add(Expression item) {
SpiExpression i = (SpiExpression) item;
exprList.add(i);
return this;
}
@Override
public Junction<T> addAll(ExpressionList<T> addList) {
exprList.addAll(addList);
return this;
}
@Override
public void addBindValues(SpiExpressionRequest request) {
List<SpiExpression> list = exprList.internalList();
@@ -100,6 +104,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public void addSql(SpiExpressionRequest request) {
List<SpiExpression> list = exprList.internalList();
@@ -122,6 +127,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
/**
* Based on Junction type and all the expression contained.
*/
@Override
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
builder.add(JunctionExpression.class).add(joinType);
List<SpiExpression> list = exprList.internalList();
@@ -130,6 +136,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
builder.add(JunctionExpression.class).add(joinType);
List<SpiExpression> list = exprList.internalList();
@@ -138,6 +145,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public int queryBindHash() {
int hc = JunctionExpression.class.getName().hashCode();
@@ -149,50 +157,62 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
return hc;
}
@Override
public ExpressionList<T> endJunction() {
return exprList.endJunction();
}
@Override
public ExpressionList<T> allEq(Map<String, Object> propertyMap) {
return exprList.allEq(propertyMap);
}
@Override
public ExpressionList<T> and(Expression expOne, Expression expTwo) {
return exprList.and(expOne, expTwo);
}
@Override
public ExpressionList<T> between(String propertyName, Object value1, Object value2) {
return exprList.between(propertyName, value1, value2);
}
@Override
public ExpressionList<T> betweenProperties(String lowProperty, String highProperty, Object value) {
return exprList.betweenProperties(lowProperty, highProperty, value);
}
@Override
public Junction<T> conjunction() {
return exprList.conjunction();
}
@Override
public ExpressionList<T> contains(String propertyName, String value) {
return exprList.contains(propertyName, value);
}
@Override
public Junction<T> disjunction() {
return exprList.disjunction();
}
@Override
public ExpressionList<T> endsWith(String propertyName, String value) {
return exprList.endsWith(propertyName, value);
}
@Override
public ExpressionList<T> eq(String propertyName, Object value) {
return exprList.eq(propertyName, value);
}
@Override
public ExpressionList<T> exampleLike(Object example) {
return exprList.exampleLike(example);
}
@Override
public ExpressionList<T> filterMany(String prop) {
throw new RuntimeException("filterMany not allowed on Junction expression list");
}
@@ -202,18 +222,22 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
return exprList.apply(pathProperties);
}
@Override
public FutureIds<T> findFutureIds() {
return exprList.findFutureIds();
}
@Override
public FutureList<T> findFutureList() {
return exprList.findFutureList();
}
@Override
public FutureRowCount<T> findFutureRowCount() {
return exprList.findFutureRowCount();
}
@Override
public List<Object> findIds() {
return exprList.findIds();
}
@@ -228,195 +252,237 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
exprList.findEachWhile(consumer);
}
@Override
public void findVisit(QueryResultVisitor<T> visitor) {
exprList.findVisit(visitor);
}
@Override
public QueryIterator<T> findIterate() {
return exprList.findIterate();
}
@Override
public List<T> findList() {
return exprList.findList();
}
@Override
public Map<?, T> findMap() {
return exprList.findMap();
}
@Override
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
return exprList.findMap(keyProperty, keyType);
}
public PagingList<T> findPagingList(int pageSize) {
return exprList.findPagingList(pageSize);
}
@Override
public PagedList<T> findPagedList(int pageIndex, int pageSize) {
return exprList.findPagedList(pageIndex, pageSize);
}
@Override
public int findRowCount() {
return exprList.findRowCount();
}
@Override
public Set<T> findSet() {
return exprList.findSet();
}
@Override
public T findUnique() {
return exprList.findUnique();
}
@Override
public ExpressionList<T> ge(String propertyName, Object value) {
return exprList.ge(propertyName, value);
}
@Override
public ExpressionList<T> gt(String propertyName, Object value) {
return exprList.gt(propertyName, value);
}
@Override
public ExpressionList<T> having() {
throw new RuntimeException("having() not allowed on Junction expression list");
}
@Override
public ExpressionList<T> icontains(String propertyName, String value) {
return exprList.icontains(propertyName, value);
}
@Override
public ExpressionList<T> idEq(Object value) {
return exprList.idEq(value);
}
@Override
public ExpressionList<T> idIn(List<?> idValues) {
return exprList.idIn(idValues);
}
@Override
public ExpressionList<T> iendsWith(String propertyName, String value) {
return exprList.iendsWith(propertyName, value);
}
@Override
public ExpressionList<T> ieq(String propertyName, String value) {
return exprList.ieq(propertyName, value);
}
@Override
public ExpressionList<T> iexampleLike(Object example) {
return exprList.iexampleLike(example);
}
@Override
public ExpressionList<T> ilike(String propertyName, String value) {
return exprList.ilike(propertyName, value);
}
@Override
public ExpressionList<T> in(String propertyName, Collection<?> values) {
return exprList.in(propertyName, values);
}
@Override
public ExpressionList<T> in(String propertyName, Object... values) {
return exprList.in(propertyName, values);
}
@Override
public ExpressionList<T> in(String propertyName, com.avaje.ebean.Query<?> subQuery) {
return exprList.in(propertyName, subQuery);
}
@Override
public ExpressionList<T> isNotNull(String propertyName) {
return exprList.isNotNull(propertyName);
}
@Override
public ExpressionList<T> isNull(String propertyName) {
return exprList.isNull(propertyName);
}
@Override
public ExpressionList<T> istartsWith(String propertyName, String value) {
return exprList.istartsWith(propertyName, value);
}
@Override
public ExpressionList<T> le(String propertyName, Object value) {
return exprList.le(propertyName, value);
}
@Override
public ExpressionList<T> like(String propertyName, String value) {
return exprList.like(propertyName, value);
}
@Override
public ExpressionList<T> lt(String propertyName, Object value) {
return exprList.lt(propertyName, value);
}
@Override
public ExpressionList<T> ne(String propertyName, Object value) {
return exprList.ne(propertyName, value);
}
@Override
public ExpressionList<T> not(Expression exp) {
return exprList.not(exp);
}
@Override
public ExpressionList<T> or(Expression expOne, Expression expTwo) {
return exprList.or(expOne, expTwo);
}
@Override
public OrderBy<T> order() {
return exprList.order();
}
@Override
public com.avaje.ebean.Query<T> order(String orderByClause) {
return exprList.order(orderByClause);
}
@Override
public OrderBy<T> orderBy() {
return exprList.orderBy();
}
@Override
public com.avaje.ebean.Query<T> orderBy(String orderBy) {
return exprList.orderBy(orderBy);
}
@Override
public com.avaje.ebean.Query<T> query() {
return exprList.query();
}
@Override
public ExpressionList<T> raw(String raw, Object value) {
return exprList.raw(raw, value);
}
@Override
public ExpressionList<T> raw(String raw, Object[] values) {
return exprList.raw(raw, values);
}
@Override
public ExpressionList<T> raw(String raw) {
return exprList.raw(raw);
}
@Override
public com.avaje.ebean.Query<T> select(String properties) {
return exprList.select(properties);
}
@Override
public com.avaje.ebean.Query<T> setFirstRow(int firstRow) {
return exprList.setFirstRow(firstRow);
}
@Override
public com.avaje.ebean.Query<T> setMapKey(String mapKey) {
return exprList.setMapKey(mapKey);
}
@Override
public com.avaje.ebean.Query<T> setMaxRows(int maxRows) {
return exprList.setMaxRows(maxRows);
}
@Override
public com.avaje.ebean.Query<T> setOrderBy(String orderBy) {
return exprList.setOrderBy(orderBy);
}
@Override
public com.avaje.ebean.Query<T> setUseCache(boolean useCache) {
return exprList.setUseCache(useCache);
}
@Override
public ExpressionList<T> startsWith(String propertyName, String value) {
return exprList.startsWith(propertyName, value);
}
@Override
public ExpressionList<T> where() {
return exprList.where();
}
@@ -6,18 +6,15 @@ import com.avaje.ebean.config.PstmtDelegate;
import com.avaje.ebeaninternal.server.lib.sql.ExtendedPreparedStatement;
/**
* Implementation of PstmtDelegate from Ebean's own
* DataSource.
*
* @author rbygrave
* Implementation of PstmtDelegate from Ebean's own DataSource.
*/
public class StandardPstmtDelegate implements PstmtDelegate {
/**
* Unwrap the PreparedStatement from Ebean's DataSource implementation.
*/
public PreparedStatement unwrap(PreparedStatement pstmt) {
return ((ExtendedPreparedStatement)pstmt).getDelegate();
}
/**
* Unwrap the PreparedStatement from Ebean's DataSource implementation.
*/
public PreparedStatement unwrap(PreparedStatement pstmt) {
return ((ExtendedPreparedStatement) pstmt).getDelegate();
}
}
@@ -31,9 +31,9 @@ public final class DaemonThreadPool extends ThreadPoolExecutor {
* the time in seconds allowed for the pool to shutdown nicely. After
* this the pool is forced to shutdown.
*/
public DaemonThreadPool(int coreSize, long keepAliveSecs, int shutdownWaitSeconds, String namePrefix) {
public DaemonThreadPool(int coreSize, int maximumPoolSize, long keepAliveSecs, int shutdownWaitSeconds, String namePrefix) {
super(coreSize, coreSize, keepAliveSecs, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DaemonThreadFactory(namePrefix));
super(coreSize, maximumPoolSize, keepAliveSecs, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DaemonThreadFactory(namePrefix));
allowCoreThreadTimeOut(true);
this.shutdownWaitSeconds = shutdownWaitSeconds;
this.namePrefix = namePrefix;
@@ -1,5 +1,11 @@
package com.avaje.ebeaninternal.server.lib;
import com.avaje.ebean.common.BootupEbeanManager;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
@@ -7,14 +13,6 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.common.BootupEbeanManager;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
/**
* Manages the shutdown of the JVM Runtime.
* <p>
@@ -33,12 +31,9 @@ public final class ShutdownManager {
static BootupEbeanManager serverFactory;
static boolean whyShutdown;
static {
// Register the Shutdown hook
registerShutdownHook();
whyShutdown = GlobalProperties.getBoolean("debug.shutdown.why",false);
}
/**
@@ -118,19 +113,11 @@ public final class ShutdownManager {
logger.debug("Shutting down");
}
if (whyShutdown) {
try {
throw new RuntimeException("debug.shutdown.why=true ...");
} catch (Throwable e) {
logger.warn("Stacktrace showing why shutdown was fired", e);
}
}
stopping = true;
deregisterShutdownHook();
String shutdownRunner = GlobalProperties.get("system.shutdown.runnable", null);
String shutdownRunner = System.getProperty("ebean.shutdown.runnable");
if (shutdownRunner != null) {
try {
// A custom runnable executed at the start of shutdown
@@ -157,7 +144,7 @@ public final class ShutdownManager {
}
}
if (GlobalProperties.getBoolean("datasource.deregisterAllDrivers", false)) {
if ("true".equalsIgnoreCase(System.getProperty("ebean.datasource.deregisterAllDrivers", "false"))) {
deregisterAllJdbcDrivers();
}
}
@@ -1,13 +1,11 @@
package com.avaje.ebeaninternal.server.lib.sql;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.server.lib.util.MailEvent;
import com.avaje.ebeaninternal.server.lib.util.MailListener;
import com.avaje.ebeaninternal.server.lib.util.MailMessage;
import com.avaje.ebeaninternal.server.lib.util.MailSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A simple smtp email alert that sends a email message on dataSourceDown and
@@ -23,7 +21,11 @@ public class SimpleDataSourceAlert implements DataSourceAlert, MailListener {
private static final Logger logger = LoggerFactory.getLogger(SimpleDataSourceAlert.class);
// boolean sendInBackGround = true;
private static String alertMailServerName = System.getProperty("ebean.datasource.alert.mailserver");
private static String fromUser = System.getProperty("ebean.datasource.alert.fromUser");
private static String fromEmail = System.getProperty("ebean.datasource.alert.fromEmail");
private static String toEmail = System.getProperty("ebean.datasource.alert.toEmail");
/**
* Create a SimpleAlerter.
@@ -79,15 +81,10 @@ public class SimpleDataSourceAlert implements DataSourceAlert, MailListener {
private void sendMessage(String subject, String msg) {
String mailServerName = GlobalProperties.get("datasource.alert.mailserver", null);
if (mailServerName == null) {
if (alertMailServerName == null) {
return;
}
String fromUser = GlobalProperties.get("datasource.alert.fromuser", null);
String fromEmail = GlobalProperties.get("datasource.alert.fromemail", null);
String toEmail = GlobalProperties.get("datasource.alert.toemail", null);
MailMessage data = new MailMessage();
data.setSender(fromUser, fromEmail);
data.addBodyLine(msg);
@@ -95,15 +92,15 @@ public class SimpleDataSourceAlert implements DataSourceAlert, MailListener {
String[] toList = toEmail.split(",");
if (toList.length == 0) {
throw new RuntimeException("alert.toemail has not been set?");
logger.error("alert.toemail has not been set?");
} else {
for (int i = 0; i < toList.length; i++) {
data.addRecipient(null, toList[i].trim());
}
MailSender sender = new MailSender(alertMailServerName);
sender.setMailListener(this);
sender.sendInBackground(data);
}
for (int i = 0; i < toList.length; i++) {
data.addRecipient(null, toList[i].trim());
}
MailSender sender = new MailSender(mailServerName);
sender.setMailListener(this);
sender.sendInBackground(data);
}
}
@@ -1,278 +0,0 @@
package com.avaje.ebeaninternal.server.lib.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A thread that belongs to a ThreadPool. It will return to the Threadpool when
* it has finished its assigned task.
*/
public class PooledThread implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(PooledThread.class);
/**
* Flag to indicate that the thread was interrupted.
*/
private boolean wasInterrupted;
/**
* The time the thread was last used.
*/
private long lastUsedTime;
/**
* The work to run
*/
private Work work;
/**
* Set to indicate the thread is stopping.
*/
private boolean isStopping;
/**
* Set when the thread has stopped.
*/
private boolean isStopped;
/**
* The background thread.
*/
private Thread thread;
/**
* The pool this worker belongs to.
*/
private ThreadPool threadPool;
/**
* The name of the Thread
*/
private String name;
/**
* The thread synchronization object.
*/
private Object threadMonitor = new Object();
/**
* The monitor for work notification.
*/
private Object workMonitor = new Object();
/**
* Total number of work performed.
*/
private int totalWorkCount;
/**
* Total work execution time.
*/
private long totalWorkExecutionTime;
/**
* Create the PooledThread.
*/
protected PooledThread(ThreadPool threadPool, String name, boolean isDaemon, Integer threadPriority) {
this.name = name;
this.threadPool = threadPool;
this.lastUsedTime = System.currentTimeMillis();
thread = new Thread(this, name);
thread.setDaemon(isDaemon);
if (threadPriority != null) {
thread.setPriority(threadPriority.intValue());
}
}
public String toString() {
return name;
}
protected void start() {
thread.start();
}
/**
* Assign work to this thread. The thread will notify the listener when it has
* finished the work.
*/
public boolean assignWork(Work work) {
synchronized (workMonitor) {
this.work = work;
workMonitor.notifyAll();
}
return true;
}
/**
* process any assigned work until stopped or interrupted.
*/
public void run() {
// process assigned work until we receive a shutdown signal
synchronized (workMonitor) {
while (!isStopping) {
try {
if (work == null) {
workMonitor.wait();
}
} catch (InterruptedException e) {
}
doTheWork();
}
}
// Tell stop() we have shut ourselves down successfully
synchronized (threadMonitor) {
threadMonitor.notifyAll();
}
if (logger.isTraceEnabled()) {
logger.trace("PooledThread [" + getName() + "] finished ");
}
isStopped = true;
}
/**
* Actually do the work and gather the appropriate measures.
*/
private void doTheWork() {
if (isStopping) {
return;
}
long startTime = System.currentTimeMillis();
if (work == null) {
// probably shutting down the thread
} else {
try {
if (logger.isTraceEnabled()) {
logger.trace("start work "+work);
}
work.setStartTime(startTime);
work.getRunnable().run();
if (logger.isTraceEnabled()) {
logger.trace("finished work "+work);
}
} catch (Throwable ex) {
logger.error(null, ex);
if (wasInterrupted) {
this.isStopping = true;
threadPool.removeThread(this);
if (logger.isInfoEnabled()) {
logger.info("PooledThread [" + name + "] removed due to interrupt");
}
try {
thread.interrupt();
} catch (Exception e) {
logger.error("Error interrupting PooledThead[" + name + "]", e);
}
return;
}
}
}
lastUsedTime = System.currentTimeMillis();
totalWorkCount++;
totalWorkExecutionTime = totalWorkExecutionTime + lastUsedTime - startTime;
this.work = null;
threadPool.returnThread(this);
}
/**
* Try to interrupt the thread.
* <p>
* If the Thread was interrupted then it will be removed from the pool.
* </p>
*/
public void interrupt() {
// set a flag so doTheWork knows that it was interrupted
// and removes rather than returns
wasInterrupted = true;
try {
if (logger.isTraceEnabled()) {
logger.trace("interrupt()");
}
thread.interrupt();
} catch (SecurityException ex) {
wasInterrupted = false;
throw ex;
}
}
/**
* Returns true if the thread has finished.
*/
public boolean isStopped() {
return isStopped;
}
/**
* Stop the thread relatively nicely. It will wait a maximum of 10 seconds for
* it to complete any existing work.
*/
protected void stop() {
isStopping = true;
synchronized (threadMonitor) {
assignWork(null);
if (logger.isTraceEnabled()) {
logger.trace("stopping thread ["+name+"]");
}
try {
threadMonitor.wait(10000);
} catch (InterruptedException e) {
;
}
}
thread = null;
threadPool.removeThread(this);
}
/**
* return the name of the thread.
*/
public String getName() {
return name;
}
/**
* Returns the currently executing work, otherwise null.
*/
public Work getWork() {
return work;
}
/**
* The total number of jobs this thread has run.
*/
public int getTotalWorkCount() {
return totalWorkCount;
}
/**
* The total time for performing all assigned work.
*/
public long getTotalWorkExecutionTime() {
return totalWorkExecutionTime;
}
/**
* Returns the time this thread was last used.
*/
public long getLastUsedTime() {
return lastUsedTime;
}
}
@@ -1,502 +0,0 @@
package com.avaje.ebeaninternal.server.lib.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.config.GlobalProperties;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
/**
* This is a pool of threads which can be assigned work.
* <p>
* The Pool will automatically grow as required up to its maximum pool size. The
* Pool will be automatically shrink by trimming threads that have been idle for
* some time.
* </p>
*/
public class ThreadPool {
private static final Logger logger = LoggerFactory.getLogger(ThreadPool.class);
/**
* The max idle time used to trim idle threads from the pool.
*/
private long maxIdleTime;
/**
* The name of the pool
*/
private String poolName;
/**
* The initial pool size.
*/
private int minSize;
/**
* Whether or not the threads are going to be Daemon threads.
*/
private boolean isDaemon;
/**
* The priority or the threads. Can be null, in which case the threads have
* the default priority.
*/
private Integer threadPriority;
/**
* Incrementing int for thread name. NB: currentThreadCount will go up and
* down as the pool grows and shrinks.
*/
private int uniqueThreadID;
/**
* The maximum number of threads to grow to. Hitting this limit will have
* performance ramifications.
*/
private int maxSize = 100;
/**
* Flag that the pool should terminate all the threads and stop.
*/
private boolean stopThePool;
/**
* Flag to indicate that the pool is being shutdown.
*/
private boolean isStopping;
/**
* List of PooledThread that are free for work.
*/
private Vector<PooledThread> freeList = new Vector<PooledThread>();
/**
* List of PooledThread that are busy.
*/
private Vector<PooledThread> busyList = new Vector<PooledThread>();
/**
* List holding queued work.
*/
private Vector<Work> workOverflowQueue = new Vector<Work>();
/**
* Create and return a ThreadPool reading configuration from GlobalProperties.
*/
public static ThreadPool createThreadPool(String poolName) {
int min = GlobalProperties.getInt("threadpool." + poolName + ".min", 0);
int max = GlobalProperties.getInt("threadpool." + poolName + ".max", 100);
int defaultIdleTime = GlobalProperties.getInt("threadpool.idletime", 60);
long idle = 1000 * GlobalProperties.getInt("threadpool." + poolName + ".idletime", defaultIdleTime);
Integer priority = null;
String threadPriority = GlobalProperties.get("threadpool." + poolName + ".priority", null);
if (threadPriority != null) {
priority = new Integer(threadPriority);
}
return new ThreadPool(poolName, true, priority, min, max, idle);
}
public ThreadPool(String poolName, boolean isDaemon, Integer threadPriority, int minSize, int maxSize, long maxIdleMillis) {
this(poolName, isDaemon, threadPriority);
this.minSize = minSize;
this.maxSize = maxSize;
this.maxIdleTime = maxIdleMillis;
}
/**
* Create the ThreadPool.
*/
public ThreadPool(String poolName, boolean isDaemon, Integer threadPriority) {
this.poolName = poolName;
this.isDaemon = isDaemon;
this.threadPriority = threadPriority;
}
/**
* Return true if the pool is shutting down.
*/
public boolean isStopping() {
synchronized (freeList) {
return isStopping;
}
}
/**
* Return the name of the thread pool.
*/
public String getName() {
return poolName;
}
/**
* Set the minimum size the pool should try to maintain.
*/
public void setMinSize(int minSize) {
if (minSize > 0) {
if (minSize > maxSize) {
this.maxSize = minSize;
}
this.minSize = minSize;
maintainPoolSize();
}
}
/**
* Return the minimum size the pool should maintain.
*/
public int getMinSize() {
return minSize;
}
/**
* Set the maximum size the pool should grow to.
*/
public void setMaxSize(int maxSize) {
if (maxSize > 0) {
if (minSize > maxSize) {
minSize = maxSize;
}
this.maxSize = maxSize;
maintainPoolSize();
}
}
/**
* Return the maximum size this pool can grow to.
*/
public int getMaxSize() {
return maxSize;
}
/**
* Return the total number of busy and free threads in the pool.
*/
public int size() {
return busyList.size() + freeList.size();
}
/**
* Return the number of currently busy threads.
*/
public int getBusyCount() {
return busyList.size();
}
/**
* Assign a task to the thread pool, specifing the options to wait or queue
* the task if the pool is fully busy and can't grow.
* <p>
* When the pool is fully busy...
* </p>
* <p>
* addToQueue=true -> work is added to queue, returns false<br>
* addToQueue=false -> work is not done or queued, returns false<br>
* </p>
*
* @param work
* the runnable work to do.
* @param addToQueueIfFull
* If the pool is maxed out and this is true then it queues the
* Runnable.
*/
public boolean assign(Runnable work, boolean addToQueueIfFull) {
if (stopThePool) {
throw new RuntimeException("Pool is stopping... no more work please.");
}
Work runWork = new Work(work);
// get the next available thread in the pool (block)
PooledThread thread = getNextAvailableThread();
if (thread != null) {
// assign the work to that thread
busyList.add(thread);
thread.assignWork(runWork);
return true;
} else {
if (addToQueueIfFull) {
runWork.setEnterQueueTime(System.currentTimeMillis());
workOverflowQueue.add(runWork);
}
return false;
}
}
/**
* Remove the thread from the pool. The thread should be stopped before it is
* removed.
*/
protected void removeThread(PooledThread thread) {
synchronized (freeList) {
busyList.remove(thread);
freeList.remove(thread);
freeList.notify();
if (logger.isTraceEnabled()) {
logger.trace("PooledThread stopped [" + getName() + "]");
}
}
}
/**
* fired when a Thread from the pool has finished, and can be put back into
* the pool.
*/
protected void returnThread(PooledThread thread) {
synchronized (freeList) {
// deregister from the busyList
busyList.remove(thread);
if (!workOverflowQueue.isEmpty()) {
// get the first bit of work off the queue
Work queuedWork = (Work) workOverflowQueue.remove(0);
// work out the queue time and counts etc
queuedWork.setExitQueueTime(System.currentTimeMillis());
busyList.add(thread);
thread.assignWork(queuedWork);
} else {
if (logger.isTraceEnabled()) {
logger.trace("returnThread - add to freeList [" + thread + "]");
}
// put the thread back onto the available list
freeList.add(thread);
// tell shutdown() one has returned
freeList.notify();
}
}
}
/**
* Get the next available thread. Block until thread is available. NB: The
* dispatcher is blocked but work can still be assigned to the dispatcher in a
* non-blocking way
*/
private PooledThread getNextAvailableThread() {
synchronized (freeList) {
if (!freeList.isEmpty()) {
return (PooledThread) freeList.remove(0);
}
if (size() < maxSize) {
return growPool(true);
}
return null;
}
}
/**
* Return an Iterator of PooledThread that are currently running. You should
* only use this for display. Use the getPooledThread() or interrupt() methods
* to interrupt a particular thread.
*
* @return an Iterator of busy PooledThread's.
*/
public Iterator<PooledThread> getBusyThreads() {
synchronized (freeList) {
return busyList.iterator();
}
}
/**
* Shutdown the threadpool stopping all the threads. This will wait until any
* busy threads have finished their assigned work.
*/
public void shutdown() {
synchronized (freeList) {
if (isStopping) {
logger.debug("already shutting down");
}
isStopping = true;
if (logger.isDebugEnabled()) {
logger.debug("ThreadPool [" + poolName + "] Shutting down; threadCount[" + size()+ "] busyCount[" + getBusyCount() + "]");
}
stopThePool = true;
while (!freeList.isEmpty()) {
PooledThread thread = (PooledThread) freeList.remove(0);
thread.stop();
}
try {
while (getBusyCount() > 0) {
String msg = "ThreadPool [" + poolName + "] has [" + getBusyCount()+ "] busy threads, waiting for those to finish.";
logger.info(msg);
Iterator<PooledThread> busyThreads = getBusyThreads();
while (busyThreads.hasNext()) {
PooledThread busyThread = (PooledThread) busyThreads.next();
String busymsg = "Busy thread [" + busyThread.getName() + "] work["+ busyThread.getWork() + "]";
logger.info(busymsg);
}
freeList.wait();
PooledThread thread = (PooledThread) freeList.remove(0);
logger.debug("wait finished on thread[" + thread.getName() + "]");
if (thread != null) {
thread.stop();
}
}
} catch (InterruptedException e) {
logger.error("Error during threadpool shutdown", e);
}
}
}
/**
* Trim or grow the pool leaving at least min free.
*/
protected void maintainPoolSize() {
synchronized (freeList) {
if (isStopping) {
// don't bother as the pool is shutting down
return;
}
int numToStop = size() - minSize;
if (numToStop > 0) {
// should trim idle threads as we are over the minSize
long usedAfter = System.currentTimeMillis() - maxIdleTime;
ArrayList<PooledThread> stopList = new ArrayList<PooledThread>();
Iterator<PooledThread> it = freeList.iterator();
while (it.hasNext() && numToStop > 0) {
PooledThread thread = (PooledThread) it.next();
if (thread.getLastUsedTime() < usedAfter) {
stopList.add(thread);
numToStop--;
}
}
Iterator<PooledThread> stopIt = stopList.iterator();
while (stopIt.hasNext()) {
PooledThread thread = (PooledThread) stopIt.next();
if (logger.isDebugEnabled()) {
logger.debug("trimming pool - stopping thread "+thread);
}
thread.stop();
}
}
int numToAdd = minSize - size();
if (numToAdd > 0) {
// should add some more to the pool
for (int i = 0; i < numToAdd; i++) {
growPool(false);
}
}
}
}
/**
* Interrupt a named thread that is currently busy.
* <p>
* Returns the thread that was interrupted or null if the thread was not
* found. If the thread was interrupted then it will automatically be stopped
* and removed from the pool.
* </p>
* <p>
* Note that it may take some time to actually interrupt the thread so an
* immediate test to see if the thread stopped will probably be wrong.
*
* <pre>
* <code>
* ThreadPool test = ThreadPoolManager.getThreadPool("test");
* PooledThread pt = test.interrupt("test.1");
* if (pt == null) {
* // the thread was not found, perhaps finished?
* } else {
* // give interrupt a little time to execute
* Thread.sleep(1000);
* boolean hasStopped = pt.isStopped();
* //..
* }
* </code>
* </pre>
*
* </p>
*
* @return the thread that was interrupted
*/
public PooledThread interrupt(String threadName) {
PooledThread thread = getBusyThread(threadName);
if (thread != null) {
thread.interrupt();
return thread;
}
return null;
}
/**
* Find a thread using its name from the busy list. Returns null if the thread
* is not found in the busy list.
*/
public PooledThread getBusyThread(String threadName) {
synchronized (freeList) {
Iterator<PooledThread> it = getBusyThreads();
while (it.hasNext()) {
PooledThread pt = (PooledThread) it.next();
if (pt.getName().equals(threadName)) {
return pt;
}
}
return null;
}
}
/**
* Grow the pool with the option of either putting it on the available list,
* or returning it.
*/
private PooledThread growPool(boolean andReturn) {
synchronized (freeList) {
String threadName = poolName + "." + uniqueThreadID++;
PooledThread bgw = new PooledThread(this, threadName, isDaemon, threadPriority);
bgw.start();
if (logger.isDebugEnabled()) {
logger.debug("ThreadPool grow created [" + threadName + "] size[" + size() + "]");
}
if (andReturn) {
return bgw;
} else {
freeList.add(bgw);
return null;
}
}
}
/**
* Return the maximum amount of time in millis that Threads can be idle before
* they are trimmed.
*/
public long getMaxIdleTime() {
return maxIdleTime;
}
/**
* Set the maxiumium amount of time in millis that Threads can be idle before
* they are trimed.
*/
public void setMaxIdleTime(long maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
}
@@ -1,96 +0,0 @@
package com.avaje.ebeaninternal.server.lib.thread;
/**
* Used internally by the ThreadPool to wrap a Runnable that is
* going to be run.
*
* <p>Used to maintains some useful times about the Runnable in terms of when
* it was queued and then eventually run.</p>
*/
public class Work {
/**
* Create a Runnable Work.
*/
public Work(Runnable runnable) {
this.runnable = runnable;
}
/**
* Return the associated Runnable object.
*/
public Runnable getRunnable() {
return runnable;
}
/**
* Return the time this work actually started.
*/
public long getStartTime() {
return startTime;
}
/**
* Sets the time this work actually started.
*/
public void setStartTime(long startTime) {
this.startTime = startTime;
}
/**
* Return the time this entered the queue.
*/
public long getEnterQueueTime() {
return enterQueueTime;
}
/**
* Set the time this entered the queue.
*/
public void setEnterQueueTime(long enterQueueTime) {
this.enterQueueTime = enterQueueTime;
}
/**
* Return the time this left the queue.
*/
public long getExitQueueTime() {
return exitQueueTime;
}
/**
* Set the time this work left the queue.
*/
public void setExitQueueTime(long exitQueueTime) {
this.exitQueueTime = exitQueueTime;
}
/**
* The same as getDescription().
*/
public String toString() {
return getDescription();
}
/**
* Return a description of this work.
*/
public String getDescription() {
StringBuffer sb = new StringBuffer();
sb.append("Work[");
if (runnable != null){
sb.append(runnable.toString());
}
sb.append("]");
return sb.toString();
}
private Runnable runnable;
private long exitQueueTime;
private long enterQueueTime;
private long startTime;
};
@@ -1,12 +0,0 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>AvajeLib</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
ThreadPool service.
<P>
Service providing thread pooling for executing background tasks.
</P>
</Body>
</HTML>

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