Compare commits

...
Author SHA1 Message Date
Robin Bygrave b4d49161af [maven-release-plugin] prepare release avaje-ebeanorm-7.9.1 2016-05-02 16:35:04 +12:00
Robin Bygrave 8a68e72305 No effective change - modify test, add l2 cache await 2016-05-02 16:32:08 +12:00
Robin Bygrave 07fe1cde6b #689 - Refactor DB Migration to support "V" prefix 2016-05-02 16:25:27 +12:00
Robin Bygrave 9da5ce01a7 #686 - Trouble with non-standard java.sql.Date values caching in 7.8.1. 2016-04-29 22:22:38 +12:00
Robin Bygrave 5bcfce9cd4 #684 - Refactor - rename @CacheStrategy to @Cache ... require explicit enableQueryCache=true for query cache use 2016-04-29 21:52:50 +12:00
Robin Bygrave a6d276d4d3 #683 - Refactor - rename @CacheTuning to @CacheBeanTuning ... for consistency 2016-04-29 09:48:36 +12:00
Robin Bygrave 5b100e6d37 #682 - Remove deprecated Model.Finder constructors that take Id type ... migrate to ones that don't 2016-04-29 09:28:43 +12:00
Robin Bygrave bf75af85c1 #681 - Remove SqlQuery.cancel() ... migrate to SqlQuery.findEachWhile() and stop while iterating 2016-04-29 09:05:36 +12:00
Robin Bygrave 5cf2d3c39a #680 - Remove deprecated SqlQuery.findFutureList() 2016-04-29 08:49:07 +12:00
Robin Bygrave 840c5f8992 #679 - Remove SqlQuery.setListener() ... migrate to SqlQuery.findEach() or SqlQuery.findEachWhile() 2016-04-29 08:43:30 +12:00
Robin Bygrave f867cd4c01 #678 - Remove SqlQuery.findSet() and SqlQuery.findMap() ... migrate to use SlqQuery.findList() 2016-04-29 08:37:52 +12:00
Robin Bygrave ad17da5b36 No effective change - format 2016-04-28 22:22:04 +12:00
Robin Bygrave 9350155c00 #677 - REFACTOR: Remove unused SqlQuery.setBackgroundFetchAfter() 2016-04-28 22:18:14 +12:00
Robin Bygrave 8a84eaf7a7 #675 - ENH: Add SqlQuery.findEach() and SqlQuery.findEachWhile() methods ... to replace setListener() 2016-04-28 22:15:41 +12:00
Robin Bygrave f0fab77ff7 #670 - Remove @EntityConcurrencyMode ... this is now rather redundant since 4.x changes 2016-04-28 20:26:17 +12:00
Robin Bygrave 41aace9dc4 #669 - Remove @EnumMapping ... migrate to use @DbEnumValue or @EnumValue 2016-04-28 20:22:37 +12:00
Robin Bygrave 7c9d752c3d Merge branch 'master' of github.com:ebean-orm/avaje-ebeanorm 2016-04-28 20:19:22 +12:00
Robin Bygrave 81531b6cda #671 - ENH: Add support for @View and extra-ddl.xml to nicely support entity beans based on views 2016-04-28 20:19:05 +12:00
Rob Bygrave b70f3574d9 Update ISSUE_TEMPLATE.md
Add "some java code" placeholder
2016-04-28 13:22:31 +12:00
Rob Bygrave be214273cc Update README.md 2016-04-28 13:05:17 +12:00
Robin Bygrave 69c2810fbe No effective change - add test for SqlQuery listener 2016-04-28 09:54:17 +12:00
Robin Bygrave 82da81116e Add github issue template 2016-04-28 09:40:05 +12:00
Robin Bygrave 7d5e135f56 [maven-release-plugin] prepare for next development iteration 2016-04-27 08:41:26 +12:00
Robin Bygrave 6b05d2343b [maven-release-plugin] prepare release avaje-ebeanorm-7.8.1 2016-04-27 08:41:06 +12:00
Robin Bygrave fc748ea9c1 #667 - Remove deprecated @SqlSelect ... migrate to use RawSql / RawSqlBuilder 2016-04-27 08:40:00 +12:00
Robin Bygrave 7b8d2a2f9d #664 - Remove reference to @SqlInsert, @SqlUpdate, @SqlDelete from comment 2016-04-27 08:14:49 +12:00
Robin Bygrave f8b5a7f312 #666 - deleteAll(beans) return int , but deleteAll(beanType,ids) return void in EbeanServer 2016-04-27 08:04:43 +12:00
Robin Bygrave ae261d3782 #660 - findPagedList() does not handle raw sql order by 2016-04-26 22:54:34 +12:00
Robin Bygrave d2ae044d1f #665 - Remove deprecated @DocStoreEmbedded ... should migrate to @DocEmbedded 2016-04-26 21:35:57 +12:00
Robin Bygrave 03189ac92a #661 - ENH: Add deletePermanent(beanType, id) and deleteAllPermanent(beanType, ids) ... (basically missing API) 2016-04-26 21:30:52 +12:00
Robin Bygrave 2067568a5e No effective change - migrate test from deprecated method 2016-04-26 21:12:22 +12:00
Robin Bygrave 57417610a6 #663 - Change Postgres default Timestamp type to be "Timestamp with time zone" 2016-04-26 17:06:01 +12:00
Robin Bygrave 4cec878242 #662 - Postgres Timestamp with time zone testing 2016-04-26 16:41:11 +12:00
Robin Bygrave 2e7aec3b3d #662 - Postgres Timestamp with time zone testing - remove extra oracle test code 2016-04-26 10:13:33 +12:00
Robin Bygrave 104dc599e8 #662 - Postgres Timestamp with time zone testing 2016-04-26 10:12:37 +12:00
Robin Bygrave e769abf737 #659 - ENH: Add support for Instant with @WhenCreated @WhenModified 2016-04-22 16:51:30 +12:00
Robin Bygrave b79bb892f7 #636 - ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) 2016-04-22 16:19:09 +12:00
Robin Bygrave 8f93f780fd Bump datasource dependency 2016-04-22 16:18:06 +12:00
Robin Bygrave 158a23a34d No effective change - reformat 2016-04-22 16:05:17 +12:00
Robin Bygrave 523ddbab63 #658 - REFACTOR: Simplify LocalDateTime -> Timestamp conversion, use Timestamp.valueOf() 2016-04-21 20:11:39 +12:00
Robin Bygrave 00d4298065 #656 - Remove remoteInsert(), remoteUpdate() remoteDelete() methods from BeanPersistListener 2016-04-21 12:33:06 +12:00
Robin Bygrave 4eb1aded2d #651 - ENH: Add type support for java.nio.file.Path ... such that it stores the underlying URI as VARCHAR/string 2016-04-21 11:34:33 +12:00
Robin Bygrave 221f5ad879 #651 - ENH: Add type support for java.nio.file.Path ... such that it stores the underlying URI as VARCHAR/string 2016-04-21 11:33:26 +12:00
Robin Bygrave 75e3b9276e #657 - POM - drop dependency on org.springframework.boot and avaje-ebeanorm-cluster 2016-04-21 10:38:26 +12:00
Robin Bygrave 29c51ecb95 #655 - DB Migration should not require running L2 cache (ie. Hazel cast server etc) 2016-04-21 10:33:09 +12:00
Robin Bygrave 8c535ba837 [maven-release-plugin] prepare for next development iteration 2016-04-18 21:01:11 +12:00
Robin Bygrave 11e02710d9 [maven-release-plugin] prepare release avaje-ebeanorm-7.7.1 2016-04-18 21:00:50 +12:00
Robin Bygrave 658ce3c5d3 #653 - Change format of all Timestamp/Date time types to use epoch millis 2016-04-18 12:49:16 +12:00
Robin Bygrave 2de78cd20f #652 - ENH: L2 cache - add @CacheQueryTuning annotation 2016-04-18 09:46:42 +12:00
Robin Bygrave 1b8b05790e #650 - Refactor: Optimise L2 cache entry for serialisation 2016-04-14 11:56:31 +12:00
Robin Bygrave 444b848c22 #648 - Test that does not reproduce this issue 2016-04-13 19:36:26 +12:00
Robin Bygrave 707dda636d #649 - ENH: Add ebean.disableL2Cache=true option ... to globally disable l2 cache 2016-04-13 17:44:37 +12:00
Robin Bygrave e8a979bc9d No effective change - imports 2016-04-13 15:59:39 +12:00
Robin Bygrave 3fb9a7414c #647 - REFACTOR: L2 cache - remove cache warming support (from @CacheStrategy and API) 2016-04-12 23:44:24 +12:00
Robin Bygrave 061652a48e Bump pom to 7.7.1-SNAPSHOT 2016-04-12 23:26:09 +12:00
Robin Bygrave 57b03cf86d #646 - REFACTOR: L2 cache towards remote data grid caches (like Ignite etc) 2016-04-12 23:25:19 +12:00
Robin Bygrave 45dbe3bb9a Merge branch 'master' of github.com:ebean-orm/avaje-ebeanorm 2016-04-12 23:21:32 +12:00
Robin Bygrave 24037eeb73 No effective change - reduce test logging 2016-04-12 23:20:54 +12:00
Robin Bygrave 2c6f637dab #646 - REFACTOR: L2 cache towards remote data grid caches (like Ignite etc) 2016-04-12 23:20:16 +12:00
Robin Bygrave a85f9c4be8 #638 - Ensure natural key cache is updated regardless of bean cache entry aging out 2016-04-11 17:09:06 +12:00
Rob Bygrave 5d9f11a362 Merge pull request #645 from icode/master
review duplicate var
2016-04-08 22:16:43 +12:00
icode 3d484c79a2 review duplicate var 2016-04-08 14:32:13 +08:00
Robin Bygrave 5881047606 [maven-release-plugin] prepare for next development iteration 2016-04-08 15:17:04 +12:00
Robin Bygrave 333eaf08c2 [maven-release-plugin] prepare release avaje-ebeanorm-7.6.1 2016-04-08 15:16:44 +12:00
Robin Bygrave a1ff83e9e4 #643 - NPE on OrmQueryProperties.java:474 2016-04-08 15:15:34 +12:00
Robin Bygrave 640db2267f #642 - ENH: Add support for OneToMany expression isEmpty() / isNotEmpty() ... which is effectively an EXISTS and NOT EXISTS subquery 2016-04-08 13:44:54 +12:00
Robin Bygrave 4059034891 #642 - ENH: Add support for OneToMany expression isEmpty() / isNotEmpty() ... which is effectively an EXISTS and NOT EXISTS subquery 2016-04-08 13:41:43 +12:00
Robin Bygrave c58db0033d No effective change - add more tests for raw use of exists / not exists 2016-04-08 08:28:13 +12:00
Robin Bygrave da90eccc12 #638 - ENH: Add Apache Ignite as L2 cache option 2016-04-08 02:03:02 +12:00
Robin Bygrave 524be4ce55 #641 - ENH: Add EbeanServer.setBeanId(bean, id) ... to make it easy to convert id value to appropriate type and set 2016-04-08 01:15:34 +12:00
Robin Bygrave 649c14644e #640 - ENH: ServerConfig - support get/put arbitrary configuration objects for use by plugins 2016-04-08 00:03:37 +12:00
Robin Bygrave c504256579 No effective change - HashQuery.toString() for l2 query cache log message 2016-04-07 23:59:13 +12:00
Robin Bygrave 97d803e3e0 No effective change - HashQuery.toString() for l2 query cache log message 2016-04-07 21:45:59 +12:00
Robin Bygrave 46bc09a09b No effective change - HashQuery.toString() for l2 query cache log message 2016-04-07 21:45:47 +12:00
Robin Bygrave fe387cf456 #637 - CachedManyIds as serializable (Ignite etc) 2016-04-06 23:43:26 +12:00
Robin Bygrave b4cd66e28c #637 - ServerCacheType 2016-04-06 21:39:39 +12:00
Robin Bygrave 7c11acaf89 #637 - Change SPI for ServerCache, add ServerCacheType to allow different cache construction for query bean and bean cache 2016-04-06 21:38:54 +12:00
Robin Bygrave 7b4d6f2d17 No effective change - code format 2016-04-06 20:37:50 +12:00
Robin Bygrave 8f100f2786 No effective change - code format 2016-04-06 20:36:50 +12:00
Robin Bygrave 6233392548 ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) #636 2016-04-06 15:14:50 +12:00
Robin Bygrave abaa1ff934 #636 - ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) 2016-04-06 14:31:57 +12:00
Robin Bygrave a7b239a1b7 ENH: Add DB Migration runner (as alternative to running via FlywayDB etc) #636 2016-04-06 14:31:20 +12:00
Robin Bygrave e8c0756ee9 Bump pom to 7.6.1-SNAPSHOT 2016-04-01 16:44:36 +13:00
Robin Bygrave e5179432a0 #634 - Refactor internal class path scanning to use avaje-classpath-scanner-api / avaje-classpath-scanner 2016-04-01 16:43:59 +13:00
Robin Bygrave d25123eb23 #633 - Remove deprecated SpiEbeanPlugin (people should have migrated to com.avaje.ebean.plugin.Plugin) 2016-04-01 15:07:49 +13:00
Robin Bygrave 27602c15b9 #632 - ENH: Alter AutoTune such that it saves back to autotune.xml (rename any existing autotune file) 2016-04-01 14:56:35 +13:00
Robin Bygrave bdc81bf6f4 [maven-release-plugin] prepare for next development iteration 2016-03-31 16:03:23 +13:00
Robin Bygrave b9237cb8a8 [maven-release-plugin] prepare release avaje-ebeanorm-7.5.1 2016-03-31 16:03:04 +13:00
Robin Bygrave 99e548db03 Modify pom - include avaje-ebeanorm-cluster by default, version range on slf4j 2016-03-31 16:02:12 +13:00
Robin Bygrave 71499d15e1 Bump pom to 7.5.1-SNAPSHOT 2016-03-31 15:47:04 +13:00
Robin Bygrave c9b0fd708a #631 - With find by id query, and id value type conversion (e.g. String to Long) this does not hit L2 cache 2016-03-31 15:18:33 +13:00
Robin Bygrave 8e43ece490 #630 - ENH: Add support for System property "disableTestProperties" ... to enable disabling the load of test-ebean.properties 2016-03-31 14:09:15 +13:00
Robin Bygrave fb2b8b4cf6 #629 - DefaultServerCache using java 1.7 compare method, drop trim logging from DEBUG to TRACE 2016-03-31 14:05:59 +13:00
Robin Bygrave 33f3920a60 #628 Refactor Clustering - tidy 2016-03-30 16:59:02 +13:00
Robin Bygrave 9a912390d4 #628 - Refactor Clustering - extract ClusterBroadcast implementations (TCP and Multicast) 2016-03-30 16:20:24 +13:00
Robin Bygrave 30a5769973 No effective change - remove http elastic test (as that is all in avaje-ebeanorm-elastic) 2016-03-29 17:00:36 +13:00
Robin Bygrave 084c9ad433 [maven-release-plugin] prepare for next development iteration 2016-03-29 16:39:51 +13:00
Robin Bygrave 737022bc57 [maven-release-plugin] prepare release avaje-ebeanorm-7.4.1 2016-03-29 16:39:32 +13:00
Robin Bygrave de6ba31996 #627 - Rename Query.includeSoftDeletes() to Query.setIncludeSoftDeletes() 2016-03-29 16:38:09 +13:00
Robin Bygrave df95f4759b Bump pom to 7.4.1-SNAPSHOT 2016-03-29 16:11:37 +13:00
Robin Bygrave 32f4a56ab4 #626 - ENH: Add support for where().idIn(...) using varargs 2016-03-29 16:10:00 +13:00
Robin Bygrave 04965892f0 #625 - Rename @DocStoreEmbedded to @DocEmbedded (with deprecation) 2016-03-29 15:46:10 +13:00
Robin Bygrave fdb2965d6e #622 - Set interrupted status for findFutureList() ... (specifically the FutureList.getUnchecked() methods) 2016-03-25 16:49:03 +13:00
Robin Bygrave ab2789dd08 #621 - EbeanServer.shutdown(true, false) followed by EbeanServerFactory create() when using the same ServerConfig give SQLException: Trying to access the Connection Pool when it is shutting down 2016-03-25 16:22:11 +13:00
Robin Bygrave 8f80280105 No effective change - tidy up in DefaultContainer 2016-03-25 16:18:29 +13:00
Robin Bygrave 1179f84611 Javadoc only update #619 2016-03-25 10:51:41 +13:00
Robin Bygrave 8593d3e496 [maven-release-plugin] prepare for next development iteration 2016-03-24 14:34:54 +13:00
Robin Bygrave 84fa373728 WIP update for migration runner 2016-02-10 20:51:55 +13:00
Robin Bygrave 12e6c0b139 Merge branch 'dbmig1' into dbmig2 2016-02-10 16:07:04 +13:00
Robin Bygrave 6f702098bc Update DB Migration runner 2016-02-09 12:55:41 +13:00
Robin Bygrave ad3292b92d WIP - DB Migration runner 2016-02-06 00:17:08 +13:00
360 changed files with 8788 additions and 9047 deletions
+9
View File
@@ -0,0 +1,9 @@
## Expected behavior
## Actual behavior
### Steps to reproduce
```java
// some java code
```
+10 -7
View File
@@ -1,14 +1,17 @@
* [![Maven Central : avaje-ebeanorm](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm) - avaje-ebeanorm
* [![Maven Central : avaje-ebeanorm-agent](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-agent) - avaje-ebeanorm-agent
* [![Maven Central : avaje-ebeanorm-agent](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-mavenenhancer/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-mavenenhancer) - avaje-ebeanorm-mavenenhancer
# Need help?
Post questions or issues to the Ebean google group - https://groups.google.com/forum/#!forum/ebean
Ebean ORM
==============
[https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
# Documentation
Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
## Maven cental links:
[Maven central - avaje-ebeanorm](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.avaje.ebeanorm%22%20AND%20a%3A%22avaje-ebeanorm%22 "maven central ebeanorm")
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cavaje-ebeanorm "maven central ebeanorm")
## Current versions
* [![Maven Central : avaje-ebeanorm](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm) - avaje-ebeanorm
* [![Maven Central : avaje-ebeanorm-agent](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-agent) - avaje-ebeanorm-agent
* [![Maven Central : avaje-ebeanorm-agent](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-mavenenhancer/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebeanorm/avaje-ebeanorm-mavenenhancer) - avaje-ebeanorm-mavenenhancer
+30 -37
View File
@@ -3,13 +3,13 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>avaje-javaparent</artifactId>
<version>1.2</version>
<artifactId>oss-parent</artifactId>
<version>1.1</version>
</parent>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>7.3.1</version>
<version>7.9.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -36,9 +36,8 @@
</developers>
<scm>
<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>
<tag>avaje-ebeanorm-7.9.1</tag>
</scm>
<dependencies>
@@ -64,27 +63,32 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>1.1.1</version>
<version>1.1.3</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.avaje</groupId>-->
<!--<artifactId>avaje-datasource-alert</artifactId>-->
<!--<version>1.1.1</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
<version>[1.7.1,1.7.99)</version>
</dependency>
<!-- provided scope for Spring boot loader classpath scanning support -->
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>1.2.7.RELEASE</version>
<scope>provided</scope>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.5</version>
</dependency>
<!-- provided scope for JsonNode support -->
@@ -95,13 +99,6 @@
<scope>provided</scope>
</dependency>
<!-- Jackson core used internally by Ebean -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.5</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
@@ -139,6 +136,8 @@
<scope>provided</scope>
</dependency>
<!-- Test scope -->
<!--<dependency>-->
<!--<groupId>oracle</groupId>-->
<!--<artifactId>oracle-jdbc</artifactId>-->
@@ -153,13 +152,6 @@
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -222,9 +214,9 @@
</dependencies>
<!-- Enhance the meta beans -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -234,12 +226,12 @@
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.9.2</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
<id>test</id>
<phase>process-test-classes</phase>
@@ -276,7 +268,8 @@
</systemProperties>
</configuration>
</plugin>
<plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
@@ -286,12 +279,13 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<doctitle>Ebean 6</doctitle>
<doctitle>Ebean 7</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
@@ -310,9 +304,7 @@
</additionalparam>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -322,6 +314,7 @@
</execution>
</executions>
</plugin>
</plugins>
</build>
+26 -38
View File
@@ -1,24 +1,21 @@
package com.avaje.ebean;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
/**
* This Ebean object is effectively a singleton that holds a map of registered
* {@link EbeanServer}s. It additionally provides a convenient way to use the
@@ -731,11 +728,25 @@ public final class Ebean {
return serverMgr.getDefaultServer().delete(beanType, id);
}
/**
* Delete permanent the bean given its type and id.
*/
public static int deletePermanent(Class<?> beanType, Object id) {
return serverMgr.getDefaultServer().deletePermanent(beanType, id);
}
/**
* Delete several beans given their type and id values.
*/
public static void deleteAll(Class<?> beanType, Collection<?> ids) {
serverMgr.getDefaultServer().deleteAll(beanType, ids);
public static int deleteAll(Class<?> beanType, Collection<?> ids) {
return serverMgr.getDefaultServer().deleteAll(beanType, ids);
}
/**
* Delete permanent several beans given their type and id values.
*/
public static int deleteAllPermanent(Class<?> beanType, Collection<?> ids) {
return serverMgr.getDefaultServer().deleteAllPermanent(beanType, ids);
}
/**
@@ -1484,29 +1495,6 @@ public final class Ebean {
return serverMgr.getDefaultServer().getBackgroundExecutor();
}
/**
* Run the cache warming queries on all bean types that have one defined for
* the default/primary EbeanServer.
* <p>
* A cache warming query can be defined via {@link CacheStrategy}.
* </p>
*/
public static void runCacheWarming() {
serverMgr.getDefaultServer().runCacheWarming();
}
/**
* Run the cache warming query for a specific bean type for the
* default/primary EbeanServer.
* <p>
* A cache warming query can be defined via {@link CacheStrategy}.
* </p>
*/
public static void runCacheWarming(Class<?> beanType) {
serverMgr.getDefaultServer().runCacheWarming(beanType);
}
/**
* Return the JsonContext for reading/writing JSON.
*/
+43 -63
View File
@@ -1,6 +1,5 @@
package com.avaje.ebean;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.meta.MetaInfoManager;
@@ -146,6 +145,17 @@ public interface EbeanServer {
*/
Object getBeanId(Object bean);
/**
* Set the Id value onto the bean converting the type of the id value if necessary.
* <p>
* For example, if the id value passed in is a String but ought to be a Long or UUID etc
* then it will automatically be converted.
* </p>
* @param bean The entity bean to set the id value on.
* @param id The id value to set.
*/
Object setBeanId(Object bean, Object id);
/**
* Return a map of the differences between two objects of the same type.
* <p>
@@ -1003,22 +1013,6 @@ public interface EbeanServer {
*/
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* 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>
*
* @param query
* the query to execute in the background
* @param transaction
* the transaction (can be null).
* @return a Future object for the list result of the query
*/
SqlFutureList findFutureList(SqlQuery query, Transaction transaction);
/**
* Return a PagedList for this query using pageIndex and pageSize.
* <p>
@@ -1183,40 +1177,23 @@ public interface EbeanServer {
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* Execute the sql query returning a set of MapBean.
* Execute the SqlQuery iterating a row at a time.
* <p>
* Generally you are able to use {@link SqlQuery#findSet()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @return the set of fetched MapBean.
*
* @see SqlQuery#findSet()
*/
Set<SqlRow> findSet(SqlQuery query, Transaction transaction);
void findEach(SqlQuery query, QueryEachConsumer<SqlRow> consumer, Transaction transaction);
/**
* Execute the sql query returning a map of MapBean.
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Generally you are able to use {@link SqlQuery#findMap()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*
* @param query
* the query to execute.
* @param transaction
* the transaction to use (can be null).
* @return the set of fetched MapBean.
*
* @see SqlQuery#findMap()
*/
Map<?, SqlRow> findMap(SqlQuery query, Transaction transaction);
void findEachWhile(SqlQuery query, QueryEachWhileConsumer<SqlRow> consumer, Transaction transaction);
/**
* Execute the sql query returning a single MapBean or null.
@@ -1340,6 +1317,16 @@ public interface EbeanServer {
*/
int delete(Class<?> beanType, Object id, Transaction transaction);
/**
* Delete permanent given the bean type and id.
*/
int deletePermanent(Class<?> beanType, Object id);
/**
* Delete permanent given the bean type and id with an explicit transaction.
*/
int deletePermanent(Class<?> beanType, Object id, Transaction transaction);
/**
* Delete all the beans in the collection.
*/
@@ -1353,13 +1340,22 @@ public interface EbeanServer {
/**
* Delete several beans given their type and id values.
*/
void deleteAll(Class<?> beanType, Collection<?> ids);
int deleteAll(Class<?> beanType, Collection<?> ids);
/**
* Delete several beans given their type and id values with an explicit
* transaction.
* Delete several beans given their type and id values with an explicit transaction.
*/
void deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction);
int deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction);
/**
* Delete permanent for several beans given their type and id values.
*/
int deleteAllPermanent(Class<?> beanType, Collection<?> ids);
/**
* Delete permanent for several beans given their type and id values with an explicit transaction.
*/
int deleteAllPermanent(Class<?> beanType, Collection<?> ids, Transaction transaction);
/**
* Execute a Sql Update Delete or Insert statement. This returns the number of
@@ -1835,22 +1831,6 @@ public interface EbeanServer {
*/
BackgroundExecutor getBackgroundExecutor();
/**
* Run the cache warming queries on all bean types that have one defined.
* <p>
* A cache warming query can be defined via {@link CacheStrategy}.
* </p>
*/
void runCacheWarming();
/**
* Run the cache warming query for a specific bean type.
* <p>
* A cache warming query can be defined via {@link CacheStrategy}.
* </p>
*/
void runCacheWarming(Class<?> beanType);
/**
* Return the JsonContext for reading/writing JSON.
* <p>
@@ -133,7 +133,7 @@ public class EbeanServerFactory {
/**
* Create the container instance using the configuration.
*/
private static SpiContainer createContainer(ContainerConfig containerConfig) {
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
String implClassName = System.getProperty("ebean.container", DEFAULT_CONTAINER);
+14
View File
@@ -209,6 +209,20 @@ public class Expr {
return Ebean.getExpressionFactory().icontains(propertyName, value);
}
/**
* For collection properties that are empty (have not existing elements).
*/
public static Expression isEmpty(String propertyName) {
return Ebean.getExpressionFactory().isEmpty(propertyName);
}
/**
* For collection properties that are not empty (have existing elements).
*/
public static Expression isNotEmpty(String propertyName) {
return Ebean.getExpressionFactory().isNotEmpty(propertyName);
}
/**
* In - property has a value in the array of values.
*/
@@ -246,11 +246,26 @@ public interface ExpressionFactory {
*/
Expression notExists(Query<?> subQuery);
/**
* Is empty expression for collection properties.
*/
Expression isEmpty(String propertyName);
/**
* Is not empty expression for collection properties.
*/
Expression isNotEmpty(String propertyName);
/**
* Id Equal to - ID property is equal to the value.
*/
Expression idEq(Object value);
/**
* Id IN a list of Id values.
*/
Expression idIn(Object... idValues);
/**
* Id IN a list of Id values.
*/
@@ -116,10 +116,16 @@ public interface ExpressionList<T> {
*/
Query<T> asDraft();
/**
* Deprecated in favour of setIncludeSoftDeletes().
*/
@Deprecated
Query<T> includeSoftDeletes();
/**
* Execute the query including soft deleted rows.
*/
Query<T> includeSoftDeletes();
Query<T> setIncludeSoftDeletes();
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
@@ -748,6 +754,16 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> notIn(String propertyName, Query<?> subQuery);
/**
* Is empty expression for collection properties.
*/
ExpressionList<T> isEmpty(String propertyName);
/**
* Is not empty expression for collection properties.
*/
ExpressionList<T> isNotEmpty(String propertyName);
/**
* Exists expression
*/
@@ -758,6 +774,11 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> notExists(Query<?> subQuery);
/**
* Id IN a list of id values.
*/
ExpressionList<T> idIn(Object... idValues);
/**
* Id IN a list of id values.
*/
-23
View File
@@ -362,29 +362,6 @@ public abstract class Model {
super(serverName, type);
}
/**
* Please migrate to use {@link Find} or constructor <code>Finder(Class)</code> that
* does not have the idType parameter.
* <p/>
* Create with the type of the ID property and entity bean and specific server name.
*
* @deprecated
*/
public Finder(Class<I> idType, Class<T> type) {
super(null, type);
}
/**
* Please migrate to use the constructor <code>Finder(String, Class)</code> that
* does not have the idType parameter.
* <p/>
* Create with the type of the ID property and entity bean and specific server name.
*
* @deprecated
*/
public Finder(String serverName, Class<I> idType, Class<T> type) {
super(serverName, type);
}
}
/**
+41 -12
View File
@@ -221,24 +221,39 @@ public final class OrderBy<T> implements Serializable {
private boolean ascending;
private String nulls;
private String highLow;
public Property(String property, boolean ascending) {
this.property = property;
this.ascending = ascending;
}
public Property(String property, boolean ascending, String nulls, String highLow) {
this.property = property;
this.ascending = ascending;
this.nulls = nulls;
this.highLow = highLow;
}
/**
* Return a copy of this Property with the path trimmed.
*/
public Property copyWithTrim(String path) {
return new Property(property.substring(path.length() + 1), ascending);
return new Property(property.substring(path.length() + 1), ascending, nulls, highLow);
}
@Override
public int hashCode() {
int hc = property.hashCode();
hc = hc * 31 + (ascending ? 0 : 1);
hc = hc * 31 + (nulls == null ? 0 : nulls.hashCode());
hc = hc * 31 + (highLow == null ? 0 : highLow.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
@@ -246,10 +261,11 @@ public final class OrderBy<T> implements Serializable {
if (!(obj instanceof Property)) {
return false;
}
Property e = (Property) obj;
return e.ascending == ascending
&& e.property.equals(property);
if (ascending != e.ascending) return false;
if (!property.equals(e.property)) return false;
if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false;
return highLow != null ? highLow.equals(e.highLow) : e.highLow == null;
}
public String toString() {
@@ -257,10 +273,20 @@ public final class OrderBy<T> implements Serializable {
}
public String toStringFormat() {
if (ascending) {
return property;
if (nulls == null) {
if (ascending) {
return property;
} else {
return property + " desc";
}
} else {
return property + " desc";
StringBuilder sb = new StringBuilder();
sb.append(property);
if (!ascending) {
sb.append(" ").append("desc");
}
sb.append(" ").append(nulls).append(" ").append(highLow);
return sb.toString();
}
}
@@ -282,7 +308,7 @@ public final class OrderBy<T> implements Serializable {
* Return a copy of this property.
*/
public Property copy() {
return new Property(property, ascending);
return new Property(property, ascending, nulls, highLow);
}
/**
@@ -323,7 +349,6 @@ public final class OrderBy<T> implements Serializable {
String[] chunks = orderByClause.split(",");
for (int i = 0; i < chunks.length; i++) {
String[] pairs = chunks[i].split(" ");
Property p = parseProperty(pairs);
if (p != null) {
@@ -353,8 +378,12 @@ public final class OrderBy<T> implements Serializable {
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();
if (wordList.size() == 4) {
// nulls high or nulls low as 3rd and 4th
boolean asc = isAscending(wordList.get(1));
return new Property(wordList.get(0), asc, wordList.get(2), wordList.get(3));
}
String m = "Expecting a 1, 2 or 4 words in [" + Arrays.toString(pairs) + "] but got " + wordList;
throw new RuntimeException(m);
}
+7 -1
View File
@@ -302,8 +302,9 @@ public interface Query<T> {
Query<T> asDraft();
/**
* Execute the query including soft deleted rows.
* Deprecated in favour of setIncludeSoftDeletes().
*/
@Deprecated
Query<T> includeSoftDeletes();
/**
@@ -375,6 +376,11 @@ public interface Query<T> {
*/
Query<T> setLazyLoadBatchSize(int lazyLoadBatchSize);
/**
* Execute the query including soft deleted rows.
*/
Query<T> setIncludeSoftDeletes();
/**
* Disable read auditing for this query.
* <p>
@@ -1,47 +0,0 @@
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>> {
SqlQuery getQuery();
}
+25 -58
View File
@@ -21,45 +21,48 @@ import java.util.Set;
* 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
* <pre>{@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;;
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* SqlQuery sqlQuery = Ebean.createSqlQuery(sql);
* sqlQuery.setParameter(&quot;name&quot;, &quot;Acme%&quot;);
* sqlQuery.setParameter(&quot;status&quot;, &quot;ACTIVE&quot;);
* SqlQuery sqlQuery = Ebean.createSqlQuery(sql);
* sqlQuery.setParameter("name", "Acme%");
* sqlQuery.setParameter("status", "ACTIVE");
*
* // execute the query returning a List of MapBean objects
* List&lt;SqlRow&gt; list = sqlQuery.findList();
* </pre>
* // execute the query returning a List of MapBean objects
* List<SqlRow> 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>
*/
void cancel();
/**
* Execute the query returning a list.
*/
List<SqlRow> findList();
/**
* Execute the query returning a set.
* Execute the SqlQuery iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
Set<SqlRow> findSet();
void findEach(QueryEachConsumer<SqlRow> consumer);
/**
* Execute the query returning a map.
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
Map<?, SqlRow> findMap();
void findEachWhile(QueryEachWhileConsumer<SqlRow> consumer);
/**
* Execute the query returning a single row or null.
@@ -70,19 +73,6 @@ public interface SqlQuery extends Serializable {
*/
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
* @deprecated
*/
SqlFutureList findFutureList();
/**
* The same as bind for named parameters.
*/
@@ -93,19 +83,6 @@ public interface SqlQuery extends Serializable {
*/
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>
*/
SqlQuery setListener(SqlQueryListener queryListener);
/**
* Set the index of the first row of the results to return.
*/
@@ -116,16 +93,6 @@ public interface SqlQuery extends Serializable {
*/
SqlQuery setMaxRows(int maxRows);
/**
* Set the index after which fetching continues in a background thread.
*/
SqlQuery setBackgroundFetchAfter(int backgroundFetchAfter);
/**
* Set the column to use to determine the keys for a Map.
*/
SqlQuery setMapKey(String mapKey);
/**
* Set a timeout on this query.
* <p>
@@ -1,33 +0,0 @@
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>
*/
void process(SqlRow bean);
}
@@ -5,25 +5,36 @@ 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 {
public @interface Cache {
/**
* 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.
* Set this to true to enable the use of query cache.
* <p>
* When a findUnique query is used with this property as the sole expression
* By default query caching is disabled as the query cache invalidates
* frequently and so it is typically used for specific bean types and cases.
* </p>
*/
boolean enableQueryCache() default false;
/**
* Set this to false to disable the use of bean cache.
* <p>
* By default bean caching is expected so this defaults to true. We might
* set this to false on a bean type that we want to use query caching but no
* bean caching (and this is expected to be a rare case).
* </p>
*/
boolean enableBeanCache() default true;
/**
* Specify the 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>
*/
@@ -42,17 +53,4 @@ public @interface CacheStrategy {
*/
boolean readOnly() default false;
/**
* Specify a query that can be used to warm the cache.
* <p>
* All the beans fetched by this query will be loaded into the bean cache and
* the query itself will be loaded into the query cache.
* </p>
* <p>
* The warming query will typically be executed at startup time after a short
* delay (defaults to a 30 seconds delay).
* </p>
*/
String warmingQuery() default "";
}
@@ -0,0 +1,56 @@
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;
/**
* Cache tuning hints for the L2 bean cache of a specific entity type.
* <p>
* Note that this is not useful when distributed L2 bean caches are used like
* ElasticSearch, Hazelcast, Ignite etc.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheBeanTuning {
/**
* The maximum size for the cache.
* <p>
* This defaults to 0 which means unlimited.
* </p>
*/
int maxSize() default 0;
/**
* The maximum time (in seconds) that a cache entry is allowed to stay in the
* cache when it has not been accessed.
* <p>
* This defaults to 0 which means unlimited.
* </p>
*/
int maxIdleSecs() default 0;
/**
* The maximum time (in seconds) a cache entry is allowed to stay in the
* cache.
* <p>
* This is not generally required as the cache entries are automatically
* evicted when related data changes are committed.
* </p>
* <p>
* This defaults to 0 which means unlimited.
* </p>
*/
int maxSecsToLive() default 0;
/**
* The frequency (in seconds) that cache trimming should occur.
* <p>
* This is a hint for cache implementations that use background cache trimming.
* </p>
*/
int trimFrequency() default 0;
}
@@ -6,14 +6,14 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify cache tuning for a specific entity type.
* Specify cache tuning for query caching on a specific entity type.
* <p>
* If this is not specified then the system default settings are used.
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheTuning {
public @interface CacheQueryTuning {
/**
* The maximum size for the cache.
@@ -18,7 +18,7 @@ import java.lang.annotation.Target;
* ...
* // include some customer details including
* // nested billingAddress
* @DocStoreEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
* @DocEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
* @ManyToOne
* Customer customer;
*
@@ -27,7 +27,7 @@ import java.lang.annotation.Target;
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DocStoreEmbedded {
public @interface DocEmbedded {
/**
* The properties on the embedded bean to include in the index.
@@ -1,19 +0,0 @@
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();
}
@@ -1,90 +0,0 @@
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;
}
@@ -6,19 +6,13 @@ 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.
* Mark and entity bean that is used solely via RawSql.
* <p>
* FUTURE: Support explicit sql for SqlInsert, SqlUpdate and SqlDelete.
* This means the entity bean has no base table specified (via @Table).
* </p>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Sql {
/**
* The sql select statements.
*/
SqlSelect[] select() default { @SqlSelect };
}
@@ -1,298 +0,0 @@
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,38 @@
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;
/**
* Annotate an entity bean with @View to indicates the bean is based on a view.
* <p>
* As such typically the view is defined in <code>extra-ddl.xml</code> using
* <code>create or replace view ...</code>.
* </p>
* <p>
* When using extra-ddl.xml Ebean will run the resulting DDL script after the
* <code>create-all</code> DDL (which is typically used during development) and for
* DB Migration will copy the scripts as <code>repeatable migration scripts</code> that
* will be run by FlywayDb (or Ebean's own migration runner) when their MD5 hash changes.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface View {
/**
* The name of the view this entity bean is based on.
*/
String name();
/**
* Tables this view is dependent on.
* <p>
* This is used with l2 caching to invalidate the query cache. Changes to these
* tables invalidate the query cache for the entity based on this view.
* </p>
*/
String[] dependentTables() default {};
}
-23
View File
@@ -1,7 +1,5 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.EbeanServer;
/**
* Represents part of the "L2" server side cache.
* <p>
@@ -17,27 +15,6 @@ import com.avaje.ebean.EbeanServer;
*/
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>
*/
void init(EbeanServer ebeanServer);
/**
* Return the configuration options for this cache.
*/
ServerCacheOptions getOptions();
/**
* Update the configuration options for this cache.
*/
void setOptions(ServerCacheOptions options);
/**
* Return the value given the key.
*/
+1 -14
View File
@@ -1,26 +1,13 @@
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>
*/
void init(EbeanServer ebeanServer);
/**
* Create the cache for the given type with options.
*/
ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions);
ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions);
}
+3 -15
View File
@@ -1,25 +1,10 @@
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>
*/
void init(EbeanServer server);
void setCaching(Class<?> beanType, boolean useCache);
/**
* Return true if there is an active bean cache for this type of bean.
*/
@@ -35,6 +20,9 @@ public interface ServerCacheManager {
*/
ServerCache getBeanCache(Class<?> beanType);
/**
* Return the cache for associated many properties of a bean type.
*/
ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName);
/**
+13 -12
View File
@@ -1,6 +1,7 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.annotation.CacheQueryTuning;
import com.avaje.ebean.annotation.CacheBeanTuning;
/**
* Options for controlling a cache.
@@ -22,23 +23,23 @@ public class ServerCacheOptions {
/**
* Create from the cacheTuning deployment annotation.
*/
public ServerCacheOptions(CacheTuning cacheTuning) {
public ServerCacheOptions(CacheBeanTuning tuning) {
this.maxSize = tuning.maxSize();
this.maxIdleSecs = tuning.maxIdleSecs();
this.maxSecsToLive = tuning.maxSecsToLive();
this.trimFrequency = tuning.trimFrequency();
}
/**
* Create from the cacheTuning deployment annotation.
*/
public ServerCacheOptions(CacheQueryTuning cacheTuning) {
this.maxSize = cacheTuning.maxSize();
this.maxIdleSecs = cacheTuning.maxIdleSecs();
this.maxSecsToLive = cacheTuning.maxSecsToLive();
this.trimFrequency = cacheTuning.trimFrequency();
}
/**
* Create merging default options with the deployment specified ones.
*/
public ServerCacheOptions(ServerCacheOptions defaults) {
this.maxSize = defaults.getMaxSize();
this.maxIdleSecs = defaults.getMaxIdleSecs();
this.maxSecsToLive = defaults.getMaxIdleSecs();
this.trimFrequency = defaults.getTrimFrequency();
}
/**
* Apply any settings from the default settings that have not already been
* specifically set.
@@ -0,0 +1,15 @@
package com.avaje.ebean.cache;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.ServerConfig;
/**
* The plugin interface that creates a ServerCacheFactory.
*/
public interface ServerCachePlugin {
/**
* Create the ServerCacheFactory given the server config and background executor service.
*/
ServerCacheFactory create(ServerConfig config, BackgroundExecutor executor);
}
@@ -0,0 +1,27 @@
package com.avaje.ebean.cache;
/**
* The type of L2 caches.
*/
public enum ServerCacheType {
/**
* Bean cache.
*/
BEAN,
/**
* Natural key cache.
*/
NATURAL_KEY,
/**
* Collection Ids for Many properties.
*/
COLLECTION_IDS,
/**
* Query cache.
*/
QUERY
}
@@ -1,6 +1,10 @@
package com.avaje.ebean.config;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
/**
* Helper to find classes taking into account the context class loader.
*/
@@ -29,6 +33,13 @@ public class ClassLoadConfig {
return isPresent("java.time.LocalDate");
}
/**
* Return true if Java7 is present.
*/
public boolean isJava7Present() {
return isPresent("java.nio.file.Path");
}
/**
* Return true if the Joda types are available and should be supported.
*/
@@ -70,6 +81,13 @@ public class ClassLoadConfig {
}
}
/**
* Return the resources for the given name.
*/
public Enumeration<URL> getResources(String name) throws IOException {
return context.getResources(name);
}
/**
* Return true if the given class is present.
*/
@@ -122,6 +140,13 @@ public class ClassLoadConfig {
return (loader != null) ? loader: callerLoader;
}
Enumeration<URL> getResources(String name) throws IOException {
if (preferredLoader != null) {
return preferredLoader.getResources(name);
}
return contextLoader().getResources(name);
}
Class<?> forName(String name) throws ClassNotFoundException {
if (preferredLoader != null) {
@@ -1,7 +1,5 @@
package com.avaje.ebean.config;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
@@ -12,453 +10,51 @@ import java.util.Properties;
*/
public class ContainerConfig {
protected boolean clusterActive;
protected Properties properties;
/**
* Communication mode used for clustering.
* Return true if clustering is active.
*/
public enum ClusterMode {
/**
* No clustering.
*/
NONE,
/**
* Use Multicast networking for cluster wide communication.
*/
MULTICAST,
/**
* Use TCP Sockets for cluster wide communication.
*/
SOCKET
public boolean isClusterActive() {
return clusterActive;
}
/**
* The cluster mode to use.
* Set to true for clustering to be active.
*/
ClusterMode mode = ClusterMode.NONE;
/**
* Configuration if using TCP sockets for clustering communication.
*/
SocketConfig socketConfig = new SocketConfig();
/**
* Configuration if using Multicast for clustering communication.
*/
MulticastConfig multicastConfig = new MulticastConfig();
// -------------------------------------------------------------------------------------------
// MulticastConfig
/**
* The configuration for clustering using Multicast networking.
*/
public static class MulticastConfig {
int managerSleepMillis = 80;
int lastSendTimeFreqSecs = 300;//5mins
int lastStatusTimeFreqSecs = 600;//10mins
int maxResendOutgoingAttempts = 200;
int maxResendIncomingRequests = 50;
int listenPort;
String listenAddress;
int sendPort;
String sendAddress;
// Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507
int maxSendPacketSize = 1500;
// Whether to send packets even when there are no other members online
boolean sendWithNoMembers = true;
// When multiple instances are on same box you need to broadcast back locally
boolean disableLoopback;
int listenTimeToLive = -1;
int listenTimeout = 1000;
int listenBufferSize = 65500;
// For multihomed environment the address the listener should bind to
String listenBindAddress;
/**
* Return the manager sleep millis.
*/
public int getManagerSleepMillis() {
return managerSleepMillis;
}
/**
* Set the manager sleep millis.
*/
public void setManagerSleepMillis(int managerSleepMillis) {
this.managerSleepMillis = managerSleepMillis;
}
/**
* Return the last send time frequency.
*/
public int getLastSendTimeFreqSecs() {
return lastSendTimeFreqSecs;
}
/**
* Set the last send time frequency.
*/
public void setLastSendTimeFreqSecs(int lastSendTimeFreqSecs) {
this.lastSendTimeFreqSecs = lastSendTimeFreqSecs;
}
/**
* Return the last status time frequency.
*/
public int getLastStatusTimeFreqSecs() {
return lastStatusTimeFreqSecs;
}
/**
* Set the last status time frequency.
*/
public void setLastStatusTimeFreqSecs(int lastStatusTimeFreqSecs) {
this.lastStatusTimeFreqSecs = lastStatusTimeFreqSecs;
}
/**
* Return the maximum number of times we will try to re-send a given packet before giving up sending
*/
public int getMaxResendOutgoingAttempts() {
return maxResendOutgoingAttempts;
}
/**
* Set the maximum retry attempts for outgoing messages.
*/
public void setMaxResendOutgoingAttempts(int maxResendOutgoingAttempts) {
this.maxResendOutgoingAttempts = maxResendOutgoingAttempts;
}
/**
* Return the maximum number of times we will ask for a packet to be resent to us before giving up asking.
*/
public int getMaxResendIncomingRequests() {
return maxResendIncomingRequests;
}
/**
* Set the maximum retry attempts for incoming messages.
*/
public void setMaxResendIncomingRequests(int maxResendIncomingRequests) {
this.maxResendIncomingRequests = maxResendIncomingRequests;
}
/**
* Return the listen port.
*/
public int getListenPort() {
return listenPort;
}
/**
* Set the listen port.
*/
public void setListenPort(int port) {
this.listenPort = port;
}
/**
* Return the listen address.
*/
public String getListenAddress() {
return listenAddress;
}
/**
* Set the listen address.
*/
public void setListenAddress(String listenAddress) {
this.listenAddress = listenAddress;
}
/**
* Return the send port.
*/
public int getSendPort() {
return sendPort;
}
/**
* Set the send port.
*/
public void setSendPort(int sendPort) {
this.sendPort = sendPort;
}
/**
* Return the send address.
*/
public String getSendAddress() {
return sendAddress;
}
/**
* Set the send address.
*/
public void setSendAddress(String sendAddress) {
this.sendAddress = sendAddress;
}
/**
* Return the maximum send packet size.
*/
public int getMaxSendPacketSize() {
return maxSendPacketSize;
}
/**
* Set the maximum send packet size. Note 1500 is Ethernet MTU and this must be less than UDP max packet size of 65507.
*/
public void setMaxSendPacketSize(int maxSendPacketSize) {
this.maxSendPacketSize = maxSendPacketSize;
}
/**
* Return true if send messages when no other members in the cluster are up.
*/
public boolean isSendWithNoMembers() {
return sendWithNoMembers;
}
/**
* Set true if send messages when no other members in the cluster are up.
*/
public void setSendWithNoMembers(boolean sendWithNoMembers) {
this.sendWithNoMembers = sendWithNoMembers;
}
/**
* Return true if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public boolean isDisableLoopback() {
return disableLoopback;
}
/**
* Set if loopback is disabled. When multiple instances are on same box you need to broadcast back locally.
*/
public void setDisableLoopback(boolean disableLoopback) {
this.disableLoopback = disableLoopback;
}
/**
* Return the listen time to live.
*/
public int getListenTimeToLive() {
return listenTimeToLive;
}
/**
* Set the listen time to live.
*/
public void setListenTimeToLive(int listenTimeToLive) {
this.listenTimeToLive = listenTimeToLive;
}
/**
* Return the listen timeout.
*/
public int getListenTimeout() {
return listenTimeout;
}
/**
* set the listen timeout.
*/
public void setListenTimeout(int listenTimeout) {
this.listenTimeout = listenTimeout;
}
/**
* Return the listen buffer size.
*/
public int getListenBufferSize() {
return listenBufferSize;
}
/**
* Set the listen buffer size.
*/
public void setListenBufferSize(int listenBufferSize) {
this.listenBufferSize = listenBufferSize;
}
/**
* Return the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public String getListenBindAddress() {
return listenBindAddress;
}
/**
* Set the listener bind address (optional). For multihomed environment the address the listener should bind to.
*/
public void setListenBindAddress(String listenBindAddress) {
this.listenBindAddress = listenBindAddress;
}
public void setClusterActive(boolean clusterActive) {
this.clusterActive = clusterActive;
}
// -------------------------------------------------------------------------------------------
// SocketConfig
/**
* Configuration for clustering using TCP sockets.
* <p>
* This is good for when there are relatively small number of cluster members.
* Return the deployment properties.
*/
public static class SocketConfig {
/**
* This local server in host:port format.
*/
String localHostPort;
/**
* All the cluster members in host:port format.
*/
List<String> members = new ArrayList<String>();
/**
* core threads for the associated thread pool.
*/
int coreThreads = 2;
/**
* Max threads for the associated thread pool.
*/
int maxThreads = 16;
String threadPoolName = "EbeanCluster";
/**
* Return the host and port for this server instance.
*/
public String getLocalHostPort() {
return localHostPort;
}
/**
* Set the host and port for this server instance.
*/
public void setLocalHostPort(String localHostPort) {
this.localHostPort = localHostPort;
}
/**
* Return all the host and port for all the members of the cluster.
*/
public List<String> getMembers() {
return members;
}
/**
* Set all the host and port for all the members of the cluster.
*/
public void setMembers(List<String> members) {
this.members = members;
}
/**
* Return the number of core threads to use.
*/
public int getCoreThreads() {
return coreThreads;
}
/**
* Set the number of core threads to use.
*/
public void setCoreThreads(int coreThreads) {
this.coreThreads = coreThreads;
}
/**
* Return the number of max threads to use.
*/
public int getMaxThreads() {
return maxThreads;
}
/**
* Set the number of max threads to use.
*/
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* Return the thread pool name.
*/
public String getThreadPoolName() {
return threadPoolName;
}
/**
* Set the thread pool name.
*/
public void setThreadPoolName(String threadPoolName) {
this.threadPoolName = threadPoolName;
}
public Properties getProperties() {
return properties;
}
// -------------------------------------------------------------------------------------------
// Members
/**
* Set the deployment properties.
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
/**
* Load the settings from properties.
*/
public void loadFromProperties(Properties properties) {
//TODO
this.properties = properties;
this.clusterActive = getProperty(properties, "ebean.cluster.active", clusterActive);
}
/**
* Return the cluster mode.
* Return the boolean property setting.
*/
public ClusterMode getMode() {
return mode;
protected boolean getProperty(Properties properties, String key, boolean defaultValue) {
return "true".equalsIgnoreCase(properties.getProperty(key, Boolean.toString(defaultValue)));
}
/**
* Set the cluster mode.
*/
public void setMode(ClusterMode mode) {
this.mode = mode;
}
/**
* Return the socket communication configuration.
*/
public SocketConfig getSocketConfig() {
return socketConfig;
}
/**
* Set the socket communication configuration.
*/
public void setSocketConfig(SocketConfig socketConfig) {
this.socketConfig = socketConfig;
}
/**
* Return the multicast communication configuration.
*/
public MulticastConfig getMulticastConfig() {
return multicastConfig;
}
/**
* Set the multicast communication configuration.
*/
public void setMulticastConfig(MulticastConfig multicastConfig) {
this.multicastConfig = multicastConfig;
}
}
@@ -6,6 +6,8 @@ import com.avaje.ebean.dbmigration.DbMigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* Configuration for the DB migration processing.
*/
@@ -58,6 +60,11 @@ public class DbMigrationConfig {
protected String applySuffix = ".sql";
/**
* Set this to "V" to be compatible with FlywayDB.
*/
protected String applyPrefix = "";
protected String modelSuffix = ".model.xml";
protected boolean includeGeneratedFileComment;
@@ -67,6 +74,36 @@ public class DbMigrationConfig {
*/
protected String generatePendingDrop;
/**
* For running migration the DB table that holds migration execution status.
*/
protected String metaTable = "db_migration";
/**
* Flag set to true means to run any outstanding migrations on startup.
*/
protected boolean runMigration;
/**
* Comma and equals delimited key/value placeholders to replace in DDL scripts.
*/
protected String runPlaceholders;
/**
* Map of key/value placeholders to replace in DDL scripts.
*/
protected Map<String,String> runPlaceholderMap;
/**
* DB user used to run the DB migration.
*/
protected String dbUser;
/**
* DB password used to run the DB migration.
*/
protected String dbPassword;
/**
* Return the DB platform to generate migration DDL for.
*
@@ -145,6 +182,20 @@ public class DbMigrationConfig {
this.applySuffix = applySuffix;
}
/**
* Return the apply prefix.
*/
public String getApplyPrefix() {
return applyPrefix;
}
/**
* Set the apply prefix. This might be set to "V" for use with FlywayDB.
*/
public void setApplyPrefix(String applyPrefix) {
this.applyPrefix = applyPrefix;
}
/**
* Return true if the generated file comment should be included.
*/
@@ -199,10 +250,119 @@ public class DbMigrationConfig {
this.modelPath = "";
}
/**
* Return the table name that holds the migration run details
* (used by DB Migration runner only).
*/
public String getMetaTable() {
return metaTable;
}
/**
* Set the table name that holds the migration run details
* (used by DB Migration runner only).
*/
public void setMetaTable(String metaTable) {
this.metaTable = metaTable;
}
/**
* Return a comma and equals delimited placeholders that are substituted in SQL scripts when running migration
* (used by DB Migration runner only).
*/
public String getRunPlaceholders() {
// environment properties take precedence
String placeholders = readEnvironment("ddl.migration.placeholders");
if (placeholders != null) {
return placeholders;
}
return runPlaceholders;
}
/**
* Set a comma and equals delimited placeholders that are substituted in SQL scripts when running migration
* (used by DB Migration runner only).
*/
public void setRunPlaceholders(String runPlaceholders) {
this.runPlaceholders = runPlaceholders;
}
/**
* Return a map of placeholder values that are substituted in SQL scripts when running migration
* (used by DB Migration runner only).
*/
public Map<String, String> getRunPlaceholderMap() {
return runPlaceholderMap;
}
/**
* Set a map of placeholder values that are substituted when running migration
* (used by DB Migration runner only).
*/
public void setRunPlaceholderMap(Map<String, String> runPlaceholderMap) {
this.runPlaceholderMap = runPlaceholderMap;
}
/**
* Return true if the DB migration should be run on startup.
*/
public boolean isRunMigration() {
// environment properties take precedence
String run = readEnvironment("ddl.migration.run");
if (run != null) {
return "true".equalsIgnoreCase(run.trim());
}
return runMigration;
}
/**
* Set to true to run the DB migration on startup.
*/
public void setRunMigration(boolean runMigration) {
this.runMigration = runMigration;
}
/**
* Return the DB user to use for running DB migrations.
*/
public String getDbUser() {
// environment properties take precedence
String user = readEnvironment("ddl.migration.user");
if (user != null) {
return user;
}
return dbUser;
}
/**
* Set the DB user to use for running DB migrations.
*/
public void setDbUser(String dbUser) {
this.dbUser = dbUser;
}
/**
* Return the DB password to use for running DB migrations.
*/
public String getDbPassword() {
String user = readEnvironment("ddl.migration.password");
if (user != null) {
return user;
}
return dbPassword;
}
/**
* Set the DB password to use for running DB migrations.
*/
public void setDbPassword(String dbPassword) {
this.dbPassword = dbPassword;
}
/**
* Load the settings from the PropertiesWrapper.
*/
public void loadSettings(PropertiesWrapper properties) {
public void loadSettings(PropertiesWrapper properties, String serverName) {
migrationPath = properties.get("migration.migrationPath", migrationPath);
if (properties.getBoolean("migration.singleDirectory", false)) {
@@ -210,6 +370,7 @@ public class DbMigrationConfig {
} else {
modelPath = properties.get("migration.modelPath", modelPath);
}
applyPrefix = properties.get("migration.applyPrefix", applyPrefix);
applySuffix = properties.get("migration.applySuffix", applySuffix);
modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
@@ -220,6 +381,18 @@ public class DbMigrationConfig {
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
name = properties.get("migration.name", name);
runMigration = properties.getBoolean("migration.run", runMigration);
metaTable = properties.get("migration.metaTable", metaTable);
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
String adminUser = properties.get("datasource."+serverName+".username", dbUser);
adminUser = properties.get("datasource."+serverName+".adminusername", adminUser);
dbUser = properties.get("migration.dbuser", adminUser);
String adminPwd = properties.get("datasource."+serverName+".password", dbPassword);
adminPwd = properties.get("datasource."+serverName+".adminpassword", adminPwd);
dbPassword = properties.get("migration.dbpassword", adminPwd);
}
/**
@@ -31,6 +31,11 @@ public final class PropertyMap implements Serializable {
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public static boolean loadTestProperties() {
String disable = System.getProperty("disableTestProperties");
return (disable == null || !disable.equalsIgnoreCase("true"));
}
public String toString() {
return map.toString();
}
@@ -18,6 +18,9 @@ final class PropertyMapLoader {
* Load the <code>test-ebean.properties</code>.
*/
public static PropertyMap loadTestProperties() {
if (!PropertyMap.loadTestProperties()) {
return new PropertyMap();
}
return load(null, "test-ebean.properties");
}
@@ -31,7 +34,7 @@ final class PropertyMapLoader {
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
loadTestProperties = true;
loadTestProperties = PropertyMap.loadTestProperties();
fileName = "ebean.properties";
}
}
@@ -5,6 +5,7 @@ import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.annotation.Encrypted;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.cache.ServerCachePlugin;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.event.BeanFindController;
@@ -26,8 +27,10 @@ import org.avaje.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
@@ -82,6 +85,12 @@ public class ServerConfig {
*/
private String name;
/**
* Typically configuration type objects that are passed by this ServerConfig
* to plugins. For example - IgniteConfiguration passed to Ignite plugin.
*/
private Map<String,Object> serviceObject = new HashMap<String, Object>();
private ContainerConfig containerConfig;
/**
@@ -216,6 +225,11 @@ public class ServerConfig {
private boolean eagerFetchLobs;
/**
* Timezone used to get/set Timestamp values via JDBC.
*/
private String dataTimeZone;
private boolean ddlGenerate;
private boolean ddlRun;
@@ -343,7 +357,7 @@ public class ServerConfig {
private DbEncrypt dbEncrypt;
private ServerCacheFactory serverCacheFactory;
private ServerCachePlugin serverCachePlugin;
private ServerCacheManager serverCacheManager;
@@ -397,12 +411,34 @@ public class ServerConfig {
private String jodaLocalTimeMode;
/**
* Set to true to globally disable L2 caching (typically for performance testing).
*/
private boolean disableL2Cache;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
public ServerConfig() {
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
* For example, put IgniteConfiguration in to be passed to the Ignite plugin.
* </p>
*/
public void putServiceObject(String key, Object configObject) {
serviceObject.put(key, configObject);
}
/**
* Return the service object given the key.
*/
public Object getServiceObject(String key) {
return serviceObject.get(key);
}
/**
* Return the Jackson JsonFactory to use.
@@ -811,6 +847,23 @@ public class ServerConfig {
this.migrationConfig = migrationConfig;
}
/**
* Return the time zone to use when reading/writing Timestamps via JDBC.
* <p>
* When set a Calendar object is used in JDBC calls when reading/writing Timestamp objects.
* </p>
*/
public String getDataTimeZone() {
return System.getProperty("ebean.dataTimeZone", dataTimeZone);
}
/**
* Set the time zone to use when reading/writing Timestamps via JDBC.
*/
public void setDataTimeZone(String dataTimeZone) {
this.dataTimeZone = dataTimeZone;
}
/**
* Return the suffix appended to the base table to derive the view that contains the union
* of the base table and the history table in order to support asOf queries.
@@ -886,17 +939,17 @@ public class ServerConfig {
}
/**
* Return the ServerCacheFactory.
* Return the ServerCachePlugin.
*/
public ServerCacheFactory getServerCacheFactory() {
return serverCacheFactory;
public ServerCachePlugin getServerCachePlugin() {
return serverCachePlugin;
}
/**
* Set the ServerCacheFactory to use.
* Set the ServerCachePlugin to use.
*/
public void setServerCacheFactory(ServerCacheFactory serverCacheFactory) {
this.serverCacheFactory = serverCacheFactory;
public void setServerCachePlugin(ServerCachePlugin serverCachePlugin) {
this.serverCachePlugin = serverCachePlugin;
}
/**
@@ -2267,7 +2320,7 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
migrationConfig.loadSettings(p);
migrationConfig.loadSettings(p, name);
namingConvention = createNamingConvention(p, namingConvention);
if (namingConvention != null) {
@@ -2288,6 +2341,7 @@ public class ServerConfig {
}
loadDocStoreSettings(p);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
@@ -2299,7 +2353,7 @@ public class ServerConfig {
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory", serverCacheFactory);
serverCachePlugin = createInstance(p, ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager);
cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay);
classPathReaderClassName = p.get("classpathreader");
@@ -2338,6 +2392,7 @@ public class ServerConfig {
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
dataTimeZone = p.get("dataTimeZone", dataTimeZone);
asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix);
asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod);
historyTableSuffix = p.get("historyTableSuffix", historyTableSuffix);
@@ -2476,6 +2531,20 @@ public class ServerConfig {
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
}
/**
* Return true if L2 cache is disabled.
*/
public boolean isDisableL2Cache() {
return disableL2Cache;
}
/**
* Set to true to disable L2 caching. Typically useful in performance testing.
*/
public void setDisableL2Cache(boolean disableL2Cache) {
this.disableL2Cache = disableL2Cache;
}
/**
* Specify how UUID is stored.
*/
@@ -9,7 +9,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
/**
* Database platform specific settings.
@@ -171,6 +176,10 @@ public class DatabasePlatform {
public DatabasePlatform() {
}
public void configure(Properties properties) {
// by default do nothing
}
/**
* Return the name of the DatabasePlatform.
* <p>
@@ -522,4 +531,28 @@ public class DatabasePlatform {
return disallowBatchOnCascade;
}
/**
* Return true if the table exists.
*/
public boolean tableExists(Connection connection, String catalog, String schema, String table) throws SQLException {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(catalog, schema, table, null);
try {
return tables.next();
} finally {
close(tables);
}
}
/**
* Close the resultSet.
*/
protected void close(ResultSet resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
logger.error("Error closing resultSet", e);
}
}
}
@@ -7,6 +7,7 @@ import com.avaje.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
import javax.sql.DataSource;
import java.sql.Types;
import java.util.Properties;
/**
* Postgres v9 specific platform.
@@ -50,6 +51,7 @@ public class PostgresPlatform extends DatabasePlatform {
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
dbTypeMap.put(Types.TIMESTAMP, new DbType("timestamptz"));
dbTypeMap.put(Types.BINARY, dbBytea);
dbTypeMap.put(Types.VARBINARY, dbBytea);
@@ -58,7 +60,16 @@ public class PostgresPlatform extends DatabasePlatform {
dbTypeMap.put(Types.CLOB, dbTypeText);
dbTypeMap.put(Types.LONGVARBINARY, dbBytea);
dbTypeMap.put(Types.LONGVARCHAR, dbTypeText);
}
@Override
public void configure(Properties properties) {
super.configure(properties);
String tsType = properties.getProperty("ebean.postgres.timestamp");
if (tsType != null) {
// set timestamp type to "timestamp" without time zone
dbTypeMap.put(Types.TIMESTAMP, new DbType(tsType));
}
}
/**
@@ -25,10 +25,14 @@ import com.avaje.ebean.dbmigration.model.ModelContainer;
import com.avaje.ebean.dbmigration.model.ModelDiff;
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.extraddl.model.DdlScript;
import com.avaje.ebeaninternal.extraddl.model.ExtraDdl;
import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -42,7 +46,7 @@ import java.util.List;
* and drop objects (drop tables, drop columns).
* </p>
* <p>
* This does not run the migration or ddl scripts but just generates them.
* This does not run the migration or ddl scripts but just generates them.
* </p>
* <pre>{@code
*
@@ -98,7 +102,7 @@ public class DbMigration {
/**
* Set the path from the current working directory to the application resources.
*
* <p>
* This defaults to maven style 'src/main/resources'.
*/
public void setPathToResources(String pathToResources) {
@@ -169,7 +173,7 @@ public class DbMigration {
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
* <p>
* This does not run the migration or ddl scripts but just generates them.
* This does not run the migration or ddl scripts but just generates them.
* </p>
* <h3>Example: Run for a single specific platform</h3>
* <pre>{@code
@@ -181,7 +185,7 @@ public class DbMigration {
* migration.generateMigration();
*
* }</pre>
*
* <p>
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
* <pre>{@code
*
@@ -206,6 +210,8 @@ public class DbMigration {
try {
Request request = createRequest();
generateExtraDdl(request);
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
@@ -220,6 +226,49 @@ public class DbMigration {
}
}
/**
* Generate "repeatable" migration scripts.
* <p>
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
* migration runner.
* </p>
*/
private void generateExtraDdl(Request request) throws IOException {
if (databasePlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (ExtraDdlXmlReader.matchPlatform(databasePlatform.getName(), script.getPlatforms())) {
writeExtraDdl(request, script);
}
}
}
}
}
/**
* Write (or override) the "repeatable" migration script.
*/
private void writeExtraDdl(Request request, DdlScript script) throws IOException {
String fullName = repeatableMigrationName(script.getName());
logger.info("writing repeatable script {}", fullName);
File file = new File(request.migrationDir, fullName);
FileWriter writer = new FileWriter(file);
writer.write(script.getValue());
writer.flush();
writer.close();
}
private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
}
/**
* Generate the diff migration.
*/
@@ -319,7 +368,7 @@ public class DbMigration {
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
writer.processMigration(dbMigration, write, request.migrationDir , fullVersion);
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
}
@@ -339,7 +388,7 @@ public class DbMigration {
/**
* Return the full version for the migration being generated.
*
* <p>
* The full version can contain a comment suffix after a "__" double underscore.
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
@@ -349,7 +398,7 @@ public class DbMigration {
version = migrationModel.getNextVersion(initialVersion);
}
String fullVersion = version;
String fullVersion = migrationConfig.getApplyPrefix() + version;
if (migrationConfig.getName() != null) {
fullVersion += "__" + toUnderScore(migrationConfig.getName());
@@ -366,7 +415,7 @@ public class DbMigration {
* Replace spaces with underscores.
*/
private String toUnderScore(String name) {
return name.replace(' ','_');
return name.replace(' ', '_');
}
/**
@@ -444,7 +493,7 @@ public class DbMigration {
*/
protected File getModelDirectory(File migrationDirectory) {
String modelPath = migrationConfig.getModelPath();
if (modelPath == null || modelPath.isEmpty()) {
if (modelPath == null || modelPath.isEmpty()) {
return migrationDirectory;
}
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
@@ -1,8 +1,10 @@
package com.avaje.ebean.dbmigration;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.model.CurrentModel;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import javax.persistence.PersistenceException;
import java.io.File;
@@ -13,6 +15,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
@@ -81,6 +85,28 @@ public class DdlGenerator {
}
}
/**
* Execute all the DDL statements in the script.
*/
public int runScript(boolean expectErrors, String content, String scriptName) {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
Transaction transaction = server.createTransaction();
Connection connection = transaction.getConnection();
try {
int count = runner.runAll(content, connection);
transaction.commit();
return count;
} catch (SQLException e) {
throw new PersistenceException("Failed to run script", e);
} finally {
transaction.end();
}
}
protected void runDropSql() throws IOException {
if (!createOnly) {
if (dropAllContent == null) {
@@ -95,6 +121,14 @@ public class DdlGenerator {
createAllContent = readFile(getCreateFileName());
}
runScript(false, createAllContent, getCreateFileName());
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl)) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
}
}
}
protected void runInitSql() throws IOException {
@@ -110,9 +144,8 @@ public class DdlGenerator {
if (sqlScript != null) {
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
if (is != null) {
DdlRunner runner = new DdlRunner(false, sqlScript);
String content = readContent(new InputStreamReader(is));
runner.runAll(content, server);
runScript(false, content, sqlScript);
}
}
}
@@ -221,13 +254,4 @@ public class DdlGenerator {
}
}
/**
* Execute all the DDL statements in the script.
*/
public int runScript(boolean expectErrors, String content, String scriptName) {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
return runner.runAll(content, server);
}
}
@@ -1,11 +1,8 @@
package com.avaje.ebean.dbmigration;
import com.avaje.ebean.Transaction;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -20,11 +17,11 @@ public class DdlRunner {
protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
protected DdlParser ddlParser = new DdlParser();
private DdlParser ddlParser = new DdlParser();
protected final String scriptName;
private final String scriptName;
protected final boolean expectErrors;
private final boolean expectErrors;
/**
* Construct with a script name (for logging) and flag indicating if errors are expected.
@@ -37,36 +34,16 @@ public class DdlRunner {
/**
* Parse the content into sql statements and execute them in a transaction.
*/
public int runAll(String content, SpiEbeanServer server) {
public int runAll(String content, Connection connection) throws SQLException {
List<String> statements = ddlParser.parse(new StringReader(content));
return runStatements(statements, server);
return runStatements(statements, connection);
}
/**
* Execute all the statements in a single transaction.
*/
public int runStatements(List<String> statements, SpiEbeanServer server) {
Transaction t = server.createTransaction();
try {
int statementCount = runStatements(expectErrors, statements, t.getConnection());
t.commit();
return statementCount;
} catch (Exception e) {
throw new PersistenceException("Error: " + e.getMessage(), e);
} finally {
t.end();
}
}
/**
* Execute the list of statements.
*/
private int runStatements(boolean expectErrors, List<String> statements, Connection c) {
private int runStatements(List<String> statements, Connection connection) throws SQLException {
List<String> noDuplicates = new ArrayList<String>();
@@ -80,7 +57,7 @@ public class DdlRunner {
for (int i = 0; i < noDuplicates.size(); i++) {
String xOfy = (i + 1) + " of " + noDuplicates.size();
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
runStatement(expectErrors, xOfy, noDuplicates.get(i), connection);
}
return noDuplicates.size();
@@ -89,7 +66,7 @@ public class DdlRunner {
/**
* Execute the statement.
*/
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) throws SQLException {
PreparedStatement pstmt = null;
try {
@@ -109,12 +86,12 @@ public class DdlRunner {
pstmt = c.prepareStatement(stmt);
pstmt.execute();
} catch (Exception e) {
} catch (SQLException e) {
if (expectErrors) {
logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
} else {
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
throw new RuntimeException(msg, e);
throw new SQLException(msg, e);
}
} finally {
@@ -0,0 +1,111 @@
package com.avaje.ebean.dbmigration;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.runner.LocalMigrationResource;
import com.avaje.ebean.dbmigration.runner.LocalMigrationResources;
import com.avaje.ebean.dbmigration.runner.MigrationTable;
import com.avaje.ebeaninternal.util.JdbcClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* Runs the DB migration typically on application start.
*/
public class MigrationRunner {
public static final Logger logger = LoggerFactory.getLogger("org.avaje.ebean.DbMigration");
private final EbeanServer server;
private final ServerConfig config;
private final DbMigrationConfig migrationConfig;
public MigrationRunner(EbeanServer server, DbMigrationConfig migrationConfig) {
this.server = server;
this.config = server.getPluginApi().getServerConfig();
this.migrationConfig = migrationConfig;
}
/**
* Run the migrations if there are any that need running.
*/
public void run() {
LocalMigrationResources resources = new LocalMigrationResources(config, migrationConfig);
if (!resources.readResources()) {
logger.debug("no migrations to check");
return;
}
String migrationUser = migrationConfig.getDbUser();
String migrationPwd = migrationConfig.getDbPassword();
if (migrationUser == null) {
throw new IllegalStateException("No DB migration user specified (to run the db migration) ?");
}
DataSource dataSource = server.getPluginApi().getDataSource();
if (dataSource == null) {
throw new IllegalStateException("No dataSource when trying to run migration? "
+"Maybe trying to generate DBMigration when ebean.migration.run=true is set? "
+"Perhaps need to set ebean.migration.run=false in test-ebean.properties?");
}
Connection connection;
try {
connection = dataSource.getConnection(migrationUser, migrationPwd);
} catch (SQLException e) {
throw new IllegalArgumentException("Error trying to connect to database using DB Migration user [" + migrationUser + "]", e);
}
try {
logger.debug("using db user [{}] to run migrations ...");
connection.setAutoCommit(false);
runMigrations(resources, connection);
connection.commit();
} catch (Exception e) {
JdbcClose.rollback(connection);
throw new RuntimeException(e);
} finally {
JdbcClose.close(connection);
}
}
/**
* Run all the migrations as needed.
*/
private void runMigrations(LocalMigrationResources resources, Connection connection) throws SQLException, IOException {
MigrationTable table = new MigrationTable(server, migrationConfig, connection);
table.createIfNeeded();
// get the migrations in version order
List<LocalMigrationResource> localVersions = resources.getVersions();
logger.info("local migrations:{} existing migrations:{}", localVersions.size(), table.size());
LocalMigrationResource priorVersion = null;
// run migrations in order
for (int i = 0; i < localVersions.size(); i++) {
LocalMigrationResource localVersion = localVersions.get(i);
if (!table.shouldRun(localVersion, priorVersion)) {
break;
}
priorVersion = localVersion;
connection.commit();
}
}
}
@@ -7,6 +7,10 @@ import java.util.Arrays;
*/
public class MigrationVersion implements Comparable<MigrationVersion> {
private static final int[] REPEAT_ORDERING = {Integer.MAX_VALUE};
private static final boolean[] REPEAT_UNDERSCORES = {false};
/**
* The raw version text.
*/
@@ -21,6 +25,19 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
private final String comment;
/**
* Construct for "repeatable" version.
*/
private MigrationVersion(String raw, String comment) {
this.raw = raw;
this.comment = comment;
this.ordering = REPEAT_ORDERING;
this.underscores = REPEAT_UNDERSCORES;
}
/**
* Construct for "normal" version.
*/
private MigrationVersion(String raw, int[] ordering, boolean[] underscores, String comment) {
this.raw = raw;
this.ordering = ordering;
@@ -28,6 +45,20 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
this.comment = comment;
}
/**
* Return true if this is a "repeatable" version.
*/
public boolean isRepeatable() {
return ordering == REPEAT_ORDERING;
}
/**
* Return the full version.
*/
public String getFull() {
return raw;
}
public String toString() {
return raw;
}
@@ -69,11 +100,15 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
/**
* Returns the version part of the string.
*
* <p>
* Normalised means always use '.' delimiters (no underscores).
* NextVersion means bump/increase the last version number by 1.
*/
private String formattedVersion(boolean normalised, boolean nextVersion) {
if (ordering == REPEAT_ORDERING) {
return "R";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ordering.length; i++) {
if (i < ordering.length - 1) {
@@ -103,8 +138,7 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
return (ordering[i] > other.ordering[i]) ? 1 : -1;
}
}
// considered the same
return 0;
return comment.compareTo(other.comment);
}
/**
@@ -119,6 +153,10 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
*/
public static MigrationVersion parse(String raw) {
if (raw.startsWith("V") || raw.startsWith("v")) {
raw = raw.substring(1);
}
String comment = "";
String value = raw;
int commentStart = raw.indexOf("__");
@@ -132,6 +170,11 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
String[] sections = value.split("\\.");
if ("r".equalsIgnoreCase(sections[0])) {
// a "repeatable" version (does not have a version number)
return new MigrationVersion(raw, comment);
}
boolean[] underscores = new boolean[sections.length];
int[] ordering = new int[sections.length];
@@ -39,7 +39,7 @@ public class VisitAllUsing {
public void visitAllBeans() {
for (BeanDescriptor<?> desc : descriptors) {
if (desc.getBaseTable() != null) {
if (desc.isBaseTable()) {
visitBean(desc, visitor);
}
}
@@ -150,8 +150,8 @@ public class VisitAllUsing {
}
public void visit(InheritInfo inheritInfo) {
BeanProperty[] propertiesLocal = inheritInfo.getBeanDescriptor().propertiesLocal();
for (int i = 0; i <propertiesLocal.length ; i++) {
BeanProperty[] propertiesLocal = inheritInfo.desc().propertiesLocal();
for (int i = 0; i < propertiesLocal.length; i++) {
owner.visit(pv, propertiesLocal[i]);
}
}
@@ -0,0 +1,32 @@
package com.avaje.ebean.dbmigration.runner;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.zip.CRC32;
/**
* Calculates the checksum for the given string content.
*/
public class Checksum {
/**
* Returns the checksum of this string.
*/
public static int calculate(String str) {
final CRC32 crc32 = new CRC32();
BufferedReader bufferedReader = new BufferedReader(new StringReader(str));
try {
String line;
while ((line = bufferedReader.readLine()) != null) {
crc32.update(line.getBytes("UTF-8"));
}
} catch (IOException e) {
throw new RuntimeException("Failed to calculate checksum", e);
}
return (int) crc32.getValue();
}
}
@@ -0,0 +1,101 @@
package com.avaje.ebean.dbmigration.runner;
import com.avaje.ebean.dbmigration.model.MigrationVersion;
import org.avaje.classpath.scanner.Resource;
/**
* A DB migration resource (DDL script with version).
*/
public class LocalMigrationResource implements Comparable<LocalMigrationResource> {
/**
* Code for repeatable migrations.
*/
private static final String REPEAT_TYPE = "R";
/**
* Code for version migrations.
*/
private static final String VERSION_TYPE = "V";
private final MigrationVersion version;
private final String location;
private final Resource resource;
/**
* Construct with version and resource.
*/
public LocalMigrationResource(MigrationVersion version, String location, Resource resource) {
this.version = version;
this.location = location;
this.resource = resource;
}
public String toString() {
return version.toString();
}
/**
* Return true if the underlying version is "repeatable".
*/
public boolean isRepeatable() {
return version.isRepeatable();
}
/**
* Return the "key" that identifies the migration.
*/
public String key() {
if (isRepeatable()) {
return version.getComment().toLowerCase();
} else {
return version.normalised();
}
}
/**
* Return the migration comment.
*/
public String getComment() {
String comment = version.getComment();
return (comment == null || comment.isEmpty()) ? "-" : comment;
}
/**
* Default ordering by version.
*/
@Override
public int compareTo(LocalMigrationResource o) {
return version.compareTo(o.version);
}
/**
* Return the underlying migration version.
*/
public MigrationVersion getVersion() {
return version;
}
/**
* Return the resource location.
*/
public String getLocation() {
return location;
}
/**
* Return the content for the migration apply ddl script.
*/
public String getContent() {
return resource.loadAsString("UTF-8");
}
/**
* Return the type code ("R" or "V") for this migration.
*/
public String getType() {
return isRepeatable() ? REPEAT_TYPE : VERSION_TYPE;
}
}
@@ -0,0 +1,91 @@
package com.avaje.ebean.dbmigration.runner;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.model.MigrationVersion;
import org.avaje.classpath.scanner.Resource;
import org.avaje.classpath.scanner.ResourceFilter;
import org.avaje.classpath.scanner.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Loads the DB migration resources and sorts them into execution order.
*/
public class LocalMigrationResources {
private static final Logger logger = LoggerFactory.getLogger(LocalMigrationResources.class);
private final ServerConfig serverConfig;
private final DbMigrationConfig migrationConfig;
private final List<LocalMigrationResource> versions = new ArrayList<LocalMigrationResource>();
/**
* Construct with configuration options.
*/
public LocalMigrationResources(ServerConfig serverConfig, DbMigrationConfig migrationConfig) {
this.serverConfig = serverConfig;
this.migrationConfig = migrationConfig;
}
/**
* Read all the migration resources (SQL scripts) returning true if there are versions.
*/
public boolean readResources() {
String migrationPath = migrationConfig.getMigrationPath();
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
Scanner scanner = new Scanner(classLoader);
List<Resource> resourceList = scanner.scanForResources(migrationPath, new Match(migrationConfig));
logger.debug("resources: {}", resourceList);
for (Resource resource : resourceList) {
String filename = resource.getFilename();
if (filename.endsWith(migrationConfig.getApplySuffix())) {
int pos = filename.lastIndexOf(migrationConfig.getApplySuffix());
String mainName = filename.substring(0, pos);
MigrationVersion migrationVersion = MigrationVersion.parse(mainName);
LocalMigrationResource res = new LocalMigrationResource(migrationVersion, resource.getLocation(), resource);
versions.add(res);
}
}
Collections.sort(versions);
return !versions.isEmpty();
}
/**
* Return the list of migration resources in version order.
*/
public List<LocalMigrationResource> getVersions() {
return versions;
}
/**
* Filter used to find the migration scripts.
*/
private static class Match implements ResourceFilter {
private final DbMigrationConfig migrationConfig;
Match(DbMigrationConfig migrationConfig) {
this.migrationConfig = migrationConfig;
}
@Override
public boolean isMatch(String name) {
return name.endsWith(migrationConfig.getApplySuffix());
}
}
}
@@ -0,0 +1,106 @@
package com.avaje.ebean.dbmigration.runner;
import com.avaje.ebean.SqlRow;
import com.avaje.ebean.SqlUpdate;
import java.sql.Timestamp;
/**
* Bean holding migration execution details stored in the migration table.
*/
class MigrationMetaRow {
private int id;
private String type;
private String version;
private String comment;
private int checksum;
private Timestamp runOn;
private String runBy;
private long runTime;
/**
* Construct for inserting into table.
*/
MigrationMetaRow(int id, String type, String version, String comment, int checksum, String runBy, Timestamp runOn, long runTime) {
this.id = id;
this.type = type;
this.version = version;
this.checksum = checksum;
this.comment = comment;
this.runBy = runBy;
this.runOn = runOn;
this.runTime = runTime;
}
/**
* Construct from the SqlRow (read from table).
*/
MigrationMetaRow(SqlRow row) {
id = row.getInteger("id");
type = row.getString("mtype");
version = row.getString("mversion");
comment = row.getString("mcomment");
checksum = row.getInteger("mchecksum");
runOn = row.getTimestamp("run_on");
runBy = row.getString("run_by");
runTime = row.getLong("run_time");
}
public String toString() {
return "id:" + id + " type:" + type + " runVersion:" + version + " comment:" + comment + " runOn:" + runOn + " runBy:" + runBy;
}
/**
* Return the id for this migration.
*/
int getId() {
return id;
}
/**
* Return the normalised version for this migration.
*/
String getVersion() {
return version;
}
/**
* Return the checksum for this migration.
*/
int getChecksum() {
return checksum;
}
/**
* Bind to the insert statement.
*/
void bindInsert(SqlUpdate insert) {
insert.setParameter(1, id);
insert.setParameter(2, type);
insert.setParameter(3, "SUCCESS");
insert.setParameter(4, version);
insert.setParameter(5, comment);
insert.setParameter(6, checksum);
insert.setParameter(7, runOn);
insert.setParameter(8, runBy);
insert.setParameter(9, runTime);
}
/**
* Return the SQL insert given the table migration meta data is stored in.
*/
static String insertSql(String table) {
return "insert into " + table
+ " (id, mtype, mstatus, mversion, mcomment, mchecksum, run_on, run_by, run_time)"
+ " values (?,?,?,?,?,?,?,?,?)";
}
}
@@ -0,0 +1,30 @@
package com.avaje.ebean.dbmigration.runner;
import com.avaje.ebean.dbmigration.DdlRunner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Runs the DDL migration scripts.
*/
public class MigrationScriptRunner {
private final Connection connection;
/**
* Construct with a given connection.
*/
public MigrationScriptRunner(Connection connection) {
this.connection = connection;
}
/**
* Execute all the DDL statements in the script.
*/
int runScript(boolean expectErrors, String content, String scriptName) throws SQLException {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
return runner.runAll(content, connection);
}
}
@@ -0,0 +1,259 @@
package com.avaje.ebean.dbmigration.runner;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlRow;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.MigrationRunner;
import com.avaje.ebean.plugin.SpiServer;
import com.avaje.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import com.avaje.ebeaninternal.util.IOUtils;
import org.slf4j.Logger;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Manages the migration table.
*/
public class MigrationTable {
private static final Logger logger = MigrationRunner.logger;
private final Connection connection;
private final EbeanServer server;
private final DatabasePlatform databasePlatform;
private final String catalog;
private final String schema;
private final String table;
private final ServerConfig serverConfig;
private final String envUserName;
private final Timestamp runOn = new Timestamp(System.currentTimeMillis());
private final ScriptTransform scriptTransform;
private final String insertSql;
private final LinkedHashMap<String, MigrationMetaRow> migrations;
private MigrationMetaRow lastMigration;
/**
* Construct with server, configuration and jdbc connection (DB admin user).
*/
public MigrationTable(EbeanServer server, DbMigrationConfig migrationConfig, Connection connection) {
this.connection = connection;
this.server = server;
this.migrations = new LinkedHashMap<String, MigrationMetaRow>();
SpiServer pluginApi = server.getPluginApi();
this.serverConfig = pluginApi.getServerConfig();
this.databasePlatform = pluginApi.getDatabasePlatform();
this.catalog = null;
this.schema = null;
this.table = migrationConfig.getMetaTable();
this.insertSql = MigrationMetaRow.insertSql(table);
this.scriptTransform = createScriptTransform(migrationConfig);
this.envUserName = System.getProperty("user.name");
}
/**
* Return the number of migrations in the DB migration table.
*/
public int size() {
return migrations.size();
}
/**
* Create the ScriptTransform for placeholder key/value replacement.
*/
private ScriptTransform createScriptTransform(DbMigrationConfig config) {
Map<String, String> map = PlaceholderBuilder.build(config.getRunPlaceholders(), config.getRunPlaceholderMap());
return new ScriptTransform(map);
}
/**
* Create the table is it does not exist.
*/
public void createIfNeeded() throws SQLException, IOException {
if (!tableExists(connection)) {
createTable(connection);
}
ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
SqlQuery sqlQuery = server.createSqlQuery("select * from " + table + " order by id for update");
List<SqlRow> metaRows = server.findList(sqlQuery, t);
for (SqlRow row : metaRows) {
MigrationMetaRow metaRow = new MigrationMetaRow(row);
addMigration(metaRow.getVersion(), metaRow);
}
}
private void createTable(Connection connection) throws IOException, SQLException {
String script = ScriptTransform.table(table, getCreateTableScript());
MigrationScriptRunner run = new MigrationScriptRunner(connection);
run.runScript(false, script, "create migration table");
}
/**
* Return the create table script.
*/
private String getCreateTableScript() throws IOException {
// supply a script to override the default table create script
String script = readResource("migration-support/create-table.sql");
if (script == null) {
// no, just use the default script
script = readResource("migration-support/default-create-table.sql");
}
return script;
}
private String readResource(String location) throws IOException {
Enumeration<URL> resources = serverConfig.getClassLoadConfig().getResources(location);
if (resources.hasMoreElements()) {
URL url = resources.nextElement();
return IOUtils.readUtf8(url.openStream());
}
return null;
}
/**
* Return true if the table exists.
*/
private boolean tableExists(Connection connection) throws SQLException {
return databasePlatform.tableExists(connection, catalog, schema, table);
}
/**
* Return true if the migration ran successfully and false if the migration failed.
*/
public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) throws SQLException {
if (priorVersion != null && !localVersion.isRepeatable()) {
if (!migrationExists(priorVersion)) {
logger.error("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
return false;
}
}
MigrationMetaRow existing = migrations.get(localVersion.key());
return runMigration(localVersion, existing);
}
/**
* Run the migration script.
*
* @param local The local migration resource
* @param existing The information for this migration existing in the table
*
* @return True if the migrations should continue
*/
private boolean runMigration(LocalMigrationResource local, MigrationMetaRow existing) throws SQLException {
String script = convertScript(local.getContent());
int checksum = Checksum.calculate(script);
if (existing != null) {
boolean matchChecksum = (existing.getChecksum() == checksum);
if (!local.isRepeatable()) {
if (!matchChecksum) {
logger.error("Checksum mismatch on migration {}", local.getLocation());
}
return true;
} else if (matchChecksum) {
logger.trace("... skip unchanged repeatable migration {}", local.getLocation());
return true;
}
}
runMigration(local, script, checksum);
return true;
}
/**
* Run a migration script as new migration or update on existing repeatable migration.
*/
private void runMigration(LocalMigrationResource local, String script, int checksum) throws SQLException {
logger.debug("run migration {}", local.getLocation());
long start = System.currentTimeMillis();
MigrationScriptRunner run = new MigrationScriptRunner(connection);
run.runScript(false, script, "run migration version: " + local.getVersion());
long exeMillis = System.currentTimeMillis() - start;
// insert new migration row
SqlUpdate insert = server.createSqlUpdate(insertSql);
MigrationMetaRow metaRow = createMetaRow(local, checksum, exeMillis);
metaRow.bindInsert(insert);
server.execute(insert, new ExternalJdbcTransaction(connection));
addMigration(local.key(), metaRow);
}
/**
* Create the MigrationMetaRow for this migration.
*/
private MigrationMetaRow createMetaRow(LocalMigrationResource migration, int checksum, long exeMillis) {
int nextId = 1;
if (lastMigration != null) {
nextId = lastMigration.getId() + 1;
}
String type = migration.getType();
String runVersion = migration.key();
String comment = migration.getComment();
return new MigrationMetaRow(nextId, type, runVersion, comment, checksum, envUserName, runOn, exeMillis);
}
/**
* Return true if the migration exists.
*/
private boolean migrationExists(LocalMigrationResource priorVersion) {
return migrations.containsKey(priorVersion.key());
}
/**
* Apply the placeholder key/value replacement on the script.
*/
private String convertScript(String script) {
return scriptTransform.transform(script);
}
/**
* Register the successfully executed migration (to allow dependant scripts to run).
*/
private void addMigration(String key, MigrationMetaRow metaRow) {
lastMigration = metaRow;
if (metaRow.getVersion() == null) {
throw new IllegalStateException("No runVersion in db migration table row? " + metaRow);
}
migrations.put(key, metaRow);
}
}
@@ -0,0 +1,53 @@
package com.avaje.ebean.dbmigration.runner;
import java.util.HashMap;
import java.util.Map;
/**
* Joins placeholder map and comma/equals delimited string.
*/
class PlaceholderBuilder {
private final Map<String,String> map = new HashMap<String,String>();
/**
* Create with raw comma and equals delimited pairs plus map of key value pairs.
*/
public static Map<String,String> build(String commaDelimited, Map<String,String> placeholders) {
PlaceholderBuilder builder = new PlaceholderBuilder();
builder.add(commaDelimited);
builder.add(placeholders);
return builder.map;
}
private PlaceholderBuilder() {
}
/**
* Add a comma and equals delimited string to parse for key value pairs.
*/
public void add(String commaDelimited) {
if (commaDelimited != null) {
String[] split = commaDelimited.split("[,;]");
for (String keyValue : split) {
String[] pair = keyValue.split("=");
if (pair.length == 2) {
map.put(pair[0].trim(), pair[1].trim());
}
}
}
}
/**
* Add a map of key value placeholder pairs.
*/
public void add(Map<String,String> placeholders) {
if (placeholders != null) {
map.putAll(placeholders);
}
}
}
@@ -0,0 +1,47 @@
package com.avaje.ebean.dbmigration.runner;
import java.util.HashMap;
import java.util.Map;
/**
* Transforms a SQL script given a map of key/value substitutions.
*/
class ScriptTransform {
/**
* Transform just ${table} with the table name.
*/
public static String table(String tableName, String script) {
return script.replace("${table}", tableName);
}
private final Map<String,String> placeholders = new HashMap<String, String>();
ScriptTransform(Map<String,String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
placeholders.put(wrapKey(entry.getKey()), entry.getValue());
}
}
private String wrapKey(String key) {
return "${"+key+"}";
}
/**
* Return true if this contains no placeholders.
*/
boolean isEmpty() {
return placeholders.isEmpty();
}
/**
* Transform the script replacing placeholders in the form <code>${key}</code> with <code>value</code>.
*/
String transform(String source) {
for (Map.Entry<String, String> entry : placeholders.entrySet()) {
source = source.replace(entry.getKey(), entry.getValue());
}
return source;
}
}
@@ -46,33 +46,4 @@ public abstract class AbstractBeanPersistListener implements BeanPersistListener
return false;
}
/**
* Notify that a bean was inserted on another node of the cluster.
*
* @param id the id value of the inserted bean
*/
@Override
public void remoteInsert(Object id) {
// do nothing
}
/**
* Notify that a bean was updated on another node of the cluster.
*
* @param id the id value of the updated bean.
*/
@Override
public void remoteUpdate(Object id) {
// do nothing
}
/**
* Notify that a bean was deleted on another node of the cluster.
*
* @param id the id value of the deleted bean.
*/
@Override
public void remoteDelete(Object id) {
// do nothing
}
}
@@ -74,28 +74,4 @@ public interface BeanPersistListener {
*/
boolean deleted(Object bean);
/**
* Notify that a bean was inserted on another node of the cluster.
*
* @param id
* the id value of the inserted bean
*/
void remoteInsert(Object id);
/**
* Notify that a bean was updated on another node of the cluster.
*
* @param id
* the id value of the updated bean.
*/
void remoteUpdate(Object id);
/**
* Notify that a bean was deleted on another node of the cluster.
*
* @param id
* the id value of the deleted bean.
*/
void remoteDelete(Object id);
}
+57 -58
View File
@@ -3,85 +3,84 @@
<TITLE>Ebean core API</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Core API (see <a href="Ebean.html">Ebean</a> and <a href="EbeanServer.html">EbeanServer</a>).
Core API (see <a href="EbeanServer.html">EbeanServer</a> and <a href="Ebean.html">Ebean</a>).
<h3>Ebean</h3>
<p>
Provides the main API for fetching and persisting beans with eBean.
</p>
<pre class="code">
// EXAMPLE 1: Simple fetch
//========================
<pre>{@code
// EXAMPLE 1: Simple fetch
//========================
// fetch order 10
Order order = Ebean.find(Order.class, 10);
// fetch order 10
Order order = Ebean.find(Order.class, 10);
// EXAMPLE 2: Fetch an Object with associations
//=============================================
// fetch Customer 7 including their billing and shipping addresses
Customer customer =
Ebean.find(Customer.class)
.setId(7)
.fetch("billingAddress")
.fetch("shippingAddress")
.findUnique();
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
// EXAMPLE 2: Fetch an Object with associations
//=============================================
// fetch Customer 7 including their billing and shipping addresses
Customer customer =
Ebean.find(Customer.class)
.setId(7)
.fetch("billingAddress")
.fetch("shippingAddress")
.findUnique();
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
// EXAMPLE 3: Create and save an Order
//=====================================
// get a Customer reference so we don't hit the database
Customer custRef = Ebean.getReference(Customer.class, 7);
// EXAMPLE 3: Create and save an Order
//=====================================
// create a new Order object
Order newOrder = new Order();
newOrder.setStatus(Order.Status.NEW);
newOrder.setCustomer(custRef);
ArrayList orderLines = new ArrayList();
newOrder.setLines(orderLines);
...
// get a Customer reference so we don't hit the database
Customer custRef = Ebean.getReference(Customer.class, 7);
// add a line to the order
Product prodRef = Ebean.getReference(Product.class, 41);
OrderLine line = new OrderLine();
line.setProduct(prodRef);
line.setQuantity(10);
orderLines.add(line);
...
// create a new Order object
Order newOrder = new Order();
newOrder.setStatus(Order.Status.NEW);
newOrder.setCustomer(custRef);
// save the order and its lines in a single transaction
// NB: assumes CascadeType.PERSIST is set on the order lines association
Ebean.save(newOrder);
ArrayList orderLines = new ArrayList();
newOrder.setLines(orderLines);
...
// add a line to the order
Product prodRef = Ebean.getReference(Product.class, 41);
OrderLine line = new OrderLine();
line.setProduct(prodRef);
line.setQuantity(10);
orderLines.add(line);
...
// save the order and its lines in a single transaction
// NB: assumes CascadeType.PERSIST is set on the order lines association
Ebean.save(newOrder);
// EXAMPLE 4: Use another database
//=================================
// EXAMPLE 4: Use another database
//=================================
// Get access to the Human Resources EbeanServer/Database
EbeanServer hrServer = Ebean.getServer(&quot;HR&quot;);
// fetch contact 3 from the HR database
Contact contact = hrServer.find(Contact.class, 3);
contact.setStatus(Contact.Status.INACTIVE);
...
// save the contact back to the HR database
hrServer.save(contact);
</pre>
// Get access to the Human Resources EbeanServer/Database
EbeanServer hrServer = Ebean.getServer(&quot;HR&quot;);
// fetch contact 3 from the HR database
Contact contact = hrServer.find(Contact.class, 3);
contact.setStatus(Contact.Status.INACTIVE);
...
// save the contact back to the HR database
hrServer.save(contact);
}</pre>
</Body>
</HTML>
@@ -4,6 +4,7 @@ import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import javax.sql.DataSource;
import java.util.List;
/**
@@ -40,4 +41,10 @@ public interface SpiServer extends EbeanServer {
* Return the bean type for a given doc store queueId.
*/
BeanType<?> getBeanTypeForQueueId(String queueId);
/**
* Return the associated DataSource for this EbeanServer instance.
*/
DataSource getDataSource();
}
@@ -0,0 +1,4 @@
/**
* Provides text search expressions like Match, TextQueryString etc.
*/
package com.avaje.ebean.search;
@@ -1,46 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter;
import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* This interface allows us to have more than one ClassPathSearch
* to scan the resources by a customized way.
*
* @author Kefeng Deng (deng@51any.com)
*/
public interface ClassPathSearchService {
/**
* Initialize this ClassPathSearchService with given parameters
*
* @param classLoader is current classLoader
* @param filter is filter
* @param matcher
* @param classPathReaderClassName
*/
void init(ClassLoader classLoader, ClassPathSearchFilter filter, ClassPathSearchMatcher matcher, String classPathReaderClassName);
/**
* Searches the class path for all matching classes.
*
* @return a collection of all matching classes
* @throws IOException if a resource is un-reachable
*/
List<Class<?>> findClasses() throws IOException;
/**
* Return the set of jars that contained classes that matched.
*/
Set<String> getJarHits();
/**
* Return the set of packages that contained classes that matched.
*/
Set<String> getPackageHits();
}
@@ -1,4 +1,4 @@
package com.avaje.ebean.annotation;
package com.avaje.ebeaninternal.api;
/**
* Optimistic concurrency mode used for updates and deletes.
@@ -17,6 +17,10 @@ public class HashQuery {
this.bindHash = bindHash;
}
public String toString() {
return "HashQuery@" + Integer.toHexString(hashCode());
}
public int hashCode() {
int hc = 31 * planHash.hashCode();
hc = 31 * hc + bindHash;
@@ -158,7 +158,7 @@ public class LoadBeanRequest extends LoadRequest {
EntityBean loadedBean = (EntityBean) list.get(i);
loadedIds.add(desc.getId(loadedBean));
if (isLoadCache()) {
desc.cacheBeanPutData(loadedBean);
desc.cacheBeanPut(loadedBean);
}
}
@@ -1,25 +0,0 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.config.ServerConfig;
/**
* Allows us to have more than one plugin (the ddl generator) active based on flags.
*
* author: Richard Vowles - http://gplus.to/RichardVowles
*/
@Deprecated
public interface SpiEbeanPlugin {
/**
* initializes the plugin.
*
* @param server - the ebean server with extensions for all of the Ebean internals
* @param serverConfig - the configured server information. It allows access to pre-collected information (but not the collector PropertySource, yet)
*/
void setup(SpiEbeanServer server, ServerConfig serverConfig);
/**
* Execute the plugin (generate DDL for example).
*/
void execute(boolean online);
}
@@ -16,6 +16,7 @@ import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import java.util.List;
@@ -187,4 +188,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* user context information (user id, user ip address etc).
*/
ReadAuditPrepare getReadAuditPrepare();
/**
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
*/
DataTimeZone getDataTimeZone();
}
@@ -3,7 +3,6 @@ package com.avaje.ebeaninternal.api;
import java.sql.PreparedStatement;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlQueryListener;
/**
* SQL query - Internal extension to SqlQuery.
@@ -20,11 +19,6 @@ public interface SpiSqlQuery extends SqlQuery {
*/
String getQuery();
/**
* Return the queryListener.
*/
SqlQueryListener getListener();
/**
* Return the first row to fetch.
*/
@@ -35,16 +29,6 @@ public interface SpiSqlQuery extends SqlQuery {
*/
int getMaxRows();
/**
* Return the number of rows after which background fetching occurs.
*/
int getBackgroundFetchAfter();
/**
* Return the key property for maps.
*/
String getMapKey();
/**
* Return the query timeout.
*/
@@ -55,23 +39,9 @@ public interface SpiSqlQuery extends SqlQuery {
*/
int getBufferFetchSizeHint();
/**
* Return true if this is a future fetch type query.
*/
boolean isFutureFetch();
/**
* Set to true if this is a future fetch type query.
*/
void setFutureFetch(boolean futureFetch);
/**
* Set the PreparedStatement for the purposes of supporting cancel.
*/
void setPreparedStatement(PreparedStatement pstmt);
/**
* Return true if the query has been cancelled.
*/
boolean isCancelled();
}
@@ -2,7 +2,6 @@ package com.avaje.ebeaninternal.api;
import java.sql.SQLException;
import com.avaje.ebean.annotation.ConcurrencyMode;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.persist.dml.DmlHandler;
import com.avaje.ebeaninternal.server.persist.dmlbind.Bindable;
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -105,20 +106,17 @@ public class TransactionEvent implements Serializable {
}
/**
* Notify the cache of bean changes.
* <p>
* This returns the TransactionEventTable so that if any
* general table changes can also be used to invalidate
* parts of the cache.
* </p>
* Build and return the cache changeSet.
*/
public void notifyCache() {
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
if (eventBeans != null) {
eventBeans.notifyCache();
eventBeans.notifyCache(changeSet);
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache();
deleteByIdMap.notifyCache(changeSet);
}
return changeSet;
}
/**
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
/**
@@ -30,10 +31,13 @@ public class TransactionEventBeans {
requests.add(request);
}
public void notifyCache() {
/**
* Collect the cache changes.
*/
public void notifyCache(CacheChangeSet changeSet) {
for (int i = 0; i < requests.size(); i++) {
requests.get(i).notifyCache();
requests.get(i).notifyCache(changeSet);
}
}
@@ -0,0 +1,116 @@
package com.avaje.ebeaninternal.extraddl.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;simpleContent>
* &lt;extension base="&lt;http://www.w3.org/2001/XMLSchema>string">
* &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="platforms" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/extension>
* &lt;/simpleContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"value"
})
@XmlRootElement(name = "ddl-script")
public class DdlScript {
@XmlValue
protected String value;
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "platforms")
protected String platforms;
/**
* Gets the value of the value property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getValue() {
return value;
}
/**
* Sets the value of the value property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setValue(String value) {
this.value = value;
}
/**
* Gets the value of the name property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getName() {
return name;
}
/**
* Sets the value of the name property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setName(String value) {
this.name = value;
}
/**
* Gets the value of the platforms property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getPlatforms() {
return platforms;
}
/**
* Sets the value of the platforms property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setPlatforms(String value) {
this.platforms = value;
}
}
@@ -0,0 +1,71 @@
package com.avaje.ebeaninternal.extraddl.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/extraddl}ddl-script" maxOccurs="unbounded"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"ddlScript"
})
@XmlRootElement(name = "extra-ddl")
public class ExtraDdl {
@XmlElement(name = "ddl-script", required = true)
protected List<DdlScript> ddlScript;
/**
* Gets the value of the ddlScript property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the ddlScript property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getDdlScript().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link DdlScript }
*
*
*/
public List<DdlScript> getDdlScript() {
if (ddlScript == null) {
ddlScript = new ArrayList<DdlScript>();
}
return this.ddlScript;
}
}
@@ -0,0 +1,82 @@
package com.avaje.ebeaninternal.extraddl.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.InputStream;
/**
* Read ExtraDdl from an XML document.
*/
public class ExtraDdlXmlReader {
private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
/**
* Return the combined extra DDL that should be run given the platform name.
*/
public static String buildExtra(String platformName) {
ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (read == null) {
return null;
}
StringBuilder sb = new StringBuilder(300);
for (DdlScript script : read.getDdlScript()) {
if (matchPlatform(platformName, script.getPlatforms())) {
logger.debug("include script {}", script.getName());
sb.append(script.getValue()).append("\n");
}
}
return sb.toString();
}
/**
* Return true if the script platforms is a match/supported for the given platform.
* @param platformName The database platform we are generating/running DDL for
* @param platforms The platforms (comma delimited) this script should run for
*/
public static boolean matchPlatform(String platformName, String platforms) {
if (platforms == null || platforms.trim().length() == 0) {
return true;
}
String[] names = platforms.split("[,;]");
for (String name : names) {
if (name.trim().toLowerCase().contains(platformName)) {
return true;
}
}
return false;
}
/**
* Read and return a ExtraDdl from an xml document at the given resource path.
*/
public static ExtraDdl read(String resourcePath) {
InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
// we expect this and check for null
return null;
}
return read(is);
}
/**
* Read and return a ExtraDdl from an xml document.
*/
public static ExtraDdl read(InputStream is) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(ExtraDdl.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (ExtraDdl) unmarshaller.unmarshal(is);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,48 @@
package com.avaje.ebeaninternal.extraddl.model;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.avaje.ebeaninternal.extraddl.model package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.extraddl.model
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link DdlScript }
*
*/
public DdlScript createDdlScript() {
return new DdlScript();
}
/**
* Create an instance of {@link ExtraDdl }
*
*/
public ExtraDdl createExtraDdl() {
return new ExtraDdl();
}
}
@@ -0,0 +1,2 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/extraddl", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.avaje.ebeaninternal.extraddl.model;
@@ -41,10 +41,10 @@ public class AutoTuneAllCollection {
/**
* Write the document as an xml file.
*/
public void writeFile(String filePrefix) {
public void writeFile(String filePrefix, boolean withNow) {
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
writer.write(document, filePrefix);
writer.write(document, filePrefix, withNow);
}
/**
@@ -76,7 +76,7 @@ public class AutoTuneDiffCollection {
public void writeFile(String filePrefix) {
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
writer.write(document, filePrefix);
writer.write(document, filePrefix, true);
}
/**
@@ -15,19 +15,27 @@ import java.util.Date;
*/
public class AutoTuneXmlWriter {
/**
* Return 'now' as a string to second precision.
*/
public static String now() {
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
return df.format(new Date());
}
/**
* Write the document as xml file with the given prefix.
*/
public void write(Autotune document, String filePrefix) {
public void write(Autotune document, String fileName, boolean withNow) {
SortAutoTuneDocument.sort(document);
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
String now = df.format(new Date());
if (withNow) {
fileName += "-" + now() + ".xml";
}
// write the file with serverName and now suffix as we can output the profiling many times
File file = new File(filePrefix + "-" + now + ".xml");
write(document, file);
write(document, new File(fileName));
}
/**
@@ -185,7 +185,15 @@ public class DefaultAutoTuneService implements AutoTuneService {
} else {
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
int size = event.size();
event.writeFile(profilingFile + "-" + serverName + "-all");
File existingTuning = new File(tuningFile);
if (existingTuning.exists()) {
// rename the existing autotune.xml file (appending 'now')
if (!existingTuning.renameTo(new File(tuningFile+"."+AutoTuneXmlWriter.now()))) {
logger.warn("Failed to rename autotune file [{}]", tuningFile);
}
}
event.writeFile(tuningFile, false);
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
}
}
@@ -252,6 +260,8 @@ public class DefaultAutoTuneService implements AutoTuneService {
}
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
// restore the interrupted status
Thread.currentThread().interrupt();
logger.warn("Error while sleeping after System.gc() request.", e);
}
}
@@ -0,0 +1,13 @@
package com.avaje.ebeaninternal.server.cache;
/**
* A change to the cache.
*/
public interface CacheChange {
/**
* Apply the change.
*/
void apply();
}
@@ -0,0 +1,23 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Change to remove bean from L2 cache.
*/
class CacheChangeBeanRemove implements CacheChange {
private final BeanDescriptor<?> descriptor;
private final Object id;
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Object id) {
this.descriptor = descriptor;
this.id = id;
}
@Override
public void apply() {
descriptor.cacheHandleDeleteById(id);
}
}
@@ -0,0 +1,30 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.Map;
/**
* Put a new bean entry into the cache.
*/
class CacheChangeBeanUpdate implements CacheChange {
private final BeanDescriptor<?> desc;
private final Object id;
private final Map<String, Object> changes;
private final boolean updateNaturalKey;
private final long version;
CacheChangeBeanUpdate(BeanDescriptor<?> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
this.desc = desc;
this.id = id;
this.changes = changes;
this.updateNaturalKey = updateNaturalKey;
this.version = version;
}
@Override
public void apply() {
desc.cacheBeanUpdate(id, changes, updateNaturalKey, version);
}
}
@@ -0,0 +1,24 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Change the natural key mapping for a bean.
*/
class CacheChangeNaturalKeyPut implements CacheChange {
private final BeanDescriptor<?> descriptor;
private final Object id;
private final Object newKey;
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, Object id, Object newKey) {
this.descriptor = descriptor;
this.id = id;
this.newKey = newKey;
}
@Override
public void apply() {
descriptor.cacheNaturalKeyPut(id, newKey);
}
}
@@ -0,0 +1,228 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* List of changes to be applied to L2 cache.
*/
public class CacheChangeSet {
private final List<CacheChange> entries = new ArrayList<CacheChange>();
private final Set<BeanDescriptor> queryCaches = new HashSet<BeanDescriptor>();
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<ManyKey, ManyChange>();
/**
* Set of "base tables" modified used to invalidate entities based on views.
*/
private final Set<String> viewInvalidation = new HashSet<String>();
private final boolean viewEntityInvalidation;
/**
* Construct specifying if we also need to process invalidation for entities based on views.
*/
public CacheChangeSet(boolean viewEntityInvalidation) {
this.viewEntityInvalidation = viewEntityInvalidation;
}
/**
* Apply the changes to the L2 cache except entity/view invalidation.
*
* Return the set of table changes to process invalidation for entities based on views.
*/
public Set<String> apply() {
for (BeanDescriptor entry : queryCaches) {
entry.queryCacheClear();
}
for (CacheChange entry : entries) {
entry.apply();
}
for (CacheChange entry : manyChangeMap.values()) {
entry.apply();
}
return viewInvalidation;
}
/**
* Add an entry to clear a query cache.
*/
public void addClearQuery(BeanDescriptor<?> descriptor) {
queryCaches.add(descriptor);
}
/**
* Add many property clear.
*/
public <T> void addManyClear(BeanDescriptor<T> desc, String manyProperty) {
many(desc, manyProperty).setClear();
}
/**
* Add many property remove.
*/
public <T> void addManyRemove(BeanDescriptor<T> desc, String manyProperty, Object parentId) {
many(desc, manyProperty).addRemove(parentId);
}
/**
* Add many property put.
*/
public <T> void addManyPut(BeanDescriptor<T> desc, String manyProperty, Object parentId, CachedManyIds entry) {
many(desc, manyProperty).addPut(parentId, entry);
}
/**
* On bean insert register table for view based entity invalidation.
*/
public void addBeanInsert(String baseTable) {
if (viewEntityInvalidation) {
viewInvalidation.add(baseTable);
}
}
/**
* Remove a bean from the cache.
*/
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
entries.add(new CacheChangeBeanRemove(desc, id));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
}
}
/**
* Update a bean entry.
*/
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
}
}
/**
* Update a natural key.
*/
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, Object id, Object val) {
entries.add(new CacheChangeNaturalKeyPut(desc, id, val));
}
/**
* Return the ManyChange for the given descriptor and property manyProperty.
*/
private ManyChange many(BeanDescriptor<?> desc, String manyProperty) {
ManyKey key = new ManyKey(desc, manyProperty);
ManyChange manyChange = manyChangeMap.get(key);
if (manyChange == null) {
manyChange = new ManyChange(key);
manyChangeMap.put(key, manyChange);
}
return manyChange;
}
/**
* Changes for a specific many property.
*/
private static class ManyChange implements CacheChange {
final ManyKey key;
final List<Object> removes = new ArrayList<Object>();
final Map<Object,CachedManyIds> puts = new LinkedHashMap<Object, CachedManyIds>();
boolean clear;
ManyChange(ManyKey key) {
this.key = key;
}
/**
* Clear all entries.
*/
void setClear() {
this.clear = true;
removes.clear();
}
/**
* Remove entry for the given parentId.
*/
void addRemove(Object parentId) {
if (!clear) {
removes.add(parentId);
}
}
/**
* Put entry for the given parentId.
*/
void addPut(Object parentId, CachedManyIds entry) {
puts.put(parentId, entry);
}
@Override
public void apply() {
if (clear) {
key.cacheClear();
} else {
for (Map.Entry<Object, CachedManyIds> entry : puts.entrySet()) {
key.cachePut(entry.getKey(), entry.getValue());
}
for (Object parentId : removes) {
key.cacheRemove(parentId);
}
}
}
}
/**
* Key for changes on a many property.
*/
private static class ManyKey {
private final BeanDescriptor<?> desc;
private final String manyProperty;
ManyKey(BeanDescriptor<?> desc, String manyProperty) {
this.desc = desc;
this.manyProperty = manyProperty;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManyKey manyKey = (ManyKey) o;
return desc.equals(manyKey.desc) && manyProperty.equals(manyKey.manyProperty);
}
@Override
public int hashCode() {
return 31 * desc.hashCode() + manyProperty.hashCode();
}
void cacheClear() {
desc.cacheManyPropClear(manyProperty);
}
void cachePut(Object parentId, CachedManyIds entry) {
desc.cacheManyPropPut(manyProperty, parentId, entry);
}
void cacheRemove(Object parentId) {
desc.cacheManyPropRemove(manyProperty, parentId);
}
}
}
@@ -1,51 +1,92 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.Arrays;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Data held in the bean cache for cached beans.
*/
public class CachedBeanData {
public class CachedBeanData implements Externalizable {
private final long whenCreated;
private final Object sharableBean;
private final boolean[] loaded;
private final Object[] data;
private final boolean naturalKeyUpdate;
private final Object naturalKey;
private final Object oldNaturalKey;
private long whenCreated;
private long version;
private String discValue;
private Map<String, Object> data;
public CachedBeanData(Object sharableBean, boolean[] loaded, Object[] data, Object naturalKey, Object oldNaturalKey) {
/**
* The sharable bean is effectively transient (near cache only).
*/
private transient Object sharableBean;
/**
* Construct from a loaded bean.
*/
public CachedBeanData(Object sharableBean, String discValue, Map<String, Object> data, long version) {
this.whenCreated = System.currentTimeMillis();
this.sharableBean = sharableBean;
this.loaded = loaded;
this.discValue = discValue;
this.data = data;
this.naturalKeyUpdate = naturalKey != null;
this.naturalKey = (naturalKey != null) ? naturalKey : oldNaturalKey;
this.oldNaturalKey = oldNaturalKey;
this.version = version;
}
/**
* Construct from serialisation.
*/
public CachedBeanData() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(version);
out.writeLong(whenCreated);
boolean hasDisc = discValue != null;
out.writeBoolean(hasDisc);
if (hasDisc) {
out.writeUTF(discValue);
}
out.writeInt(data.size());
for (Map.Entry<String, Object> entry : data.entrySet()) {
out.writeUTF(entry.getKey());
out.writeObject(entry.getValue());
}
}
@Override
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
version = in.readLong();
whenCreated = in.readLong();
if (in.readBoolean()) {
discValue = in.readUTF();
}
data = new LinkedHashMap<String, Object>();
int count = in.readInt();
for (int i = 0; i < count; i++) {
String key = in.readUTF();
Object val = in.readObject();
data.put(key, val);
}
}
public String toString() {
return Arrays.toString(data);
}
/**
* Return a copy of the property data.
*/
public Object[] copyData() {
Object[] dest = new Object[data.length];
System.arraycopy(data, 0, dest, 0, data.length);
return dest;
return data.toString();
}
/**
* Return a copy of the loaded status for the properties.
* Create and return a new version of CachedBeanData based on this
* entry applying the given changes.
*/
public boolean[] copyLoaded() {
boolean[] dest = new boolean[data.length];
System.arraycopy(loaded, 0, dest, 0, dest.length);
return dest;
public CachedBeanData update(Map<String, Object> changes, long version) {
Map<String, Object> copy = new HashMap<String, Object>();
copy.putAll(data);
copy.putAll(changes);
return new CachedBeanData(null, discValue, copy, version);
}
/**
@@ -56,45 +97,44 @@ public class CachedBeanData {
}
/**
* Return a sharable (immutable read only) bean.
* Return the version value.
*/
public long getVersion() {
return version;
}
/**
* Return the raw discriminator value.
*/
public String getDiscValue() {
return discValue;
}
/**
* Return a sharable (immutable read only) bean. Near cache only use.
*/
public Object getSharableBean() {
return sharableBean;
}
/**
* Return true if this data requires an update to the natural key cache.
* Return true if the property is held.
*/
public boolean isNaturalKeyUpdate() {
return naturalKeyUpdate;
public boolean isLoaded(String propertyName) {
return data.containsKey(propertyName);
}
/**
* Return the new/current natural key value.
* Return the value for a given property name.
*/
public Object getNaturalKey() {
return naturalKey;
}
/**
* Return the old natural key (its entry should be removed).
*/
public Object getOldNaturalKey() {
return oldNaturalKey;
public Object getData(String propertyName) {
return data.get(propertyName);
}
/**
* Return the data for the specific property.
* Return all the property data.
*/
public Object getData(int i) {
return data[i];
public Map<String,Object> getData() {
return data;
}
/**
* Return true if the property is contained in this data.
*/
public boolean isLoaded(int i) {
return loaded[i];
}
}
@@ -5,56 +5,49 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.util.LinkedHashMap;
import java.util.Map;
public class CachedBeanDataFromBean {
public static CachedBeanData extract(BeanDescriptor<?> desc, EntityBean bean) {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
Object[] data = new Object[desc.getPropertyCount()];
boolean[] loaded = new boolean[desc.getPropertyCount()];
Map<String,Object> data = new LinkedHashMap<String,Object>();
BeanProperty idProperty = desc.getIdProperty();
if (idProperty != null) {
int propertyIndex = idProperty.getPropertyIndex();
if (ebi.isLoadedProperty(propertyIndex)) {
// extract the id property value
data[propertyIndex] = idProperty.getCacheDataValue(bean);
loaded[propertyIndex] = true;
data.put(idProperty.getName(), idProperty.getCacheDataValue(bean));
}
}
BeanProperty[] props = desc.propertiesNonMany();
Object naturalKey = null;
// extract all the non-many properties
for (int i = 0; i < props.length; i++) {
BeanProperty prop = props[i];
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
int propertyIndex = prop.getPropertyIndex();
data[propertyIndex] = prop.getCacheDataValue(bean);
loaded[propertyIndex] = true;
if (prop.isNaturalKey()) {
naturalKey = prop.getValue(bean);
}
data.put(prop.getName(), prop.getCacheDataValue(bean));
}
}
long version = desc.getVersion(bean);
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
return new CachedBeanData(sharableBean, loaded, data, naturalKey, null);
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
}
private static EntityBean createSharableBean(BeanDescriptor<?> desc, EntityBean bean, EntityBeanIntercept beanEbi) {
if (!desc.isCacheSharableBeans() || !beanEbi.isFullyLoadedBean()) {
return null;
}
if (beanEbi.isReadOnly()) {
return bean;
}
}
// create a readOnly sharable instance by copying the data
EntityBean sharableBean = desc.createEntityBean();
BeanProperty idProp = desc.getIdProperty();
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -9,40 +10,36 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
public class CachedBeanDataToBean {
public static void load(BeanDescriptor<?> desc, EntityBean bean, CachedBeanData cacheBeanData) {
public static void load(BeanDescriptor<?> desc, EntityBean bean, CachedBeanData cacheBeanData, PersistenceContext context) {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
BeanProperty idProperty = desc.getIdProperty();
if (idProperty != null) {
// load the id property
loadProperty(bean, cacheBeanData, ebi, idProperty);
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
}
// load the non-many properties
BeanProperty[] props = desc.propertiesNonMany();
for (int i = 0; i < props.length; i++) {
loadProperty(bean, cacheBeanData, ebi, props[i]);
loadProperty(bean, cacheBeanData, ebi, props[i], context);
}
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
for (int i = 0; i < manys.length; i++) {
manys[i].createReferenceIfNull(bean);
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
for (int i = 0; i < many.length; i++) {
many[i].createReferenceIfNull(bean);
}
ebi.setLoadedLazy();
}
private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop) {
private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop, PersistenceContext context) {
int propertyIndex = prop.getPropertyIndex();
if (cacheBeanData.isLoaded(propertyIndex)) {
//noinspection StatementWithEmptyBody
if (ebi.isLoadedProperty(propertyIndex)) {
// already loaded (lazy load on partially loaded bean)
} else {
Object data = cacheBeanData.getData(propertyIndex);
prop.setCacheDataValue(bean, data);
if (cacheBeanData.isLoaded(prop.getName())) {
if (!ebi.isLoadedProperty(prop.getPropertyIndex())) {
Object value = cacheBeanData.getData(prop.getName());
prop.setCacheDataValue(bean, value, context);
}
}
}
@@ -1,44 +0,0 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
/**
* Create a new CachedBeanData based on the existing CachedBeanData and the updated bean.
*/
public class CachedBeanDataUpdate {
/**
* Create a new CachedBeanData based on the existing CachedBeanData and the updated bean.
*/
public static CachedBeanData update(BeanDescriptor<?> desc, CachedBeanData existingData, EntityBean updateBean) {
// take a copy of the raw data and loaded status
boolean[] copyLoaded = existingData.copyLoaded();
Object[] copyData = existingData.copyData();
EntityBeanIntercept ebi = updateBean._ebean_getIntercept();
Object newNaturalKey = null;
Object oldNaturalKey = existingData.getNaturalKey();
BeanProperty[] props = desc.propertiesNonMany();
for (int i = 0; i < props.length; i++) {
// check if the properties was in the update
int propertyIndex = props[i].getPropertyIndex();
if (ebi.isLoadedProperty(propertyIndex)) {
if (props[i].isNaturalKey()) {
newNaturalKey = updateBean._ebean_getField(propertyIndex);
}
// set the cache safe value for the property and mark it as loaded
copyData[propertyIndex] = props[i].getCacheDataValue(updateBean);
copyLoaded[propertyIndex] = true;
}
}
return new CachedBeanData(null, copyLoaded, copyData, newNaturalKey, oldNaturalKey);
}
}
@@ -1,5 +1,11 @@
package com.avaje.ebeaninternal.server.cache;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
@@ -8,18 +14,41 @@ import java.util.List;
* This is effectively just the Id values for each of the beans in the collection.
* </p>
*/
public class CachedManyIds {
public class CachedManyIds implements Externalizable {
private final List<Object> idList;
private List<Object> idList;
public CachedManyIds(List<Object> idList) {
this.idList = idList;
}
/**
* Construct for serialization.
*/
public CachedManyIds() {
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(idList.size());
for (Object id : idList) {
out.writeObject(id);
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int size = in.readInt();
idList = new ArrayList<Object>(size);
for (int i = 0; i < size; i++) {
idList.add(in.readObject());
}
}
public String toString() {
return idList.toString();
}
public List<Object> getIdList() {
return idList;
}
@@ -1,120 +1,121 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.annotation.CacheQueryTuning;
import com.avaje.ebean.annotation.CacheBeanTuning;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCacheType;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the construction of caches.
*/
public class DefaultCacheHolder {
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
private final ConcurrentHashMap<String, ServerCache> concMap = new ConcurrentHashMap<String, ServerCache>();
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
private final HashMap<String, ServerCache> synchMap = new HashMap<String, ServerCache>();
private final Object monitor = new Object();
private final Object monitor = new Object();
private final ServerCacheFactory cacheFactory;
private final ServerCacheFactory cacheFactory;
private final ServerCacheOptions defaultOptions;
private final ServerCacheOptions defaultOptions;
private final boolean useBeanTuning;
/**
* Create with a cache factory and default cache options.
*
* @param cacheFactory the factory for creating the cache
* @param defaultOptions the default options for tuning the cache
*/
public DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions defaultOptions) {
this.cacheFactory = cacheFactory;
this.defaultOptions = defaultOptions;
}
/**
* Create with a cache factory and default cache options.
*
* @param cacheFactory
* the factory for creating the cache
* @param defaultOptions
* the default options for tuning the cache
* @param useBeanTuning
* if true then use the bean class specific tuning. This is
* generally false for the query cache.
*/
public DefaultCacheHolder(ServerCacheFactory cacheFactory,
ServerCacheOptions defaultOptions, boolean useBeanTuning) {
/**
* Return the cache for a given bean type.
*/
public ServerCache getCache(String cacheKey, ServerCacheType type) {
this.cacheFactory = cacheFactory;
this.defaultOptions = defaultOptions;
this.useBeanTuning = useBeanTuning;
}
ServerCache cache = concMap.get(cacheKey);
if (cache != null) {
return cache;
}
synchronized (monitor) {
cache = synchMap.get(cacheKey);
if (cache == null) {
ServerCacheOptions options = getCacheOptions(cacheKey, type);
cache = cacheFactory.createCache(type, cacheKey, options);
synchMap.put(cacheKey, cache);
concMap.put(cacheKey, cache);
}
return cache;
}
}
/**
* Return the default cache options.
*/
public ServerCacheOptions getDefaultOptions() {
return defaultOptions;
}
public void clearCache(String cacheKey) {
/**
* Return the cache for a given bean type.
*/
public ServerCache getCache(String cacheKey) {
ServerCache cache = concMap.get(cacheKey);
if (cache != null) {
cache.clear();
}
}
ServerCache cache = concMap.get(cacheKey);
if (cache != null) {
return cache;
}
synchronized (monitor) {
cache = synchMap.get(cacheKey);
if (cache == null) {
ServerCacheOptions options = getCacheOptions(cacheKey);
cache = cacheFactory.createCache(cacheKey, options);
synchMap.put(cacheKey, cache);
concMap.put(cacheKey, cache);
}
return cache;
}
}
/**
* Return true if there is an active cache for this bean type.
*/
public boolean isCaching(String beanType) {
return concMap.containsKey(beanType);
}
public void clearCache(String cacheKey) {
public void clearAll() {
for (ServerCache serverCache : concMap.values()) {
serverCache.clear();
}
}
ServerCache cache = concMap.get(cacheKey);
if (cache != null) {
cache.clear();
}
}
/**
* Return true if there is an active cache for this bean type.
*/
public boolean isCaching(String beanType) {
return concMap.containsKey(beanType);
}
/**
* Return the cache options for a given bean type.
*/
ServerCacheOptions getCacheOptions(String beanType, ServerCacheType type) {
public void clearAll() {
for (ServerCache serverCache : concMap.values()) {
serverCache.clear();
}
}
try {
Class<?> cls = Class.forName(beanType);
switch (type) {
case QUERY:
return getQueryOptions(cls);
default:
return getBeanOptions(cls);
}
} catch (ClassNotFoundException e) {
// ignore
}
/**
* Return the cache options for a given bean type.
*/
private ServerCacheOptions getCacheOptions(String beanType) {
return defaultOptions.copy();
}
if (useBeanTuning) {
// read the deployment annotation
try {
Class<?> cls = Class.forName(beanType);
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
if (cacheTuning != null) {
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
o.applyDefaults(defaultOptions);
return o;
}
} catch (ClassNotFoundException e){
// ignore
}
}
private ServerCacheOptions getQueryOptions(Class<?> cls) {
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
if (tuning != null) {
ServerCacheOptions o = new ServerCacheOptions(tuning);
o.applyDefaults(defaultOptions);
return o;
}
return defaultOptions.copy();
}
return defaultOptions.copy();
}
private ServerCacheOptions getBeanOptions(Class<?> cls) {
CacheBeanTuning tuning = cls.getAnnotation(CacheBeanTuning.class);
if (tuning != null) {
ServerCacheOptions o = new ServerCacheOptions(tuning);
o.applyDefaults(defaultOptions);
return o;
}
return defaultOptions.copy();
}
}
@@ -10,7 +10,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -30,7 +34,6 @@ public class DefaultServerCache implements ServerCache {
*/
public static final CompareByLastAccess BY_LAST_ACCESS = new CompareByLastAccess();
/**
* The underlying map (ConcurrentHashMap or similar)
*/
@@ -85,18 +88,31 @@ public class DefaultServerCache implements ServerCache {
this.maxSize = maxSize;
this.maxIdleSecs = maxIdleSecs;
this.maxSecsToLive = maxSecsToLive;
this.trimFrequency = trimFrequency;
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
}
@Override
public void init(EbeanServer server) {
/**
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
*/
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
if (trimFrequency > 0) {
return trimFrequency;
}
if (maxIdleSecs > 0) {
return maxIdleSecs / 2 - 1;
}
if (maxSecsToLive > 0) {
return maxSecsToLive / 2 - 1;
}
return 0;
}
public void periodicTrim(BackgroundExecutor executor) {
EvictionRunnable trim = new EvictionRunnable();
// default to trimming the cache every 60 seconds
long trimFreqSecs = (trimFrequency == 0) ? 60 : trimFrequency;
BackgroundExecutor executor = server.getBackgroundExecutor();
executor.executePeriodically(trim, trimFreqSecs, TimeUnit.SECONDS);
}
@@ -157,33 +173,6 @@ public class DefaultServerCache implements ServerCache {
}
}
/**
* Return the options controlling the cache.
*/
@Override
public ServerCacheOptions getOptions() {
synchronized (monitor) {
ServerCacheOptions options = new ServerCacheOptions();
options.setMaxIdleSecs(maxIdleSecs);
options.setMaxSize(maxSize);
options.setMaxSecsToLive(maxSecsToLive);
options.setTrimFrequency(trimFrequency);
return options;
}
}
/**
* Set the options controlling the cache
*/
@Override
public void setOptions(ServerCacheOptions options) {
synchronized (monitor) {
maxIdleSecs = options.getMaxIdleSecs();
maxSize = options.getMaxSize();
maxSecsToLive = options.getMaxSecsToLive();
}
}
/**
* Return the name of the cache.
*/
@@ -333,8 +322,8 @@ public class DefaultServerCache implements ServerCache {
evictByTTL.add(trimmedByTTL);
evictByLRU.add(trimmedByLRU);
if (logger.isDebugEnabled()) {
logger.debug("Executed trim of cache {} in [{}]millis idle[{}] timeToLive[{}] accessTime[{}]"
if (logger.isTraceEnabled()) {
logger.trace("Executed trim of cache {} in [{}]millis idle[{}] timeToLive[{}] accessTime[{}]"
, name, exeMicros, trimmedByIdle, trimmedByTTL, trimmedByLRU);
}
}
@@ -358,7 +347,9 @@ public class DefaultServerCache implements ServerCache {
private static final long serialVersionUID = 1L;
public int compare(CacheEntry entry1, CacheEntry entry2) {
return Long.compare(entry1.getLastAccessTime(), entry2.getLastAccessTime());
long x = entry1.getLastAccessTime();
long y = entry2.getLastAccessTime();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
@@ -1,27 +1,40 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCacheType;
/**
* Default implementation of ServerCacheFactory.
*/
public class DefaultServerCacheFactory implements ServerCacheFactory {
class DefaultServerCacheFactory implements ServerCacheFactory {
private final BackgroundExecutor executor;
/**
* Construct when l2 cache is disabled.
*/
public DefaultServerCacheFactory() {
this.executor = null;
}
/**
* Construct with executor service.
*/
public DefaultServerCacheFactory(BackgroundExecutor executor) {
this.executor = executor;
}
public ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions) {
DefaultServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
if (executor != null) {
cache.periodicTrim(executor);
}
return cache;
}
private EbeanServer ebeanServer;
public void init(EbeanServer ebeanServer){
this.ebeanServer = ebeanServer;
}
public ServerCache createCache(String cacheKey, ServerCacheOptions cacheOptions) {
ServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
cache.init(ebeanServer);
return cache;
}
}
@@ -1,115 +1,91 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebean.cache.ServerCacheType;
/**
* Manages the bean and query caches.
* Manages the bean and query caches.
*/
public class DefaultServerCacheManager implements ServerCacheManager {
private final DefaultCacheHolder beanCache;
private final DefaultCacheHolder beanCache;
private final DefaultCacheHolder queryCache;
private final DefaultCacheHolder queryCache;
private final DefaultCacheHolder naturalKeyCache;
private final DefaultCacheHolder naturalKeyCache;
private final DefaultCacheHolder collectionIdsCache;
private final ServerCacheFactory cacheFactory;
private SpiEbeanServer ebeanServer;
/**
* Create with a cache factory and default cache options.
*/
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
this.cacheFactory = cacheFactory;
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions, true);
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
}
public void init(EbeanServer server) {
cacheFactory.init(server);
this.ebeanServer = (SpiEbeanServer)server;
}
private final DefaultCacheHolder collectionIdsCache;
/**
* Set bean caching on or off for a given bean type.
* Create with a cache factory and default cache options.
*/
public void setCaching(Class<?> beanType, boolean useCache) {
ebeanServer.getBeanDescriptor(beanType).setUseCache(useCache);
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions);
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
}
/**
* Clear both the bean cache and the query cache for a
* given bean type.
*/
public void clear(Class<?> beanType) {
String beanName = beanType.getName();
beanCache.clearCache(beanName);
naturalKeyCache.clearCache(beanName);
collectionIdsCache.clearCache(beanName);
queryCache.clearCache(beanName);
}
/**
* Construct when l2 cache is disabled.
*/
public DefaultServerCacheManager() {
this(new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
}
/**
* Clear both the bean cache and the query cache for a
* given bean type.
*/
public void clear(Class<?> beanType) {
String beanName = beanType.getName();
beanCache.clearCache(beanName);
naturalKeyCache.clearCache(beanName);
collectionIdsCache.clearCache(beanName);
queryCache.clearCache(beanName);
}
public void clearAll() {
beanCache.clearAll();
queryCache.clearAll();
naturalKeyCache.clearAll();
collectionIdsCache.clearAll();
}
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
return collectionIdsCache.getCache(beanType.getName()+"."+propertyName);
}
public boolean isCollectionIdsCaching(Class<?> beanType) {
return collectionIdsCache.isCaching(beanType.getName());
}
public ServerCache getNaturalKeyCache(Class<?> beanType) {
return naturalKeyCache.getCache(beanType.getName());
}
public boolean isNaturalKeyCaching(Class<?> beanType) {
return naturalKeyCache.isCaching(beanType.getName());
}
/**
* Return the query cache for a given bean type.
*/
public ServerCache getQueryCache(Class<?> beanType) {
return queryCache.getCache(beanType.getName());
}
/**
* Return the bean cache for a given bean type.
*/
public ServerCache getBeanCache(Class<?> beanType) {
return beanCache.getCache(beanType.getName());
}
/**
* Return true if there is an active cache for the given bean type.
*/
public boolean isBeanCaching(Class<?> beanType) {
return beanCache.isCaching(beanType.getName());
}
public void clearAll() {
beanCache.clearAll();
queryCache.clearAll();
naturalKeyCache.clearAll();
collectionIdsCache.clearAll();
}
public boolean isQueryCaching(Class<?> beanType) {
return queryCache.isCaching(beanType.getName());
}
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
return collectionIdsCache.getCache(beanType.getName() + "." + propertyName, ServerCacheType.COLLECTION_IDS);
}
public ServerCache getNaturalKeyCache(Class<?> beanType) {
return naturalKeyCache.getCache(beanType.getName(), ServerCacheType.NATURAL_KEY);
}
/**
* Return the query cache for a given bean type.
*/
public ServerCache getQueryCache(Class<?> beanType) {
return queryCache.getCache(beanType.getName(), ServerCacheType.QUERY);
}
/**
* Return the bean cache for a given bean type.
*/
public ServerCache getBeanCache(Class<?> beanType) {
return beanCache.getCache(beanType.getName(), ServerCacheType.BEAN);
}
/**
* Return true if there is an active cache for the given bean type.
*/
public boolean isBeanCaching(Class<?> beanType) {
return beanCache.isCaching(beanType.getName());
}
}
@@ -0,0 +1,20 @@
package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCachePlugin;
import com.avaje.ebean.config.ServerConfig;
/**
* Default implementation of ServerCachePlugin.
*/
public class DefaultServerCachePlugin implements ServerCachePlugin {
/**
* Creates the default ServerCacheFactory.
*/
@Override
public ServerCacheFactory create(ServerConfig config, BackgroundExecutor executor) {
return new DefaultServerCacheFactory(executor);
}
}
@@ -12,7 +12,7 @@ public interface ClusterBroadcast {
* Inform the other cluster members that this instance has come online and
* start any listeners etc.
*/
void startup(ClusterManager clusterManager);
void startup();
/**
* Inform the other cluster members that this instance is leaving and
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.server.cluster;
import java.util.Properties;
/**
* Factory to create the cluster broadcast service.
*/
public interface ClusterBroadcastFactory {
/**
* Create the cluster transport with the manager and deployment properties.
*/
ClusterBroadcast create(ClusterManager manager, Properties properties);
}
@@ -2,12 +2,12 @@ package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebeaninternal.server.cluster.mcast.McastClusterManager;
import com.avaje.ebeaninternal.server.cluster.socket.SocketClusterBroadcast;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -15,6 +15,8 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class ClusterManager {
private static final Logger clusterLogger = LoggerFactory.getLogger("org.avaje.ebean.Cluster");
private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);
private final ConcurrentHashMap<String, EbeanServer> serverMap = new ConcurrentHashMap<String, EbeanServer>();
@@ -25,30 +27,33 @@ public class ClusterManager {
private boolean started;
public ClusterManager(ContainerConfig containerConfig) {
ContainerConfig.ClusterMode mode = containerConfig.getMode();
try {
switch (mode) {
case SOCKET: {
this.broadcast = new SocketClusterBroadcast(containerConfig);
break;
}
case MULTICAST: {
this.broadcast = new McastClusterManager(containerConfig);
break;
}
default: {
this.broadcast = null;
}
}
} catch (Exception e) {
logger.error("Error initialising ClusterManager type [" + mode + "]", e);
throw new RuntimeException(e);
public ClusterManager(ContainerConfig config) {
if (!config.isClusterActive()) {
broadcast = null;
} else {
ClusterBroadcastFactory factory = createFactory();
broadcast = factory.create(this, config.getProperties());
}
}
/**
* Return the ClusterTransportFactory via ServiceLoader.
*/
private ClusterBroadcastFactory createFactory() {
ServiceLoader<ClusterBroadcastFactory> load = ServiceLoader.load(ClusterBroadcastFactory.class);
ClusterBroadcastFactory factory = null;
Iterator<ClusterBroadcastFactory> iterator = load.iterator();
if (iterator.hasNext()) {
factory = iterator.next();
}
if (factory == null) {
throw new IllegalStateException("No ClusterTransportFactory found in classpath. "
+ " Probably need to add the avaje-ebeanorm-cluster dependency");
}
return factory;
}
public void registerServer(EbeanServer server) {
synchronized (monitor) {
serverMap.put(server.getName(), server);
@@ -67,7 +72,7 @@ public class ClusterManager {
private void startup() {
started = true;
if (broadcast != null) {
broadcast.startup(this);
broadcast.startup();
}
}
@@ -81,9 +86,12 @@ public class ClusterManager {
/**
* Send the message headers and payload to every server in the cluster.
*/
public void broadcast(RemoteTransactionEvent remoteTransEvent) {
public void broadcast(RemoteTransactionEvent event) {
if (broadcast != null) {
broadcast.broadcast(remoteTransEvent);
if (clusterLogger.isDebugEnabled()) {
clusterLogger.debug("sending: {}", event);
}
broadcast.broadcast(event);
}
}
@@ -1,24 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.Serializable;
/**
* Simple holder of binary data.
* Used to use Packet based serialisation of RemoteTransactionEvent
* with simple Java Serialisation of the DataHolder.
*/
public class DataHolder implements Serializable {
private static final long serialVersionUID = 9090748723571322192L;
private final byte[] data;
public DataHolder(byte[] data) {
this.data = data;
}
public byte[] getData() {
return data;
}
}
@@ -1,193 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Represents the contents sent as a single DatagramPacket.
* <p>
* The contents is typically multiple messages (ACK,PING etc) or all or part of
* a RemoteTransactionEvent.
* </p>
* <p>
* Due to the hard limit on the size of UDP packets a RemoteTransactionEvent
* with lots of information could be broken up into multiple packets.
* </p>
*
* @author rbygrave
*/
public class Packet {
/**
* A Packet that holds protocol messages like ACK, PING etc.
*/
public static final short TYPE_MESSAGES = 1;
/**
* A Packet that holds TransactionEvent information such as Bean
* and or Table IUD information.
*/
public static final short TYPE_TRANSEVENT = 2;
/**
* The type of Packet.
*/
protected final short packetType;
/**
* The PacketId.
*/
protected final long packetId;
/**
* The timestamp the Packet was created.
*/
protected final long timestamp;
/**
* The EbeanServer name this relates to if relevant.
*/
protected final String serverName;
protected ByteArrayOutputStream buffer;
protected DataOutputStream dataOut;
protected byte[] bytes;
/**
* The number of messages in this Packet.
*/
private int messageCount;
/**
* The number of times this Packet was resent.
*/
private int resendCount;
/**
* Create a Packet for writing messages to.
*/
public static Packet forWrite(short packetType, long packetId, long timestamp, String serverName) throws IOException {
return new Packet(true, packetType, packetId, timestamp, serverName);
}
/**
* Create a Packet just reading the Header information.
*/
public static Packet readHeader(DataInput dataInput) throws IOException {
short packetType = dataInput.readShort();
long packetId = dataInput.readLong();
long timestamp = dataInput.readLong();
String serverName = dataInput.readUTF();
return new Packet(false, packetType, packetId, timestamp, serverName);
}
protected Packet(boolean write, short packetType, long packetId, long timestamp, String serverName) throws IOException {
this.packetType = packetType;
this.packetId = packetId;
this.timestamp = timestamp;
this.serverName = serverName;
if (write) {
this.buffer = new ByteArrayOutputStream();
this.dataOut = new DataOutputStream(buffer);
writeHeader();
} else {
this.buffer = null;
this.dataOut = null;
}
}
private void writeHeader() throws IOException {
dataOut.writeShort(packetType);
dataOut.writeLong(packetId);
dataOut.writeLong(timestamp);
dataOut.writeUTF(serverName);
}
public int incrementResendCount() {
return resendCount++;
}
public short getPacketType() {
return packetType;
}
public long getPacketId() {
return packetId;
}
public long getTimestamp() {
return timestamp;
}
public String getServerName() {
return serverName;
}
public void writeEof() throws IOException {
dataOut.writeBoolean(false);
}
public void read(DataInput dataInput) throws IOException {
boolean more = dataInput.readBoolean();
while (more) {
int msgType = dataInput.readInt();
readMessage(dataInput, msgType);
// see if there is more information
more = dataInput.readBoolean();
}
}
/**
* Overridden by more specific Packet implementations to read the messages.
*/
protected void readMessage(DataInput dataInput, int msgType) throws IOException {
}
/**
* Write a binary message to this packet returning true if there was
* enough room to do so. Return false if the message was too large for
* the remaining space left - in this case another Packet should be
* created to put that message into.
*/
public boolean writeBinaryMessage(BinaryMessage msg, int maxPacketSize) throws IOException {
byte[] bytes = msg.getByteArray();
if (messageCount > 0 && (bytes.length + buffer.size() > maxPacketSize)) {
// we are actually going to ignore the maxPacketSize iff we have one
// large message.
// false = no more messages
dataOut.writeBoolean(false);
return false;
}
++messageCount;
// true = another message follows
dataOut.writeBoolean(true);
dataOut.write(bytes);
return true;
}
public int getSize() {
return getBytes().length;
}
/**
* Return the Packet as raw bytes.
*/
public byte[] getBytes() {
if (bytes == null) {
bytes = buffer.toByteArray();
buffer = null;
dataOut = null;
}
return bytes;
}
}
@@ -1,69 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.server.cluster.mcast.Message;
import com.avaje.ebeaninternal.server.cluster.mcast.MessageAck;
import com.avaje.ebeaninternal.server.cluster.mcast.MessageControl;
import com.avaje.ebeaninternal.server.cluster.mcast.MessageResend;
import java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A Packet that contains Ack, Resend and Control messages.
*
* @author rbygrave
*/
public class PacketMessages extends Packet {
private final ArrayList<Message> messages;
public static PacketMessages forWrite(long packetId, long timestamp, String serverName) throws IOException {
return new PacketMessages(true, packetId, timestamp, serverName);
}
public static PacketMessages forRead(Packet header) throws IOException {
return new PacketMessages(header);
}
private PacketMessages(boolean write, long packetId, long timestamp, String serverName) throws IOException {
super(write, TYPE_MESSAGES, packetId, timestamp, serverName);
this.messages = null;
}
private PacketMessages(Packet header) throws IOException {
super(false, TYPE_MESSAGES, header.packetId, header.timestamp, header.serverName);
this.messages = new ArrayList<Message>();
}
/**
* Return the messages contained in this Packet.
*/
public List<Message> getMessages() {
return messages;
}
/**
* Read the messages (Ack, Resend or Control) contained in this packet.
*/
protected void readMessage(DataInput dataInput, int msgType) throws IOException {
switch (msgType) {
case BinaryMessage.TYPE_MSGCONTROL:
messages.add(MessageControl.readBinaryMessage(dataInput));
break;
case BinaryMessage.TYPE_MSGACK:
messages.add(MessageAck.readBinaryMessage(dataInput));
break;
case BinaryMessage.TYPE_MSGRESEND:
messages.add(MessageResend.readBinaryMessage(dataInput));
break;
default:
throw new RuntimeException("Invalid Transaction msgType " + msgType);
}
}
}
@@ -1,64 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIds;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.io.DataInput;
import java.io.IOException;
/**
* A Packet holding TransactionEvent data.
* <p>
* Due to the hard limit for UDP packet sizes a RemoteTransactionEvent
* is actually broken up into smaller messages.
* </p>
*/
public class PacketTransactionEvent extends Packet {
private final SpiEbeanServer server;
private final RemoteTransactionEvent event;
public static PacketTransactionEvent forWrite(long packetId, long timestamp, String serverName) throws IOException {
return new PacketTransactionEvent(true, packetId, timestamp, serverName);
}
private PacketTransactionEvent(boolean write, long packetId, long timestamp, String serverName) throws IOException {
super(write, TYPE_TRANSEVENT, packetId, timestamp, serverName);
this.server = null;
this.event = null;
}
private PacketTransactionEvent(Packet header, SpiEbeanServer server) throws IOException {
super(false, TYPE_TRANSEVENT, header.packetId, header.timestamp, header.serverName);
this.server = server;
this.event = new RemoteTransactionEvent(server);
}
public static PacketTransactionEvent forRead(Packet header, SpiEbeanServer server) throws IOException {
return new PacketTransactionEvent(header, server);
}
public RemoteTransactionEvent getEvent() {
return event;
}
protected void readMessage(DataInput dataInput, int msgType) throws IOException {
switch (msgType) {
case BinaryMessage.TYPE_BEANIUD:
event.addBeanPersistIds(BeanPersistIds.readBinaryMessage(server, dataInput));
break;
case BinaryMessage.TYPE_TABLEIUD:
event.addTableIUD(TableIUD.readBinaryMessage(dataInput));
break;
default:
throw new RuntimeException("Invalid Transaction msgType " + msgType);
}
}
}
@@ -1,160 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.server.cluster.mcast.Message;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Creates Packets for either RemoteTransactionEvents or Messages (Ping, ACK,
* Join, Leave etc).
*
* @author rbygrave
*/
public class PacketWriter {
private final PacketIdGenerator idGenerator;
private final PacketBuilder messagesPacketBuilder;
private final PacketBuilder transEventPacketBuilder;
/**
* Create a PacketWriter with an expected max packet size.
* <p>
* In theory we would prefer to create packets up to the MTU size which for
* Ethernet will likely be 1500. Note that the maxPacketSize is ignored for
* large single messages.
* </p>
*/
public PacketWriter(int maxPacketSize) {
this.idGenerator = new PacketIdGenerator();
this.messagesPacketBuilder = new PacketBuilder(maxPacketSize, idGenerator, new MessagesPacketFactory());
this.transEventPacketBuilder = new PacketBuilder(maxPacketSize, idGenerator, new TransPacketFactory());
}
/**
* Return the currentPacketId.
*/
public long currentPacketId() {
return idGenerator.currentPacketId();
}
/**
* Create Packets for a given list of messages.
* <p>
* Typically this creates a single Packet but there is a hard limit for UDP
* packet sizes.
* </p>
*/
public List<Packet> write(boolean requiresAck, List<? extends Message> messages) throws IOException {
BinaryMessageList binaryMsgList = new BinaryMessageList();
for (int i = 0; i < messages.size(); i++) {
Message message = messages.get(i);
message.writeBinaryMessage(binaryMsgList);
}
return messagesPacketBuilder.write(requiresAck, binaryMsgList, "");
}
/**
* Create Packets for a given RemoteTransactionEvent.
* <p>
* Typically this creates a single Packet but there is a hard limit for UDP
* packet sizes.
* </p>
*/
public List<Packet> write(RemoteTransactionEvent transEvent) throws IOException {
BinaryMessageList messageList = new BinaryMessageList();
// split into reasonably small independent messages
transEvent.writeBinaryMessage(messageList);
return transEventPacketBuilder.write(true, messageList, transEvent.getServerName());
}
/**
* Reuse the same packetIdCounter for building Packets for both Message and
* RemoteTransactionEvent
*/
private static class PacketIdGenerator {
long packetIdCounter;
public long nextPacketId() {
return ++packetIdCounter;
}
public long currentPacketId() {
return packetIdCounter;
}
}
interface PacketFactory {
Packet createPacket(long packetId, long timestamp, String serverName) throws IOException;
}
private static class TransPacketFactory implements PacketFactory {
public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException {
return PacketTransactionEvent.forWrite(packetId, timestamp, serverName);
}
}
private static class MessagesPacketFactory implements PacketFactory {
public Packet createPacket(long packetId, long timestamp, String serverName) throws IOException {
return PacketMessages.forWrite(packetId, timestamp, serverName);
}
}
/**
* Helper class for building Packets from messages or
* RemoteTransactionEvents.
*/
private static class PacketBuilder {
private final PacketIdGenerator idGenerator;
private final PacketFactory packetFactory;
private final int maxPacketSize;
private PacketBuilder(int maxPacketSize, PacketIdGenerator idGenerator, PacketFactory packetFactory) {
this.maxPacketSize = maxPacketSize;
this.idGenerator = idGenerator;
this.packetFactory = packetFactory;
}
private List<Packet> write(boolean requiresAck, BinaryMessageList messageList, String serverName)
throws IOException {
List<BinaryMessage> list = messageList.getList();
ArrayList<Packet> packets = new ArrayList<Packet>(1);
long timestamp = System.currentTimeMillis();
long packetId = requiresAck ? idGenerator.nextPacketId() : 0;
Packet p = packetFactory.createPacket(packetId, timestamp, serverName);
packets.add(p);
for (int i = 0; i < list.size(); i++) {
BinaryMessage binMsg = list.get(i);
if (!p.writeBinaryMessage(binMsg, maxPacketSize)) {
// didn't fit into the package so put into another packet
packetId = requiresAck ? idGenerator.nextPacketId() : 0;
p = packetFactory.createPacket(packetId, timestamp, serverName);
packets.add(p);
p.writeBinaryMessage(binMsg, maxPacketSize);
}
}
p.writeEof();
return packets;
}
}
}
@@ -1,60 +0,0 @@
package com.avaje.ebeaninternal.server.cluster;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.List;
/**
* Mechanism to convert RemoteTransactionEvent to/from byte[] content.
*/
public abstract class SerialiseTransactionHelper {
private static final Logger logger = LoggerFactory.getLogger(SerialiseTransactionHelper.class);
private final PacketWriter packetWriter;
public SerialiseTransactionHelper() {
packetWriter = new PacketWriter(Integer.MAX_VALUE);
}
public abstract SpiEbeanServer getEbeanServer(String serverName);
/**
* Convert the RemoteTransactionEvent to byte[] content.
*/
public DataHolder createDataHolder(RemoteTransactionEvent transEvent) throws IOException {
List<Packet> packetList = packetWriter.write(transEvent);
if (packetList.size() != 1) {
throw new RuntimeException("Always expecting 1 Packet but got " + packetList.size());
}
byte[] data = packetList.get(0).getBytes();
return new DataHolder(data);
}
/**
* Convert the byte[] content to RemoteTransactionEvent.
*/
public RemoteTransactionEvent read(DataHolder dataHolder) throws IOException {
ByteArrayInputStream bi = new ByteArrayInputStream(dataHolder.getData());
DataInputStream dataInput = new DataInputStream(bi);
Packet header = Packet.readHeader(dataInput);
SpiEbeanServer server = getEbeanServer(header.getServerName());
if (server == null) {
logger.error("server [{}] not found/registered?", header.getServerName());
}
PacketTransactionEvent tranEventPacket = PacketTransactionEvent.forRead(header, server);
tranEventPacket.read(dataInput);
return tranEventPacket.getEvent();
}
}

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