Compare commits

..
Author SHA1 Message Date
rbygrave e937d36ca2 [maven-release-plugin] prepare release avaje-ebeanorm-3.4.2 2015-06-19 20:09:51 +12:00
rbygrave 32b7c1a755 Change tests to be more robust 2015-06-19 20:01:00 +12:00
Rob Bygrave c766793f2e Merge pull request #272 from rlamarche/develop-3.3
Backport fix for #154 - @OrderBy on a @OneToMany property is not used when lazy loading
2015-05-08 09:29:28 +12:00
Rob Bygraveandromlam b30b456dbd Fix for #154 - @OrderBy on a @OneToMany property is not used when lazy loading
Conflicts:
	src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java
	src/test/java/com/avaje/tests/query/orderby/TestOrderByWithMany.java
2015-04-07 14:09:19 +02:00
Rob Bygrave 6bc86efed7 [maven-release-plugin] prepare for next development iteration 2014-05-28 00:23:27 +12:00
Rob Bygrave 5c447511c1 [maven-release-plugin] prepare release avaje-ebeanorm-3.4.1 2014-05-28 00:23:04 +12:00
Rob Bygrave f91789886c Backport of fix to 3.x for #131 - Memory issue (Hard refs) when using findIterate 2014-05-28 00:21:57 +12:00
Rob Bygrave 7d14ede1b9 [maven-release-plugin] prepare for next development iteration 2014-05-22 20:02:42 +12:00
Rob Bygrave 1ba8bc4557 [maven-release-plugin] prepare release avaje-ebeanorm-3.3.4 2014-05-22 20:02:26 +12:00
Rob Bygrave a234bd1201 Merge pull request #129 from jroper/java16-compat
Backport to JDK 6
2014-05-22 19:36:32 +12:00
James Roper 1c0c0893b6 Backport to JDK 6
Replaced all uses of ju.Objects.hashCode and ju.Objects.equals with a
copy of their implementations inlined into the code.

Also changed source/target for compiler plugin.

To test, I compiled my own avaje launchagent against 6, and changed to
use that in the pom, compiled/tested the whole project using JDK 7,
then ran mvn surefire:test using JDK 6 - running surefire:test ensures
that mvn doesn't try to recompile everything against 6, since that's not
possible because of some of the delegate classes having delegate methods
to JDK 7 jdbc classes.
2014-05-22 15:20:21 +10:00
Rob Bygrave ebbc560ae8 [maven-release-plugin] prepare for next development iteration 2014-05-19 01:03:43 +12:00
Rob Bygrave fcb9df05ad [maven-release-plugin] prepare release avaje-ebeanorm-3.3.3 2014-05-19 01:03:31 +12:00
Rob Bygrave 3de79b211a Change version in pom to 3.3.3-SNAPSHOT in preparation for release 2014-05-19 01:02:13 +12:00
Rob Bygrave d93952a920 Backport of fix for #56 - Using findIterate with MySQL streams 2014-05-19 00:59:05 +12:00
3611 changed files with 139351 additions and 247221 deletions
-12
View File
@@ -1,12 +0,0 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
spaces_around_operators = true
-19
View File
@@ -1,19 +0,0 @@
GITHUB ISSUES ARE STRICTLY CONTROLLED FOR THIS PROJECT.
Refer to http://ebean-orm.github.io/support for the policies controlling the use of github issues.
Please post issues to the Ebean group https://groups.google.com/forum/#!forum/ebean first.
## Expected behavior
## Actual behavior
### Steps to reproduce
```java
// some java code
```
```console
// some logging output
```
+2 -13
View File
@@ -1,22 +1,11 @@
*.autofetch
*create-all.sql
*drop-all.sql
*.orig
*.sql
.classpath
.project
.settings/
target/
logs/
log/
ebean-autotune.xml
ebean-profiling*.xml
/db
/mydb.db
!src/test/ddl-review/*.sql
profiling/
# Intellij project files
*.iml
*.ipr
*.iws
.idea/
.idea
-33
View File
@@ -1,33 +0,0 @@
dist: trusty
sudo: required
language: java
jdk:
- oraclejdk8
git:
depth: 3
addons:
postgresql: "9.6"
services:
- postgresql
before_script:
- ./.travis/setup_database
env:
- EBEAN_DB=h2
- EBEAN_DB=pg
install: true
script:
- mvn clean test
after_failure:
- ./.travis/print_surefire_reports
cache:
directories:
- $HOME/.m2
-10
View File
@@ -1,10 +0,0 @@
#!/bin/sh
echo "\n=== SUREFIRE REPORTS ===\n"
for file in target/surefire-reports/*.txt
do
echo ${file}
cat ${file}
echo
done
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh
echo "\n=== SETUP DATABASE ===\n"
#set -e
#set -x
# Setup postgres' users and databases
sudo -u postgres psql -c "CREATE USER unit WITH PASSWORD 'unit';"
sudo -u postgres psql -c 'CREATE DATABASE unit WITH OWNER unit;'
sudo -u postgres psql unit -c 'CREATE EXTENSION hstore;'
sudo -u postgres psql unit -c 'CREATE EXTENSION pgcrypto;'
#sudo -u postgres psql test2 -c 'CREATE EXTENSION postgis;'
+12 -15
View File
@@ -1,17 +1,14 @@
# Need help?
Post questions or issues to the Ebean google group - https://groups.google.com/forum/#!forum/ebean
avaje-ebeanorm
==============
# Documentation
Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
## Maven cental links:
[Maven central - ebean](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.ebean%22%20AND%20a%3A%22ebean%22 "maven central ebean")
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
## Current versions
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
Main EbeanORM artifact
Maven Dependency
----------------
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>3.3.2</version>
</dependency>
-152
View File
@@ -1,152 +0,0 @@
This is a short description how to run tests
============================================
Run tests from maven
--------------------
Just type `mvn test` - it will run the whole test suite with the settings
from `src/test/resources/ebean.properties`.
Run tests from eclipse
----------------------
Running tests requires, that the classes are enhanced. By default, the
`BaseTestCase` tries to load the enhancer-agent in a very early stage. This
also works for most test cases, but it does not work if you try to run a
complete test-suite, as classes that are loaded before the enhancer-agent
kicks in will not get enhanced. To solve this problem, you need either the
[Eclipse-Enhancer-Plugin](https://github.com/ebean-orm-tools/ebean-eclipse-enhancer)
or add the agent to your JVM arguments (Preferences->Installed JREs) or to
your run configuration:
`-javaagent:<USER_HOME>/.m2/repository/io/ebean/ebean-agent/<VERSION>/ebean-agent-<VERSION>.jar=debug=0`
Platform Tests
==============
By default, the `h2` platform is used. To test a different platform, add
`-Ddatasource.default=xxx` to your maven command or JVM arguments.
'h2' platform
-------------
Reqires no setup
run: `mvn clean test`
Current status: PASS
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
'pg' platform
-------------
Reqirements
- a locally installed PostgreSQL server with PostGis.
- Create a user "unit" with password "unit" and a database "unit"
- Give the user all permissions to that db
run: `mvn clean test -Ddatasource.default=pg`
Current status: PASS
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
'mysql' platform
-------------
Reqirements
- a locally installed MySQL server.
- Create a user "unit" with password "unit" and a database "unit"
- Give the user all permissions to that db
run: `mvn clean test -Ddatasource.default=mysql`
Current status: FAIL
Tests run: 1986, Failures: 2, Errors: 4, Skipped: 16
'sqlserver' platform
--------------------
Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-server-linux/
- Create a user "ebean" with password "ebean" and a database "ebean_unittest"
- Adjust the connection string and/or username in ebean.properties
run: `mvn clean test -Ddatasource.default=mssql`
Current status: FAIL
Tests run: 1986, Failures: 19, Errors: 43, Skipped: 16
'oracle' platform
-----------------
Reqires a locally installed Oracle server.
- Create a user "unit" with password "unit" with all permissions
- The test wirtes directly to the "xe" SID - so do not use a productive server!
- As the Oracle JDBC driver is only available on an oracle repository,
you need a special maven setup as described [here](http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010)
run: `mvn clean test -Ddatasource.default=ora -Poracle`
Current status: FAIL
Tests run: 1986, Failures: 17, Errors: 34, Skipped: 16
'db2' platform
--------------
Reqires a locally installed DB2 Express-C server.
- Create a user "unit" with password "unit" with all permissions
- Install the db2jcc4 driver (see pom.xml)
run: `mvn clean test -Ddatasource.default=db2 -Pdb2`
Current status: FAIL
Tests run: 1986, Failures: 33, Errors: 26, Skipped: 16
'sqlite' platform
-----------------
Reqires no setup
run: `mvn clean test -Ddatasource.default=sqlite`
Current status: FAIL
Tests run: 1986, Failures: 24, Errors: 86, Skipped: 16
After some time, the db locks up, every test takes 3 seconds and fails with
"the database file is locked"
'hsqldb' platform
-----------------
Reqires no setup
run: `mvn clean test -Ddatasource.default=hsqldb`
Current status: FAIL
Tests run: 1980, Failures: 3, Errors: 657, Skipped: 16
@Rob FYI
The problem is https://sourceforge.net/p/hsqldb/bugs/1364/
In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
'id' in lowercase. Changing that to uppercase gives me this result:
`Tests run: 1986, Failures: 2, Errors: 18, Skipped: 16`
(as quick hack I added temporary a toUpperCase to BindableIdScalar.getIdentityColumn)
'sqlanywhere' platform
----------------------
TODO: not yet tested
+104 -252
View File
@@ -3,155 +3,52 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>java8-parent</artifactId>
<version>1.3</version>
<artifactId>avaje-javaparent</artifactId>
<version>1.2</version>
</parent>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.6.1</version>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>3.4.2</version>
<packaging>jar</packaging>
<name>ebean</name>
<url>http://ebean-orm.github.io/</url>
<name>avaje-ebeanorm</name>
<url>http://www.avaje.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>rbygrave</id>
<name>Rob Bygrave</name>
<email>robin.bygrave@gmail.com</email>
</developer>
</developers>
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.6.1</tag>
<connection>scm:git:https://github.com/ebean-orm/avaje-ebeanorm.git</connection>
<developerConnection>scm:git:https://github.com/ebean-orm/avaje-ebeanorm.git</developerConnection>
<url>https://github.com/ebean-orm/avaje-ebeanorm.git</url>
</scm>
<profiles>
<profile>
<!-- Note: to use this profile, you need credentials for the oracle repository in your
settings.xml as described here:
http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010 -->
<id>oracle</id>
<dependencies>
<dependency>
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven.oracle.com</id>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>https://maven.oracle.com</url>
<layout>default</layout>
</repository>
</repositories>
</profile>
<profile>
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
After that, install it into your local maven repository:
mvn install:install-file \
-Dfile=db2jcc4.jar \
-DgroupId=com.ibm.jdbc \
-DartifactId=db2jcc4 \
-Dversion=4.23.42 \
-Dpackaging=jar
-->
<id>db2</id>
<dependencies>
<dependency>
<groupId>com.ibm.jdbc</groupId>
<artifactId>db2jcc4</artifactId>
<version>4.23.42</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
<dependencies>
<!--
Class retention Nonnull and Nullable annotations
to assist with IDE auto-completion with Ebean API
-->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jsr305</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>2.2.1</version>
<version>1.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>[1.7.1,1.7.99)</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.6</version>
</dependency>
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
@@ -159,145 +56,153 @@
<scope>provided</scope>
</dependency>
<!-- provided scope to read validation annotations Size etc -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
<version>1.0.0.GA</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.7</version>
<version>1.6</version>
<scope>provided</scope>
</dependency>
<!-- Provided scope for Postgres JSON/JSONB support -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.2</version>
<scope>provided</scope>
</dependency>
<!-- Test scope -->
<!--<dependency>-->
<!--<groupId>oracle</groupId>-->
<!--<artifactId>oracle-jdbc</artifactId>-->
<!--<version>7.0</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>microsoft</groupId>-->
<!--<artifactId>sqlserver-jdbc</artifactId>-->
<!--<version>4.2</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
<version>2.1.2</version>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.5.1</version>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>3.2.2</version>
<scope>test</scope>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
and not require a separate module for it -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<scope>provided</scope>
<version>1.3.153</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.15.1</version>
<version>3.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.1.3.jre8-preview</version>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- Do not upgrade to 6.0.6: https://bugs.mysql.com/bug.php?id=82896 -->
<version>5.1.43</version>
<version>5.1.27</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje.composite</groupId>
<artifactId>avaje-composite-testing</artifactId>
<version>1.1</version>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.9</version>
<scope>test</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje.moduuid</groupId>
<artifactId>avaje-moduuid</artifactId>
<version>2.1</version>
<version>2.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Enhance the meta beans -->
<build>
<plugins>
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.5.1</version>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>3.3.2</version>
<executions>
<execution>
<id>main</id>
<phase>process-classes</phase>
<configuration>
<classSource>target/classes</classSource>
<packages>com.avaje.ebean.meta.**</packages>
<transformArgs>debug=1</transformArgs>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
<execution>
<id>test</id>
<phase>process-test-classes</phase>
<configuration>
<classSource>target/test-classes</classSource>
<packages>com.avaje.tests.**</packages>
<transformArgs>debug=1</transformArgs>
</configuration>
<goals>
<goal>testEnhance</goal>
<goal>enhance</goal>
</goals>
</execution>
</executions>
@@ -315,62 +220,9 @@
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<systemProperties>
<property>
<!-- transfer datasource.default parameter -->
<name>datasource.default</name>
<value>${datasource.default}</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<doctitle>Ebean 10</doctitle>
<overview>src/main/java/io/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>io.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>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -0,0 +1,137 @@
package com.avaje.ebean;
/**
* Administrative control of Autofetch during runtime.
*/
public interface AdminAutofetch {
/**
* Return true if profiling is enabled.
*/
public boolean isProfiling();
/**
* Set to true to enable profiling.
*/
public void setProfiling(boolean enable);
/**
* Return true if autoFetch automatic query tuning is enabled.
*/
public boolean isQueryTuning();
/**
* Set to true to enable autoFetch automatic query tuning.
*/
public void setQueryTuning(boolean enable);
/**
* Returns the rate which profiling is collected. This is an int between 0 and
* 100.
*/
public double getProfilingRate();
/**
* Set the rate at which profiling is collected after the base.
*
* @param rate
* a int between 0 and 100.
*/
public void setProfilingRate(double rate);
/**
* Return the number of queries profiled after which profiling is collected at
* a percentage rate.
*/
public int getProfilingBase();
/**
* Set a base number of queries to profile per query point.
* <p>
* After this amount of profiling has been obtained profiling is collected at
* the Profiling Percentage rate.
* </p>
*/
public void setProfilingBase(int profilingBase);
/**
* Return the minimum number of queries profiled before autoFetch will start
* automatically tuning the queries.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
public int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before autoFetch
* will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
public void setProfilingMin(int autoFetchMinThreshold);
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather the usage profiling information.
*/
public String collectUsageViaGC();
/**
* This will take the current profiling information and update the "tuned
* query detail".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
*
* @return a summary of the updates that occurred
*/
public String updateTunedQueryInfo();
/**
* Clear all the tuned query info.
* <p>
* Should only need do this for testing and playing around.
* </p>
*
* @return the amount of tuned query information cleared.
*/
public int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
*
* @return the amount of profiled information cleared.
*/
public int clearProfilingInfo();
/**
* Clear the query execution statistics.
*/
public void clearQueryStatistics();
/**
* Return the number of queries tuned by AutoFetch.
*/
public int getTotalTunedQueryCount();
/**
* Return the size of the TuneQuery map.
*/
public int getTotalTunedQuerySize();
/**
* Return the size of the profile map.
*/
public int getTotalProfileSize();
}
@@ -1,40 +1,40 @@
package io.ebean;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Background thread pool service for executing of tasks asynchronously.
* <p>
* This service is used internally by Ebean for executing background tasks such
* as the {@link Query#findFutureList()} and also for executing background tasks
* periodically.
* </p>
* <p>
* This service has been made available so you can use it for your application
* code if you want. It can be useful for some server caching implementations
* (background population and trimming of the cache etc).
* </p>
*
* @author rbygrave
*/
public interface BackgroundExecutor {
/**
* Execute a task in the background.
*/
void execute(Runnable r);
/**
* Execute a task periodically with a fixed delay between each execution.
* <p>
* For example, execute a runnable every minute.
* </p>
* <p>
* The delay is the time between executions no matter how long the task took.
* That is, this method has the same behaviour characteristics as
* {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)}
* </p>
*/
void executePeriodically(Runnable r, long delay, TimeUnit unit);
}
package com.avaje.ebean;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Background thread pool service for executing of tasks asynchronously.
* <p>
* This service is used internally by Ebean for executing background tasks such
* as the {@link Query#findFutureList()} and also for executing background tasks
* periodically.
* </p>
* <p>
* This service has been made available so you can use it for your application
* code if you want. It can be useful for some server caching implementations
* (background population and trimming of the cache etc).
* </p>
*
* @author rbygrave
*/
public interface BackgroundExecutor {
/**
* Execute a task in the background.
*/
public void execute(Runnable r);
/**
* Execute a task periodically with a fixed delay between each execution.
* <p>
* For example, execute a runnable every minute.
* </p>
* <p>
* The delay is the time between executions no matter how long the task took.
* That is, this method has the same behaviour characteristics as
* {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)}
* </p>
*/
public void executePeriodically(Runnable r, long delay, TimeUnit unit);
}
@@ -0,0 +1,94 @@
package com.avaje.ebean;
import java.beans.PropertyChangeListener;
import java.util.Set;
/**
* Provides access to the internal state of an entity bean.
*/
public interface BeanState {
/**
* Return true if this is a lazy loading reference bean.
* <p>
* If so the this bean only holds the Id property and will invoke lazy loading
* if any other property is get or set.
* </p>
*/
public boolean isReference();
/**
* Return true if the bean is new (and not yet saved).
*/
public boolean isNew();
/**
* Return true if the bean is new or dirty (and probably needs to be saved).
*/
public boolean isNewOrDirty();
/**
* Return true if the bean has been changed but not yet saved.
*/
public boolean isDirty();
/**
* For partially populated beans returns the properties that are loaded on the
* bean.
* <p>
* Accessing another property will cause lazy loading to occur.
* </p>
*/
public Set<String> getLoadedProps();
/**
* Return the set of changed properties.
*/
public Set<String> getChangedProps();
/**
* Return true if the bean is readOnly.
* <p>
* If a setter is called on a readOnly bean it will throw an exception.
* </p>
*/
public boolean isReadOnly();
/**
* Set the readOnly status for the bean.
*/
public void setReadOnly(boolean readOnly);
/**
* Add a propertyChangeListener.
*/
public void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Remove a propertyChangeListener.
*/
public void removePropertyChangeListener(PropertyChangeListener listener);
/**
* Advanced - Used to programmatically build a reference object.
* <p>
* You can create a new EntityBean (
* {@link EbeanServer#createEntityBean(Class)}, set its Id property and then
* call this setReference() method.
* </p>
*/
public void setReference();
/**
* Advanced - Used to programmatically build a partially or fully loaded
* entity bean. First create an entity bean via
* {@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(Set<String> loadedProperties);
}
@@ -0,0 +1,194 @@
package com.avaje.ebean;
import java.sql.CallableStatement;
import java.sql.SQLException;
/**
* For making calls to stored procedures. Refer to the Ebean execute() method.
* <p>
* Note that UpdateSql is designed for general DML sql and CallableSql is
* designed for use with stored procedures. Also note that when using this in
* batch mode the out parameters are not read.
* </p>
* <p>
* Example 1:
* </p>
*
* <pre class="code">
* String sql = &quot;{call sp_order_mod(?,?)}&quot;;
*
* CallableSql cs = Ebean.createCallableSql(sql);
* cs.setParameter(1, &quot;turbo&quot;);
* cs.registerOut(2, Types.INTEGER);
*
* Ebean.execute(cs);
*
* // read the out parameter
* Integer returnValue = (Integer) cs.getObject(2);
* </pre>
*
* <p>
* Example 2:<br>
* Includes batch mode, table modification information and label. Note that the
* label is really only to help people reading the transaction logs to identify
* the procedure called etc.
* </p>
*
* <pre class="code">
* String sql = &quot;{call sp_insert_order(?,?)}&quot;;
*
* CallableSql cs = Ebean.createCallableSql(sql);
*
* // Inform Ebean this stored procedure inserts into the
* // oe_order table and inserts + updates the oe_order_detail table.
* // this is used to invalidate objects in the cache
* cs.addModification(&quot;oe_order&quot;, true, false, false);
* cs.addModification(&quot;oe_order_detail&quot;, true, true, false);
*
* Transaction t = Ebean.startTransaction();
*
* // execute using JDBC batching 10 statements at a time
* t.setBatchMode(true);
* t.setBatchSize(10);
* try {
* cs.setParameter(1, &quot;Was&quot;);
* cs.setParameter(2, &quot;Banana&quot;);
* Ebean.execute(cs);
*
* cs.setParameter(1, &quot;Here&quot;);
* cs.setParameter(2, &quot;Kumera&quot;);
* Ebean.execute(cs);
*
* cs.setParameter(1, &quot;More&quot;);
* cs.setParameter(2, &quot;Apple&quot;);
* Ebean.execute(cs);
*
* // Ebean.externalModification(&quot;oe_order&quot;,true,false,false);
* // Ebean.externalModification(&quot;oe_order_detail&quot;,true,true,false);
* Ebean.commitTransaction();
*
* } finally {
* Ebean.endTransaction();
* }
* </pre>
*
* @see com.avaje.ebean.SqlUpdate
* @see com.avaje.ebean.Ebean#execute(CallableSql)
*/
public interface CallableSql {
/**
* Return the label that is put into the transaction log.
*/
public String getLabel();
/**
* Set the label that is put in the transaction log.
*/
public CallableSql setLabel(String label);
/**
* Return the statement execution timeout.
*/
public int getTimeout();
/**
* Return the callable sql.
*/
public String getSql();
/**
* Set the statement execution timeout. Zero implies unlimited time.
* <p>
* This is set to the underlying CallableStatement.
* </p>
*/
public CallableSql setTimeout(int secs);
/**
* Set the callable sql.
*/
public CallableSql setSql(String sql);
/**
* Bind a parameter that is bound as a IN parameter.
* <p>
* position starts at value 1 (not 0) to be consistent with CallableStatement.
* </p>
* <p>
* This is designed so that you do not need to set params in index order. You
* can set/register param 2 before param 1 etc.
* </p>
*
* @param position
* the index position of the parameter.
* @param value
* the value of the parameter.
*/
public CallableSql bind(int position, Object value);
/**
* Bind a positioned parameter (same as bind method).
*
* @param position
* the index position of the parameter.
* @param value
* the value of the parameter.
*/
public CallableSql setParameter(int position, Object value);
/**
* Register an OUT parameter.
* <p>
* Note that position starts at value 1 (not 0) to be consistent with
* CallableStatement.
* </p>
* <p>
* This is designed so that you do not need to register params in index order.
* You can set/register param 2 before param 1 etc.
* </p>
*
* @param position
* the index position of the parameter (starts with 1).
* @param type
* the jdbc type of the OUT parameter that will be read.
*/
public CallableSql registerOut(int position, int type);
/**
* Return an OUT parameter value.
* <p>
* position starts at value 1 (not 0) to be consistent with CallableStatement.
* </p>
* <p>
* This can only be called after the CallableSql has been executed. When run
* in batch mode you effectively can't use this method.
* </p>
*/
public Object getObject(int position);
/**
*
* You can extend this object and override this method for more advanced
* stored procedure calls. This would be the case when ResultSets are returned
* etc.
*/
public boolean executeOverride(CallableStatement cstmt) throws SQLException;
/**
* Add table modification information to the TransactionEvent.
* <p>
* This would be similar to using the
* <code>Ebean.externalModification()</code> method. It may be easier and make
* more sense to set it here with the CallableSql.
* </p>
* <p>
* For UpdateSql the table modification information is derived by parsing the
* sql to determine the table name and whether it was an insert, update or
* delete.
* </p>
*/
public CallableSql addModification(String tableName, boolean inserts, boolean updates,
boolean deletes);
}
@@ -0,0 +1,96 @@
package com.avaje.ebean;
import java.util.ArrayList;
import javax.persistence.PersistenceException;
import com.avaje.ebean.RawSql.ColumnMapping;
/**
* Parses columnMapping (select clause) mapping columns to bean properties.
*/
final class DRawSqlColumnsParser {
private final int end;
private final String sqlSelect;
private int pos;
private int indexPos;
public static ColumnMapping parse(String sqlSelect) {
return new DRawSqlColumnsParser(sqlSelect).parse();
}
private DRawSqlColumnsParser(String sqlSelect) {
this.sqlSelect = sqlSelect;
this.end = sqlSelect.length();
}
private ColumnMapping parse() {
ArrayList<ColumnMapping.Column> columns = new ArrayList<ColumnMapping.Column>();
while (pos <= end) {
ColumnMapping.Column c = nextColumnInfo();
columns.add(c);
}
return new ColumnMapping(columns);
}
private ColumnMapping.Column nextColumnInfo() {
int start = pos;
nextComma();
String colInfo = sqlSelect.substring(start, pos++);
colInfo = colInfo.trim();
String[] split = colInfo.split(" ");
if (split.length > 1) {
ArrayList<String> tmp = new ArrayList<String>(split.length);
for (int i = 0; i < split.length; i++) {
if (split[i].trim().length() > 0) {
tmp.add(split[i].trim());
}
}
split = tmp.toArray(new String[tmp.size()]);
}
if (split.length == 0) {
throw new PersistenceException("Huh? Not expecting length=0 when parsing column " + colInfo);
}
if (split.length == 1) {
// default to column the same name as the property
return new ColumnMapping.Column(indexPos++, split[0], null);
}
if (split.length == 2) {
return new ColumnMapping.Column(indexPos++, split[0], split[1]);
}
// Ok, we now expect/require the AS keyword and it should be the
// second to last word in the colInfo content
if (!split[split.length - 2].equalsIgnoreCase("as")) {
throw new PersistenceException("Expecting AS keyword as second to last word when parsing column " + colInfo);
}
// build back the 'column formula' that precedes the AS keyword
StringBuilder sb = new StringBuilder();
sb.append(split[0]);
for (int i = 1; i < split.length-2; i++) {
sb.append(" ").append(split[i]);
}
return new ColumnMapping.Column(indexPos++, sb.toString(), split[split.length - 1]);
}
private int nextComma() {
boolean inQuote = false;
while (pos < end) {
char c = sqlSelect.charAt(pos);
if (c == '\'') {
inQuote = !inQuote;
} else if (!inQuote && c == ',') {
return pos;
}
pos++;
}
return pos;
}
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.rawsql;
package com.avaje.ebean;
import io.ebeaninternal.server.rawsql.SpiRawSql.Sql;
import io.ebeaninternal.server.querydefn.SimpleTextParser;
import com.avaje.ebean.RawSql.Sql;
/**
* Parses sql-select queries to try and determine the location where WHERE and
@@ -9,13 +8,15 @@ import io.ebeaninternal.server.querydefn.SimpleTextParser;
*/
class DRawSqlParser {
private static final String $_AND_HAVING = "${andHaving}";
public static final String $_AND_HAVING = "${andHaving}";
private static final String $_HAVING = "${having}";
public static final String $_HAVING = "${having}";
private static final String $_AND_WHERE = "${andWhere}";
public static final String $_AND_WHERE = "${andWhere}";
private static final String $_WHERE = "${where}";
public static final String $_WHERE = "${where}";
private static final String ORDER_BY = "order by";
private final SimpleTextParser textParser;
@@ -25,7 +26,7 @@ class DRawSqlParser {
private int placeHolderAndWhere;
private int placeHolderHaving;
private int placeHolderAndHaving;
private final boolean hasPlaceHolders;
private boolean hasPlaceHolders;
private int selectPos = -1;
private int distinctPos = -1;
@@ -34,7 +35,6 @@ 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;
@@ -47,7 +47,6 @@ class DRawSqlParser {
private DRawSqlParser(String sqlString) {
sqlString = sqlString.trim();
sqlString = sqlString.replace('\n', ' ');
this.sql = sqlString;
this.hasPlaceHolders = findAndRemovePlaceHolders();
this.textParser = new SimpleTextParser(sqlString);
@@ -67,12 +66,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, preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd,
orderBySql, (distinctPos > -1));
}
/**
@@ -100,7 +99,19 @@ class DRawSqlParser {
}
private boolean hasPlaceHolders() {
return placeHolderWhere > -1 || placeHolderAndWhere > -1 || placeHolderHaving > -1 || placeHolderAndHaving > -1;
if (placeHolderWhere > -1) {
return true;
}
if (placeHolderAndWhere > -1) {
return true;
}
if (placeHolderHaving > -1) {
return true;
}
if (placeHolderAndHaving > -1) {
return true;
}
return false;
}
/**
@@ -122,7 +133,8 @@ class DRawSqlParser {
// trim of distinct keyword
String distinct = preWhereExprSql.substring(0, 9);
if (!distinct.equalsIgnoreCase("distinct ")) {
throw new RuntimeException("Expecting [" + preWhereExprSql + "] to start with \"select distinct\"");
throw new RuntimeException("Expecting [" + preWhereExprSql
+ "] to start with \"select distinct\"");
}
preWhereExprSql = preWhereExprSql.substring(9);
}
@@ -130,12 +142,12 @@ class DRawSqlParser {
return preWhereExprSql;
}
private String findOrderByPrefixSql() {
return (orderByPos < 1) ? null : sql.substring(orderByPos, orderByStmtPos);
}
private String findOrderBySql() {
return (orderByStmtPos < 1) ? null : sql.substring(orderByStmtPos).trim();
if (orderByPos > -1) {
int pos = orderByPos + ORDER_BY.length();
return sql.substring(pos).trim();
}
return null;
}
private String findPreHavingSql() {
@@ -214,11 +226,6 @@ 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() {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
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.ServerConfig;
import com.avaje.ebean.util.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates EbeanServer instances.
* <p>
* This uses either a ServerConfig or properties in the ebean.properties file to
* configure and create a EbeanServer instance.
* </p>
* <p>
* The EbeanServer instance can either be registered with the Ebean singleton or
* not. The Ebean singleton effectively holds a map of EbeanServers by a name.
* If the EbeanServer is registered with the Ebean singleton you can retrieve it
* later via {@link Ebean#getServer(String)}.
* </p>
* <p>
* One EbeanServer can be nominated as the 'default/primary' EbeanServer. Many
* methods on the Ebean singleton such as {@link Ebean#find(Class)} are just a
* convenient way of using the 'default/primary' EbeanServer.
* </p>
*
* @author Rob Bygrave
*
*/
public class EbeanServerFactory {
private static final Logger logger = LoggerFactory.getLogger(EbeanServerFactory.class);
private static BootupEbeanManager serverFactory = createServerFactory();
/**
* Create using ebean.properties to configure the server.
*/
public static EbeanServer create(String name) {
EbeanServer server = serverFactory.createServer(name);
return server;
}
/**
* Create using the ServerConfig object to configure the server.
*/
public static EbeanServer create(ServerConfig config) {
if (config.getName() == null) {
throw new PersistenceException("The name is null (it is required)");
}
EbeanServer server = serverFactory.createServer(config);
if (config.isDefaultServer()) {
GlobalProperties.setSkipPrimaryServer(true);
}
if (config.isRegister()) {
Ebean.register(server, config.isDefaultServer());
}
return server;
}
private static BootupEbeanManager createServerFactory() {
// String d___ =
// com.avaje.ebean.server.core.DefaultServerFactory.class.getName();
String dflt = "com.avaje.ebeaninternal.server.core.DefaultServerFactory";
String implClassName = GlobalProperties.get("ebean.serverfactory", dflt);
int delaySecs = GlobalProperties.getInt("ebean.start.delay", 0);
if (delaySecs > 0) {
try {
// perhaps useful to delay the startup to give time to
// attach a debugger when running in a server like tomcat.
String m = "Ebean sleeping " + delaySecs + " seconds due to ebean.start.delay";
logger.info(m);
Thread.sleep(delaySecs * 1000);
} catch (InterruptedException e) {
String m = "Interrupting debug.start.delay of " + delaySecs;
logger.error(m, e);
}
}
try {
// use a client side implementation?
return (BootupEbeanManager) ClassUtil.newInstance(implClassName);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
@@ -0,0 +1,93 @@
package com.avaje.ebean;
/**
* Query by Example expression.
* <p>
* Pass in an example entity and for each non-null scalar properties an
* expression is added.
* </p>
* <p>
* By Default this case sensitive, will ignore numeric zero values and will use
* a Like for string values (you must put in your own wildcards).
* </p>
* <p>
* To get control over the options you can create an ExampleExpression and set
* those options such as case insensitive etc.
* </p>
*
* <pre class="code">
* // create an example bean and set the properties
* // with the query parameters you want
* Customer example = new Customer();
* example.setName(&quot;Rob%&quot;);
* example.setNotes(&quot;%something%&quot;);
*
* List&lt;Customer&gt; list =
* Ebean.find(Customer.class)
* .where()
* // pass the bean into the where() clause
* .exampleLike(example)
* // you can add other expressions to the same query
* .gt(&quot;id&quot;, 2)
* .findList();
*
* </pre>
*
* Similarly you can create an ExampleExpression
*
* <pre>
* Customer example = new Customer();
* example.setName(&quot;Rob%&quot;);
* example.setNotes(&quot;%something%&quot;);
*
* // create a ExampleExpression with more control
* ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO)
* .includeZeros();
*
* List&lt;Customer&gt; list =
* Ebean.find(Customer.class)
* .where()
* .add(qbe)
* .findList();
* </pre>
*
* @author Rob Bygrave
*/
public interface ExampleExpression extends Expression {
/**
* By calling this method zero value properties are going to be included in
* the expression.
* <p>
* By default numeric zero values are excluded as they can result from
* primitive int and long types.
* </p>
*/
public ExampleExpression includeZeros();
/**
* Set case insensitive to true.
*/
public ExampleExpression caseInsensitive();
/**
* Use startsWith expression for string properties.
*/
public ExampleExpression useStartsWith();
/**
* Use contains expression for string properties.
*/
public ExampleExpression useContains();
/**
* Use endsWith expression for string properties.
*/
public ExampleExpression useEndsWith();
/**
* Use equal to expression for string properties.
*/
public ExampleExpression useEqualTo();
}
@@ -1,333 +1,322 @@
package io.ebean;
import java.util.Collection;
import java.util.Map;
/**
* Expression factory for creating standard expressions for WHERE and HAVING
* clauses.
* <p>
* Generally you will only need to use this object for creating OR, JUNCTION or
* CONJUNCTION expressions. To create simple expressions you will most likely
* just use the methods on the ExpressionList object that is returned via
* {@link Query#where()}.
* </p>
* <p>
* This provides a convenient way to create expressions for the 'Default'
* server. It is actually a short cut for using the ExpressionFactory of the
* 'default' EbeanServer.
* <p>
* See also {@link Ebean#getExpressionFactory()}
* </p>
* <p>
* Creates standard common expressions for using in a Query Where or Having
* clause.
* </p>
* <pre>{@code
*
* // Example: Using an Expr.or() method
* Query<Order> query = Ebean.createQuery(Order.class);
* query.where(
* Expr.or(Expr.eq("status", Order.NEW),
* Expr.gt("orderDate", lastWeek));
*
* List<Order> list = query.findList();
* ...
* }</pre>
*
* @see Query#where()
*/
public class Expr {
private Expr() {
}
/**
* Equal To - property equal to the given value.
*/
public static Expression eq(String propertyName, Object value) {
return Ebean.getExpressionFactory().eq(propertyName, value);
}
/**
* Not Equal To - property not equal to the given value.
*/
public static Expression ne(String propertyName, Object value) {
return Ebean.getExpressionFactory().ne(propertyName, value);
}
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
public static Expression ieq(String propertyName, String value) {
return Ebean.getExpressionFactory().ieq(propertyName, value);
}
/**
* Between - property between the two given values.
*/
public static Expression between(String propertyName, Object value1, Object value2) {
return Ebean.getExpressionFactory().between(propertyName, value1, value2);
}
/**
* Between - value between two given properties.
*/
public static Expression between(String lowProperty, String highProperty, Object value) {
return Ebean.getExpressionFactory().betweenProperties(lowProperty, highProperty, value);
}
/**
* Greater Than - property greater than the given value.
*/
public static Expression gt(String propertyName, Object value) {
return Ebean.getExpressionFactory().gt(propertyName, value);
}
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public static Expression ge(String propertyName, Object value) {
return Ebean.getExpressionFactory().ge(propertyName, value);
}
/**
* Less Than - property less than the given value.
*/
public static Expression lt(String propertyName, Object value) {
return Ebean.getExpressionFactory().lt(propertyName, value);
}
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public static Expression le(String propertyName, Object value) {
return Ebean.getExpressionFactory().le(propertyName, value);
}
/**
* Is Null - property is null.
*/
public static Expression isNull(String propertyName) {
return Ebean.getExpressionFactory().isNull(propertyName);
}
/**
* Is Not Null - property is not null.
*/
public static Expression isNotNull(String propertyName) {
return Ebean.getExpressionFactory().isNotNull(propertyName);
}
/**
* Case insensitive {@link #exampleLike(Object)}
*/
public static ExampleExpression iexampleLike(Object example) {
return Ebean.getExpressionFactory().iexampleLike(example);
}
/**
* Create the query by Example expression which is case sensitive and using
* LikeType.RAW (you need to add you own wildcards % and _).
*/
public static ExampleExpression exampleLike(Object example) {
return Ebean.getExpressionFactory().exampleLike(example);
}
/**
* Create the query by Example expression specifying more options.
*/
public static ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType) {
return Ebean.getExpressionFactory().exampleLike(example, caseInsensitive, likeType);
}
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
public static Expression like(String propertyName, String value) {
return Ebean.getExpressionFactory().like(propertyName, value);
}
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
public static Expression ilike(String propertyName, String value) {
return Ebean.getExpressionFactory().ilike(propertyName, value);
}
/**
* Starts With - property like value%.
*/
public static Expression startsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().startsWith(propertyName, value);
}
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
public static Expression istartsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().istartsWith(propertyName, value);
}
/**
* Ends With - property like %value.
*/
public static Expression endsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().endsWith(propertyName, value);
}
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
public static Expression iendsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().iendsWith(propertyName, value);
}
/**
* Contains - property like %value%.
*/
public static Expression contains(String propertyName, String value) {
return Ebean.getExpressionFactory().contains(propertyName, value);
}
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
public static Expression icontains(String propertyName, String value) {
return Ebean.getExpressionFactory().icontains(propertyName, value);
}
/**
* For collection properties that are empty (have not existing elements).
*/
public static Expression isEmpty(String propertyName) {
return Ebean.getExpressionFactory().isEmpty(propertyName);
}
/**
* For collection properties that are not empty (have existing elements).
*/
public static Expression isNotEmpty(String propertyName) {
return Ebean.getExpressionFactory().isNotEmpty(propertyName);
}
/**
* In - property has a value in the array of values.
*/
public static Expression in(String propertyName, Object[] values) {
return Ebean.getExpressionFactory().in(propertyName, values);
}
/**
* In - using a subQuery.
*/
public static Expression in(String propertyName, Query<?> subQuery) {
return Ebean.getExpressionFactory().in(propertyName, subQuery);
}
/**
* In - property has a value in the collection of values.
*/
public static Expression in(String propertyName, Collection<?> values) {
return Ebean.getExpressionFactory().in(propertyName, values);
}
/**
* Id Equal to - ID property is equal to the value.
*/
public static Expression idEq(Object value) {
return Ebean.getExpressionFactory().idEq(value);
}
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap a map keyed by property names.
*/
public static Expression allEq(Map<String, Object> propertyMap) {
return Ebean.getExpressionFactory().allEq(propertyMap);
}
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
*/
public static Expression raw(String raw, Object value) {
return Ebean.getExpressionFactory().raw(raw, value);
}
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
*/
public static Expression raw(String raw, Object[] values) {
return Ebean.getExpressionFactory().raw(raw, values);
}
/**
* Add raw expression with no parameters.
*/
public static Expression raw(String raw) {
return Ebean.getExpressionFactory().raw(raw);
}
/**
* And - join two expressions with a logical and.
*/
public static Expression and(Expression expOne, Expression expTwo) {
return Ebean.getExpressionFactory().and(expOne, expTwo);
}
/**
* Or - join two expressions with a logical or.
*/
public static Expression or(Expression expOne, Expression expTwo) {
return Ebean.getExpressionFactory().or(expOne, expTwo);
}
/**
* Negate the expression (prefix it with NOT).
*/
public static Expression not(Expression exp) {
return Ebean.getExpressionFactory().not(exp);
}
/**
* Return a list of expressions that will be joined by AND's.
*/
public static <T> Junction<T> conjunction(Query<T> query) {
return Ebean.getExpressionFactory().conjunction(query);
}
/**
* Return a list of expressions that will be joined by OR's.
*/
public static <T> Junction<T> disjunction(Query<T> query) {
return Ebean.getExpressionFactory().disjunction(query);
}
}
package com.avaje.ebean;
import java.util.Collection;
import java.util.Map;
/**
* Expression factory for creating standard expressions for WHERE and HAVING
* clauses.
* <p>
* Generally you will only need to use this object for creating OR, JUNCTION or
* CONJUNCTION expressions. To create simple expressions you will most likely
* just use the methods on the ExpressionList object that is returned via
* {@link Query#where()}.
* </p>
* <p>
* This provides a convenient way to create expressions for the 'Default'
* server. It is actually a short cut for using the ExpressionFactory of the
* 'default' EbeanServer.
* <p>
* See also {@link Ebean#getExpressionFactory()}
* </p>
* <p>
* Creates standard common expressions for using in a Query Where or Having
* clause.
* </p>
*
* <pre class="code">
* // Example: Using an Expr.or() method
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* query.where(
* Expr.or(Expr.eq(&quot;status&quot;, Order.NEW),
* Expr.gt(&quot;orderDate&quot;, lastWeek));
*
* List&lt;Order&gt; list = query.findList();
* ...
* </pre>
*
* @see Query#where()
* @author Rob Bygrave
*/
public class Expr {
private Expr() {
}
/**
* Equal To - property equal to the given value.
*/
public static Expression eq(String propertyName, Object value) {
return Ebean.getExpressionFactory().eq(propertyName, value);
}
/**
* Not Equal To - property not equal to the given value.
*/
public static Expression ne(String propertyName, Object value) {
return Ebean.getExpressionFactory().ne(propertyName, value);
}
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
public static Expression ieq(String propertyName, String value) {
return Ebean.getExpressionFactory().ieq(propertyName, value);
}
/**
* Between - property between the two given values.
*/
public static Expression between(String propertyName, Object value1, Object value2) {
return Ebean.getExpressionFactory().between(propertyName, value1, value2);
}
/**
* Between - value between two given properties.
*/
public static Expression between(String lowProperty, String highProperty, Object value) {
return Ebean.getExpressionFactory().betweenProperties(lowProperty, highProperty, value);
}
/**
* Greater Than - property greater than the given value.
*/
public static Expression gt(String propertyName, Object value) {
return Ebean.getExpressionFactory().gt(propertyName, value);
}
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public static Expression ge(String propertyName, Object value) {
return Ebean.getExpressionFactory().ge(propertyName, value);
}
/**
* Less Than - property less than the given value.
*/
public static Expression lt(String propertyName, Object value) {
return Ebean.getExpressionFactory().lt(propertyName, value);
}
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public static Expression le(String propertyName, Object value) {
return Ebean.getExpressionFactory().le(propertyName, value);
}
/**
* Is Null - property is null.
*/
public static Expression isNull(String propertyName) {
return Ebean.getExpressionFactory().isNull(propertyName);
}
/**
* Is Not Null - property is not null.
*/
public static Expression isNotNull(String propertyName) {
return Ebean.getExpressionFactory().isNotNull(propertyName);
}
/**
* Case insensitive {@link #exampleLike(Object)}
*/
public static ExampleExpression iexampleLike(Object example) {
return Ebean.getExpressionFactory().iexampleLike(example);
}
/**
* Create the query by Example expression which is case sensitive and using
* LikeType.RAW (you need to add you own wildcards % and _).
*/
public static ExampleExpression exampleLike(Object example) {
return Ebean.getExpressionFactory().exampleLike(example);
}
/**
* Create the query by Example expression specifying more options.
*/
public static ExampleExpression exampleLike(Object example, boolean caseInsensitive,
LikeType likeType) {
return Ebean.getExpressionFactory().exampleLike(example, caseInsensitive, likeType);
}
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
public static Expression like(String propertyName, String value) {
return Ebean.getExpressionFactory().like(propertyName, value);
}
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
public static Expression ilike(String propertyName, String value) {
return Ebean.getExpressionFactory().ilike(propertyName, value);
}
/**
* Starts With - property like value%.
*/
public static Expression startsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().startsWith(propertyName, value);
}
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
public static Expression istartsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().istartsWith(propertyName, value);
}
/**
* Ends With - property like %value.
*/
public static Expression endsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().endsWith(propertyName, value);
}
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
public static Expression iendsWith(String propertyName, String value) {
return Ebean.getExpressionFactory().iendsWith(propertyName, value);
}
/**
* Contains - property like %value%.
*/
public static Expression contains(String propertyName, String value) {
return Ebean.getExpressionFactory().contains(propertyName, value);
}
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
public static Expression icontains(String propertyName, String value) {
return Ebean.getExpressionFactory().icontains(propertyName, value);
}
/**
* In - property has a value in the array of values.
*/
public static Expression in(String propertyName, Object[] values) {
return Ebean.getExpressionFactory().in(propertyName, values);
}
/**
* In - using a subQuery.
*/
public static Expression in(String propertyName, Query<?> subQuery) {
return Ebean.getExpressionFactory().in(propertyName, subQuery);
}
/**
* In - property has a value in the collection of values.
*/
public static Expression in(String propertyName, Collection<?> values) {
return Ebean.getExpressionFactory().in(propertyName, values);
}
/**
* Id Equal to - ID property is equal to the value.
*/
public static Expression idEq(Object value) {
return Ebean.getExpressionFactory().idEq(value);
}
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap
* a map keyed by property names.
*/
public static Expression allEq(Map<String, Object> propertyMap) {
return Ebean.getExpressionFactory().allEq(propertyMap);
}
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
*/
public static Expression raw(String raw, Object value) {
return Ebean.getExpressionFactory().raw(raw, value);
}
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
*/
public static Expression raw(String raw, Object[] values) {
return Ebean.getExpressionFactory().raw(raw, values);
}
/**
* Add raw expression with no parameters.
*/
public static Expression raw(String raw) {
return Ebean.getExpressionFactory().raw(raw);
}
/**
* And - join two expressions with a logical and.
*/
public static Expression and(Expression expOne, Expression expTwo) {
return Ebean.getExpressionFactory().and(expOne, expTwo);
}
/**
* Or - join two expressions with a logical or.
*/
public static Expression or(Expression expOne, Expression expTwo) {
return Ebean.getExpressionFactory().or(expOne, expTwo);
}
/**
* Negate the expression (prefix it with NOT).
*/
public static Expression not(Expression exp) {
return Ebean.getExpressionFactory().not(exp);
}
/**
* Return a list of expressions that will be joined by AND's.
*/
public static <T> Junction<T> conjunction(Query<T> query) {
return Ebean.getExpressionFactory().conjunction(query);
}
/**
* Return a list of expressions that will be joined by OR's.
*/
public static <T> Junction<T> disjunction(Query<T> query) {
return Ebean.getExpressionFactory().disjunction(query);
}
}
@@ -0,0 +1,10 @@
package com.avaje.ebean;
import java.io.Serializable;
/**
* An expression that is part of a WHERE or HAVING clause.
*/
public interface Expression extends Serializable {
}
@@ -0,0 +1,252 @@
package com.avaje.ebean;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Expression factory for creating standard expressions.
* <p>
* Creates standard common expressions for using in a Query Where or Having
* clause.
* </p>
* <p>
* You will often not use this class directly but instead just add expressions
* via the methods on ExpressionList such as
* {@link ExpressionList#gt(String, Object)}.
* </p>
* <p>
* The ExpressionList is returned from {@link Query#where()}.
* </p>
*
* <pre class="code">
* // Example: fetch orders where status equals new or orderDate > lastWeek.
*
* Expression newOrLastWeek =
* Expr.or(Expr.eq(&quot;status&quot;, Order.Status.NEW),
* Expr.gt(&quot;orderDate&quot;, lastWeek));
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* query.where().add(newOrLastWeek);
* List&lt;Order&gt; list = query.findList();
* ...
* </pre>
*
* @see Query#where()
*/
public interface ExpressionFactory {
/**
* Equal To - property equal to the given value.
*/
public Expression eq(String propertyName, Object value);
/**
* Not Equal To - property not equal to the given value.
*/
public Expression ne(String propertyName, Object value);
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
public Expression ieq(String propertyName, String value);
/**
* Between - property between the two given values.
*/
public Expression between(String propertyName, Object value1, Object value2);
/**
* Between - value between two given properties.
*/
public Expression betweenProperties(String lowProperty, String highProperty, Object value);
/**
* Greater Than - property greater than the given value.
*/
public Expression gt(String propertyName, Object value);
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public Expression ge(String propertyName, Object value);
/**
* Less Than - property less than the given value.
*/
public Expression lt(String propertyName, Object value);
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public Expression le(String propertyName, Object value);
/**
* Is Null - property is null.
*/
public Expression isNull(String propertyName);
/**
* Is Not Null - property is not null.
*/
public Expression isNotNull(String propertyName);
/**
* Case insensitive {@link #exampleLike(Object)}
*/
public ExampleExpression iexampleLike(Object example);
/**
* Create the query by Example expression which is case sensitive and using
* LikeType.RAW (you need to add you own wildcards % and _).
*/
public ExampleExpression exampleLike(Object example);
/**
* Create the query by Example expression specifying more options.
*/
public ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType);
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
public Expression like(String propertyName, String value);
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
public Expression ilike(String propertyName, String value);
/**
* Starts With - property like value%.
*/
public Expression startsWith(String propertyName, String value);
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
public Expression istartsWith(String propertyName, String value);
/**
* Ends With - property like %value.
*/
public Expression endsWith(String propertyName, String value);
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
public Expression iendsWith(String propertyName, String value);
/**
* Contains - property like %value%.
*/
public Expression contains(String propertyName, String value);
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
public Expression icontains(String propertyName, String value);
/**
* In - property has a value in the array of values.
*/
public Expression in(String propertyName, Object[] values);
/**
* In - using a subQuery.
*/
public Expression in(String propertyName, Query<?> subQuery);
/**
* In - property has a value in the collection of values.
*/
public Expression in(String propertyName, Collection<?> values);
/**
* Id Equal to - ID property is equal to the value.
*/
public Expression idEq(Object value);
/**
* Id IN a list of Id values.
*/
public Expression idIn(List<?> idList);
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap
* a map keyed by property names.
*/
public Expression allEq(Map<String, Object> propertyMap);
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
*/
public Expression raw(String raw, Object value);
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
*/
public Expression raw(String raw, Object[] values);
/**
* Add raw expression with no parameters.
*/
public Expression raw(String raw);
/**
* And - join two expressions with a logical and.
*/
public Expression and(Expression expOne, Expression expTwo);
/**
* Or - join two expressions with a logical or.
*/
public Expression or(Expression expOne, Expression expTwo);
/**
* Negate the expression (prefix it with NOT).
*/
public Expression not(Expression exp);
/**
* Return a list of expressions that will be joined by AND's.
*/
public <T> Junction<T> conjunction(Query<T> query);
/**
* Return a list of expressions that will be joined by OR's.
*/
public <T> Junction<T> disjunction(Query<T> query);
/**
* Return a list of expressions that will be joined by AND's.
*/
public <T> Junction<T> conjunction(Query<T> query, ExpressionList<T> parent);
/**
* Return a list of expressions that will be joined by OR's.
*/
public <T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent);
}
@@ -0,0 +1,559 @@
package com.avaje.ebean;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* List of Expressions that make up a where or having clause.
* <p>
* An ExpressionList is returned from {@link Query#where()}.
* </p>
* <p>
* The ExpressionList has a list of convenience methods that create the standard
* expressions and add them to this list.
* </p>
* <p>
* The ExpressionList also duplicates methods that are found on the Query such
* as findList() and orderBy(). The purpose of these methods is provide a fluid
* API. The upside of this approach is that you can build and execute a query
* via chained methods. The down side is that this ExpressionList object has
* more methods than you would initially expect (the ones duplicated from
* Query).
* </p>
*
* @see Query#where()
*/
public interface ExpressionList<T> extends Serializable {
/**
* Return the query that owns this expression list.
* <p>
* This is a convenience method solely to support a fluid API where the
* methods are chained together. Adding expressions returns this expression
* list and this method can be used after that to return back the original
* query so that further things can be added to it.
* </p>
*/
public Query<T> query();
/**
* Set the order by clause replacing the existing order by clause if there is
* one.
* <p>
* This follows SQL syntax using commas between each property with the
* optional asc and desc keywords representing ascending and descending order
* respectively.
* </p>
* <p>
* This is EXACTLY the same as {@link #orderBy(String)}.
* </p>
*/
public Query<T> order(String orderByClause);
/**
* Return the OrderBy so that you can append an ascending or descending
* property to the order by clause.
* <p>
* This will never return a null. If no order by clause exists then an 'empty'
* OrderBy object is returned.
* </p>
*/
public OrderBy<T> order();
/**
* Return the OrderBy so that you can append an ascending or descending
* property to the order by clause.
* <p>
* This will never return a null. If no order by clause exists then an 'empty'
* OrderBy object is returned.
* </p>
*/
public OrderBy<T> orderBy();
/**
* Add an orderBy clause to the query.
*
* @see Query#orderBy(String)
*/
public Query<T> orderBy(String orderBy);
/**
* Add an orderBy clause to the query.
*
* @see Query#orderBy(String)
*/
public Query<T> setOrderBy(String orderBy);
/**
* Execute the query iterating over the results.
*
* @see Query#findIterate()
*/
public QueryIterator<T> findIterate();
/**
* Execute the query visiting the results.
*
* @see Query#findVisit(QueryResultVisitor)
*/
public void findVisit(QueryResultVisitor<T> visitor);
/**
* Execute the query returning a list.
*
* @see Query#findList()
*/
public List<T> findList();
/**
* Execute the query returning the list of Id's.
*
* @see Query#findIds()
*/
public List<Object> findIds();
/**
* Return the count of entities this query should return.
* <p>
* This is the number of 'top level' or 'root level' entities.
* </p>
*/
public int findRowCount();
/**
* Execute the query returning a set.
*
* @see Query#findSet()
*/
public Set<T> findSet();
/**
* Execute the query returning a map.
*
* @see Query#findMap()
*/
public Map<?, T> findMap();
/**
* Return a typed map specifying the key property and type.
*/
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType);
/**
* Execute the query returning a single bean.
*
* @see Query#findUnique()
*/
public T findUnique();
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the row count query
*/
public FutureRowCount<T> findFutureRowCount();
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list of Id's
*/
public FutureIds<T> findFutureIds();
/**
* Execute find list query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list result of the query
*/
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
*
*/
public PagingList<T> findPagingList(int pageSize);
public ExpressionList<T> filterMany(String prop);
/**
* Specify specific properties to fetch on the main/root bean (aka partial
* object).
*
* @see Query#select(String)
*/
public Query<T> select(String properties);
/**
* Specify a property (associated bean) to join and <em>fetch</em> including
* all its properties.
*
* @see Query#join(String)
*/
public Query<T> join(String assocProperties);
/**
* Specify a property (associated bean) to join and <em>fetch</em> with its
* specific properties to include (aka partial object).
*
* @see Query#join(String,String)
*/
public Query<T> join(String assocProperty, String assocProperties);
/**
* Set the first row to fetch.
*
* @see Query#setFirstRow(int)
*/
public Query<T> setFirstRow(int firstRow);
/**
* Set the maximum number of rows to fetch.
*
* @see Query#setMaxRows(int)
*/
public Query<T> setMaxRows(int maxRows);
/**
* Set the name of the property which values become the key of a map.
*
* @see Query#setMapKey(String)
*/
public Query<T> setMapKey(String mapKey);
/**
* Set to true to use the query for executing this query.
*
* @see Query#setUseCache(boolean)
*/
public Query<T> setUseCache(boolean useCache);
/**
* Add expressions to the having clause.
* <p>
* The having clause is only used for queries based on raw sql (via SqlSelect
* annotation etc).
* </p>
*/
public ExpressionList<T> having();
/**
* Add another expression to the where clause.
*/
public ExpressionList<T> where();
/**
* Add an Expression to the list.
* <p>
* This returns the list so that add() can be chained.
* </p>
*
* <pre class="code">
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
* query.where()
* .like(&quot;name&quot;,&quot;Rob%&quot;)
* .eq(&quot;status&quot;, Customer.ACTIVE);
* List&lt;Customer&gt; list = query.findList();
* ...
* </pre>
*/
public ExpressionList<T> add(Expression expr);
/**
* Add a list of Expressions to this ExpressionList.s
*/
public ExpressionList<T> addAll(ExpressionList<T> exprList);
/**
* Equal To - property is equal to a given value.
*/
public ExpressionList<T> eq(String propertyName, Object value);
/**
* Not Equal To - property not equal to the given value.
*/
public ExpressionList<T> ne(String propertyName, Object value);
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
public ExpressionList<T> ieq(String propertyName, String value);
/**
* Between - property between the two given values.
*/
public ExpressionList<T> between(String propertyName, Object value1, Object value2);
/**
* Between - value between the two properties.
*/
public ExpressionList<T> betweenProperties(String lowProperty, String highProperty, Object value);
/**
* Greater Than - property greater than the given value.
*/
public ExpressionList<T> gt(String propertyName, Object value);
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public ExpressionList<T> ge(String propertyName, Object value);
/**
* Less Than - property less than the given value.
*/
public ExpressionList<T> lt(String propertyName, Object value);
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public ExpressionList<T> le(String propertyName, Object value);
/**
* Is Null - property is null.
*/
public ExpressionList<T> isNull(String propertyName);
/**
* Is Not Null - property is not null.
*/
public ExpressionList<T> isNotNull(String propertyName);
/**
* A "Query By Example" type of expression.
* <p>
* Pass in an example entity and for each non-null scalar properties an
* expression is added.
* </p>
* <p>
* By Default this case sensitive, will ignore numeric zero values and will
* use a Like for string values (you must put in your own wildcards).
* </p>
* <p>
* To get control over the options you can create an ExampleExpression and set
* those options such as case insensitive etc.
* </p>
*
* <pre class="code">
* // create an example bean and set the properties
* // with the query parameters you want
* Customer example = new Customer();
* example.setName(&quot;Rob%&quot;);
* example.setNotes(&quot;%something%&quot;);
*
* List&lt;Customer&gt; list = Ebean.find(Customer.class).where()
* // pass the bean into the where() clause
* .exampleLike(example)
* // you can add other expressions to the same query
* .gt(&quot;id&quot;, 2).findList();
*
* </pre>
*
* Similarly you can create an ExampleExpression
*
* <pre>
* Customer example = new Customer();
* example.setName(&quot;Rob%&quot;);
* example.setNotes(&quot;%something%&quot;);
*
* // create a ExampleExpression with more control
* ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO).includeZeros();
*
* List&lt;Customer&gt; list = Ebean.find(Customer.class).where().add(qbe).findList();
* </pre>
*/
public ExpressionList<T> exampleLike(Object example);
/**
* Case insensitive version of {@link #exampleLike(Object)}
*/
public ExpressionList<T> iexampleLike(Object example);
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
public ExpressionList<T> like(String propertyName, String value);
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
public ExpressionList<T> ilike(String propertyName, String value);
/**
* Starts With - property like value%.
*/
public ExpressionList<T> startsWith(String propertyName, String value);
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
public ExpressionList<T> istartsWith(String propertyName, String value);
/**
* Ends With - property like %value.
*/
public ExpressionList<T> endsWith(String propertyName, String value);
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
public ExpressionList<T> iendsWith(String propertyName, String value);
/**
* Contains - property like %value%.
*/
public ExpressionList<T> contains(String propertyName, String value);
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
public ExpressionList<T> icontains(String propertyName, String value);
/**
* In - using a subQuery.
*/
public ExpressionList<T> in(String propertyName, Query<?> subQuery);
/**
* In - property has a value in the array of values.
*/
public ExpressionList<T> in(String propertyName, Object... values);
/**
* In - property has a value in the collection of values.
*/
public ExpressionList<T> in(String propertyName, Collection<?> values);
/**
* Id IN a list of id values.
*/
public ExpressionList<T> idIn(List<?> idValues);
/**
* Id Equal to - ID property is equal to the value.
*/
public ExpressionList<T> idEq(Object value);
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap
* a map keyed by property names.
*/
public ExpressionList<T> allEq(Map<String, Object> propertyMap);
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
* <p>
* When properties in the clause are fully qualified as table-column names
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
*/
public ExpressionList<T> raw(String raw, Object value);
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
* <p>
* When properties in the clause are fully qualified as table-column names
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
*/
public ExpressionList<T> raw(String raw, Object[] values);
/**
* Add raw expression with no parameters.
* <p>
* When properties in the clause are fully qualified as table-column names
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
*/
public ExpressionList<T> raw(String raw);
/**
* And - join two expressions with a logical and.
*/
public ExpressionList<T> and(Expression expOne, Expression expTwo);
/**
* Or - join two expressions with a logical or.
*/
public ExpressionList<T> or(Expression expOne, Expression expTwo);
/**
* Negate the expression (prefix it with NOT).
*/
public ExpressionList<T> not(Expression exp);
/**
* Return a list of expressions that will be joined by AND's.
*/
public Junction<T> conjunction();
/**
* Return a list of expressions that will be joined by OR's.
*/
public Junction<T> disjunction();
/**
* End a Conjunction or Disjunction returning the parent expression list.
* <p>
* Alternatively you can always use where() to return the top level expression
* list.
* </p>
*/
public ExpressionList<T> endJunction();
}
@@ -0,0 +1,252 @@
package com.avaje.ebean;
import java.io.Serializable;
/**
* Defines the configuration options for a "query fetch" or a
* "lazy loading fetch". This gives you the ability to use multiple smaller
* queries to populate an object graph as opposed to a single large query.
* <p>
* The primary goal is to provide efficient ways of loading complex object
* graphs avoiding SQL Cartesian product and issues around populating object
* graphs that have multiple *ToMany relationships.
* </p>
* <p>
* It also provides the ability to control the lazy loading queries (batch size,
* selected properties and fetches) to avoid N+1 queries etc.
* <p>
* There can also be cases loading across a single OneToMany where 2 SQL queries
* using Ebean FetchConfig.query() can be more efficient than one SQL query.
* When the "One" side is wide (lots of columns) and the cardinality difference
* is high (a lot of "Many" beans per "One" bean) then this can be more
* efficient loaded as 2 SQL queries.
* </p>
*
* <pre class="code">
* // Normal fetch join results in a single SQL query
* List&lt;Order&gt; list = Ebean.find(Order.class).fetch(&quot;details&quot;).findList();
*
* // Find Orders join details using a single SQL query
* </pre>
* <p>
* Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL
* queries
* </p>
*
* <pre class="code">
* // This will use 2 SQL queries to build this object graph
* List&lt;Order&gt; list =
* Ebean.find(Order.class)
* .fetch(&quot;details&quot;, new FetchConfig().query())
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
* </pre>
* <p>
* Example: Using 2 "query joins"
* </p>
*
* <pre class="code">
* // This will use 3 SQL queries to build this object graph
* List&lt;Order&gt; list =
* Ebean.find(Order.class)
* .fetch(&quot;details&quot;, new FetchConfig().query())
* .fetch(&quot;customer&quot;, new FetchConfig().queryFirst(5))
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
* // query 3) find customer where id in (?,?,?,?,?) // first 5 customers
* </pre>
* <p>
* Example: Using "query joins" and partial objects
* </p>
*
* <pre class="code">
* // This will use 3 SQL queries to build this object graph
* List&lt;Order&gt; list =
* Ebean.find(Order.class)
* .select(&quot;status, shipDate&quot;)
* .fetch(&quot;details&quot;, &quot;quantity, price&quot;, new FetchConfig().query())
* .fetch(&quot;details.product&quot;, &quot;sku, name&quot;)
* .fetch(&quot;customer&quot;, &quot;name&quot;, new FetchConfig().queryFirst(5))
* .fetch(&quot;customer.contacts&quot;)
* .fetch(&quot;customer.shippingAddress&quot;)
* .findList();
*
* // query 1) find order (status, shipDate)
* // query 2) find orderDetail (quantity, price) fetch product (sku, name) where
* // order.id in (?,? ...)
* // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
* // where id in (?,?,?,?,?)
*
* // Note: the fetch of &quot;details.product&quot; is automatically included into the
* // fetch of &quot;details&quot;
* //
* // Note: the fetch of &quot;customer.contacts&quot; and &quot;customer.shippingAddress&quot;
* // are automatically included in the fetch of &quot;customer&quot;
* </pre>
* <p>
* You can use query() and lazy together on a single join. The query is executed
* immediately and the lazy defines the batch size to use for further lazy
* loading (if lazy loading is invoked).
* </p>
*
* <pre class="code">
* List&lt;Order&gt; list =
* Ebean.find(Order.class)
* .fetch(&quot;customer&quot;, new FetchConfig().query(10).lazy(5))
* .findList();
*
* // query 1) find order
* // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
* // .. then if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* </pre>
*
* <p>
* Example of controlling the lazy loading query:
* </p>
* <p>
* This gives us the ability to optimise the lazy loading query for a given use
* case.
* </p>
*
* <pre class="code">
* List&lt;Order&gt; list = Ebean.find(Order.class)
* .fetch(&quot;customer&quot;,&quot;name&quot;, new FetchConfig().lazy(5))
* .fetch(&quot;customer.contacts&quot;,&quotcontactName, phone, email&quot)
* .fetch(&quot;customer.shippingAddress&quot;)
* .where().eq(&quot;status&quot;,Order.Status.NEW)
* .findList();
*
* // query 1) find order where status = Order.Status.NEW
* //
* // .. if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* find customer (name)
* fetch customer.contacts (contactName, phone, email)
* fetch customer.shippingAddress (*)
* where id in (?,?,?,?,?)
*
* </pre>
*
* @author mario
* @author rbygrave
*/
public class FetchConfig implements Serializable {
private static final long serialVersionUID = 1L;
private int lazyBatchSize = -1;
private int queryBatchSize = -1;
private boolean queryAll;
/**
* Construct the fetch configuration object.
*/
public FetchConfig() {
}
/**
* Specify that this path should be lazy loaded using the default batch load
* size.
*/
public FetchConfig lazy() {
this.lazyBatchSize = 0;
this.queryAll = false;
return this;
}
/**
* Specify that this path should be lazy loaded with a specified batch size.
*
* @param lazyBatchSize
* the batch size for lazy loading
*/
public FetchConfig lazy(int lazyBatchSize) {
this.lazyBatchSize = lazyBatchSize;
this.queryAll = false;
return this;
}
/**
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* This will use the default batch size for separate query which is 100.
* </p>
*/
public FetchConfig query() {
this.queryBatchSize = 0;
this.queryAll = true;
return this;
}
/**
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* The queryBatchSize is the number of parent id's that this separate query
* will load per batch.
* </p>
* <p>
* This will load all beans on this path eagerly unless a {@link #lazy(int)}
* is also used.
* </p>
*
* @param queryBatchSize
* the batch size used to load beans on this path
*/
public FetchConfig query(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
// queryAll true as long as a lazy batch size has not already been set
this.queryAll = (lazyBatchSize == -1);
return this;
}
/**
* Eagerly fetch the first batch of beans on this path.
* This is similar to {@link #query(int)} but only fetches the first batch.
* <p>
* If there are more parent beans than the batch size then they will not be
* loaded eagerly but instead use lazy loading.
* </p>
*
* @param queryBatchSize
* the number of parent beans this path is populated for
*/
public FetchConfig queryFirst(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
this.queryAll = false;
return this;
}
/**
* Return the batch size for lazy loading.
*/
public int getLazyBatchSize() {
return lazyBatchSize;
}
/**
* Return the batch size for separate query load.
*/
public int getQueryBatchSize() {
return queryBatchSize;
}
/**
* Return true if the query fetch should fetch 'all' rather than just the
* 'first' batch.
*/
public boolean isQueryAll() {
return queryAll;
}
}
+197
View File
@@ -0,0 +1,197 @@
package com.avaje.ebean;
import java.util.List;
import java.util.Set;
/**
* Provides support for filtering and sorting lists of entities without going
* back to the database.
* <p>
* That is, it uses local in-memory sorting and filtering of a list of entity
* beans. It is not used in a Database query or invoke a Database query.
* </p>
* <p>
* You can optionally specify a sortByClause and if so, the sort will always
* execute prior to the filter expressions. You can specify any number of filter
* expressions and they are effectively joined by logical "AND".
* </p>
* <p>
* The result of the filter method will leave the original list unmodified and
* return a new List instance.
* </p>
*
* <pre class="code">
*
* // get a list of entities (query execution statistics in this case)
*
* List&lt;MetaQueryStatistic&gt; list =
* Ebean.find(MetaQueryStatistic.class).findList();
*
* long nowMinus24Hrs = System.currentTimeMillis() - 24 * (1000 * 60 * 60);
*
* // sort and filter the list returning a filtered list...
*
* List&lt;MetaQueryStatistic&gt; filteredList =
* Ebean.filter(MetaQueryStatistic.class)
* .sort(&quot;avgTimeMicros desc&quot;)
* .gt(&quot;executionCount&quot;, 0)
* .gt(&quot;lastQueryTime&quot;, nowMinus24Hrs)
* .eq(&quot;autofetchTuned&quot;, true)
* .maxRows(10)
* .filter(list);
*
* </pre>
* <p>
* The propertyNames can traverse the object graph (e.g. customer.name) by using
* dot notation. If any point during the object graph traversal to get a
* property value is null then null is returned.
* </p>
*
* <pre>
* // examples of property names that
* // ... will traverse the object graph
* // ... where customer is a property of our bean
*
* customer.name
* customer.shippingAddress.city
* </pre>
*
* </p>
*
* <pre class="code">
*
* // get a list of entities (query execution statistics)
*
* List&lt;Order&gt; orders =
* Ebean.find(Order.class).findList();
*
* // Apply a filter...
*
* List&lt;Order&gt; filteredOrders =
* Ebean.filter(Order.class)
* .startsWith(&quot;customer.name&quot;, &quot;Rob&quot;)
* .eq(&quot;customer.shippingAddress.city&quot;, &quot;Auckland&quot;)
* .filter(orders);
*
* </pre>
*
* @param <T>
* the entity bean type
*/
public interface Filter<T> {
/**
* Specify a sortByClause.
* <p>
* The sort (if specified) will always execute first followed by the filter
* expressions.
* </p>
* <p>
* Refer to {@link Ebean#sort(List, String)} for more detail.
* </p>
*/
public Filter<T> sort(String sortByClause);
/**
* Specify the maximum number of rows/elements to return.
*/
public Filter<T> maxRows(int maxRows);
/**
* Equal To - property equal to the given value.
*/
public Filter<T> eq(String prop, Object value);
/**
* Not Equal To - property not equal to the given value.
*/
public Filter<T> ne(String propertyName, Object value);
/**
* Case Insensitive Equal To.
*/
public Filter<T> ieq(String propertyName, String value);
/**
* Between - property between the two given values.
*/
public Filter<T> between(String propertyName, Object value1, Object value2);
/**
* Greater Than - property greater than the given value.
*/
public Filter<T> gt(String propertyName, Object value);
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public Filter<T> ge(String propertyName, Object value);
/**
* Less Than - property less than the given value.
*/
public Filter<T> lt(String propertyName, Object value);
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public Filter<T> le(String propertyName, Object value);
/**
* Is Null - property is null.
*/
public Filter<T> isNull(String propertyName);
/**
* Is Not Null - property is not null.
*/
public Filter<T> isNotNull(String propertyName);
/**
* Starts With.
*/
public Filter<T> startsWith(String propertyName, String value);
/**
* Case insensitive Starts With.
*/
public Filter<T> istartsWith(String propertyName, String value);
/**
* Ends With.
*/
public Filter<T> endsWith(String propertyName, String value);
/**
* Case insensitive Ends With.
*/
public Filter<T> iendsWith(String propertyName, String value);
/**
* Contains - property contains the string "value".
*/
public Filter<T> contains(String propertyName, String value);
/**
* Case insensitive Contains.
*/
public Filter<T> icontains(String propertyName, String value);
/**
* In - property has a value contained in the set of values.
*/
public Filter<T> in(String propertyName, Set<?> values);
/**
* Apply the filter to the list returning a new list of the matching elements
* in the sorted order.
* <p>
* The sourceList will remain unmodified.
* </p>
*
* @return Returns a new list with the sorting and filters applied.
*/
public List<T> filter(List<T> sourceList);
}
@@ -0,0 +1,34 @@
package com.avaje.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* FutureIds represents the result of a background query execution for the Id's.
* <p>
* It extends the java.util.concurrent.Future with the ability to get the Id's
* while the query is still executing in the background.
* </p>
*
* @author rbygrave
*/
public interface FutureIds<T> extends Future<List<Object>> {
/**
* Returns the original query used to fetch the Id's.
*/
public Query<T> getQuery();
/**
* Return the list of Id's which could be partially populated.
* <p>
* That is the query getting the id's could still be running and adding id's
* to this list.
* </p>
* <p>
* To get the list of Id's ensuring the query has finished use the
* {@link Future#get()} method instead of this one.
* </p>
*/
public List<Object> getPartialIds();
}
@@ -0,0 +1,53 @@
package com.avaje.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* FutureList represents the result of a background query execution that will
* return a list of entities.
* <p>
* It extends the java.util.concurrent.Future with the ability to cancel the
* query, check if it is finished and get the resulting list waiting for the
* query to finish (ie. the standard features of java.util.concurrent.Future).
* </p>
* <p>
* A simple example:
* </p>
*
* <pre class="code">
* // create a query to find all orders
* Query&lt;Order&gt; query = Ebean.find(Order.class);
*
* // execute the query in a background thread
* // immediately returning the futureList
* FutureList&lt;Order&gt; futureList = query.findFutureList();
*
* // do something else ...
*
* if (!futureList.isDone()){
* // we can cancel the query execution. This will cancel
* // the underlying query if that is supported by the JDBC
* // driver and database
* futureList.cancel(true);
* }
*
*
* if (!futureList.isCancelled()){
* // wait for the query to finish and return the list
* List&lt;Order&gt; list = futureList.get();
* ...
* }
*
* </pre>
*
* @author rbygrave
*/
public interface FutureList<T> extends Future<List<T>> {
/**
* Return the query that is being executed by a background thread.
*/
public Query<T> getQuery();
}
@@ -1,16 +1,15 @@
package io.ebean;
import java.util.concurrent.Future;
/**
* Represents the result of a background query execution for the total row count
* for a query.
* <p>
* It extends the java.util.concurrent.Future.
* </p>
*
* @param <T> the BeanType
* @author rbygrave
*/
public interface FutureRowCount<T> extends Future<Integer> {
}
package com.avaje.ebean;
import java.util.concurrent.Future;
/**
* Represents the result of a background query execution for the total row count
* for a query.
* <p>
* It extends the java.util.concurrent.Future.
* </p>
*
* @author rbygrave
*/
public interface FutureRowCount<T> extends Future<Integer> {
}
@@ -0,0 +1,78 @@
package com.avaje.ebean;
/**
* Represents a Conjunction or a Disjunction.
* <p>
* Basically with a Conjunction you join together many expressions with AND, and
* with a Disjunction you join together many expressions with OR.
* </p>
* <p>
* Note: where() always takes you to the top level WHERE expression list.
* </p>
*
* <pre class="code">
* Query q =
* Ebean.find(Person.class)
* .where().disjunction()
* .like(&quot;name&quot;, &quot;Rob%&quot;)
* .eq(&quot;status&quot;, Status.NEW)
*
* // where() returns us to the top level expression list
* .where().gt(&quot;id&quot;, 10);
*
* // read as...
* // where ( ((name like Rob%) or (status = NEW)) AND (id &gt; 10) )
* </pre>
*
* <p>
* Note: endJunction() takes you to the parent expression list
* </p>
*
* <pre class="code">
* Query q =
* Ebean.find(Person.class)
* .where().disjunction()
* .like(&quot;name&quot;, &quot;Rob%&quot;)
* .eq(&quot;status&quot;, Status.NEW)
* .endJunction()
*
* // endJunction().. takes us to the 'parent' expression list
* // which in this case is the top level (same as where())
*
* .gt(&quot;id&quot;, 10);
*
* // read as...
* // where ( ((name like Rob%) or (status = NEW)) AND (id &gt; 10) )
* </pre>
*
* <p>
* Example of a nested disjunction.
* </p>
*
* <pre class="code">
* Query&lt;Customer&gt; q =
* Ebean.find(Customer.class)
* .where()
* .disjunction()
* .conjunction()
* .startsWith(&quot;name&quot;, &quot;r&quot;)
* .eq(&quot;anniversary&quot;, onAfter)
* .endJunction()
* .conjunction()
* .eq(&quot;status&quot;, Customer.Status.ACTIVE)
* .gt(&quot;id&quot;, 0)
* .endJunction()
* .order().asc(&quot;name&quot;);
*
* q.findList();
* String s = q.getGeneratedSql();
*
* // this produces an expression like:
*
* ( name like ? and c.anniversary = ? ) or (c.status = ? and c.id &gt; ? )
*
* </pre>
*/
public interface Junction<T> extends Expression, ExpressionList<T> {
}
@@ -0,0 +1,35 @@
package com.avaje.ebean;
/**
* Used to specify the type of like matching used.
*/
public enum LikeType {
/**
* You need to put in your own wildcards.
*/
RAW,
/**
* The % wildcard is added to the end of the search word.
*/
STARTS_WITH,
/**
* The % wildcard is added to the beginning of the search word.
*/
ENDS_WITH,
/**
* The % wildcard is added to the beginning and end of the search word.
*/
CONTAINS,
/**
* Uses equal to rather than a LIKE with wildcards.
* <p>
* This is mainly here to be available for use with ExampleExpression.
* </p>
*/
EQUAL_TO
}
+351
View File
@@ -0,0 +1,351 @@
package com.avaje.ebean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Represents an Order By for a Query.
* <p>
* Is a ordered list of OrderBy.Property objects each specifying a property and
* whether it is ascending or descending order.
* </p>
* <p>
* Typically you will not construct an OrderBy yourself but use one that exists
* on the Query object.
* </p>
*/
public final class OrderBy<T> implements Serializable {
private static final long serialVersionUID = 9157089257745730539L;
private transient Query<T> query;
private final List<Property> list;
/**
* Create an empty OrderBy with no associated query.
*/
public OrderBy() {
this.list = new ArrayList<Property>(2);
}
private OrderBy(List<Property> list) {
this.list = list;
}
/**
* Create an orderBy parsing the order by clause.
* <p>
* The order by clause follows SQL order by clause with comma's between each
* property and optionally "asc" or "desc" to represent ascending or
* descending order respectively.
* </p>
*/
public OrderBy(String orderByClause) {
this(null, orderByClause);
}
/**
* Construct with a given query and order by clause.
*/
public OrderBy(Query<T> query, String orderByClause) {
this.query = query;
this.list = new ArrayList<Property>(2);
parse(orderByClause);
}
/**
* Reverse the ascending/descending order on all the properties.
*/
public void reverse() {
for (int i = 0; i < list.size(); i++) {
list.get(i).reverse();
}
}
/**
* Add a property with ascending order to this OrderBy.
*/
public Query<T> asc(String propertyName) {
list.add(new Property(propertyName, true));
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
public Query<T> desc(String propertyName) {
list.add(new Property(propertyName, false));
return query;
}
/**
* Return a copy of this OrderBy with the path trimmed.
*/
public OrderBy<T> copyWithTrim(String path) {
List<Property> newList = new ArrayList<Property>(list.size());
for (int i = 0; i < list.size(); i++) {
newList.add(list.get(i).copyWithTrim(path));
}
return new OrderBy<T>(newList);
}
/**
* Return the properties for this OrderBy.
*/
public List<Property> getProperties() {
// not returning an Immutable list at this point
return list;
}
/**
* Return true if this OrderBy does not have any properties.
*/
public boolean isEmpty() {
return list.isEmpty();
}
/**
* Return the associated query if there is one.
*/
public Query<T> getQuery() {
return query;
}
/**
* Associate this OrderBy with a query.
*/
public void setQuery(Query<T> query) {
this.query = query;
}
/**
* Return a copy of the OrderBy.
*/
public OrderBy<T> copy() {
OrderBy<T> copy = new OrderBy<T>();
for (int i = 0; i < list.size(); i++) {
copy.add(list.get(i).copy());
}
return copy;
}
/**
* Add a property to the order by.
*/
public void add(Property p) {
list.add(p);
}
public String toString() {
return list.toString();
}
/**
* Returns the OrderBy in string format.
*/
public String toStringFormat() {
if (list.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
Property property = list.get(i);
if (i > 0) {
sb.append(", ");
}
sb.append(property.toStringFormat());
}
return sb.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof OrderBy<?>)) {
return false;
}
OrderBy<?> e = (OrderBy<?>) obj;
return e.list.equals(list);
}
/**
* Return a hash value for this OrderBy. This can be to determine logical
* equality for OrderBy clauses.
*/
public int hashCode() {
return list.hashCode();
}
/**
* A property and its ascending descending order.
*/
public static final class Property implements Serializable {
private static final long serialVersionUID = 1546009780322478077L;
private String property;
private boolean ascending;
public Property(String property, boolean ascending) {
this.property = property;
this.ascending = ascending;
}
/**
* Return a copy of this Property with the path trimmed.
*/
public Property copyWithTrim(String path) {
return new Property(property.substring(path.length() + 1), ascending);
}
public int hashCode() {
int hc = property.hashCode();
hc = hc * 31 + (ascending ? 0 : 1);
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Property)) {
return false;
}
Property e = (Property) obj;
return e.ascending == ascending
&& e.property.equals(property);
}
public String toString() {
return toStringFormat();
}
public String toStringFormat() {
if (ascending) {
return property;
} else {
return property + " desc";
}
}
/**
* Reverse the ascending/descending order for this property.
*/
public void reverse() {
this.ascending = !ascending;
}
/**
* Trim off the pathPrefix.
*/
public void trim(String pathPrefix) {
property = property.substring(pathPrefix.length() + 1);
}
/**
* Return a copy of this property.
*/
public Property copy() {
return new Property(property, ascending);
}
/**
* Return the property name.
*/
public String getProperty() {
return property;
}
/**
* Set the property name.
*/
public void setProperty(String property) {
this.property = property;
}
/**
* Return true if the order is ascending.
*/
public boolean isAscending() {
return ascending;
}
/**
* Set to true if the order is ascending.
*/
public void setAscending(boolean ascending) {
this.ascending = ascending;
}
}
private void parse(String orderByClause) {
if (orderByClause == null) {
return;
}
String[] chunks = orderByClause.split(",");
for (int i = 0; i < chunks.length; i++) {
String[] pairs = chunks[i].split(" ");
Property p = parseProperty(pairs);
if (p != null) {
list.add(p);
}
}
}
private Property parseProperty(String[] pairs) {
if (pairs.length == 0) {
return null;
}
ArrayList<String> wordList = new ArrayList<String>(pairs.length);
for (int i = 0; i < pairs.length; i++) {
if (!isEmptyString(pairs[i])) {
wordList.add(pairs[i]);
}
}
if (wordList.isEmpty()) {
return null;
}
if (wordList.size() == 1) {
return new Property(wordList.get(0), true);
}
if (wordList.size() == 2) {
boolean asc = isAscending(wordList.get(1));
return new Property(wordList.get(0), asc);
}
String m = "Expecting a max of 2 words in [" + Arrays.toString(pairs)
+ "] but got " + wordList.size();
throw new RuntimeException(m);
}
private boolean isAscending(String s) {
s = s.toLowerCase();
if (s.startsWith("asc")) {
return true;
}
if (s.startsWith("desc")) {
return false;
}
String m = "Expecting [" + s + "] to be asc or desc?";
throw new RuntimeException(m);
}
private boolean isEmptyString(String s) {
return s == null || s.length() == 0;
}
}
+74
View File
@@ -0,0 +1,74 @@
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);
}
@@ -0,0 +1,127 @@
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);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
package com.avaje.ebean;
import java.util.Iterator;
/**
* Used to provide iteration over query results.
* <p>
* This can be used when you want to process a very large number of results and
* means that you don't have to hold all the results in memory at once (unlike
* findList(), findSet() etc where all the beans are held in the List or Set
* etc).
* </p>
*
* <pre class="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);
*
* QueryIterator&lt;Customer&gt; it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer...
* }
* } finally {
* // close the associated resources
* it.close();
* }
* </pre>
*
* @author rbygrave
*
* @param <T>
* the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
*/
public boolean hasNext();
/**
* Returns the next element in the iteration.
*/
public T next();
/**
* Remove is not allowed.
*/
public void remove();
/**
* Close the underlying resources held by this iterator.
*/
public void close();
}
@@ -0,0 +1,43 @@
package com.avaje.ebean;
/**
* Deprecated, please migrate to using {@link #findIterate()} or {@link #findVisit(QueryResultVisitor)}
* <p>
* Provides a mechanism for processing a query one bean at a time.
* </p>
* <p>
* This is useful when the query will return a large number of results and you
* want to process the beans one at a time rather than whole all of the beans in
* memory at once.
* </p>
*
* <pre class="code">
* QueryListener&lt;Order&gt; listener = ...;
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
*
* // set the listener that will process each order one at a time
* query.setListener(listener);
*
* // execute the query. Note that the returned
* // list will be empty ... so don't bother assigning it
* query.findList();
* </pre>
*
* @param <T>
* the type of entity bean
* @deprecated Please migrate to using {@link #findIterate()} or
* {@link #findVisit(QueryResultVisitor)}
*/
public interface QueryListener<T> {
/**
* Process the bean that has just been read.
* <p>
* This bean will not be added to the List Set or Map and nor will it be put
* into the PersistenceContext. This is what makes this a good way to process
* a large result set (which could normally use a lot of memory).
* </p>
*/
public void process(T bean);
}
@@ -0,0 +1,49 @@
package com.avaje.ebean;
/**
* Used to process a query result one bean at a time via a callback to this
* visitor.
* <p>
* If you wish to stop further processing return false from the accept method.
* </p>
* <p>
* Unlike findList() and findSet() using a QueryResultVisitor does not require
* all the beans in the query result to be held in memory at once. This makes
* QueryResultVisitor useful for processing large queries.
* </p>
*
* <pre class="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);
*
* query.findVisit(new QueryResultVisitor&lt;Customer&gt;() {
*
* public boolean accept(Customer customer) {
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* return true;
* }
* });
* </pre>
*
* @author rbygrave
*
* @param <T>
* the type of entity bean being queried.
*/
public interface QueryResultVisitor<T> {
/**
* Process the bean and return true if you want to continue processing more
* beans. Return false if you want to stop processing further.
*
* @param bean
* the entity bean to process
* @return true to continue processing or false to stop.
*/
public boolean accept(T bean);
}
+573
View File
@@ -0,0 +1,573 @@
package com.avaje.ebean;
import java.io.Serializable;
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 com.avaje.ebean.util.CamelCaseHelper;
/**
* Used to build object graphs based on a raw SQL statement (rather than
* generated by Ebean).
* <p>
* If you don't want to build object graphs you can use {@link SqlQuery} instead
* which returns {@link SqlRow} objects rather than entity beans.
* </p>
* <p>
* <b>Unparsed RawSql:</b>
* </p>
* <p>
* When RawSql is created via RawSqlBuilder.unparsed(sql) 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
* 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}).
* </p>
* <p>
* If the SQL already includes a WHERE clause put in ${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.
* </p>
* <p>
* <b>Aggregates:</b>
* </p>
* <p>
* Often RawSql will be used with Aggregate functions (sum, avg, max etc). The
* follow example shows an example based on Total Order Amount -
* sum(d.order_qty*d.unit_price).
* </p>
* <p>
* We can use a OrderAggregate bean that has a &#064;Sql to indicate it is based
* on RawSql and not based on a real DB Table or DB View. It has some properties
* 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>
*
* <pre class="code">
* ...
* // &#064;Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* &#064;Entity
* &#064;Sql
* public class OrderAggregate {
*
* &#064;OneToOne
* Order order;
*
* Double totalAmount;
*
* Double totalItems;
*
* // getters and setters
* ...
* </pre>
* <p>
* <b>Example 1:</b>
* </p>
*
* <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;;
*
* 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;)
* // 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;)
* .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&lt;OrderAggregate&gt; list = query.findList();
* </pre>
*
* <p>
* <b>Example 2:</b>
* </p>
*
* <p>
* The following example uses a FetchConfig().query() so that after the initial
* RawSql query is executed Ebean executes a secondary query to fetch the
* 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;;
*
* RawSql rawSql = RawSqlBuilder.parse(sql).columnMapping(&quot;order_id&quot;, &quot;order.id&quot;)
* .columnMappingIgnore(&quot;'ignoreMe'&quot;).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>
*
* <p>
* Note that lazy loading also works with object graphs built with RawSql.
* </p>
*
* @author rbygrave
*
*/
public final class RawSql implements Serializable {
private static final long serialVersionUID = 1L;
private final Sql sql;
private final ColumnMapping columnMapping;
protected RawSql(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
this.columnMapping = columnMapping;
}
/**
* Return the Sql either unparsed or in parsed (broken up) form.
*/
public Sql getSql() {
return sql;
}
/**
* Return the column mapping for the SQL columns to bean properties.
*/
public ColumnMapping getColumnMapping() {
return columnMapping;
}
/**
* Return the hash for this query.
*/
public int queryHash() {
return 31 * sql.queryHash() + columnMapping.queryHash();
}
/**
* Represents the sql part of the query. For parsed RawSql the sql is broken
* up so that Ebean can insert extra WHERE and HAVING expressions into the
* SQL.
*/
public static final class Sql implements Serializable {
private static final long serialVersionUID = 1L;
private final boolean parsed;
private final String unparsedSql;
private final String preFrom;
private final String preWhere;
private final boolean andWhereExpr;
private final String preHaving;
private final boolean andHavingExpr;
private final String orderBy;
private final boolean distinct;
private final int queryHashCode;
/**
* Construct for unparsed SQL.
*/
protected Sql(String unparsedSql) {
this.queryHashCode = unparsedSql.hashCode();
this.parsed = false;
this.unparsedSql = unparsedSql;
this.preFrom = null;
this.preHaving = null;
this.preWhere = null;
this.andHavingExpr = false;
this.andWhereExpr = false;
this.orderBy = null;
this.distinct = false;
}
/**
* Construct for parsed SQL.
*/
protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr,
String preHaving, boolean andHavingExpr,
String orderBy, boolean distinct) {
this.queryHashCode = queryHashCode;
this.parsed = true;
this.unparsedSql = null;
this.preFrom = preFrom;
this.preHaving = preHaving;
this.preWhere = preWhere;
this.andHavingExpr = andHavingExpr;
this.andWhereExpr = andWhereExpr;
this.orderBy = orderBy;
this.distinct = distinct;
}
/**
* Return a hash for this query.
*/
public int queryHash() {
return queryHashCode;
}
public String toString() {
if (!parsed) {
return "unparsed[" + unparsedSql + "]";
}
return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving
+ "] orderBy[" + orderBy + "]";
}
public boolean isDistinct() {
return distinct;
}
/**
* Return true if the SQL is left completely unmodified.
* <p>
* This means Ebean can't add WHERE or HAVING expressions into the query -
* it will be left completely unmodified.
* </p>
*/
public boolean isParsed() {
return parsed;
}
/**
* Return the SQL when it is unparsed.
*/
public String getUnparsedSql() {
return unparsedSql;
}
/**
* Return the SQL prior to FROM clause.
*/
public String getPreFrom() {
return preFrom;
}
/**
* Return the SQL prior to WHERE clause.
*/
public String getPreWhere() {
return preWhere;
}
/**
* Return true if there is already a WHERE clause and any extra where
* expressions start with AND.
*/
public boolean isAndWhereExpr() {
return andWhereExpr;
}
/**
* Return the SQL prior to HAVING clause.
*/
public String getPreHaving() {
return preHaving;
}
/**
* Return true if there is already a HAVING clause and any extra having
* expressions start with AND.
*/
public boolean isAndHavingExpr() {
return andHavingExpr;
}
/**
* Return the SQL ORDER BY clause.
*/
public String getOrderBy() {
return orderBy;
}
}
/**
* Defines the column mapping for raw sql DB columns to bean properties.
*/
public static final class ColumnMapping implements Serializable {
private static final long serialVersionUID = 1L;
private final LinkedHashMap<String, Column> dbColumnMap;
private final Map<String, String> propertyMap;
private final Map<String, Column> propertyColumnMap;
private final boolean parsed;
private final boolean immutable;
private final int queryHashCode;
/**
* Construct from parsed sql where the columns have been identified.
*/
protected ColumnMapping(List<Column> columns) {
this.queryHashCode = 0;
this.immutable = false;
this.parsed = true;
this.propertyMap = null;
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<String, Column>();
for (int i = 0; i < columns.size(); i++) {
Column c = columns.get(i);
dbColumnMap.put(c.getDbColumn(), c);
}
}
/**
* Construct for unparsed sql.
*/
protected ColumnMapping() {
this.queryHashCode = 0;
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<String, Column>();
}
/**
* Construct an immutable ColumnMapping based on collected information.
*/
protected ColumnMapping(boolean parsed, LinkedHashMap<String, Column> dbColumnMap) {
this.immutable = true;
this.parsed = parsed;
this.dbColumnMap = dbColumnMap;
int hc = ColumnMapping.class.getName().hashCode();
HashMap<String, Column> pcMap = new HashMap<String, Column>();
HashMap<String, String> pMap = new HashMap<String, String>();
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();
}
this.propertyMap = Collections.unmodifiableMap(pMap);
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
this.queryHashCode = hc;
}
/**
* Creates an immutable copy of this ColumnMapping.
*
* @throws IllegalStateException
* when a propertyName has not been defined for a column.
*/
protected ColumnMapping createImmutableCopy() {
for (Column c : dbColumnMap.values()) {
c.checkMapping();
}
return new ColumnMapping(parsed, dbColumnMap);
}
protected void columnMapping(String dbColumn, String propertyName) {
if (immutable) {
throw new IllegalStateException("Should never happen");
}
if (!parsed) {
int pos = dbColumnMap.size();
dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName));
} else {
Column column = dbColumnMap.get(dbColumn);
if (column == null) {
String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of ["
+ dbColumnMap.keySet() + "]";
throw new IllegalArgumentException(msg);
}
column.setPropertyName(propertyName);
}
}
/**
* Return the query hash for this column mapping.
*/
public int queryHash() {
if (queryHashCode == 0) {
throw new RuntimeException("Bug: queryHashCode == 0");
}
return queryHashCode;
}
/**
* Returns true if the Columns where supplied by parsing the sql select
* clause.
* <p>
* In the case where the columns where parsed then we can do extra checks on
* the column mapping such as, is the column a valid one in the sql and
* whether all the columns in the sql have been mapped.
* </p>
*/
public boolean isParsed() {
return parsed;
}
/**
* Return the number of columns in this column mapping.
*/
public int size() {
return dbColumnMap.size();
}
/**
* Return the column mapping.
*/
protected Map<String, Column> mapping() {
return dbColumnMap;
}
/**
* Return the mapping by DB column.
*/
public Map<String, String> getMapping() {
return propertyMap;
}
/**
* Return the index position by bean property name.
*/
public int getIndexPosition(String property) {
Column c = propertyColumnMap.get(property);
return c == null ? -1 : c.getIndexPos();
}
/**
* Return an iterator of the Columns.
*/
public Iterator<Column> getColumns() {
return dbColumnMap.values().iterator();
}
/**
* A Column of the RawSql that is mapped to a bean property (or ignored).
*/
public static class Column implements Serializable {
private static final long serialVersionUID = 1L;
private final int indexPos;
private final String dbColumn;
private final String dbAlias;
private String propertyName;
/**
* Construct a Column.
*/
public Column(int indexPos, String dbColumn, String dbAlias) {
this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn));
}
private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) {
this.indexPos = indexPos;
this.dbColumn = dbColumn;
this.dbAlias = dbAlias;
if (propertyName == null && dbAlias != null) {
this.propertyName = dbAlias;
} else {
this.propertyName = propertyName;
}
}
private static String derivePropertyName(String dbAlias, String dbColumn) {
if (dbAlias != null) {
return dbAlias;
}
int dotPos = dbColumn.indexOf('.');
if (dotPos > -1) {
dbColumn = dbColumn.substring(dotPos + 1);
}
return CamelCaseHelper.toCamelFromUnderscore(dbColumn);
}
private void checkMapping() {
if (propertyName == null) {
String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]";
throw new IllegalStateException(msg);
}
}
public String toString() {
return dbColumn + "->" + propertyName;
}
/**
* Return the index position of this column.
*/
public int getIndexPos() {
return indexPos;
}
/**
* Return the DB column name including table alias (if it has one).
*/
public String getDbColumn() {
return dbColumn;
}
/**
* Return the DB column alias (if it has one).
*/
public String getDbAlias() {
return dbAlias;
}
/**
* Return the bean property this column is mapped to.
*/
public String getPropertyName() {
return propertyName;
}
private void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
}
}
}
@@ -0,0 +1,104 @@
package com.avaje.ebean;
import com.avaje.ebean.RawSql.ColumnMapping;
import com.avaje.ebean.RawSql.Sql;
/**
* Builds RawSql instances from a SQL string and column mappings.
* <p>
* Note that RawSql can also be defined in ebean-orm.xml files and be used as a
* named query.
* </p>
*
* @author rbygrave
*
* @see RawSql
*/
public class RawSqlBuilder {
/**
* Special property name assigned to a DB column that should be ignored.
*/
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
private final Sql sql;
private final ColumnMapping columnMapping;
/**
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
* modified - so no additional WHERE or HAVING expressions can be added to
* this query.
*/
public static RawSqlBuilder unparsed(String sql) {
Sql s = new Sql(sql);
return new RawSqlBuilder(s, new ColumnMapping());
}
/**
* Return a RawSqlBuilder parsing the sql.
* <p>
* The sql statement will be parsed so that Ebean can determine where it can
* insert additional WHERE or HAVING expressions.
* </p>
* <p>
* Additionally the selected columns are parsed to determine the column
* ordering. This also means additional checks can be made with the column
* mapping - specifically we can check that all columns are mapped and that
* correct column names are entered into the mapping.
* </p>
*/
public static RawSqlBuilder parse(String sql) {
Sql sql2 = DRawSqlParser.parse(sql);
String select = sql2.getPreFrom();
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
return new RawSqlBuilder(sql2, mapping);
}
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
this.columnMapping = columnMapping;
}
/**
* Set the mapping of a DB Column to a bean property.
* <p>
* For Unparsed SQL the columnMapping MUST be defined in the same order that
* the columns appear in the SQL statement.
* </p>
*
* @param dbColumn
* the DB column that we are mapping to a bean property
* @param propertyName
* the bean property that we are mapping the DB column to.
*/
public RawSqlBuilder columnMapping(String dbColumn, String propertyName) {
columnMapping.columnMapping(dbColumn, propertyName);
return this;
}
/**
* Ignore this DB column. It is not mapped to any bean property.
*/
public RawSqlBuilder columnMappingIgnore(String dbColumn) {
return columnMapping(dbColumn, IGNORE_COLUMN);
}
/**
* Create the immutable RawSql object. Do this after all the column mapping
* has been defined.
*/
public RawSql create() {
return new RawSql(sql, columnMapping.createImmutableCopy());
}
/**
* Return the internal parsed Sql object (for testing).
*/
protected Sql getSql() {
return sql;
}
}
@@ -0,0 +1,177 @@
package com.avaje.ebean;
public class SimpleTextParser {
private final String oql;
private final char[] chars;
private final int eof;
private int pos;
private String word;
private String lowerWord;
private int openParenthesisCount;
public SimpleTextParser(String oql) {
this.oql = oql;
this.chars = oql.toCharArray();
this.eof = oql.length();
}
public int getPos() {
return pos;
}
public String getOql() {
return oql;
}
public String getWord() {
return word;
}
public String peekNextWord() {
int origPos = pos;
String nw = nextWordInternal();
pos = origPos;
return nw;
}
/**
* Match the current and the next word.
*/
public boolean isMatch(String lowerMatch, String nextWordMatch) {
if (isMatch(lowerMatch)) {
String nw = peekNextWord();
if (nw != null) {
nw = nw.toLowerCase();
return nw.equals(nextWordMatch);
}
}
return false;
}
public boolean isFinished() {
return word == null;
}
public int findWordLower(String lowerMatch, int afterPos) {
this.pos = afterPos;
return findWordLower(lowerMatch);
}
public int findWordLower(String lowerMatch) {
do {
if (nextWord() == null) {
return -1;
}
if (lowerMatch.equals(lowerWord)) {
return pos - lowerWord.length();
}
} while (true);
}
/**
* Match the current word.
*/
public boolean isMatch(String lowerMatch) {
return lowerMatch.equals(lowerWord);
}
public String nextWord() {
word = nextWordInternal();
if (word != null) {
lowerWord = word.toLowerCase();
}
return word;
}
private String nextWordInternal() {
trimLeadingWhitespace();
if (pos >= eof) {
return null;
}
int start = pos;
if (chars[pos] == '(') {
moveToClose();
} else {
moveToEndOfWord();
}
return oql.substring(start, pos);
}
private void moveToClose() {
pos++;
openParenthesisCount = 0;
for (; pos < eof; pos++) {
char c = chars[pos];
if (c == '(') {
// count nested parenthesis
openParenthesisCount++;
} else if (c == ')') {
if (openParenthesisCount > 0) {
// still in nested parenthesis
--openParenthesisCount;
} else {
// we have found the end
pos++;
return;
}
}
}
}
private void moveToEndOfWord() {
char c = chars[pos];
boolean isOperator = isOperator(c);
for (; pos < eof; pos++) {
c = chars[pos];
if (isWordTerminator(c, isOperator)) {
return;
}
}
}
private boolean isWordTerminator(char c, boolean isOperator) {
if (Character.isWhitespace(c)) {
return true;
}
if (isOperator(c)) {
return !isOperator;
}
if (c == '(') {
return true;
}
return isOperator;
}
private boolean isOperator(char c) {
switch (c) {
case '<':
return true;
case '>':
return true;
case '=':
return true;
case '!':
return true;
default:
return false;
}
}
private void trimLeadingWhitespace() {
for (; pos < eof; pos++) {
char c = chars[pos];
if (!Character.isWhitespace(c)) {
break;
}
}
}
}
@@ -0,0 +1,47 @@
package com.avaje.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* The SqlFutureList represents the result of a background SQL query execution.
*
* <p>
* It extends the java.util.concurrent.Future.
* </p>
*
* <pre class="code">
* // create a query
* String sql = ... ;
* SqlQuery sqlQuery = Ebean.createSqlQuery(sql);
*
* // execute the query in a background thread
* SqlFutureList sqlFuture = sqlQuery.findFutureList();
*
* // do something else ... we will sleep
* Thread.sleep(3000);
* System.out.println("end of sleep");
*
* if (!futureList.isDone()){
* // we can cancel the query execution
* futureList.cancel(true);
* }
*
* System.out.println("and... done:"+futureList.isDone());
*
* if (!futureList.isCancelled()){
* // wait for the query to finish and return the list
* List&lt;SqlRow&gt; list = futureList.get();
* System.out.println("list:"+list);
* }
*
* </pre>
*
* @author rob
*
*/
public interface SqlFutureList extends Future<List<SqlRow>> {
public SqlQuery getQuery();
}
+150
View File
@@ -0,0 +1,150 @@
package com.avaje.ebean;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Query object for performing native SQL queries that return SqlRow's.
* <p>
* Firstly note that you can use your own sql queries with <em>entity beans</em>
* by using the SqlSelect annotation. This should be your first approach when
* wanting to use your own SQL queries.
* </p>
* <p>
* If ORM Mapping is too tight and constraining for your problem then SqlQuery
* could be a good approach.
* </p>
* <p>
* The returned SqlRow objects are similar to a LinkedHashMap with some type
* conversion support added.
* </p>
*
* <pre class="code">
* // its typically a good idea to use a named query
* // and put the sql in the orm.xml instead of in your code
*
* String sql = &quot;select id, name from customer where name like :name and status_code = :status&quot;;
*
* SqlQuery sqlQuery = Ebean.createSqlQuery(sql);
* sqlQuery.setParameter(&quot;name&quot;, &quot;Acme%&quot;);
* sqlQuery.setParameter(&quot;status&quot;, &quot;ACTIVE&quot;);
*
* // execute the query returning a List of MapBean objects
* List&lt;SqlRow&gt; list = sqlQuery.findList();
* </pre>
*
*/
public interface SqlQuery extends Serializable {
/**
* Cancel the query if support by the underlying database and driver.
* <p>
* This must be called from a different thread to the one executing the query.
* </p>
*/
public void cancel();
/**
* Execute the query returning a list.
*/
public List<SqlRow> findList();
/**
* Execute the query returning a set.
*/
public Set<SqlRow> findSet();
/**
* Execute the query returning a map.
*/
public Map<?, SqlRow> findMap();
/**
* Execute the query returning a single row or null.
* <p>
* If this query finds 2 or more rows then it will throw a
* PersistenceException.
* </p>
*/
public SqlRow findUnique();
/**
* Execute find list SQL query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list result of the query
*/
public SqlFutureList findFutureList();
/**
* The same as bind for named parameters.
*/
public SqlQuery setParameter(String name, Object value);
/**
* The same as bind for positioned parameters.
*/
public SqlQuery setParameter(int position, Object value);
/**
* Set a listener to process the query on a row by row basis.
* <p>
* It this case the rows are not loaded into the persistence context and
* instead can be processed by the query listener.
* </p>
* <p>
* Use this when you want to process a large query and do not want to hold the
* entire query result in memory.
* </p>
*/
public SqlQuery setListener(SqlQueryListener queryListener);
/**
* Set the index of the first row of the results to return.
*/
public SqlQuery setFirstRow(int firstRow);
/**
* Set the maximum number of query results to return.
*/
public SqlQuery setMaxRows(int maxRows);
/**
* Set the index after which fetching continues in a background thread.
*/
public SqlQuery setBackgroundFetchAfter(int backgroundFetchAfter);
/**
* Set the column to use to determine the keys for a Map.
*/
public SqlQuery setMapKey(String mapKey);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs
* the query timeout limit in seconds. Zero means there is no limit.
*/
public SqlQuery setTimeout(int secs);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
*/
public SqlQuery setBufferFetchSizeHint(int bufferFetchSizeHint);
}
@@ -0,0 +1,33 @@
package com.avaje.ebean;
/**
* Provides a mechanism for processing a SqlQuery one SqlRow at a time.
* <p>
* This is useful when the query will return a large number of results and you
* want to process the beans one at a time rather than have all of the beans in
* memory at once.
* </p>
*
* <pre class="code">
* SqlQueryListener listener = ...;
*
* SqlQuery query = Ebean.createSqlQuery(&quot;my.large.query&quot;);
*
* // set the listener that will process each row one at a time
* query.setListener(listener);
*
* // execute the query. Note that the returned
* // list will be empty ... so don't bother assigning it...
* query.findList();
* </pre>
*/
public interface SqlQueryListener {
/**
* Process the bean that has just been read.
* <p>
* Note this bean will not be added to the List Set or Map.
* </p>
*/
public void process(SqlRow bean);
}
+168
View File
@@ -0,0 +1,168 @@
package com.avaje.ebean;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Used to return raw SQL query results.
* <p>
* Refer to {@link SqlQuery} for examples.
* </p>
* <p>
* There are convenience methods such as getInteger(), getBigDecimal() etc. The
* reason for these methods is that the values put into this map often come
* straight from the JDBC resultSet. Depending on the JDBC driver it may put a
* different type into a given property. For example an Integer, BigDecimal,
* Double could all be put into a property depending on the JDBC driver used.
* These convenience methods automatically convert the value as required
* returning the type you expect.
* </p>
*/
public interface SqlRow extends Serializable, Map<String, Object> {
/**
* Return the property names (String).
* <p>
* Internally this uses LinkedHashMap and so the order of the property names
* should be predictable and ordered by the use of LinkedHashMap.
* </p>
*/
public Iterator<String> keys();
/**
* Remove a property from the map. Returns the value of the removed property.
*/
public Object remove(Object name);
/**
* Return a property value by its name.
*/
public Object get(Object name);
/**
* Set a value to a property.
*/
public Object put(String name, Object value);
/**
* Exactly the same as the put method.
* <p>
* I added this method because it seems more bean like to have get and set
* methods.
* </p>
*/
public Object set(String name, Object value);
/**
* Return a property as a Boolean.
*/
public Boolean getBoolean(String name);
/**
* Return a property as a UUID.
*/
public UUID getUUID(String name);
/**
* Return a property as an Integer.
*/
public Integer getInteger(String name);
/**
* Return a property value as a BigDecimal.
*/
public BigDecimal getBigDecimal(String name);
/**
* Return a property value as a Long.
*/
public Long getLong(String name);
/**
* Return the property value as a Double.
*/
public Double getDouble(String name);
/**
* Return the property value as a Float.
*/
public Float getFloat(String name);
/**
* Return a property as a String.
*/
public String getString(String name);
/**
* Return the property as a java.util.Date.
*/
public java.util.Date getUtilDate(String name);
/**
* Return the property as a sql date.
*/
public Date getDate(String name);
/**
* Return the property as a sql timestamp.
*/
public Timestamp getTimestamp(String name);
/**
* String description of the underlying map.
*/
public String toString();
/**
* Clear the map.
*/
public void clear();
/**
* Returns true if the map contains the property.
*/
public boolean containsKey(Object key);
/**
* Returns true if the map contains the value.
*/
public boolean containsValue(Object value);
/**
* Returns the entrySet of the map.
*/
public Set<Map.Entry<String, Object>> entrySet();
/**
* Returns true if the map is empty.
*/
public boolean isEmpty();
/**
* Returns the key set of the map.
*/
public Set<String> keySet();
/**
* Put all the values from t into this map.
*/
public void putAll(Map<? extends String, ? extends Object> t);
/**
* Return the size of the map.
*/
public int size();
/**
* Return the values from this map.
*/
public Collection<Object> values();
}
@@ -0,0 +1,141 @@
package com.avaje.ebean;
/**
* A SqlUpdate for executing insert update or delete statements.
* <p>
* Provides a simple way to execute raw SQL insert update or delete statements
* without having to resort to JDBC.
* </p>
* <p>
* Supports the use of positioned or named parameters and can automatically
* notify Ebean of the table modified so that Ebean can maintain its cache.
* </p>
* <p>
* Note that {@link #setAutoTableMod(boolean)} and
* Ebean#externalModification(String, boolean, boolean, boolean)} can be to
* notify Ebean of external changes and enable Ebean to maintain it's "L2"
* server cache.
* </p>
*
* <pre class="code">
* // example that uses 'named' parameters
* String s = &quot;UPDATE f_topic set post_count = :count where id = :id&quot;
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter(&quot;id&quot;, 1);
* update.setParameter(&quot;count&quot;, 50);
*
* int modifiedCount = Ebean.execute(update);
*
* String msg = &quot;There where &quot; + modifiedCount + &quot;rows updated&quot;
* </pre>
*
* @see Update
* @see SqlQuery
* @see CallableSql
*/
public interface SqlUpdate {
/**
* Execute the update returning the number of rows modified.
* <p>
* After you have executed the SqlUpdate you can bind new variables using
* {@link #setParameter(String, Object)} etc and then execute the SqlUpdate
* again.
* </p>
* <p>
* For JDBC batch processing refer to
* {@link Transaction#setBatchMode(boolean)} and
* {@link Transaction#setBatchSize(int)}.
* </p>
*
* @see com.avaje.ebean.Ebean#execute(SqlUpdate)
*/
public int execute();
/**
* Return true if eBean should automatically deduce the table modification
* information and process it.
* <p>
* If this is true then cache invalidation and text index management are aware
* of the modification.
* </p>
*/
public boolean isAutoTableMod();
/**
* Set this to false if you don't want eBean to automatically deduce the table
* modification information and process it.
* <p>
* Set this to false if you don't want any cache invalidation or text index
* management to occur. You may do this when say you update only one column
* and you know that it is not important for cached objects or text indexes.
* </p>
*/
public SqlUpdate setAutoTableMod(boolean isAutoTableMod);
/**
* Return the label that can be seen in the transaction logs.
*/
public String getLabel();
/**
* Set a descriptive text that can be put into the transaction log.
* <p>
* Useful when identifying the statement in the transaction log.
* </p>
*/
public SqlUpdate setLabel(String label);
/**
* Return the sql statement.
*/
public String getSql();
/**
* Return the timeout used to execute this statement.
*/
public int getTimeout();
/**
* Set the timeout in seconds. Zero implies no limit.
* <p>
* This will set the query timeout on the underlying PreparedStatement. If the
* timeout expires a SQLException will be throw and wrapped in a
* PersistenceException.
* </p>
*/
public SqlUpdate setTimeout(int secs);
/**
* Set a parameter via its index position.
*/
public SqlUpdate setParameter(int position, Object value);
/**
* Set a null parameter via its index position. Exactly the same as
* {@link #setNull(int, int)}.
*/
public SqlUpdate setNull(int position, int jdbcType);
/**
* Set a null valued parameter using its index position.
*/
public SqlUpdate setNullParameter(int position, int jdbcType);
/**
* Set a named parameter value.
*/
public SqlUpdate setParameter(String name, Object param);
/**
* Set a named parameter that has a null value. Exactly the same as
* {@link #setNullParameter(String, int)}.
*/
public SqlUpdate setNull(String name, int jdbcType);
/**
* Set a named parameter that has a null value.
*/
public SqlUpdate setNullParameter(String name, int jdbcType);
}
@@ -0,0 +1,285 @@
package com.avaje.ebean;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.io.Closeable;
import java.sql.Connection;
/**
* The Transaction object. Typically representing a JDBC or JTA transaction.
*/
public interface Transaction extends Closeable {
/**
* Read Committed transaction isolation. Same as
* java.sql.Connection.TRANSACTION_READ_COMMITTED.
*/
public static final int READ_COMMITTED = java.sql.Connection.TRANSACTION_READ_COMMITTED;
/**
* Read Uncommitted transaction isolation. Same as
* java.sql.Connection.TRANSACTION_READ_UNCOMMITTED.
*/
public static final int READ_UNCOMMITTED = java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
/**
* Repeatable read transaction isolation. Same as
* java.sql.Connection.TRANSACTION_REPEATABLE_READ.
*/
public static final int REPEATABLE_READ = java.sql.Connection.TRANSACTION_REPEATABLE_READ;
/**
* Serializable transaction isolation. Same as
* java.sql.Connection.TRANSACTION_SERIALIZABLE.
*/
public static final int SERIALIZABLE = java.sql.Connection.TRANSACTION_SERIALIZABLE;
/**
* Return true if this transaction is read only.
*/
public boolean isReadOnly();
/**
* Set whether this transaction should be readOnly.
*/
public void setReadOnly(boolean readOnly);
/**
* Commit the transaction.
*/
public void commit() throws RollbackException;
/**
* Rollback the transaction.
*/
public void rollback() throws PersistenceException;
/**
* Rollback the transaction specifying a throwable that caused the rollback to
* occur.
* <p>
* If you are using transaction logging this will log the throwable in the
* transaction logs.
* </p>
*/
public void rollback(Throwable e) throws PersistenceException;
/**
* If the transaction is active then perform rollback. Otherwise do nothing.
*/
public void end() throws PersistenceException;
/**
* Return true if the transaction is active.
*/
public boolean isActive();
/**
* Explicitly turn off or on the cascading nature of save() and delete(). This
* gives the developer exact control over what beans are saved and deleted
* rather than Ebean cascading detecting 'dirty/modified' beans etc.
* <p>
* This is useful if you can getting back entity beans from a layer of code
* (potentially remote) and you prefer to have exact control.
* </p>
* <p>
* This may also be useful if you are using jdbc batching with jdbc drivers
* that do not support getGeneratedKeys.
* </p>
*/
public void setPersistCascade(boolean persistCascade);
/**
* Turn on or off statement batching. Statement batching can be transparent
* for drivers and databases that support getGeneratedKeys. Otherwise you may
* wish to specifically control when batching is used via this method.
* <p>
* Refer to <code>java.sql.PreparedStatement.addBatch();</code>
* <p>
* Note that you may also wish to use the setPersistCascade method to stop
* save and delete cascade behaviour. You may do this to have full control
* over the order of execution rather than the normal cascading fashion.
* </p>
* <p>
* Note that the <em>execution order</em> in batch mode may be different from
* non batch mode execution order. Also note that <em>insert behaviour</em>
* may be different depending on the JDBC driver and its support for
* getGeneratedKeys. That is, for JDBC drivers that do not support
* getGeneratedKeys you may not get back the generated IDs (used for inserting
* associated detail beans etc).
* </p>
* <p>
* Calls to save(), delete(), insert() and execute() all support batch
* processing. This includes normal beans, MapBean, CallableSql and UpdateSql.
* </p>
* <p>
* The flushing of the batched statements is automatic but you can call
* batchFlush when you like. Note that flushing occurs when a query is
* executed or when you mix UpdateSql and CallableSql with save and delete of
* beans.
* </p>
* <p>
* 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;;
*
* String[] da = data.split(&quot; &quot;);
*
* String sql = &quot;{call sp_t3(?,?)}&quot;;
*
* 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);
*
* Transaction t = Ebean.beginTransaction();
* t.setBatchMode(true);
* t.setBatchSize(3);
* try {
* for (int i = 0; i &lt; da.length;) {
*
* cs.setParameter(1, da[i]);
* Ebean.execute(cs);
* }
*
* // NB: commit implicitly flushes
* Ebean.commitTransaction();
*
* } finally {
* Ebean.endTransaction();
* }
* </pre>
*
*/
public void setBatchMode(boolean useBatch);
/**
* Specify the number of statements before a batch is flushed automatically.
*/
public void setBatchSize(int batchSize);
/**
* Specify if you want batched inserts to use getGeneratedKeys.
* <p>
* By default batched inserts will try to use getGeneratedKeys if it is
* supported by the underlying jdbc driver and database.
* </p>
* <p>
* You may want to turn getGeneratedKeys off when you are inserting a large
* number of objects and you don't care about getting back the ids.
* </p>
*/
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys);
/**
* By default when mixing UpdateSql (or CallableSql) with Beans the batch is
* automatically flushed when you change (between persisting beans and
* executing UpdateSql or CallableSql).
* <p>
* If you want to execute both WITHOUT having the batch automatically flush
* you need to call this with batchFlushOnMixed = false.
* </p>
* <p>
* Note that UpdateSql and CallableSql are ALWAYS executed first (before the
* beans are executed). This is because the UpdateSql and CallableSql have
* already been bound to their PreparedStatements. The beans on the other hand
* have a 2 step process (delayed binding).
* </p>
*/
public void setBatchFlushOnMixed(boolean batchFlushOnMixed);
/**
* By default executing a query will automatically flush any batched
* statements (persisted beans, executed UpdateSql etc).
* <p>
* Calling this method with batchFlushOnQuery = false means that you can
* execute a query and the batch will not be automatically flushed.
* </p>
*/
public void setBatchFlushOnQuery(boolean batchFlushOnQuery);
/**
* Return true if the batch (of persisted beans or executed UpdateSql etc)
* should be flushed prior to executing a query.
* <p>
* The default is for this to be true.
* </p>
*/
public boolean isBatchFlushOnQuery();
/**
* The batch will be flushing automatically but you can use this to explicitly
* flush the batch if you like.
* <p>
* Flushing occurs automatically when:
* </p>
* <ul>
* <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>
* </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>
* Useful where a Developer wishes to use the JDBC API directly. Note that the
* commit() rollback() and end() methods on the Transaction should still be
* used. Calling these methods on the Connection would be a big no no unless
* you know what you are doing.
* </p>
* <p>
* Examples of when a developer may wish to use the connection directly are:
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
* </p>
*/
public Connection getConnection();
/**
* Add table modification information to the TransactionEvent.
* <p>
* Use this in conjunction with getConnection() and raw JDBC.
* </p>
* <p>
* This effectively informs Ebean of the data that has been changed by the
* transaction and this information is normally automatically handled by Ebean
* when you save entity beans or use UpdateSql etc.
* </p>
* <p>
* If you use raw JDBC then you can use this method to inform Ebean for the
* tables that have been modified. Ebean uses this information to keep its
* caches in synch and maintain text indexes.
* </p>
*/
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes);
/**
* Add an arbitrary user object to the transaction. The objects added have no
* impact on any internals of ebena and are solely meant as a convenient
* method push user information to e.g. the
* {@link com.avaje.ebean.event.TransactionEventListener}.
*/
public void putUserObject(String name, Object value);
/**
* Get an object added with {@link #putUserObject(String, Object)}.
*/
public Object getUserObject(String name);
}
@@ -0,0 +1,45 @@
package com.avaje.ebean;
/**
* Execute a TxCallable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxCallable)} method.
* </p>
* <p>
* Note that this is basically the same as TxRunnable except that it returns an
* Object (and you specify the return type via generics).
* </p>
* <p>
* See also {@link TxRunnable}.
* </p>
*
* <pre class="code">
* Ebean.execute(new TxCallable&lt;String&gt;() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName(&quot;u1 mod&quot;);
* u2.setName(&quot;u2 mod&quot;);
*
* Ebean.save(u1);
* Ebean.save(u2);
*
* return u1.getEmail();
* }
* });
* </pre>
*
* @see TxRunnable
*/
public interface TxCallable<T> {
/**
* Execute the method within a transaction scope returning the result.
* <p>
* If you do not want to return a result you should look to use TxRunnable
* instead.
* </p>
*/
public T call();
}
@@ -0,0 +1,100 @@
package com.avaje.ebean;
import java.sql.Connection;
/**
* The Transaction Isolation levels.
* <p>
* These match those of java.sql.Connection with the addition of DEFAULT which
* implies the configured default of the DataSource.
* </p>
* <p>
* This can be used with TxScope to define transactional scopes to execute
* method within.
* </p>
*
* @see TxScope
*/
public enum TxIsolation {
/**
* Read Committed Isolation level. This is typically the default for most
* configurations.
*/
READ_COMMITED(Connection.TRANSACTION_READ_COMMITTED),
/**
* Read uncommitted Isolation level.
*/
READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
/**
* Repeatable Read Isolation level.
*/
REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
/**
* Serializable Isolation level.
*/
SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE),
/**
* No Isolation level.
*/
NONE(Connection.TRANSACTION_NONE),
/**
* The default isolation level. This typically means the default that the
* DataSource is using or configured to use.
*/
DEFAULT(-1);
final int level;
private TxIsolation(int level) {
this.level = level;
}
/**
* Return the level as per java.sql.Connection.
* <p>
* Note that -1 denotes the default isolation level.
* </p>
*/
public int getLevel() {
return level;
}
/**
* Return the TxIsolation given the java.sql.Connection isolation level.
* <p>
* Note that -1 denotes the default isolation level.
* </p>
*/
public static TxIsolation fromLevel(int connectionIsolationLevel) {
switch (connectionIsolationLevel) {
case Connection.TRANSACTION_READ_UNCOMMITTED:
return TxIsolation.READ_UNCOMMITTED;
case Connection.TRANSACTION_READ_COMMITTED:
return TxIsolation.READ_COMMITED;
case Connection.TRANSACTION_REPEATABLE_READ:
return TxIsolation.REPEATABLE_READ;
case Connection.TRANSACTION_SERIALIZABLE:
return TxIsolation.SERIALIZABLE;
case Connection.TRANSACTION_NONE:
return TxIsolation.NONE;
case -1:
return TxIsolation.DEFAULT;
default:
throw new RuntimeException("Unknown isolation level " + connectionIsolationLevel);
}
}
}
@@ -0,0 +1,39 @@
package com.avaje.ebean;
/**
* Execute a TxRunnable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxRunnable)} method.
* </p>
* <p>
* See also {@link TxCallable}.
* </p>
*
* <pre class="code">
*
* // this run method runs in a transaction scope
* // which by default is TxScope.REQUIRED
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName(&quot;u1 mod&quot;);
* u2.setName(&quot;u2 mod&quot;);
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
* });
* </pre>
*
* @see TxCallable
*/
public interface TxRunnable {
/**
* Run the method in a transaction sope.
*/
public void run();
}
+231
View File
@@ -0,0 +1,231 @@
package com.avaje.ebean;
import java.util.ArrayList;
/**
* Holds the definition of how a transactional method should run.
* <p>
* This information matches the features of the Transactional annotation. You
* can use it directly with TxRunnable or TxCallable via
* {@link Ebean#execute(TxScope, TxCallable)} or
* {@link Ebean#execute(TxScope, TxRunnable)}.
* </p>
* <p>
* This object is used internally with the enhancement of a method with
* Transactional annotation.
* </p>
*
* @see TxCallable
* @see TxRunnable
* @see Ebean#execute(TxScope, TxCallable)
* @see Ebean#execute(TxScope, TxRunnable)
*/
public final class TxScope {
TxType type;
String serverName;
TxIsolation isolation;
boolean readOnly;
ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Helper method to create a TxScope with REQUIRES.
*/
public static TxScope required() {
return new TxScope(TxType.REQUIRED);
}
/**
* Helper method to create a TxScope with REQUIRES_NEW.
*/
public static TxScope requiresNew() {
return new TxScope(TxType.REQUIRES_NEW);
}
/**
* Helper method to create a TxScope with MANDATORY.
*/
public static TxScope mandatory() {
return new TxScope(TxType.MANDATORY);
}
/**
* Helper method to create a TxScope with SUPPORTS.
*/
public static TxScope supports() {
return new TxScope(TxType.SUPPORTS);
}
/**
* Helper method to create a TxScope with NOT_SUPPORTED.
*/
public static TxScope notSupported() {
return new TxScope(TxType.NOT_SUPPORTED);
}
/**
* Helper method to create a TxScope with NEVER.
*/
public static TxScope never() {
return new TxScope(TxType.NEVER);
}
/**
* Create a REQUIRED transaction scope.
*/
public TxScope() {
this.type = TxType.REQUIRED;
}
/**
* Create with a given transaction scope type.
*/
public TxScope(TxType type) {
this.type = type;
}
/**
* Describes this TxScope instance.
*/
public String toString() {
return "TxScope[" + type + "] readOnly[" + readOnly + "] isolation[" + isolation
+ "] serverName[" + serverName
+ "] rollbackFor[" + rollbackFor + "] noRollbackFor[" + noRollbackFor + "]";
}
/**
* Return the transaction type.
*/
public TxType getType() {
return type;
}
/**
* Set the transaction type.
*/
public TxScope setType(TxType type) {
this.type = type;
return this;
}
/**
* Return if the transaction should be treated as read only.
*/
public boolean isReadonly() {
return readOnly;
}
/**
* Set if the transaction should be treated as read only.
*/
public TxScope setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
/**
* Return the Isolation level this transaction should run with.
*/
public TxIsolation getIsolation() {
return isolation;
}
/**
* Set the transaction isolation level this transaction should run with.
*/
public TxScope setIsolation(TxIsolation isolation) {
this.isolation = isolation;
return this;
}
/**
* Return the serverName for this transaction. If this is null then the
* default server (default DataSource) will be used.
*/
public String getServerName() {
return serverName;
}
/**
* Set the serverName (DataSource name) for which this transaction will be. If
* the serverName is not specified (left null) then the default server will be
* used.
*/
public TxScope setServerName(String serverName) {
this.serverName = serverName;
return this;
}
/**
* Return the throwable's that should cause a rollback.
*/
public ArrayList<Class<? extends Throwable>> getRollbackFor() {
return rollbackFor;
}
/**
* Set a Throwable that should explicitly cause a rollback.
*/
public TxScope setRollbackFor(Class<? extends Throwable> rollbackThrowable) {
if (rollbackFor == null) {
rollbackFor = new ArrayList<Class<? extends Throwable>>(2);
}
rollbackFor.add(rollbackThrowable);
return this;
}
/**
* Set multiple throwable's that will cause a rollback.
*/
@SuppressWarnings("unchecked")
public TxScope setRollbackFor(Class<?>[] rollbackThrowables) {
if (rollbackFor == null) {
rollbackFor = new ArrayList<Class<? extends Throwable>>(rollbackThrowables.length);
}
for (int i = 0; i < rollbackThrowables.length; i++) {
rollbackFor.add((Class<? extends Throwable>) rollbackThrowables[i]);
}
return this;
}
/**
* Return the throwable's that should NOT cause a rollback.
*/
public ArrayList<Class<? extends Throwable>> getNoRollbackFor() {
return noRollbackFor;
}
/**
* Add a Throwable to a list that will NOT cause a rollback. You are able to
* call this method multiple times with different throwable's and they will
* added to a list.
*/
public TxScope setNoRollbackFor(Class<? extends Throwable> noRollback) {
if (noRollbackFor == null) {
noRollbackFor = new ArrayList<Class<? extends Throwable>>(2);
}
this.noRollbackFor.add(noRollback);
return this;
}
/**
* Set multiple throwable's that will NOT cause a rollback.
*/
@SuppressWarnings("unchecked")
public TxScope setNoRollbackFor(Class<?>[] noRollbacks) {
if (noRollbackFor == null) {
noRollbackFor = new ArrayList<Class<? extends Throwable>>(noRollbacks.length);
}
for (int i = 0; i < noRollbacks.length; i++) {
noRollbackFor.add((Class<? extends Throwable>) noRollbacks[i]);
}
return this;
}
}
+50
View File
@@ -0,0 +1,50 @@
package com.avaje.ebean;
/**
* Used to define the transactional scope for executing a method. Matches the
* types defined in the EJB TransactionAttributeType.
* <p>
* Used with the Transactional annotation and the {@link TxScope} with
* {@link Ebean#execute(TxScope, TxCallable)} and
* {@link Ebean#execute(TxScope, TxRunnable)}.
* </p>
*
* @see TxScope
*/
public enum TxType {
/**
* Uses an existing transaction and if none exists will starts a new
* Transaction. This is the default.
*/
REQUIRED,
/**
* A transaction MUST already have been started. Throws
* TransactionRequiredException.
*/
MANDATORY,
/**
* Uses the existing transaction if one exists, otherwise the method does not
* run with a transaction. Used this with caution.
*/
SUPPORTS,
/**
* Always start a new transaction. Suspend an existing once if required.
*/
REQUIRES_NEW,
/**
* Suspends an existing transaction if required. Method runs without a
* transaction.
*/
NOT_SUPPORTED,
/**
* If there is an existing transaction throws an Exception. Method runs
* without a transaction.
*/
NEVER;
}
+169
View File
@@ -0,0 +1,169 @@
package com.avaje.ebean;
/**
* An Insert Update or Delete statement.
* <p>
* Generally a named update will be defined on the entity bean. This will take
* the form of either an actual sql insert update delete statement or a similar
* statement with bean name and property names in place of database table and
* column names. The statement will likely include named parameters.
* </p>
* <p>
* The following is an example of named updates on an entity bean.
* </p>
*
* <pre type="class">
* ...
* &#064;NamedUpdates(value = {
* &#064;NamedUpdate(
* name = &quot;setTitle&quot;,
* notifyCache = false,
* update = &quot;update topic set title = :title, postCount = :count where id = :id&quot;),
* &#064;NamedUpdate(
* name = &quot;setPostCount&quot;,
* notifyCache = false,
* update = &quot;update f_topic set post_count = :postCount where id = :id&quot;),
* &#064;NamedUpdate(
* name = &quot;incrementPostCount&quot;,
* notifyCache = false,
* update = &quot;update Topic set postCount = postCount + 1 where id = :id&quot;)
* //update = &quot;update f_topic set post_count = post_count + 1 where id = :id&quot;)
* })
* &#064;Entity
* &#064;Table(name = &quot;f_topic&quot;)
* public class Topic {
* ...
* </pre>
*
* <p>
* The following show code that would use a named update on the Topic entity
* bean.
* </p>
*
* <pre class="code">
* Update&lt;Topic&gt; update = Ebean.createUpdate(Topic.class, &quot;incrementPostCount&quot;);
* update.setParameter(&quot;id&quot;, 1);
* int rows = update.execute();
* </pre>
*
* @param <T>
* the type of entity beans inserted updated or deleted
*/
public interface Update<T> {
/**
* Return the name if it is a named update.
*/
public String getName();
/**
* Set this to false if you do not want the cache to invalidate related
* objects.
* <p>
* If you don't set this Ebean will automatically invalidate the appropriate
* parts of the "L2" server cache.
* </p>
*/
public Update<T> setNotifyCache(boolean notifyCache);
/**
* Set a timeout for statement execution.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs
* the timeout in seconds. Zero implies unlimited.
*/
public Update<T> setTimeout(int secs);
/**
* Execute the statement returning the number of rows modified.
*/
public int execute();
/**
* Set an ordered bind parameter.
* <p>
* position starts at value 1 (not 0) to be consistent with PreparedStatement.
* </p>
* <p>
* Set a value for each ? you have in the sql.
* </p>
*
* @param position
* the index position of the parameter starting with 1.
* @param value
* the parameter value to bind.
*/
public Update<T> set(int position, Object value);
/**
* Set and ordered bind parameter (same as bind).
*
* @param position
* the index position of the parameter starting with 1.
* @param value
* the parameter value to bind.
*/
public Update<T> setParameter(int position, Object value);
/**
* Set an ordered parameter that is null. The JDBC type of the null must be
* specified.
* <p>
* position starts at value 1 (not 0) to be consistent with PreparedStatement.
* </p>
*/
public Update<T> setNull(int position, int jdbcType);
/**
* Set an ordered parameter that is null (same as bind).
*/
public Update<T> setNullParameter(int position, int jdbcType);
/**
* Set a named parameter. Named parameters have a colon to prefix the name.
* <p>
* A more succinct version of setParameter() to be consistent with Query.
* </p>
*
* @param name
* the parameter name.
* @param value
* the parameter value.
*/
public Update<T> set(String name, Object value);
/**
* Bind a named parameter (same as bind).
*/
public Update<T> setParameter(String name, Object param);
/**
* Set a named parameter that is null. The JDBC type of the null must be
* specified.
* <p>
* A more succinct version of setNullParameter().
* </p>
*
* @param name
* the parameter name.
* @param jdbcType
* the type of the property being bound.
*/
public Update<T> setNull(String name, int jdbcType);
/**
* Bind a named parameter that is null (same as bind).
*/
public Update<T> setNullParameter(String name, int jdbcType);
/**
* Return the sql that is actually executed.
*/
public String getGeneratedSql();
}
@@ -0,0 +1,34 @@
package com.avaje.ebean;
/**
* Holds two values as the result of a difference comparison.
*/
public class ValuePair {
final Object value1;
final Object value2;
public ValuePair(Object value1, Object value2) {
this.value1 = value1;
this.value2 = value2;
}
/**
* Return the first value.
*/
public Object getValue1() {
return value1;
}
/**
* Return the second value.
*/
public Object getValue2() {
return value2;
}
public String toString() {
return value1 + "," + value2;
}
}
@@ -0,0 +1,58 @@
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.Query;
/**
* Specify the default cache use specific entity type.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheStrategy {
/**
* When set to true the bean cache will be used unless explicitly stated not
* to in a query via {@link Query#setUseCache(boolean)}.
*/
boolean useBeanCache() default true;
/**
* A single property that is a natural unique identifier for the bean.
* <p>
* When a findUnique query is used with this property as the sole expression
* then there will be a lookup into the L2 natural key cache.
* </p>
*/
String naturalKey() default "";
/**
* When set to true the beans returned from a query will default to be
* readOnly.
* <p>
* If the bean is readOnly and has no relationships then it may be sharable.
* </p>
* <p>
* If you try to modify a readOnly bean it will throw an
* IllegalStateException.
* </p>
*/
boolean readOnly() default false;
/**
* Specify a query that can be used to warm the cache.
* <p>
* All the beans fetched by this query will be loaded into the bean cache and
* the query itself will be loaded into the query cache.
* </p>
* <p>
* The warming query will typically be executed at startup time after a short
* delay (defaults to a 30 seconds delay).
* </p>
*/
String warmingQuery() default "";
};
@@ -0,0 +1,47 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify cache tuning for a specific entity type.
* <p>
* If this is not specified then the system default settings are used.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheTuning {
/**
* The maximum size for the cache.
* <p>
* This defaults to 0 which means unlimited.
* </p>
*/
int maxSize() default 0;
/**
* The maximum time (in seconds) that a cache entry is allowed to stay in the
* cache when it has not been accessed.
* <p>
* This defaults to 0 which means unlimited.
* </p>
*/
int maxIdleSecs() default 0;
/**
* The maximum time (in seconds) a cache entry is allowed to stay in the
* cache.
* <p>
* This is not generally required as the cache entries are automatically
* evicted when related data changes are committed.
* </p>
* <p>
* This defaults to 0 which means unlimited.
* </p>
*/
int maxSecsToLive() default 0;
};
@@ -0,0 +1,22 @@
package com.avaje.ebean.annotation;
/**
* Optimistic concurrency mode used for updates and deletes.
*/
public enum ConcurrencyMode {
/**
* No concurrency checking.
*/
NONE,
/**
* Use a version column.
*/
VERSION,
/**
* Use all the columns (except Lobs).
*/
ALL
}
@@ -0,0 +1,31 @@
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;
/**
* For a timestamp property that is set to the datetime when the entity is
* created/inserted.
* <p>
* An alternative to using this annotation would be to use insertable=false,
* updateable=false with @Column and have the DB insert the current time
* (default value on the DB column is SYSTIME etc).
* </p>
* <p>
* The downside to this approach is that the inserted entity does not have the
* timestamp value after the insert has occurred. You need to fetch the entity
* back to get the inserted timestamp if you want to used it.
* </p>
*
* <pre class="code">
* &#064;Column(insertable = false, updateable = false)
* Timestamp cretimestamp;
* </pre>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CreatedTimestamp {
};
@@ -0,0 +1,31 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify property name to db column mapping for Embedded beans.
* <p>
* This is designed to be easier to use than the AttributeOverride annotation in
* standard JPA.
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EmbeddedColumns {
/**
* A list of property names mapped to DB columns.
* <p>
* For example <code>currency=IN_CURR, amount=IN_AMOUNT</code>
* </p>
* <p>
* Where currency and amount are properties and IN_CURR and IN_AMOUNT are the
* respective DB columns these properties will be mapped to.
* </p>
*/
String columns() default "";
};
@@ -0,0 +1,24 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify that the property is stored in encrypted form.
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypted {
/**
* When true try to use DB encryption rather than local java encryption.
*/
boolean dbEncryption() default true;
/**
* Used to specify the DB column length.
*/
int dbLength() default 0;
};
@@ -0,0 +1,19 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify explicit ConcurrencyMode for entity bean.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityConcurrencyMode {
/**
* The ConcurrencyMode value.
*/
ConcurrencyMode value();
}
@@ -0,0 +1,90 @@
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;
/**
* For mapping the values of an Enum to and from Database values.
* <p>
* Also refer to the {@link EnumValue} approach which probably the preferred now
* (preferred over using this EnumMapping annotation).
* </p>
* <p>
* Both of the approaches defined in the JPA have significant problems!!!
* </p>
* <p>
* Using the ordinal value is VERY RISKY because that depends on the compile
* order of the enum values. Aka if you change the order of the enum values you
* have changed their ordinal values and now your DB values are WRONG - a HUGE
* disaster!!!.
* </p>
* <p>
* Using the String values of enums is fairly restrictive because in a Database
* these values are usually truncated into short codes (e.g. "A" short for
* "ACTIVE") so space used in the database is minimised. Making your enum names
* match the database values would give them very short less meaningful names -
* not a great solution.
* </p>
* <p>
* You can use this annotation to control the mapping of your enums to database
* values.
* </p>
* <p>
* The design of this using nameValuePairs is not optimal for safety or
* refactoring so if you have a better solution I'm all ears. The other
* solutions would probably involve modifying each enumeration with a method
* which may be ok.
* </p>
* <p>
* An example mapping the UserState enum.
* </p>
*
* <pre class="code">
* ...
* &#064;EnumMapping(nameValuePairs=&quot;NEW=N, ACTIVE=A, INACTIVE=I&quot;)
* public enum UserState {
* NEW,
* ACTIVE,
* INACTIVE;
* }
* </pre>
*
* @see EnumValue
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumMapping {
/**
* A comma delimited list of name=value pairs.
* <p>
* e.g. "ACTIVE=A, INACTIVE=I, NEW=N".
* </p>
* <p>
* Where ACTIVE, INACTIVE and NEW are the enumeration values and "A", "I" and
* "N" are the database values.
* </p>
* <p>
* This is not really an optimal approach so if you have a better one I'm all
* ears - thanks.
* </p>
*/
String nameValuePairs();
/**
* Defaults to mapping values to database VARCHAR type. If this is set to true
* then the values will be converted to INTEGER and mapped to the database
* integer type.
* <p>
* e.g. "ACTIVE=1, INACTIVE=0, NEW=2".
* </p>
*/
boolean integerType() default false;
/**
* The length of DB column if mapping to string values.
*/
int length() default 0;
};
@@ -0,0 +1,46 @@
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;
/**
* Enables you to specify a value to use to persist for an enum value.
*
* <pre class="code">
* public enum Status {
* &#064;EnumValue(&quot;N&quot;)
* NEW,
*
* &#064;EnumValue(&quot;A&quot;)
* ACTIVE,
*
* &#064;EnumValue(&quot;I&quot;)
* INACTIVE,
* }
*
* </pre>
* <p>
* This is an alternative to using the JPA standard approach or Ebean's
* {@link EnumMapping} annotation.
* </p>
* <p>
* Note that if all the EnumValue values are parsable as Integers then Ebean
* will persist and fetch them as integers - otherwise they will be persisted
* and fetched as strings.
* </p>
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValue {
/**
* Specify the value to persist for a specific enum value.
* <p>
* If all the values are parsable as Integers then Ebean will persist and
* fetch them as integers rather than strings.
* </p>
*/
String value();
};
@@ -0,0 +1,28 @@
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;
/*
Copied from gson!!!
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
/**
* If {@code true}, the field marked with this annotation is written out in the JSON while
* serializing. If {@code false}, the field marked with this annotation is skipped from the
* serialized output. Defaults to {@code true}.
*/
public boolean serialize() default true;
/**
* If {@code true}, the field marked with this annotation is deserialized from the JSON.
* If {@code false}, the field marked with this annotation is skipped during deserialization.
* Defaults to {@code true}.
*/
public boolean deserialize() default true;
}
@@ -0,0 +1,103 @@
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.Query;
/**
* Assign to a property to be based on a SQL formula.
* <p>
* This is typically a SQL Literal value, SQL case statement, SQL function or
* similar.
* </p>
* <p>
* Any property based on a formula becomes a read only property.
* </p>
* <p>
* 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)}.
* 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>
*
* <pre class="code">
* // On the Order &quot;master&quot; bean
* // ... a formula using the Order details
* // ... sum(order_qty*unit_price)
* &#064;Transient
* &#064;Formula(select = &quot;_b${ta}.total_amount&quot;, join = &quot;join (select order_id, sum(order_qty*unit_price) as total_amount from o_order_detail group by order_id) as _b${ta} on _b${ta}.order_id = ${ta}.id&quot;)
* Double totalAmount;
*
* </pre>
* <p>
* As the totalAmount formula is also Transient it is not included by default in
* queries - it needs to be explicitly included.
* </p>
*
* <pre class="code">
* // find by Id
* Order o1 = Ebean.find(Order.class)
* .select(&quot;id, totalAmount&quot;)
* .setId(1).findUnique();
*
* // find list ... using totalAmount in the where clause
* List&lt;Order&gt; list = Ebean.find(Order.class)
* .select(&quot;id, totalAmount&quot;)
* .where()
* .eq(&quot;status&quot;, Order.Status.NEW)
* .gt(&quot;totalAmount&quot;, 10)
* .findList();
*
* // as a join from customer
* List&lt;Customer&gt; l0 = Ebean.find(Customer.class)
* .select(&quot;id, name&quot;)
* .join(&quot;orders&quot;, &quot;status, totalAmount&quot;)
* .where()
* .gt(&quot;id&quot;, 0)
* .gt(&quot;orders.totalAmount&quot;, 10)
* .findList();
*
* </pre>
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Formula {
/**
* The SQL to be used in the SELECT part of the SQL to populate a property.
*/
String select();
/**
* OPTIONAL - the SQL to be used in the JOIN part of the SQL to support the
* formula.
* <p>
* This is commonly used to join a 'dynamic view' to support aggregation such
* as count, sum etc.
* </p>
* <p>
* The join string should start with either "left outer join" or "join".
* </p>
*
* <p>
* You will almost certainly use the "${ta}" as a place holder for the table
* alias of the table you are joining back to (the "base table" of the entity
* bean).
* </p>
* <p>
* The example below is used to support a total count of topics created by a
* user.
* </p>
*
* <pre class="code">
* join (select user_id, count(*) as topic_count from f_topic group by user_id) as _tc on _tc.user_id = ${ta}.id
* </pre>
*/
String join() default "";
};
@@ -0,0 +1,37 @@
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;
/**
* An Update statement for a particular entity bean type.
* <p>
* The update can either be a sql insert,update or delete statement with tables
* and columns etc or the equivalent statement but with table names and columns
* expressed as bean types and bean properties.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface NamedUpdate {
/**
* The name of the update.
*/
String name();
/**
* The insert, update or delete statement.
*/
String update();
/**
* Set this to false if you do not want the cache to be notified. If true the
* cache will invalidate appropriate objects from the cache (after a
* successful transaction commit).
*/
boolean notifyCache() default true;
};
@@ -0,0 +1,20 @@
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;
/**
* Holds an array of named Update statements for a particular entity bean type.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface NamedUpdates {
/**
* An array of named updates.
*/
NamedUpdate[] value();
};
@@ -0,0 +1,34 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify that the elements of a OneToMany are private owned.
* <p>
* This means that if they are removed from the List/Set/Map they will be
* deleted when their parent object is saved.
* </p>
* <p>
* This could also be described as deleting orphans - in that beans removed from
* the List/Set/Map will be deleted automatically when the parent bean is saved.
* They are considered 'orphans' when they have been removed from the collection
* in that they are no longer associated/linked to their parent bean.
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrivateOwned {
/**
* Set this to false if you don't want cascade REMOVE on this relationship.
* <p>
* That is, by default PrivateOwned implicitly adds a cascade REMOVE to the
* relationship and if you don't want that you need to set this to false.
* </p>
*/
boolean cascadeRemove() default true;
};
@@ -0,0 +1,24 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify explicit sql for multiple select statements. Need to use this if you
* have more than one SqlSelect for a given bean.
* <p>
* FUTURE: Support explicit sql for SqlInsert, SqlUpdate and SqlDelete.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Sql {
/**
* The sql select statements.
*/
SqlSelect[] select() default { @SqlSelect };
};
@@ -0,0 +1,298 @@
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.Query;
/**
* Specify an explicit sql select statement to use for querying an entity bean.
* <p>
* The reason for using explicit sql is that you want better control over the
* exact sql or sql that Ebean does not generate for you (such as group by,
* union, intersection, window functions, recursive queries).
* </p>
* <p>
* An example of two sql select queries deployed on the ReportTopic entity bean.
* The first one has no name specified so it becomes the default query. The
* second query extends the first adding a where clause with a named parameter.
* </p>
*
* <pre class="code">
* ...
* &#064;Entity
* &#064;Sql(select = {
* &#064;SqlSelect(query =
* &quot;select t.id, t.title, count(p.id) as score &quot;+
* &quot;from f_topic t &quot;+
* &quot;join f_topic_post p on p.topic_id = t.id &quot;+
* &quot;group by t.id, t.title&quot;),
* &#064;SqlSelect(
* name = &quot;with.title&quot;,
* extend = &quot;default&quot;,
* debug = true,
* where = &quot;title like :likeTitle&quot;)
* })
* public class ReportTopic
* &#064;Id Integer id;
* String title;
* Double score;
* ...
* </pre>
*
* <p>
* An example using the first "default" query.
* </p>
*
* <pre class="code">
*
* List&lt;ReportTopic&gt; list =
* Ebean.find(ReportTopic.class)
* .having().gt(&quot;score&quot;, 0)
* .findList();
*
* </pre>
*
* <p>
* The resulting sql, note the having clause has been added.
* </p>
*
* <pre class="code">
* select t.id, t.title, count(p.id) as score
* from f_topic t join f_topic_post p on p.topic_id = t.id
* group by t.id, t.title
* having count(p.id) &gt; ?
* </pre>
*
* <p>
* An example using the second query. Note the named parameter "likeTitle" must
* be set.
* </p>
*
* <pre class="code">
* List&lt;ReportTopic&gt; list =
* Ebean.find(ReportTopic.class, &quot;with.title&quot;)
* .set(&quot;likeTitle&quot;, &quot;a%&quot;)
* .findList();
* </pre>
*
* <p>
* Ebean tries to parse the sql in the query to determine 4 things
* <li>Location for inserting WHERE expressions (if required)</li>
* <li>Location for inserting HAVING expressions (if required)</li>
* <li>Mapping of columns to bean properties</li>
* <li>The order by clause</li>
* </p>
* <p>
* If Ebean is unable to parse out this information (perhaps because the sql
* contains multiple select from keywords etc) then you need to manually specify
* it.
* </p>
* <p>
* Insert ${where} or ${andWhere} into the location where Ebean can insert any
* expressions added to the where clause. Use ${andWhere} if the sql already has
* the WHERE keyword and Ebean will instead start with a AND keyword.
* </p>
* <p>
* Insert ${having} or ${andHaving} into the location where Ebean can insert any
* expressions added to the having clause. Use ${andHaving} if the sql already
* has a HAVING keyword and Ebean will instead start with a AND keyword.
* </p>
* <p>
* Use the columnMapping property if Ebean is unable to determine the columns
* and map them to bean properties.
* </p>
* <p>
* Example with ${andWhere} & ${having}.
* </p>
*
* <pre class="code">
* &#064;SqlSelect(
* name = &quot;explicit.where&quot;,
* query =
* &quot;select t.id, t.title, count(p.id) as score &quot;+
* &quot;from f_topic t, f_topic_post p &quot;+
* &quot;where p.topic_id = t.id ${andWhere} &quot;+
* &quot;group by t.id, t.title ${having}&quot;),
* </pre>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface SqlSelect {
/**
* The name of the query. If left blank this is assumed to be the default
* query for this bean type.
* <p>
* This will default to "default" and in that case becomes the default query
* used for the bean.
* </p>
*/
String name() default "default";
/**
* The tableAlias used when adding where expressions to the query.
*/
String tableAlias() default "";
/**
* The sql select statement.
* <p>
* If this query <em>extends</em> another then this string is appended to the
* parent query string. Often when using <em>extend</em> you will leave the
* query part blank and just specify a where and/or having clauses.
* </p>
* <p>
* This sql <em>CAN NOT</em> contain named parameters. You have to put these
* in the separate where and/or having sections.
* </p>
* <p>
* Ebean automatically tries to determine the location in the sql string for
* putting in additional where or having clauses. If Ebean is unable to
* successfully determine this then you have to explicitly specify these
* locations by including
* <em>${where} or ${andWhere} and ${having} or ${andHaving}</em> in the sql.
* </p>
* <p>
* <b>${where}</b> location of where clause (and will add WHERE ... ) <br/>
* Use this when there is no where clause in the sql. If expressions are added
* to the where clause Ebean will put them in at this location starting with
* the WHERE keyword.
* <p>
* <p>
* <b>${andWhere}</b> <br/>
* Use this instead of ${where} if there IS an existing where clause in the
* sql. Ebean will add the expressions starting with the AND keyword.
* <p>
* <b>${having}</b> location of having clause (and will add HAVING... ) <br/>
* </p>
* <p>
* <b>${andHaving}</b> <br/>
* Use this instead of ${having} when there IS an existing HAVING clause.
* Ebean will add the expressions starting with the AND keyword.
* </p>
* <p>
* You can include one of ${where} OR ${andWhere} but not both.
* </p>
* <p>
* You can include one of ${having} OR ${andHaving} but not both.
* </p>
*/
String query() default "";
/**
* Specify the name of a sql-select query that this one 'extends'.
* <p>
* When a query is extended the sql query contents are appended together. The
* where and having clauses are NOT appended but overridden.
* </p>
*/
String extend() default "";
/**
* Specify a where clause typically containing named parameters.
* <p>
* If a where clause is specified with named parameters then they will need to
* be set on the query via {@link Query#setParameter(String, Object)}.
* </p>
* <p>
* In the example below the query specifies a where clause that includes a
* named parameter "likeTitle".
* </p>
*
* <pre class="code">
* ...
* &#064;Entity
* &#064;Sql(select = {
* ...
* &#064;SqlSelect(
* name = &quot;with.title&quot;,
* extend = &quot;default&quot;,
* debug = true,
* where = &quot;title like :likeTitle&quot;)
* })
* public class ReportTopic
* ...
* </pre>
*
* <p>
* Example use of the above named query.
* </p>
*
* <pre class="code">
*
* Query&lt;ReportTopic&gt; query0 = Ebean.createQuery(ReportTopic.class, &quot;with.title&quot;);
*
* query0.set(&quot;likeTitle&quot;, &quot;Bana%&quot;);
*
* List&lt;ReportTopic&gt; list0 = query0.findList();
* </pre>
*
*/
String where() default "";
/**
* Specify a having clause typically containing named parameters.
* <p>
* If a having clause is specified with named parameters then they will need
* to be set on the query via {@link Query#setParameter(String, Object)}.
* </p>
*/
String having() default "";
/**
* (Optional) Explicitly specify column to property mapping.
* <p>
* This is required when Ebean is unable to parse the sql. This could occur if
* the sql contains multiple select keywords etc.
* </p>
* <p>
* Specify the columns and property names they map to in the format.
* </p>
*
* <pre class="code">
* column1 propertyName1, column2 propertyName2, ...
* </pre>
*
* <p>
* Optionally put a AS keyword between the column and property.
* </p>
*
* <pre class="code">
* // the AS keyword is optional
* column1 AS propertyName1, column2 propertyName2, ...
* </pre>
*
* <p>
* <b>column</b> should contain the table alias if there is one
* </p>
* <p>
* <b>propertyName</b> should match the property name.
* </p>
*
* <p>
* Example mapping 5 columns to properties.
* </p>
*
* <pre class="code">
* columnMapping=&quot;t.id, t.bug_body description, t.bug_title as title, count(p.id) as scoreValue&quot;,
* </pre>
*
* <p>
* Without this set Ebean will parse the sql looking for the select clause and
* try to map the columns to property names. It is expected that Ebean will
* not be able to successfully parse some sql and for those cases you should
* specify the column to property mapping explicitly.
* </p>
*
*/
String columnMapping() default "";
/**
* Set this to true to have debug output when Ebean parses the sql-select.
*/
boolean debug() default false;
};
@@ -0,0 +1,118 @@
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>
*
* <p>
* During development and testing you can set a debug level which will log the
* transaction begin, commit and rollback events so that you can easily confirm
* it is behaving as you would expect.
* </p>
*
* <pre class="code">
* ## in ebean.properties file
*
* ## Log transaction begins and ends etc
* ## (0=NoLogging 1=minimal ... 9=logAll)
* ebean.debug.transaction=3
*
* </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 {};
};
@@ -0,0 +1,34 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify the update mode for the specific entity type.
* <p>
* Control whether all 'loaded' properties are included in an Update or whether
* just properties that have changed will be included in the update.
* </p>
* <p>
* Note that the default can be set via ebean.properties.
* </p>
*
* <pre>
* ## Set to update all loaded properties
* ebean.updateChangesOnly=false
* </pre>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface UpdateMode {
/**
* Set to false if you want to include all the 'loaded' properties in the
* update. Otherwise, just the properties that have changed will be included
* in the update.
*/
boolean updateChangesOnly() default true;
};
@@ -0,0 +1,16 @@
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;
/**
* For a timestamp property that is set to the datetime when the entity was last
* updated.
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface UpdatedTimestamp {
};
@@ -0,0 +1,51 @@
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;
/**
* Add an Literal to add to the where clause when a many property (List, Set or
* Map) is loaded or refreshed.
*
* <pre class="code">
* // on a OneToMany property...
*
* &#064;OneToMany
* &#064;Where(clause = &quot;deleted='y'&quot;)
* List&lt;Topic&gt; topics;
* </pre>
*
* <p>
* Note that you can include "${ta}" as a place holder for the table alias if
* you need to include the table alias in the clause.
* </p>
*
* <pre class="code">
* // ... including the ${ta} table alias placeholder...
*
* &#064;OneToMany
* &#064;Where(clause = &quot;${ta}.deleted='y'&quot;)
* List&lt;Topic&gt; topics;
* </pre>
*
* <p>
* This will be added to the where clause when lazy loading the OneToMany
* property or when there is a join to that OneToMany property.
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Where {
/**
* The clause added to the lazy load query.
* <p>
* Note that you can include "${ta}" as a place holder for the table alias if
* you need to include the table alias in the clause.
* </p>
*/
String clause();
};
@@ -0,0 +1,14 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Extra deployment annotations</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Extra deployment annotations
<p>
Extra deployment annotations for entity beans.
</p>
</Body>
</HTML>
@@ -0,0 +1,248 @@
package com.avaje.ebean.bean;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.Query;
/**
* Lazy loading capable Maps, Lists and Sets.
* <p>
* This also includes the ability to listen for additions and removals to or
* from the Map Set or List. The purpose of gathering the additions and removals
* is to support persisting ManyToMany objects. The additions and removals
* become inserts and deletes from the intersection table.
* </p>
* <p>
* Technically this is <em>NOT</em> an extension of
* <em>java.util.Collection</em>. The reason being that java.util.Map is not a
* Collection. I realise this makes this name confusing so I apologise for that.
* </p>
*/
public interface BeanCollection<E> extends Serializable {
public enum ModifyListenMode {
/** The common mode */
NONE,
/** Mode used for PrivateOwned */
REMOVALS,
/** Mode used for ManyToMany relationships */
ALL
}
/**
* Return the bean that owns this collection.
*/
public Object getOwnerBean();
/**
* Return the bean property name this collection represents.
*/
public String getPropertyName();
/**
* Return the index position of this collection in the lazy/query loader.
* <p>
* Used for batch loading of collections.
* </p>
*/
public int getLoaderIndex();
/**
* Check after the lazy load that the underlying collection is not null
* (handle case where join to many not outer).
* <p>
* That is, if the collection was not loaded due to filterMany predicates etc
* then make sure the collection is set to empty.
* </p>
*/
public boolean checkEmptyLazyLoad();
/**
* Return the filter (if any) that was used in building this collection.
* <p>
* This is so that the filter can be applied on refresh.
* </p>
*/
public ExpressionList<?> getFilterMany();
/**
* Set the filter that was used in building this collection.
*/
public void setFilterMany(ExpressionList<?> filterMany);
/**
* Set when this collection is being loaded via a background thread.
* <p>
* Refer to {@link Query#setBackgroundFetchAfter(int)}
* </p>
*/
public void setBackgroundFetch(Future<Integer> future);
/**
* Wait for the fetch to complete with a given timeout.
* <p>
* Refer to {@link Query#setBackgroundFetchAfter(int)}
* </p>
*/
public void backgroundFetchWait(long wait, TimeUnit timeUnit);
/**
* Wait for the fetch to complete.
* <p>
* Refer to {@link Query#setBackgroundFetchAfter(int)}
* </p>
*/
public void backgroundFetchWait();
/**
* Set a listener to be notified when the BeanCollection is first touched.
*/
public void setBeanCollectionTouched(BeanCollectionTouched notify);
/**
* Set the loader that will be used to lazy/query load this collection.
*/
public void setLoader(int beanLoaderIndex, BeanCollectionLoader beanLoader);
/**
* Set to true if you want the BeanCollection to be treated as read only. This
* means no elements can be added or removed etc.
*/
public void setReadOnly(boolean readOnly);
/**
* Return true if the collection should be treated as readOnly and no elements
* can be added or removed etc.
*/
public boolean isReadOnly();
/**
* Add the bean to the collection.
* <p>
* This is disallowed for BeanMap.
* </p>
*/
public void internalAdd(Object bean);
/**
* Returns the underlying List Set or Map object.
*/
public Object getActualCollection();
/**
* Return the number of elements in the List Set or Map.
*/
public int size();
/**
* Return true if the List Set or Map is empty.
*/
public boolean isEmpty();
/**
* Returns the underlying collection of beans from the Set, Map or List.
*/
public Collection<E> getActualDetails();
/**
* Returns the underlying entries so for Maps this is a collection of
* Map.Entry.
* <p>
* For maps this returns the entrySet as we need the keys of the map.
* </p>
*/
public Collection<?> getActualEntries();
/**
* Set to true if maxRows was hit and there are actually more rows available.
* <p>
* Can be used by client code that is paging through results using
* setFirstRow() setMaxRows(). If this returns true then the client can
* display a 'next' button etc.
* </p>
*/
public boolean hasMoreRows();
/**
* Set to true when maxRows is hit but there are actually more rows available.
* This is set so that client code knows that there is more data available.
*/
public void setHasMoreRows(boolean hasMoreRows);
/**
* Returns true if the fetch has finished. False if the fetch is continuing in
* a background thread.
*/
public boolean isFinishedFetch();
/**
* Set to true when a fetch has finished. Used when a fetch continues in the
* background.
*/
public void setFinishedFetch(boolean finishedFetch);
/**
* return true if there are real rows held. Return false is this is using
* Deferred fetch to lazy load the rows and the rows have not yet been
* fetched.
*/
public boolean isPopulated();
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
public boolean isReference();
/**
* Set modify listening on or off. This is used to keep track of objects that
* have been added to or removed from the list set or map.
* <p>
* This is required only for ManyToMany collections. The additions and
* deletions are used to insert or delete entries from the intersection table.
* Otherwise modifyListening is false.
* </p>
*/
public void setModifyListening(ModifyListenMode modifyListenMode);
/**
* Add an object to the additions list.
* <p>
* This will potentially end up as an insert into a intersection table for a
* ManyToMany.
* </p>
*/
public void modifyAddition(E bean);
/**
* Add an object to the deletions list.
* <p>
* This will potentially end up as an delete from an intersection table for a
* ManyToMany.
* </p>
*/
public void modifyRemoval(Object bean);
/**
* Return the list of objects added to the list set or map. These will used to
* insert rows into the intersection table of a ManyToMany.
*/
public Set<E> getModifyAdditions();
/**
* Return the list of objects removed from the list set or map. These will
* used to delete rows from the intersection table of a ManyToMany.
*/
public Set<E> getModifyRemovals();
/**
* Reset the set of additions and deletions. This is called after the
* additions and removals have been processed.
*/
public void modifyReset();
}
@@ -1,16 +1,16 @@
package io.ebean.bean;
/**
* Interface to define the addition of a bean to the underlying collection.
* <p>
* For maps this takes into account the map key. For List and Set this simply
* adds the bean to the underlying list or set.
* </p>
*/
public interface BeanCollectionAdd {
/**
* Add a loaded bean to the collection.
*/
void addEntityBean(EntityBean bean);
}
package com.avaje.ebean.bean;
/**
* Interface to define the addition of a bean to the underlying collection.
* <p>
* For maps this takes into account the map key. For List and Set this simply
* adds the bean to the underlying list or set.
* </p>
*/
public interface BeanCollectionAdd {
/**
* Add a loaded bean to the collection.
*/
public void addBean(Object bean);
}
@@ -0,0 +1,21 @@
package com.avaje.ebean.bean;
/**
* Loads a entity bean collection.
* <p>
* Typically invokes lazy loading for a single or batch of collections.
* </p>
*/
public interface BeanCollectionLoader {
/**
* Return the name of the associated EbeanServer.
*/
public String getName();
/**
* Invoke the lazy loading for this bean collection.
*/
public void loadMany(BeanCollection<?> collection, boolean onlyIds);
}
@@ -0,0 +1,20 @@
package com.avaje.ebean.bean;
/**
* Used to specify a listener to be notified when a BeanCollection is first
* used.
* <p>
* To use this you can set a BeanCollectionTouched onto a BeanCollection before
* it has been used. When the BeanCollection is first used by the client code
* then the BeanCollectionTouched is notified. It can only be notified once.
* </p>
*
* @author rbygrave
*/
public interface BeanCollectionTouched {
/**
* Notify the listener that the bean collection has been used.
*/
public void notifyTouched(BeanCollection<?> c);
}
@@ -0,0 +1,21 @@
package com.avaje.ebean.bean;
/**
* Loads a entity bean.
* <p>
* Typically invokes lazy loading for a single or batch of entity beans.
* </p>
*/
public interface BeanLoader {
/**
* Return the name of the associated EbeanServer.
*/
public String getName();
/**
* Invoke the lazy loading for this bean.
*/
public void loadBean(EntityBeanIntercept ebi);
}
@@ -0,0 +1,118 @@
package com.avaje.ebean.bean;
import java.io.Serializable;
import java.util.Arrays;
/**
* Represent the call stack (stack trace elements).
* <p>
* Used with a query to identify a CallStackQuery for AutoFetch automatic query
* tuning.
* </p>
* <p>
* This is used so that a single query called from different methods can be
* tuned for each different call stack.
* </p>
* <p>
* Note the call stack is trimmed to remove the common ebean internal elements.
* </p>
*/
public final class CallStack implements Serializable {
private static final long serialVersionUID = -8590644046907438579L;
private final String zeroHash;
private final String pathHash;
private final StackTraceElement[] callStack;
public CallStack(StackTraceElement[] callStack) {
this.callStack = callStack;
this.zeroHash = enc(callStack[0].hashCode());
int hc = 0;
for (int i = 1; i < callStack.length; i++) {
hc = 31 * hc + callStack[i].hashCode();
}
this.pathHash = enc(hc);
}
public int hashCode() {
int hc = 0;
for (int i = 0; i < callStack.length; i++) {
hc = 31 * hc + callStack[i].hashCode();
}
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CallStack)) {
return false;
}
CallStack e = (CallStack) obj;
return Arrays.equals(callStack, e.callStack);
}
/**
* Return the first element of the call stack.
*/
public StackTraceElement getFirstStackTraceElement() {
return callStack[0];
}
/**
* Return the call stack.
*/
public StackTraceElement[] getCallStack() {
return callStack;
}
/**
* Return the hash for the first stack element.
*/
public String getZeroHash() {
return zeroHash;
}
/**
* Return the hash for the stack elements (excluding first stack element).
*/
public String getPathHash() {
return pathHash;
}
public String toString() {
return zeroHash + ":" + pathHash + ":" + callStack[0];
}
public String getOriginKey(int queryHash) {
return zeroHash + "." + enc(queryHash) + "." + pathHash;
}
private static final int radix = 1 << 6;
private static final int mask = radix - 1;
/**
* Convert the integer to unsigned base 64.
*/
public static String enc(int i) {
char[] buf = new char[32];
int charPos = 32;
do {
buf[--charPos] = intToBase64[i & mask];
i >>>= 6;
} while (i != 0);
return new String(buf, charPos, (32 - charPos));
}
private static final char intToBase64[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
}
@@ -1,4 +1,4 @@
package io.ebean.bean;
package com.avaje.ebean.bean;
/**
* Marker interface for classes enhanced to support Transactional methods.
@@ -0,0 +1,127 @@
package com.avaje.ebean.bean;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
/**
* Bean that is aware of EntityBeanIntercept.
* <p>
* This interface and implementation of these methods is added to Entity Beans
* via instrumentation. These methods have a funny _ebean_ prefix to avoid any
* clash with normal methods these beans would have. These methods are not for
* general application consumption.
* </p>
*/
public interface EntityBean extends Serializable {
/**
* Return the enhancement marker value.
* <p>
* This is the class name of the enhanced class and used to check that all
* entity classes are enhanced (specifically not just a super class).
* </p>
*/
public String _ebean_getMarker();
/**
* Create and return a new entity bean instance.
*/
public Object _ebean_newInstance();
/**
* Add a PropertyChangeListener to this bean.
*/
public void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Remove a PropertyChangeListener from this bean.
*/
public void removePropertyChangeListener(PropertyChangeListener listener);
/**
* Generated method that sets the loaded state on all the embedded beans on
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
*/
public void _ebean_setEmbeddedLoaded();
/**
* Return true if any embedded beans are new or dirty.
*/
public boolean _ebean_isEmbeddedNewOrDirty();
/**
* Return the intercept for this object.
*/
public EntityBeanIntercept _ebean_getIntercept();
/**
* Similar to _ebean_getIntercept() except it checks to see if the intercept
* field is null and will create it if required.
* <p>
* This is really only required when transientInternalFields=true as an
* enhancement option. In this case the intercept field is transient and will
* be null after a bean has been deserialised.
* </p>
* <p>
* This transientInternalFields=true option was to support some serialization
* frameworks that can't take into account our ebean fields.
* </p>
*/
public EntityBeanIntercept _ebean_intercept();
/**
* Create a copy of this entity bean.
* <p>
* This occurs when a bean is changed. The copy represents the bean as it was
* initially (oldValues) before any changes where made. This is used for
* optimistic concurrency control.
* </p>
*/
public Object _ebean_createCopy();
/**
* Return the fields in their index order.
*/
public String[] _ebean_getFieldNames();
/**
* Set the value of a field of an entity bean of this type.
* <p>
* Note that using this method bypasses any interception that otherwise occurs
* on entity beans. That means lazy loading and oldValues creation.
* </p>
*
* @param fieldIndex
* the index of the field
* @param entityBean
* the entityBean of this type to modify
* @param value
* the value to set
*/
public void _ebean_setField(int fieldIndex, Object entityBean, Object value);
/**
* Set the field value with interception.
*/
public void _ebean_setFieldIntercept(int fieldIndex, Object entityBean, Object value);
/**
* Return the value of a field from an entity bean of this type.
* <p>
* Note that using this method bypasses any interception that otherwise occurs
* on entity beans. That means lazy loading.
* </p>
*
* @param fieldIndex
* the index of the field
* @param entityBean
* the entityBean to get the value from
*/
public Object _ebean_getField(int fieldIndex, Object entityBean);
/**
* Return the field value with interception.
*/
public Object _ebean_getFieldIntercept(int fieldIndex, Object entityBean);
}
@@ -0,0 +1,904 @@
package com.avaje.ebean.bean;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.Ebean;
/**
* This is the object added to every entity bean using byte code enhancement.
* <p>
* This provides the mechanisms to support deferred fetching of reference beans
* and oldValues generation for concurrency checking.
* </p>
*/
public final class EntityBeanIntercept implements Serializable {
private static final long serialVersionUID = -3664031775464862648L;
private transient NodeUsageCollector nodeUsageCollector;
private transient PropertyChangeSupport pcs;
private transient PersistenceContext persistenceContext;
private transient BeanLoader beanLoader;
private int beanLoaderIndex;
private String ebeanServerName;
/**
* The actual entity bean that 'owns' this intercept.
*/
private EntityBean owner;
/**
* The parent bean by relationship (1-1 or 1-M).
*/
private Object parentBean;
/**
* true if the bean properties have been loaded. false if it is a reference
* bean (will lazy load etc).
*/
private volatile boolean loaded;
/**
* Flag set to disable lazy loading - typically for SQL "report" type entity
* beans.
*/
private boolean disableLazyLoad;
/**
* Flag set when lazy loading failed due to the underlying bean being deleted
* in the DB.
*/
private boolean lazyLoadFailure;
/**
* Set true when loaded or reference. Used to bypass interception when created
* by user code.
*/
private boolean intercepting;
/**
* The state of the Bean (DEFAULT,UDPATE,READONLY,SHARED).
*/
private boolean readOnly;
/**
* The bean as it was before it was modified. Null if no non-transient setters
* have been called.
*/
private Object oldValues;
/**
* Used when a bean is partially filled.
*/
private volatile Set<String> loadedProps;
/**
* Set of changed properties.
*/
private HashSet<String> changedProps;
private String lazyLoadProperty;
/**
* Create a intercept with a given entity.
* <p>
* Refer to agent ProxyConstructor.
* </p>
*/
public EntityBeanIntercept(Object owner) {
this.owner = (EntityBean) owner;
}
/**
* Copy the internal state of the intercept to another intercept.
*/
public void copyStateTo(EntityBeanIntercept dest) {
dest.loadedProps = loadedProps;
dest.ebeanServerName = ebeanServerName;
if (loaded) {
dest.setLoaded();
}
}
/**
* Return the 'owning' entity bean.
*/
public EntityBean getOwner() {
return owner;
}
public String toString() {
if (!loaded) {
return "Reference...";
}
return "OldValues: " + oldValues;
}
/**
* Return the persistenceContext.
*/
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
/**
* Set the persistenceContext.
*/
public void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
/**
* Add a property change listener for this entity bean.
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (pcs == null) {
pcs = new PropertyChangeSupport(owner);
}
pcs.addPropertyChangeListener(listener);
}
/**
* Add a property change listener for this entity bean for a specific
* property.
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (pcs == null) {
pcs = new PropertyChangeSupport(owner);
}
pcs.addPropertyChangeListener(propertyName, listener);
}
/**
* Remove a property change listener for this entity bean.
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
if (pcs != null) {
pcs.removePropertyChangeListener(listener);
}
}
/**
* Remove a property change listener for this entity bean for a specific
* property.
*/
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (pcs != null) {
pcs.removePropertyChangeListener(propertyName, listener);
}
}
/**
* Turn on profile collection.
*/
public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
this.nodeUsageCollector = usageCollector;
}
/**
* Return the parent bean (by relationship).
*/
public Object getParentBean() {
return parentBean;
}
/**
* Special case for a OneToOne, Set the parent bean (by relationship). This is
* the owner of a 1-1.
*/
public void setParentBean(Object parentBean) {
this.parentBean = parentBean;
}
/**
* Return the index position for batch loading via BeanLoader.
*/
public int getBeanLoaderIndex() {
return beanLoaderIndex;
}
/**
* Set Lazy Loading by ebeanServerName.
* <p>
* This is for reference beans created by themselves.
* </p>
*/
public void setBeanLoaderByServerName(String ebeanServerName) {
this.beanLoaderIndex = 0;
this.beanLoader = null;
this.ebeanServerName = ebeanServerName;
}
/**
* Set the BeanLoader for general lazy loading.
*/
public void setBeanLoader(int index, BeanLoader beanLoader, PersistenceContext ctx) {
this.beanLoaderIndex = index;
this.beanLoader = beanLoader;
this.persistenceContext = ctx;
this.ebeanServerName = beanLoader.getName();
}
/**
* Return true if this bean has been directly modified (it has oldValues) or
* if any embedded beans are either new or dirty (and hence need saving).
*/
public boolean isDirty() {
if (oldValues != null) {
return true;
}
// need to check all the embedded beans
return owner._ebean_isEmbeddedNewOrDirty();
}
/**
* Return true if this entity bean is new and not yet saved.
*/
public boolean isNew() {
return !intercepting && !loaded;
}
/**
* Return true if the entity bean is new or dirty (and should be saved).
*/
public boolean isNewOrDirty() {
return isNew() || isDirty();
}
/**
* Return true if the entity is a reference.
*/
public boolean isReference() {
return intercepting && !loaded;
}
/**
* Set this as a reference object.
*/
public void setReference() {
this.loaded = false;
this.intercepting = true;
}
/**
* Return the old values used for ConcurrencyMode.ALL.
*/
public Object getOldValues() {
return oldValues;
}
/**
* Return true if the bean should be treated as readOnly. If a setter method
* is called when it is readOnly an Exception is thrown.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set the readOnly status. If readOnly then calls to setter methods through
* an exception.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Return true if the bean currently has interception on.
* <p>
* With interception on the bean will invoke lazy loading and dirty checking.
* </p>
*/
public boolean isIntercepting() {
return intercepting;
}
/**
* Turn interception off or on.
* <p>
* This is to support custom serialisation mechanisms that just read all the
* properties on the bean.
* </p>
*
*/
public void setIntercepting(boolean intercepting) {
this.intercepting = intercepting;
}
/**
* Return true if the entity has been loaded.
*/
public boolean isLoaded() {
return loaded;
}
/**
* Set the loaded state to true.
* <p>
* Calls to setter methods after the bean is loaded can result in 'Old Values'
* being created to support ConcurrencyMode.ALL
* </p>
* <p>
* Worth noting that this is also set after a insert/update. By doing so it
* 'resets' the bean for making further changes and saving again.
* </p>
*/
public void setLoaded() {
this.loaded = true;
this.oldValues = null;
this.intercepting = true;
this.owner._ebean_setEmbeddedLoaded();
this.lazyLoadProperty = null;
this.changedProps = null;
}
/**
* When finished loading for lazy or refresh on an already partially populated
* bean.
*/
public void setLoadedLazy() {
this.loaded = true;
this.intercepting = true;
this.lazyLoadProperty = null;
}
/**
* Mark this bean as having failed lazy loading due to the underlying row
* being deleted.
* <p>
* We mark the bean this way rather than immediately fail as we might be batch
* lazy loading and this bean might not be used by the client code at all.
* Instead we will fail as soon as the client code tries to use this bean.
* </p>
*/
public void setLazyLoadFailure() {
this.lazyLoadFailure = true;
}
/**
* Return true if the bean is marked as having failed lazy loading.
*/
public boolean isLazyLoadFailure() {
return lazyLoadFailure;
}
/**
* Return true if lazy loading is disabled.
*/
public boolean isDisableLazyLoad() {
return disableLazyLoad;
}
/**
* Set true to turn off lazy loading.
* <p>
* Typically used to disable lazy loading on SQL based report beans.
* </p>
*/
public void setDisableLazyLoad(boolean disableLazyLoad) {
this.disableLazyLoad = disableLazyLoad;
}
/**
* Set the loaded status for the embedded bean.
*/
public void setEmbeddedLoaded(Object embeddedBean) {
if (embeddedBean instanceof EntityBean) {
EntityBean eb = (EntityBean) embeddedBean;
eb._ebean_getIntercept().setLoaded();
}
}
/**
* Return true if the embedded bean is new or dirty and hence needs saving.
*/
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
if (embeddedBean == null) {
// if it was previously set then the owning bean would
// have oldValues containing the previous embedded bean
return false;
}
if (embeddedBean instanceof EntityBean) {
return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();
} else {
// non-enhanced so must assume it is new and needs to be saved
return true;
}
}
/**
* Set the property names for a partially loaded bean.
*
* @param loadedPropertyNames
* the names of the loaded properties
*/
public void setLoadedProps(Set<String> loadedPropertyNames) {
this.loadedProps = loadedPropertyNames;
}
/**
* Return the set of property names for a partially loaded bean.
*/
public Set<String> getLoadedProps() {
return loadedProps;
}
/**
* Return the set of property names for changed properties.
*/
public Set<String> getChangedProps() {
return changedProps;
}
/**
* Return the property read or write that triggered the lazy load.
*/
public String getLazyLoadProperty() {
return lazyLoadProperty;
}
/**
* Load the bean when it is a reference.
*/
protected void loadBean(String loadProperty) {
synchronized (this) {
if (beanLoader == null) {
BeanLoader serverLoader = (BeanLoader) Ebean.getServer(ebeanServerName);
if (serverLoader == null) {
throw new PersistenceException("Server [" + ebeanServerName + "] was not found?");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, serverLoader);
return;
}
}
synchronized (beanLoader) {
// Lazy loading using LoadBeanContext which supports batch loading
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
loadBeanInternal(loadProperty, beanLoader);
}
}
/**
* Invoke the lazy loading. This method is synchronised externally.
*/
private void loadBeanInternal(String loadProperty, BeanLoader loader) {
if (loaded && (loadedProps == null || loadedProps.contains(loadProperty))) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (disableLazyLoad) {
loaded = true;
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("Bean has been deleted - lazy loading failed");
}
if (lazyLoadProperty == null) {
lazyLoadProperty = loadProperty;
if (nodeUsageCollector != null) {
nodeUsageCollector.setLoadProperty(lazyLoadProperty);
}
loader.loadBean(this);
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Bean has been deleted - lazy loading failed");
}
// bean should be loaded and intercepting now. setLoaded() has
// been called by the lazy loading mechanism
}
}
/**
* Create a copy of the bean as it is now. This is the original or 'old
* values' prior to any modification. This is used to perform concurrency
* testing.
*/
protected void createOldValues() {
oldValues = owner._ebean_createCopy();
if (nodeUsageCollector != null) {
nodeUsageCollector.setModified();
}
}
/**
* Helper method to check if two objects are equal.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected boolean areEqual(Object obj1, Object obj2) {
if (obj1 == null) {
return (obj2 == null);
}
if (obj2 == null) {
return false;
}
if (obj1 == obj2) {
return true;
}
if (obj1 instanceof BigDecimal) {
// Use comparable for BigDecimal as equals
// uses scale in comparison...
if (obj2 instanceof BigDecimal) {
Comparable com1 = (Comparable) obj1;
return (com1.compareTo(obj2) == 0);
} else {
return false;
}
}
if (obj1 instanceof URL) {
// use the string format to determine if dirty
return obj1.toString().equals(obj2.toString());
}
return obj1.equals(obj2);
}
/**
* Method that is called prior to a getter method on the actual entity.
* <p>
* This checks if the bean is a reference and should be loaded.
* </p>
*/
public void preGetter(String propertyName) {
if (!intercepting) {
return;
}
if (!loaded) {
loadBean(propertyName);
} else if (loadedProps != null && !loadedProps.contains(propertyName)) {
loadBean(propertyName);
}
if (nodeUsageCollector != null && loaded) {
nodeUsageCollector.addUsed(propertyName);
}
}
/**
* Called for "enhancement" postSetter processing. This is around a PUTFIELD
* so no need to check the newValue afterwards.
*/
public void postSetter(PropertyChangeEvent event) {
if (pcs != null && event != null) {
pcs.firePropertyChange(event);
}
}
/**
* Called for "subclassed" postSetter processing. Here the newValue has to be
* re-fetched (and passed into this method) in case there is code inside the
* setter that further mutates the value.
*/
public void postSetter(PropertyChangeEvent event, Object newValue) {
if (pcs != null && event != null) {
if (newValue != null && newValue.equals(event.getNewValue())) {
pcs.firePropertyChange(event);
} else {
pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
}
}
}
/**
* OneToMany and ManyToMany don't have any interception so just check for
* PropertyChangeSupport.
*/
public PropertyChangeEvent preSetterMany(boolean interceptField, String propertyName,
Object oldValue, Object newValue) {
// skip setter interception on many's
if (pcs != null) {
return new PropertyChangeEvent(owner, propertyName, oldValue, newValue);
} else {
return null;
}
}
private final void addDirty(String propertyName) {
if (!intercepting) {
return;
}
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
if (loaded) {
if (oldValues == null) {
// first time this bean is being made dirty
createOldValues();
}
if (changedProps == null) {
changedProps = new HashSet<String>();
}
changedProps.add(propertyName);
}
}
/**
* Check to see if the values are not equal. If they are not equal then create
* the old values for use with ConcurrencyMode.ALL.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, Object oldValue,
Object newValue) {
boolean changed = !areEqual(oldValue, newValue);
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, oldValue, newValue);
}
return null;
}
/**
* Check for primitive boolean.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, boolean oldValue,
boolean newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Boolean.valueOf(oldValue),
Boolean.valueOf(newValue));
}
return null;
}
/**
* Check for primitive int.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, int oldValue,
int newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Integer.valueOf(oldValue),
Integer.valueOf(newValue));
}
return null;
}
/**
* long.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, long oldValue,
long newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Long.valueOf(oldValue),
Long.valueOf(newValue));
}
return null;
}
/**
* double.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, double oldValue,
double newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Double.valueOf(oldValue),
Double.valueOf(newValue));
}
return null;
}
/**
* float.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, float oldValue,
float newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Float.valueOf(oldValue),
Float.valueOf(newValue));
}
return null;
}
/**
* short.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, short oldValue,
short newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Short.valueOf(oldValue),
Short.valueOf(newValue));
}
return null;
}
/**
* char.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, char oldValue,
char newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Character.valueOf(oldValue),
Character.valueOf(newValue));
}
return null;
}
/**
* char.
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, byte oldValue,
byte newValue) {
boolean changed = oldValue != newValue;
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, Byte.valueOf(oldValue),
Byte.valueOf(newValue));
}
return null;
}
/**
* char[].
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, char[] oldValue,
char[] newValue) {
boolean changed = !areEqualChars(oldValue, newValue);
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, oldValue, newValue);
}
return null;
}
/**
* byte[].
*/
public PropertyChangeEvent preSetter(boolean intercept, String propertyName, byte[] oldValue,
byte[] newValue) {
boolean changed = !areEqualBytes(oldValue, newValue);
if (intercept && changed) {
addDirty(propertyName);
}
if (changed && pcs != null) {
return new PropertyChangeEvent(owner, propertyName, oldValue, newValue);
}
return null;
}
private static boolean areEqualBytes(byte[] b1, byte[] b2) {
if (b1 == null) {
return (b2 == null);
} else if (b2 == null) {
return false;
} else if (b1 == b2) {
return true;
} else if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
private static boolean areEqualChars(char[] b1, char[] b2) {
if (b1 == null) {
return (b2 == null);
} else if (b2 == null) {
return false;
} else if (b1 == b2) {
return true;
} else if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
}
@@ -1,127 +1,126 @@
package io.ebean.bean;
import java.lang.ref.WeakReference;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Collects profile information for a bean (or reference/proxy bean) at a given node.
* <p>
* The node identifies the location of the bean in the object graph.
* </p>
* <p>
* It has to use a weak reference so as to ensure that it does not stop the
* associated bean from being garbage collected.
* </p>
*/
public final class NodeUsageCollector {
/**
* The point in the object graph for a specific query and call stack point.
*/
private final ObjectGraphNode node;
/**
* Weak to allow garbage collection.
*/
private final WeakReference<NodeUsageListener> managerRef;
/**
* The properties used at this profile point.
*/
private final Set<String> used = new LinkedHashSet<>();
/**
* set to true if the bean is modified (setter called)
*/
private boolean modified;
/**
* The property that cause a reference to lazy load.
*/
private String loadProperty;
public NodeUsageCollector(ObjectGraphNode node, WeakReference<NodeUsageListener> managerRef) {
this.node = node;
// weak to allow garbage collection.
this.managerRef = managerRef;
}
/**
* The bean has been modified by a setter method.
*/
public void setModified() {
modified = true;
}
/**
* Add the name of a property that has been used.
*/
public void addUsed(String property) {
used.add(property);
}
/**
* The property that invoked a lazy load.
*/
public void setLoadProperty(String loadProperty) {
this.loadProperty = loadProperty;
}
/**
* Publish the usage info to the manager.
*/
private void publishUsageInfo() {
NodeUsageListener manager = managerRef.get();
if (manager != null) {
manager.collectNodeUsage(this);
}
}
/**
* publish the collected usage information when garbage collection occurs.
*/
@Override
protected void finalize() throws Throwable {
publishUsageInfo();
super.finalize();
}
/**
* Return the associated node which identifies the location in the object
* graph of the bean/reference.
*/
public ObjectGraphNode getNode() {
return node;
}
/**
* Return true if no properties where used.
*/
public boolean isEmpty() {
return used.isEmpty();
}
/**
* Return the set of used properties.
*/
public Set<String> getUsed() {
return used;
}
/**
* Return true if the bean was modified by a setter.
*/
public boolean isModified() {
return modified;
}
public String getLoadProperty() {
return loadProperty;
}
@Override
public String toString() {
return node + " read:" + used + " modified:" + modified;
}
}
package com.avaje.ebean.bean;
import java.lang.ref.WeakReference;
import java.util.HashSet;
/**
* Collects profile information for a bean (or reference/proxy bean) at a given
* AutoFetchNode.
* <p>
* The AutoFetchNode identifies the location of the bean in the object graph.
* </p>
* <p>
* It has to use a weak reference so as to ensure that it does not stop the
* associated bean from being garbage collected.
* </p>
*/
public final class NodeUsageCollector {
/**
* The point in the object graph for a specific query and call stack point.
*/
private final ObjectGraphNode node;
/**
* Weak to allow garbage collection.
*/
private final WeakReference<NodeUsageListener> managerRef;
/**
* The properties used at this profile point.
*/
private final HashSet<String> used = new HashSet<String>();
/**
* set to true if the bean is modified (setter called)
*/
private boolean modified;
/**
* The property that cause a reference to lazy load.
*/
private String loadProperty;
public NodeUsageCollector(ObjectGraphNode node, WeakReference<NodeUsageListener> managerRef) {
this.node = node;
// weak to allow garbage collection.
this.managerRef = managerRef;
}
/**
* The bean has been modified by a setter method.
*/
public void setModified() {
modified = true;
}
/**
* Add the name of a property that has been used.
*/
public void addUsed(String property) {
used.add(property);
}
/**
* The property that invoked a lazy load.
*/
public void setLoadProperty(String loadProperty) {
this.loadProperty = loadProperty;
}
/**
* Publish the usage info to the manager.
*/
private void publishUsageInfo() {
NodeUsageListener manager = managerRef.get();
if (manager != null) {
manager.collectNodeUsage(this);
}
}
/**
* publish the collected usage information when garbage collection occurs.
*/
@Override
protected void finalize() throws Throwable {
publishUsageInfo();
super.finalize();
}
/**
* Return the associated node which identifies the location in the object
* graph of the bean/reference.
*/
public ObjectGraphNode getNode() {
return node;
}
/**
* Return true if no properties where used.
*/
public boolean isEmpty() {
return used.isEmpty();
}
/**
* Return the set of used properties.
*/
public HashSet<String> getUsed() {
return used;
}
/**
* Return true if the bean was modified by a setter.
*/
public boolean isModified() {
return modified;
}
public String getLoadProperty() {
return loadProperty;
}
public String toString() {
return node + " read:" + used + " modified:" + modified;
}
}
@@ -0,0 +1,16 @@
package com.avaje.ebean.bean;
/**
* Collects the profile information.
*/
public interface NodeUsageListener {
/**
* Collect node usage "profiling" information.
* <p>
* This is the properties that are used for a given bean in the object graph.
* This information is used by autoFetch to tune queries.
* </p>
*/
public void collectNodeUsage(NodeUsageCollector collector);
}
@@ -1,92 +1,88 @@
package io.ebean.bean;
import java.io.Serializable;
/**
* Identifies a unique node of an object graph.
* <p>
* It represents a location relative to the root of an object graph and specific
* to a query and call stack hash.
* </p>
*/
public final class ObjectGraphNode implements Serializable {
private static final long serialVersionUID = 2087081778650228996L;
/**
* Identifies the origin.
*/
private final ObjectGraphOrigin originQueryPoint;
/**
* The path relative to the root.
*/
private final String path;
/**
* Create at a sub level.
*/
public ObjectGraphNode(ObjectGraphNode parent, String path) {
this.originQueryPoint = parent.getOriginQueryPoint();
this.path = parent.getChildPath(path);
}
/**
* Create an the root level.
*/
public ObjectGraphNode(ObjectGraphOrigin originQueryPoint, String path) {
this.originQueryPoint = originQueryPoint;
this.path = path;
}
/**
* Return the origin query point.
*/
public ObjectGraphOrigin getOriginQueryPoint() {
return originQueryPoint;
}
private String getChildPath(String childPath) {
if (path == null) {
return childPath;
} else if (childPath == null) {
return path;
} else {
return path + "." + childPath;
}
}
/**
* Return the path relative to the root.
*/
public String getPath() {
return path;
}
@Override
public String toString() {
return "origin:" + originQueryPoint + " path[" + path + "]";
}
@Override
public int hashCode() {
int hc = 92821 * originQueryPoint.hashCode();
hc = 92821 * hc + (path == null ? 0 : path.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphNode)) {
return false;
}
ObjectGraphNode e = (ObjectGraphNode) obj;
//noinspection StringEquality
return ((e.path == path) || (e.path != null && e.path.equals(path)))
&& e.originQueryPoint.equals(originQueryPoint);
}
}
package com.avaje.ebean.bean;
import java.io.Serializable;
/**
* Identifies a unique node of an object graph.
* <p>
* It represents a location relative to the root of an object graph and specific
* to a query and call stack hash.
* </p>
*/
public final class ObjectGraphNode implements Serializable {
private static final long serialVersionUID = 2087081778650228996L;
/**
* Identifies the origin.
*/
private final ObjectGraphOrigin originQueryPoint;
/**
* The path relative to the root.
*/
private final String path;
/**
* Create at a sub level.
*/
public ObjectGraphNode(ObjectGraphNode parent, String path) {
this.originQueryPoint = parent.getOriginQueryPoint();
this.path = parent.getChildPath(path);
}
/**
* Create an the root level.
*/
public ObjectGraphNode(ObjectGraphOrigin originQueryPoint, String path) {
this.originQueryPoint = originQueryPoint;
this.path = path;
}
/**
* Return the origin query point.
*/
public ObjectGraphOrigin getOriginQueryPoint() {
return originQueryPoint;
}
private String getChildPath(String childPath) {
if (path == null) {
return childPath;
} else if (childPath == null) {
return path;
} else {
return path + "." + childPath;
}
}
/**
* Return the path relative to the root.
*/
public String getPath() {
return path;
}
public String toString() {
return "origin:" + originQueryPoint + " path[" + path+"]";
}
public int hashCode() {
int hc = 31 * originQueryPoint.hashCode();
hc = 31 * hc + (path == null ? 0 : path.hashCode());
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphNode)) {
return false;
}
ObjectGraphNode e = (ObjectGraphNode) obj;
return ((e.path == path) || (e.path != null && e.path.equals(path)))
&& e.originQueryPoint.equals(originQueryPoint);
}
}
@@ -1,87 +1,84 @@
package io.ebean.bean;
import java.io.Serializable;
/**
* Represents a "origin" of an ORM object graph. This combines the call stack
* and query plan hash.
* <p>
* The call stack is included so that the query can have different tuned fetches
* for each unique call stack. For example, a query to fetch a customer could be
* called by three different methods and each can be treated as a separate
* origin point (and autoTune can tune each one separately).
* </p>
*/
public final class ObjectGraphOrigin implements Serializable {
private static final long serialVersionUID = 410937765287968708L;
private final CallStack callStack;
private final String beanType;
private final int queryHash;
private final String key;
public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) {
this.callStack = callStack;
this.beanType = beanType;
this.queryHash = queryHash;
this.key = callStack.getOriginKey(queryHash);
}
/**
* The key includes the queryPlan hash and the callStack hash. This becomes
* the unique identifier for a query point.
*/
public String getKey() {
return key;
}
/**
* The type of bean the query is fetching.
*/
public String getBeanType() {
return beanType;
}
/**
* The call stack involved.
*/
public CallStack getCallStack() {
return callStack;
}
public String getFirstStackElement() {
return callStack.getFirstStackTraceElement().toString();
}
@Override
public String toString() {
return "key[" + key + "] type[" + beanType + "] " + callStack.getFirstStackTraceElement() + " ";
}
@Override
public int hashCode() {
int hc = 92821 * callStack.hashCode();
hc = 92821 * hc + beanType.hashCode();
hc = 92821 * hc + queryHash;
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphOrigin)) {
return false;
}
ObjectGraphOrigin e = (ObjectGraphOrigin) obj;
return e.queryHash == queryHash
&& e.beanType.equals(beanType)
&& e.callStack.equals(callStack);
}
}
package com.avaje.ebean.bean;
import java.io.Serializable;
/**
* Represents a "origin" of an ORM object graph. This combines the call stack
* and query plan hash.
* <p>
* The call stack is included so that the query can have different tuned fetches
* for each unique call stack. For example, a query to fetch a customer could be
* called by three different methods and each can be treated as a separate
* origin point (and autoFetch can tune each one separately).
* </p>
*/
public final class ObjectGraphOrigin implements Serializable {
private static final long serialVersionUID = 410937765287968708L;
private final CallStack callStack;
private final String beanType;
private final int queryHash;
private final String key;
public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) {
this.callStack = callStack;
this.beanType = beanType;
this.queryHash = queryHash;
this.key = callStack.getOriginKey(queryHash);
}
/**
* The key includes the queryPlan hash and the callStack hash. This becomes
* the unique identifier for a query point.
*/
public String getKey() {
return key;
}
/**
* The type of bean the query is fetching.
*/
public String getBeanType() {
return beanType;
}
/**
* The call stack involved.
*/
public CallStack getCallStack() {
return callStack;
}
public String getFirstStackElement() {
return callStack.getFirstStackTraceElement().toString();
}
public String toString() {
return "key["+ key + "] type[" + beanType + "] " + callStack.getFirstStackTraceElement()+" ";
}
public int hashCode() {
int hc = 31 * callStack.hashCode();
hc = 31 * hc + beanType.hashCode();
hc = 31 * hc + queryHash;
return hc;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphOrigin)) {
return false;
}
ObjectGraphOrigin e = (ObjectGraphOrigin) obj;
return e.queryHash == queryHash
&& e.beanType.equals(beanType)
&& e.callStack.equals(callStack);
}
}
@@ -0,0 +1,110 @@
package com.avaje.ebean.bean;
/**
* Holds entity beans by there type and id.
* <p>
* This is used to ensure only one instance for a given entity type and id is
* used to build object graphs from queries and lazy loading.
* </p>
*/
public interface PersistenceContext {
/**
* Put the entity bean into the PersistanceContext.
*/
public void put(Object id, Object bean);
/**
* Put the entity bean into the PersistanceContext if one is not already
* present (for this id).
* <p>
* Returns an existing entity bean (if one is already there) and otherwise
* returns null.
* </p>
*/
public Object putIfAbsent(Object id, Object bean);
/**
* Return an object given its type and unique id.
*/
public Object get(Class<?> beanType, Object uid);
/**
* Get the bean from the persistence context also checked to see if it had
* been previously deleted (if so then you also can't hit the L2 cache to
* fetch the bean for this particular persistence context).
*/
public WithOption getWithOption(Class<?> beanType, Object uid);
/**
* Clear all the references.
*/
public void clear();
/**
* Clear all the references for a given type of entity bean.
*/
public void clear(Class<?> beanType);
/**
* Clear the reference to a specific entity bean.
*/
public void clear(Class<?> beanType, Object uid);
/**
* Clear the reference as a result of an entity being deleted.
*/
public void deleted(Class<?> beanType, Object id);
/**
* Return the number of beans of the given type in the persistence context.
*/
public int size(Class<?> beanType);
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
* <p>
* If a bean has been deleted then for the same persistence context is should
* not be able to be fetched from persistence context or L2 cache.
* </p>
*/
public static class WithOption {
/**
* The bean was previously deleted from this persistence context (can't hit
* L2 cache).
*/
public static WithOption DELETED = new WithOption(true);
private final boolean deleted;
private final Object bean;
private WithOption(boolean deleted) {
this.deleted = true;
this.bean = null;
}
/**
* The bean exists in the persistence context (and not been previously deleted).
*/
public WithOption(Object bean) {
this.deleted = false;
this.bean = bean;
}
/**
* Return true if the bean was deleted. This means you can't hit the L2
* cache.
*/
public boolean isDeleted() {
return deleted;
}
/**
* Return the bean (from the persistence context).
*/
public Object getBean() {
return bean;
}
}
}
@@ -0,0 +1,12 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Enhanced beans API and Support objects</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Enhanced beans API and Support objects
</Body>
</HTML>
+89
View File
@@ -0,0 +1,89 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.EbeanServer;
/**
* Represents part of the "L2" server side cache.
* <p>
* This is used to cache beans or query results (bean collections).
* </p>
* <p>
* There are 2 ServerCache's for each bean type. One is used as the 'bean cache'
* which holds beans of a given type. The other is the 'query cache' holding
* query results for a given type.
* </p>
*
* @author rbygrave
*/
public interface ServerCache {
/**
* Just after a cache is created this init method is called. This is so that a
* cache implementation can make use of the BackgroundExecutor service to
* trim/cleanup itself or use the EbeanServer to populate itself.
* <p>
* This method is called after the cache is constructed but before the cache
* is made available for use.
* </p>
*/
public void init(EbeanServer ebeanServer);
/**
* Return the configuration options for this cache.
*/
public ServerCacheOptions getOptions();
/**
* Update the configuration options for this cache.
*/
public void setOptions(ServerCacheOptions options);
/**
* Return the value given the key.
*/
public Object get(Object id);
/**
* Put the value in the cache with a given id.
*/
public Object put(Object id, Object value);
/**
* Put the value in the cache but only if a matching value is not already in
* the cache.
*/
public Object putIfAbsent(Object id, Object value);
/**
* Remove a entry from the cache given its id.
*/
public Object remove(Object id);
/**
* Clear all entries from the cache.
* <p>
* NOTE: Be careful using this method in that most of the time application
* code should clear BOTH the bean and query caches at the same time. This can
* be done via {@link ServerCacheManager#clear(Class)}.
* </p>
*/
public void clear();
/**
* Return the number of entries in the cache.
*/
public int size();
/**
* Return the hit ratio the cache is currently getting.
*/
public int getHitRatio();
/**
* Return statistics for the cache.
*
* @param reset
* if true the statistics are reset.
*/
public ServerCacheStatistics getStatistics(boolean reset);
}
@@ -0,0 +1,26 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.EbeanServer;
/**
* Defines method for constructing caches for beans and queries.
*/
public interface ServerCacheFactory {
/**
* Just after the ServerCacheFactory is constructed this method is called
* passing the EbeanServer.
* <p>
* This is so that a cache implementation can utilise the EbeanServer to
* populate itself or use the BackgroundExecutor service to schedule periodic
* cache trimming/cleanup.
* </p>
*/
public void init(EbeanServer ebeanServer);
/**
* Create the cache for the given type with options.
*/
public ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions);
}
@@ -0,0 +1,55 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.EbeanServer;
/**
* The cache service for server side caching of beans and query results.
*/
public interface ServerCacheManager {
/**
* This method is called just after the construction of the
* ServerCacheManager.
* <p>
* The EbeanServer is provided so that cache implementations can make use of
* EbeanServer and BackgroundExecutor for automatically populating and
* background trimming of the cache.
* </p>
*/
public void init(EbeanServer server);
public void setCaching(Class<?> beanType, boolean useCache);
/**
* Return true if there is an active bean cache for this type of bean.
*/
public boolean isBeanCaching(Class<?> beanType);
/**
* Return the cache for mapping natural keys to id values.
*/
public ServerCache getNaturalKeyCache(Class<?> beanType);
/**
* Return the cache for beans of a particular type.
*/
public ServerCache getBeanCache(Class<?> beanType);
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName);
/**
* Return the cache for query results of a particular type of bean.
*/
public ServerCache getQueryCache(Class<?> beanType);
/**
* This clears both the bean and query cache for a given type.
*/
public void clear(Class<?> beanType);
/**
* Clear all the caches.
*/
public void clearAll();
}
@@ -0,0 +1,110 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.annotation.CacheTuning;
/**
* Options for controlling a cache.
*/
public class ServerCacheOptions {
private int maxSize;
private int maxIdleSecs;
private int maxSecsToLive;
/**
* Construct with no set options.
*/
public ServerCacheOptions() {
}
/**
* Create from the cacheTuning deployment annotation.
*/
public ServerCacheOptions(CacheTuning cacheTuning) {
this.maxSize = cacheTuning.maxSize();
this.maxIdleSecs = cacheTuning.maxIdleSecs();
this.maxSecsToLive = cacheTuning.maxSecsToLive();
}
/**
* Create merging default options with the deployment specified ones.
*/
public ServerCacheOptions(ServerCacheOptions d) {
this.maxSize = d.getMaxSize();
this.maxIdleSecs = d.getMaxIdleSecs();
this.maxSecsToLive = d.getMaxIdleSecs();
}
/**
* Apply any settings from the default settings that have not already been
* specifically set.
*/
public void applyDefaults(ServerCacheOptions defaults) {
if (maxSize == 0) {
maxSize = defaults.getMaxSize();
}
if (maxIdleSecs == 0) {
maxIdleSecs = defaults.getMaxIdleSecs();
}
if (maxSecsToLive == 0) {
maxSecsToLive = defaults.getMaxSecsToLive();
}
}
/**
* Return a copy of this object.
*/
public ServerCacheOptions copy() {
ServerCacheOptions copy = new ServerCacheOptions();
copy.maxSize = maxSize;
copy.maxIdleSecs = maxIdleSecs;
copy.maxSecsToLive = maxSecsToLive;
return copy;
}
/**
* Return the maximum cache size.
*/
public int getMaxSize() {
return maxSize;
}
/**
* Set the maximum cache size.
*/
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
/**
* Return the maximum idle time.
*/
public int getMaxIdleSecs() {
return maxIdleSecs;
}
/**
* Set the maximum idle time.
*/
public void setMaxIdleSecs(int maxIdleSecs) {
this.maxIdleSecs = maxIdleSecs;
}
/**
* Return the maximum time to live.
*/
public int getMaxSecsToLive() {
return maxSecsToLive;
}
/**
* Set the maximum time to live.
*/
public void setMaxSecsToLive(int maxSecsToLive) {
this.maxSecsToLive = maxSecsToLive;
}
}
@@ -0,0 +1,124 @@
package com.avaje.ebean.cache;
/**
* The statistics collected per cache.
* <p>
* These can be monitored to review the effectiveness of a particular cache.
* </p>
*
* @author rbygrave
*
*/
public class ServerCacheStatistics {
protected String cacheName;
protected int maxSize;
protected int size;
protected int hitCount;
protected int missCount;
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(cacheName);
sb.append(" size:").append(size);
sb.append(" hitRatio:").append(getHitRatio());
sb.append(" hitCount:").append(hitCount);
sb.append(" missCount:").append(missCount);
sb.append(" maxSize:").append(maxSize);
return sb.toString();
}
/**
* Return the name of the cache.
*/
public String getCacheName() {
return cacheName;
}
/**
* Set the name of the cache.
*/
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
/**
* Return the hit count. The number of successful gets.
*/
public int getHitCount() {
return hitCount;
}
/**
* Set the hit count.
*/
public void setHitCount(int hitCount) {
this.hitCount = hitCount;
}
/**
* Return the miss count. The number of gets that returned null.
*/
public int getMissCount() {
return missCount;
}
/**
* Set the miss count.
*/
public void setMissCount(int missCount) {
this.missCount = missCount;
}
/**
* Return the size of the cache.
*/
public int getSize() {
return size;
}
/**
* Set the size of the cache.
*/
public void setSize(int size) {
this.size = size;
}
/**
* Return the maximum size of the cache.
* <p>
* Can be used in conjunction with the size to determine if the cache use is
* being potentially limited by its maximum size.
* </p>
*/
public int getMaxSize() {
return maxSize;
}
/**
* Set the maximum size of the cache.
*/
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
/**
* Returns an int from 0 to 100 (percentage) for the hit ratio.
* <p>
* A hit ratio of 100 means every get request against the cache hits an entry.
* </p>
*/
public int getHitRatio() {
int totalCount = hitCount + missCount;
if (totalCount == 0) {
return 0;
} else {
return hitCount * 100 / totalCount;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Server Cache Service</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Server Cache Service
</Body>
</HTML>
@@ -0,0 +1,290 @@
package com.avaje.ebean.common;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.persistence.PersistenceException;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.BeanCollectionTouched;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
/**
* Base class for List Set and Map implementations of BeanCollection.
*
* @author rbygrave
*/
public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
private static final long serialVersionUID = 3365725236140187588L;
protected boolean readOnly;
/**
* The EbeanServer this is associated with. (used for lazy fetch).
*/
protected transient BeanCollectionLoader loader;
protected transient ExpressionList<?> filterMany;
protected int loaderIndex;
protected String ebeanServerName;
protected transient BeanCollectionTouched beanCollectionTouched;
protected transient Future<Integer> fetchFuture;
/**
* The owning bean (used for lazy fetch).
*/
protected final Object ownerBean;
/**
* The name of this property in the owning bean (used for lazy fetch).
*/
protected final String propertyName;
/**
* Can be false when a background thread is used to continue the fetch the
* rows. It will set this to true when it is finished. If no background thread
* is used then this should already be true.
*/
protected boolean finishedFetch = true;
/**
* Flag set to true if rows are limited by firstRow maxRows and more rows
* exist. For use by client to enable 'next' for paging.
*/
protected boolean hasMoreRows;
protected ModifyHolder<E> modifyHolder;
protected ModifyListenMode modifyListenMode;
protected boolean modifyAddListening;
protected boolean modifyRemoveListening;
protected boolean modifyListening;
/**
* Constructor not non-lazy loading collection.
*/
public AbstractBeanCollection() {
this.ownerBean = null;
this.propertyName = null;
}
/**
* Used to create deferred fetch proxy.
*/
public AbstractBeanCollection(BeanCollectionLoader loader, Object ownerBean, String propertyName) {
this.loader = loader;
this.ebeanServerName = loader.getName();
this.ownerBean = ownerBean;
this.propertyName = propertyName;
if (ownerBean instanceof EntityBean) {
EntityBeanIntercept ebi = ((EntityBean) ownerBean)._ebean_getIntercept();
this.readOnly = ebi.isReadOnly();
}
}
public Object getOwnerBean() {
return ownerBean;
}
public String getPropertyName() {
return propertyName;
}
public int getLoaderIndex() {
return loaderIndex;
}
public ExpressionList<?> getFilterMany() {
return filterMany;
}
public void setFilterMany(ExpressionList<?> filterMany) {
this.filterMany = filterMany;
}
protected void lazyLoadCollection(boolean onlyIds) {
if (loader == null) {
loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName);
}
if (loader == null) {
String msg = "Lazy loading but LazyLoadEbeanServer is null?"
+ " The LazyLoadEbeanServer needs to be set after deserialization"
+ " to support lazy loading.";
throw new PersistenceException(msg);
}
loader.loadMany(this, onlyIds);
checkEmptyLazyLoad();
}
protected void touched() {
if (beanCollectionTouched != null) {
// only call this once
beanCollectionTouched.notifyTouched(this);
beanCollectionTouched = null;
}
}
public void setBeanCollectionTouched(BeanCollectionTouched notify) {
this.beanCollectionTouched = notify;
}
public void setLoader(int beanLoaderIndex, BeanCollectionLoader loader) {
this.loaderIndex = beanLoaderIndex;
this.loader = loader;
this.ebeanServerName = loader.getName();
}
public boolean isReadOnly() {
return readOnly;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Set to true if maxRows was hit and there are actually more rows available.
* <p>
* Can be used by client code that is paging through results using
* setFirstRow() setMaxRows(). If this returns true then the client can
* display a 'next' button etc.
* </p>
*/
public boolean hasMoreRows() {
return hasMoreRows;
}
/**
* Set to true when maxRows is hit but there are actually more rows available.
* This is set so that client code knows that there is more data available.
*/
public void setHasMoreRows(boolean hasMoreRows) {
this.hasMoreRows = hasMoreRows;
}
/**
* Returns true if the fetch has finished. False if the fetch is continuing in
* a background thread.
*/
public boolean isFinishedFetch() {
return finishedFetch;
}
/**
* Set to true when a fetch has finished. Used when a fetch continues in the
* background.
*/
public void setFinishedFetch(boolean finishedFetch) {
this.finishedFetch = finishedFetch;
}
public void setBackgroundFetch(Future<Integer> fetchFuture) {
this.fetchFuture = fetchFuture;
}
public void backgroundFetchWait(long wait, TimeUnit timeUnit) {
if (fetchFuture != null) {
try {
fetchFuture.get(wait, timeUnit);
} catch (Exception e) {
throw new PersistenceException(e);
}
}
}
public void backgroundFetchWait() {
if (fetchFuture != null) {
try {
fetchFuture.get();
} catch (Exception e) {
throw new PersistenceException(e);
}
}
}
protected void checkReadOnly() {
if (readOnly) {
String msg = "This collection is in ReadOnly mode";
throw new IllegalStateException(msg);
}
}
// ---------------------------------------------------------
// Support for modify additions deletions etc - ManyToMany
// ---------------------------------------------------------
/**
* set modifyListening to be on or off.
*/
public void setModifyListening(ModifyListenMode mode) {
this.modifyListenMode = mode;
this.modifyAddListening = ModifyListenMode.ALL.equals(mode);
this.modifyRemoveListening = modifyAddListening || ModifyListenMode.REMOVALS.equals(mode);
this.modifyListening = modifyRemoveListening || modifyAddListening;
if (modifyListening) {
// lose any existing modifications
modifyHolder = null;
}
}
/**
* Return the modify listening mode this collection is using.
*/
public ModifyListenMode getModifyListenMode() {
return modifyListenMode;
}
protected ModifyHolder<E> getModifyHolder() {
if (modifyHolder == null) {
modifyHolder = new ModifyHolder<E>();
}
return modifyHolder;
}
public void modifyAddition(E bean) {
if (modifyAddListening) {
getModifyHolder().modifyAddition(bean);
}
}
public void modifyRemoval(Object bean) {
if (modifyRemoveListening) {
getModifyHolder().modifyRemoval(bean);
}
}
public void modifyReset() {
if (modifyHolder != null) {
modifyHolder.reset();
}
}
public Set<E> getModifyAdditions() {
if (modifyHolder == null) {
return null;
} else {
return modifyHolder.getModifyAdditions();
}
}
public Set<E> getModifyRemovals() {
if (modifyHolder == null) {
return null;
} else {
return modifyHolder.getModifyRemovals();
}
}
}
@@ -0,0 +1,451 @@
package com.avaje.ebean.common;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
/**
* List capable of lazy loading.
*/
public final class BeanList<E> extends AbstractBeanCollection<E> implements List<E>,
BeanCollectionAdd {
/**
* The underlying List implementation.
*/
private List<E> list;
/**
* Specify the underlying List implementation.
*/
public BeanList(List<E> list) {
super();
this.list = list;
}
/**
* Uses an ArrayList as the underlying List implementation.
*/
public BeanList() {
this(new ArrayList<E>());
}
/**
* Used to create deferred fetch proxy.
*/
public BeanList(BeanCollectionLoader loader, Object ownerBean, String propertyName) {
super(loader, ownerBean, propertyName);
}
@SuppressWarnings("unchecked")
public void addBean(Object bean) {
list.add((E) bean);
}
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (list == null) {
list = new ArrayList<E>();
}
list.add((E) bean);
}
public boolean checkEmptyLazyLoad() {
if (list == null) {
list = new ArrayList<E>();
return true;
} else {
return false;
}
}
private void initClear() {
synchronized (this) {
if (list == null) {
if (modifyListening) {
lazyLoadCollection(true);
} else {
list = new ArrayList<E>();
}
}
touched();
}
}
private void init() {
synchronized (this) {
if (list == null) {
lazyLoadCollection(false);
}
touched();
}
}
/**
* Set the actual underlying list.
* <p>
* This is primarily for the deferred fetching function.
* </p>
*/
@SuppressWarnings("unchecked")
public void setActualList(List<?> list) {
this.list = (List<E>) list;
}
/**
* Return the actual underlying list.
*/
public List<E> getActualList() {
return list;
}
public Collection<E> getActualDetails() {
return list;
}
@Override
public Collection<?> getActualEntries() {
return list;
}
/**
* Returns the underlying list.
*/
public Object getActualCollection() {
return list;
}
/**
* Return true if the underlying list is populated.
*/
public boolean isPopulated() {
return list != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
public boolean isReference() {
return list == null;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("BeanList ");
if (isReadOnly()) {
sb.append("readOnly ");
}
if (list == null) {
sb.append("deferred ");
} else {
sb.append("size[").append(list.size()).append("] ");
sb.append("hasMoreRows[").append(hasMoreRows).append("] ");
sb.append("list").append(list).append("");
}
return sb.toString();
}
/**
* Equal if obj is a List and equal in a list sense.
* <p>
* Specifically obj does not need to be a BeanList but any list. This does not
* use the FindMany, fetchedMaxRows or finishedFetch properties in the equals
* test.
* </p>
*/
public boolean equals(Object obj) {
init();
return list.equals(obj);
}
public int hashCode() {
init();
return list.hashCode();
}
// -----------------------------------------------------//
// The additional methods are here
// -----------------------------------------------------//
// -----------------------------------------------------//
// proxy method for List
// -----------------------------------------------------//
public void add(int index, E element) {
checkReadOnly();
init();
if (modifyAddListening) {
modifyAddition(element);
}
list.add(index, element);
}
public boolean add(E o) {
checkReadOnly();
init();
if (modifyAddListening) {
if (list.add(o)) {
modifyAddition(o);
return true;
} else {
return false;
}
}
return list.add(o);
}
public boolean addAll(Collection<? extends E> c) {
checkReadOnly();
init();
if (modifyAddListening) {
// all elements in c are added (no contains checking)
getModifyHolder().modifyAdditionAll(c);
}
return list.addAll(c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkReadOnly();
init();
if (modifyAddListening) {
// all elements in c are added (no contains checking)
getModifyHolder().modifyAdditionAll(c);
}
return list.addAll(index, c);
}
public void clear() {
checkReadOnly();
// TODO: when clear() and not initialised could be more clever
// and fetch just the Id's
initClear();
if (modifyRemoveListening) {
for (int i = 0; i < list.size(); i++) {
getModifyHolder().modifyRemoval(list.get(i));
}
}
list.clear();
}
public boolean contains(Object o) {
init();
return list.contains(o);
}
public boolean containsAll(Collection<?> c) {
init();
return list.containsAll(c);
}
public E get(int index) {
init();
return list.get(index);
}
public int indexOf(Object o) {
init();
return list.indexOf(o);
}
public boolean isEmpty() {
init();
return list.isEmpty();
}
public Iterator<E> iterator() {
init();
if (isReadOnly()) {
return new ReadOnlyListIterator<E>(list.listIterator());
}
if (modifyListening) {
Iterator<E> it = list.iterator();
return new ModifyIterator<E>(this, it);
}
return list.iterator();
}
public int lastIndexOf(Object o) {
init();
return list.lastIndexOf(o);
}
public ListIterator<E> listIterator() {
init();
if (isReadOnly()) {
return new ReadOnlyListIterator<E>(list.listIterator());
}
if (modifyListening) {
ListIterator<E> it = list.listIterator();
return new ModifyListIterator<E>(this, it);
}
return list.listIterator();
}
public ListIterator<E> listIterator(int index) {
init();
if (isReadOnly()) {
return new ReadOnlyListIterator<E>(list.listIterator(index));
}
if (modifyListening) {
ListIterator<E> it = list.listIterator(index);
return new ModifyListIterator<E>(this, it);
}
return list.listIterator(index);
}
public E remove(int index) {
checkReadOnly();
init();
if (modifyRemoveListening) {
E o = list.remove(index);
modifyRemoval(o);
return o;
}
return list.remove(index);
}
public boolean remove(Object o) {
checkReadOnly();
init();
if (modifyRemoveListening) {
boolean isRemove = list.remove(o);
if (isRemove) {
modifyRemoval(o);
}
return isRemove;
}
return list.remove(o);
}
public boolean removeAll(Collection<?> c) {
checkReadOnly();
init();
if (modifyRemoveListening) {
boolean changed = false;
Iterator<?> it = c.iterator();
while (it.hasNext()) {
Object o = (Object) it.next();
if (list.remove(o)) {
modifyRemoval(o);
changed = true;
}
}
return changed;
}
return list.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
checkReadOnly();
init();
if (modifyRemoveListening) {
boolean changed = false;
Iterator<E> it = list.iterator();
while (it.hasNext()) {
Object o = (Object) it.next();
if (!c.contains(o)) {
it.remove();
modifyRemoval(o);
changed = true;
}
}
return changed;
}
return list.retainAll(c);
}
public E set(int index, E element) {
checkReadOnly();
init();
if (modifyListening) {
E o = list.set(index, element);
modifyAddition(element);
modifyRemoval(o);
return o;
}
return list.set(index, element);
}
public int size() {
init();
return list.size();
}
public List<E> subList(int fromIndex, int toIndex) {
init();
if (isReadOnly()) {
return Collections.unmodifiableList(list.subList(fromIndex, toIndex));
}
if (modifyListening) {
return new ModifyList<E>(this, list.subList(fromIndex, toIndex));
}
return list.subList(fromIndex, toIndex);
}
public Object[] toArray() {
init();
return list.toArray();
}
public <T> T[] toArray(T[] a) {
init();
return list.toArray(a);
}
private static class ReadOnlyListIterator<E> implements ListIterator<E>, Serializable {
private static final long serialVersionUID = 3097271091406323699L;
private final ListIterator<E> i;
ReadOnlyListIterator(ListIterator<E> i) {
this.i = i;
}
public void add(E o) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
public void set(E o) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
public boolean hasNext() {
return i.hasNext();
}
public boolean hasPrevious() {
return i.hasPrevious();
}
public E next() {
return i.next();
}
public int nextIndex() {
return i.nextIndex();
}
public E previous() {
return i.previous();
}
public int previousIndex() {
return i.previousIndex();
}
}
}
@@ -0,0 +1,280 @@
package com.avaje.ebean.common;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.avaje.ebean.bean.BeanCollectionLoader;
/**
* Map capable of lazy loading.
*/
public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Map<K, E> {
/**
* The underlying map implementation.
*/
private Map<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(Map<K, E> map) {
this.map = map;
}
/**
* Create using a underlying LinkedHashMap.
*/
public BeanMap() {
this(new LinkedHashMap<K, E>());
}
public BeanMap(BeanCollectionLoader ebeanServer, Object ownerBean, String propertyName) {
super(ebeanServer, ownerBean, propertyName);
}
@SuppressWarnings("unchecked")
public void internalPut(Object key, Object bean) {
if (map == null) {
map = new LinkedHashMap<K, E>();
}
map.put((K)key, (E)bean);
}
public void internalAdd(Object bean) {
throw new RuntimeException("Not allowed for map");
}
/**
* Return true if the underlying map has been populated. Returns false if it
* has a deferred fetch pending.
*/
public boolean isPopulated() {
return map != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
public boolean isReference() {
return map == null;
}
public boolean checkEmptyLazyLoad() {
if (map == null) {
map = new LinkedHashMap<K, E>();
return true;
} else {
return false;
}
}
private void initClear() {
synchronized (this) {
if (map == null) {
if (modifyListening) {
lazyLoadCollection(true);
} else {
map = new LinkedHashMap<K, E>();
}
}
touched();
}
}
private void init() {
synchronized (this) {
if (map == null) {
lazyLoadCollection(false);
}
touched();
}
}
/**
* Set the actual underlying map. Used for performing lazy fetch.
*/
@SuppressWarnings("unchecked")
public void setActualMap(Map<?, ?> map) {
this.map = (Map<K, E>) map;
}
/**
* Return the actual underlying map.
*/
public Map<K, E> getActualMap() {
return map;
}
/**
* Returns the collection of beans (map values).
*/
public Collection<E> getActualDetails() {
return map.values();
}
/**
* Returns the map entrySet.
* <p>
* This is because the key values may need to be set against the details (so
* they don't need to be set twice).
* </p>
*/
public Collection<?> getActualEntries() {
return map.entrySet();
}
/**
* Returns the underlying map.
*/
public Object getActualCollection() {
return map;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("BeanMap ");
if (isReadOnly()) {
sb.append("readOnly ");
}
if (map == null) {
sb.append("deferred ");
} else {
sb.append("size[").append(map.size()).append("]");
sb.append(" hasMoreRows[").append(hasMoreRows).append("]");
sb.append(" map").append(map);
}
return sb.toString();
}
/**
* Equal if obj is a Map and equal in a Map sense.
*/
public boolean equals(Object obj) {
init();
return map.equals(obj);
}
public int hashCode() {
init();
return map.hashCode();
}
public void clear() {
checkReadOnly();
initClear();
if (modifyRemoveListening) {
for (K key : map.keySet()) {
E o = map.remove(key);
modifyRemoval(o);
}
}
map.clear();
}
public boolean containsKey(Object key) {
init();
return map.containsKey(key);
}
public boolean containsValue(Object value) {
init();
return map.containsValue(value);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public Set<Entry<K, E>> entrySet() {
init();
if (isReadOnly()) {
return Collections.unmodifiableSet(map.entrySet());
}
if (modifyListening) {
Set<Entry<K, E>> s = map.entrySet();
return new ModifySet(this, s);
}
return map.entrySet();
}
public E get(Object key) {
init();
return map.get(key);
}
public boolean isEmpty() {
init();
return map.isEmpty();
}
public Set<K> keySet() {
init();
if (isReadOnly()) {
return Collections.unmodifiableSet(map.keySet());
}
// we don't really care about modifications to the ketSet?
return map.keySet();
}
public E put(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
Object o = map.put(key, value);
modifyAddition(value);
modifyRemoval(o);
}
return map.put(key, value);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void putAll(Map<? extends K, ? extends E> t) {
checkReadOnly();
init();
if (modifyListening) {
Iterator it = t.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Object o = map.put((K) entry.getKey(), (E) entry.getValue());
modifyAddition((E) entry.getValue());
modifyRemoval(o);
}
}
map.putAll(t);
}
public E remove(Object key) {
checkReadOnly();
init();
if (modifyRemoveListening) {
E o = map.remove(key);
modifyRemoval(o);
return o;
}
return map.remove(key);
}
public int size() {
init();
return map.size();
}
public Collection<E> values() {
init();
if (isReadOnly()) {
return Collections.unmodifiableCollection(map.values());
}
if (modifyListening) {
Collection<E> c = map.values();
return new ModifyCollection<E>(this, c);
}
return map.values();
}
}
@@ -0,0 +1,323 @@
package com.avaje.ebean.common;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
/**
* Set capable of lazy loading.
*/
public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E>, BeanCollectionAdd {
/**
* The underlying Set implementation.
*/
private Set<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(Set<E> set) {
this.set = set;
}
/**
* Create using an underlying LinkedHashSet.
*/
public BeanSet() {
this(new LinkedHashSet<E>());
}
public BeanSet(BeanCollectionLoader loader, Object ownerBean, String propertyName) {
super(loader, ownerBean, propertyName);
}
@SuppressWarnings("unchecked")
public void addBean(Object bean) {
set.add((E) bean);
}
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (set == null) {
set = new LinkedHashSet<E>();
}
set.add((E) bean);
}
/**
* Returns true if the underlying set has its data.
*/
public boolean isPopulated() {
return set != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
public boolean isReference() {
return set == null;
}
public boolean checkEmptyLazyLoad() {
if (set == null) {
set = new LinkedHashSet<E>();
return true;
} else {
return false;
}
}
private void initClear() {
synchronized (this) {
if (set == null) {
if (modifyListening) {
lazyLoadCollection(true);
} else {
set = new LinkedHashSet<E>();
}
}
touched();
}
}
private void init() {
synchronized (this) {
if (set == null) {
lazyLoadCollection(true);
}
touched();
}
}
/**
* Set the underlying set (used for lazy fetch).
*/
@SuppressWarnings("unchecked")
public void setActualSet(Set<?> set) {
this.set = (Set<E>) set;
}
/**
* Return the actual underlying set.
*/
public Set<E> getActualSet() {
return set;
}
public Collection<E> getActualDetails() {
return set;
}
@Override
public Collection<?> getActualEntries() {
return set;
}
/**
* Returns the underlying set.
*/
public Object getActualCollection() {
return set;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("BeanSet ");
if (isReadOnly()) {
sb.append("readOnly ");
}
if (set == null) {
sb.append("deferred ");
} else {
sb.append("size[").append(set.size()).append("]");
sb.append(" hasMoreRows[").append(hasMoreRows).append("]");
sb.append(" set").append(set);
}
return sb.toString();
}
/**
* Equal if obj is a Set and equal in a Set sense.
*/
public boolean equals(Object obj) {
init();
return set.equals(obj);
}
public int hashCode() {
init();
return set.hashCode();
}
// -----------------------------------------------------//
// proxy method for map
// -----------------------------------------------------//
public boolean add(E o) {
checkReadOnly();
init();
if (modifyAddListening) {
if (set.add(o)) {
modifyAddition(o);
return true;
} else {
return false;
}
}
return set.add(o);
}
public boolean addAll(Collection<? extends E> c) {
checkReadOnly();
init();
if (modifyAddListening) {
boolean changed = false;
Iterator<? extends E> it = c.iterator();
while (it.hasNext()) {
E o = it.next();
if (set.add(o)) {
modifyAddition(o);
changed = true;
}
}
return changed;
}
return set.addAll(c);
}
public void clear() {
checkReadOnly();
initClear();
if (modifyRemoveListening) {
Iterator<E> it = set.iterator();
while (it.hasNext()) {
E e = it.next();
modifyRemoval(e);
}
}
set.clear();
}
public boolean contains(Object o) {
init();
return set.contains(o);
}
public boolean containsAll(Collection<?> c) {
init();
return set.containsAll(c);
}
public boolean isEmpty() {
init();
return set.isEmpty();
}
public Iterator<E> iterator() {
init();
if (isReadOnly()) {
return new ReadOnlyIterator<E>(set.iterator());
}
if (modifyListening) {
return new ModifyIterator<E>(this, set.iterator());
}
return set.iterator();
}
public boolean remove(Object o) {
checkReadOnly();
init();
if (modifyRemoveListening) {
if (set.remove(o)) {
modifyRemoval(o);
return true;
}
return false;
}
return set.remove(o);
}
public boolean removeAll(Collection<?> c) {
checkReadOnly();
init();
if (modifyRemoveListening) {
boolean changed = false;
Iterator<?> it = c.iterator();
while (it.hasNext()) {
Object o = (Object) it.next();
if (set.remove(o)) {
modifyRemoval(o);
changed = true;
}
}
return changed;
}
return set.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
checkReadOnly();
init();
if (modifyRemoveListening) {
boolean changed = false;
Iterator<?> it = set.iterator();
while (it.hasNext()) {
Object o = it.next();
if (!c.contains(o)) {
it.remove();
modifyRemoval(o);
changed = true;
}
}
return changed;
}
return set.retainAll(c);
}
public int size() {
init();
return set.size();
}
public Object[] toArray() {
init();
return set.toArray();
}
public <T> T[] toArray(T[] a) {
init();
return set.toArray(a);
}
private static class ReadOnlyIterator<E> implements Iterator<E>, Serializable {
private static final long serialVersionUID = 2577697326745352605L;
private final Iterator<E> it;
ReadOnlyIterator(Iterator<E> it) {
this.it = it;
}
public boolean hasNext() {
return it.hasNext();
}
public E next() {
return it.next();
}
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
}
@@ -0,0 +1,38 @@
package com.avaje.ebean.common;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.ServerConfig;
/**
* Creates the EbeanServer implementations. This is used by the Ebean singleton
* to determine the implementation for each server name.
* <p>
* Note that on a remote client it is expected that this factory will return
* EbeanServers that behave as a proxy using http or tcp sockets etc to talk to
* the EbeanServer on the application server.
* </p>
*/
public interface BootupEbeanManager {
/**
* Create the EbeanServer for a given configuration.
*
* @param configuration
* The configuration information for this server.
*/
public EbeanServer createServer(ServerConfig configuration);
/**
* Create an EbeanServer just using the name.
* <p>
* In this case the dataSource parameters etc will be defined on the global
* avaje.properties file.
* </p>
*/
public EbeanServer createServer(String name);
/**
* Shutdown any Ebean wide resources such as clustering.
*/
public void shutdown();
}

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