Compare commits

..
Author SHA1 Message Date
Robin Bygrave 905a0ce9ec [maven-release-plugin] prepare release avaje-ebeanorm-6.10.4 2015-11-09 14:44:05 +13:00
Robin Bygrave bfa8837e61 #451 - avaje-ebeanorm-jackson - com.fasterxml.jackson.core.JsonParseException: Expecting start_array event but got START_OBJECT at 2015-11-05 16:38:22 +13:00
Robin Bygrave 8d5845b9a2 [maven-release-plugin] prepare for next development iteration 2015-11-04 22:10:38 +13:00
Robin Bygrave 87a7d4958e [maven-release-plugin] prepare release avaje-ebeanorm-6.10.3 2015-11-04 22:10:13 +13:00
Robin Bygrave 511ebd8a29 #450 - @ManyToMany with cascade and jdbc batch - ensure flush occurs 2015-11-03 16:31:27 +13:00
Robin Bygrave 11f4c8d7a3 #449 - Remove whitespace from generated Postgres trigger DDL (as Play evolution not parsing it) 2015-11-03 16:28:00 +13:00
Robin Bygrave d417a92843 [maven-release-plugin] prepare for next development iteration 2015-11-02 16:42:33 +13:00
Robin Bygrave 4bd5e52df3 [maven-release-plugin] prepare release avaje-ebeanorm-6.10.2 2015-11-02 16:42:10 +13:00
Robin Bygrave 788a200d16 #445 - Multiple @*ToMany relations referencing the same type 2015-11-02 14:11:23 +13:00
Robin Bygrave e18d2004dc #448 - ENH: Postgres Support for UUID mapping to native DB UUID type (relating to #430) 2015-11-02 12:57:47 +13:00
Robin Bygrave b326724139 #443 - ENH: Enum mapping - Add annotation @DbEnumValue that can be used to on a enum method which provides DB values 2015-10-30 21:42:21 +13:00
Robin Bygrave fc0b47342b #436 - @DiscriminatorColumn mapped also as @Column leads to NullPointerException 2015-10-29 22:01:28 +13:00
Robin Bygrave ec1a68a133 #435 - Duplicate foreign key name with ManyToMany relations 2015-10-29 17:44:29 +13:00
Robin Bygrave 8ca12521d7 No effective change - reduce debug level 2015-10-29 17:43:54 +13:00
Robin Bygrave 2362ef4130 [maven-release-plugin] prepare for next development iteration 2015-10-29 09:01:17 +13:00
Robin Bygrave 7e233edcc3 [maven-release-plugin] prepare release avaje-ebeanorm-6.10.1 2015-10-29 09:00:55 +13:00
Robin Bygrave a8b98282a1 Bump pom to 6.10.1-SNAPSHOT 2015-10-29 09:00:00 +13:00
Robin Bygrave b4506aa718 #444 - Spring Boot classpath scanning not finding entity beans - part2 - embedded jar scanning 2015-10-29 08:59:06 +13:00
Robin Bygrave 848a3b8888 #444 - Spring Boot classpath scanning not finding entity beans 2015-10-28 15:45:09 +13:00
Robin Bygrave 67c711d75f #442 - @JsonIgnore / @Expose ... not working on @OneToMany or @ManyToMany - was - @Transient field on @MappedSuperclass is not ignored by toJson() 2015-10-28 09:10:19 +13:00
Robin Bygrave 3d62f58930 #439 - @EnumValue("01") stored as integer rather than string - ENum stores strings as numerics? 2015-10-28 08:53:14 +13:00
Robin Bygrave a30948d38d #438 - findPagedList and getTotalRowCount, the query.getAlias() is null 2015-10-28 08:15:15 +13:00
Robin Bygrave a8232d2d8b #441 - ENH: Add support for test-ebean.properties ... for mechanism to configure ebean to run tests 2015-10-27 00:17:47 +13:00
Robin Bygrave d4b8f23a5a Fix test - MySql limitations with delete query syntax 2015-10-26 21:52:35 +13:00
Robin Bygrave bcf2a44187 #430 - ENH: Support for UUID mapping to native DB UUID type (Postgres, H2) 2015-10-26 20:52:16 +13:00
Robin Bygrave f3102ba02c #432 - Delete using Query does not commit 2015-10-22 13:29:21 +13:00
Robin Bygrave 0351eab3a7 #431 - Annotation PrivateOwned seems not enough to update an change in the list of children (fix) 2015-10-22 11:10:41 +13:00
Robin Bygrave 409e2b75ce #431 - Add failing test ... Annotation PrivateOwned seems not enough to update an change in the list of children 2015-10-22 09:27:32 +13:00
Robin Bygrave dc968f16bf Q: Ebean @ManyToMany(cascade=REMOVE) described #429 2015-10-06 14:38:05 +13:00
Robin Bygrave bbbb6482fb [maven-release-plugin] prepare for next development iteration 2015-10-02 13:03:34 +13:00
Robin Bygrave 001968fcf8 [maven-release-plugin] prepare release avaje-ebeanorm-6.9.1 2015-10-02 13:03:07 +13:00
Robin Bygrave 6abcb186a4 Bump pom to 6.9.1-SNAPSHOT 2015-10-02 13:02:13 +13:00
Robin Bygrave f250447fbe Add test for ManyToMany delete scenario 2015-10-01 22:21:47 +13:00
Robin Bygrave bfa86f241b #427 - ENH: Add support for Removing ShutdownManager.shutdownHook ... manually control entire shutdown. 2015-10-01 10:33:09 +13:00
Robin Bygrave 19459b71b4 Fix for #425 - Extra instance of root level bean with findIterate() and fetch of OneToMany relationship - Ebean parses the first bean correctly and the rest not 2015-09-30 23:35:10 +13:00
Robin Bygrave 5e97c3bbbc #425 - Extra instance of root level bean with findIterate() and fetch of OneToMany relationship - Ebean parses the first bean correctly and the rest not 2015-09-30 23:24:53 +13:00
Robin Bygrave 986eadd023 #424 - DDL generation - support turning off lower casing of table and column names DbConstraintNaming, DbConstraintNormalise 2015-09-30 22:44:47 +13:00
Robin Bygrave c6787d0901 Update javadoc 2015-09-30 17:26:30 +13:00
Robin Bygrave 3792e06859 Update javadoc 2015-09-30 17:26:05 +13:00
Robin Bygrave c77446ba24 Update javadoc 2015-09-30 17:25:26 +13:00
Robin Bygrave dba245c517 #421 - ENH: Add support for unsetting the loaded state of a bean property (via BeanState) 2015-09-22 13:21:19 +12:00
Robin Bygrave 489eb483c0 No effective change - parameter name from val -> value 2015-09-22 12:34:30 +12:00
Robin Bygrave 11301846be #422 - ENH: Add jsonBetween() expression 2015-09-22 12:32:46 +12:00
Robin Bygrave d022e4aec8 No effective change - parameter name from val -> value 2015-09-22 11:48:41 +12:00
Robin Bygrave aa788edb68 #421 - ENH: Add support for unsetting the loaded state of a bean property (via BeanState) ... initial test 2015-09-22 09:00:06 +12:00
Robin Bygrave 288adcdf68 Update README 2015-09-21 18:01:31 +12:00
Robin Bygrave ddb07ca202 Update README 2015-09-21 17:59:45 +12:00
Robin Bygrave 524b592e82 [maven-release-plugin] prepare for next development iteration 2015-09-21 17:45:47 +12:00
91 changed files with 2002 additions and 354 deletions
+1 -10
View File
@@ -5,16 +5,7 @@
Ebean ORM
==============
[http://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
Maven Dependency
----------------
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.7.3</version>
</dependency>
[https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
[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")
+12 -4
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.8.1</version>
<version>6.10.4</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -61,6 +61,14 @@
<version>1.7.7</version>
</dependency>
<!-- provided scope for Spring boot loader classpath scanning support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>1.2.7.RELEASE</version>
<scope>provided</scope>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -220,7 +228,7 @@
<configuration>
<classSource>target/test-classes</classSource>
<packages>com.avaje.tests.**</packages>
<transformArgs>debug=2</transformArgs>
<transformArgs>debug=1</transformArgs>
</configuration>
<goals>
<goal>enhance</goal>
@@ -265,11 +273,11 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<doctitle>Ebean 4</doctitle>
<doctitle>Ebean 6</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util:com.avaje.ebean.dbmigration.ddlgeneration:com.avaje.ebean.dbmigration.migration:com.avaje.ebean.dbmigration.migrationreader:com.avaje.ebean.dbmigration.model</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
@@ -38,6 +38,33 @@ public interface BeanState {
*/
void setDisableLazyLoad(boolean disableLazyLoading);
/**
* Set the loaded state of the property given it's name.
*
* <p>
* Typically this would be used to set the loaded state of a property
* to false to ensure that the specific property is excluded from a
* stateless update.
* </p>
*
* <pre>{@code
*
* // populate a bean via say JSON
* User user = ...;
*
* // set loaded state on the email property to false so that
* // the email property is not included in a stateless update
* Ebean.getBeanState(user).setPropertyLoaded("email", false);
*
* user.update();
*
* }</pre>
*
*
* This will throw an IllegalArgumentException if the property is unknown.
*/
void setPropertyLoaded(String propertyName, boolean loaded);
/**
* For partially populated beans returns the properties that are loaded on the
* bean.
@@ -76,6 +76,11 @@ public interface ExpressionFactory {
*/
Expression jsonLessOrEqualTo(String propertyName, String path, Object val);
/**
* Between - for the given path in a JSON document.
*/
Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
/**
* Equal To - property equal to the given value.
*/
@@ -412,7 +412,7 @@ public interface ExpressionList<T> extends Serializable {
*
* }</pre>
*/
ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val);
ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object value);
/**
* Greater than or equal to - for the given path in a JSON document.
@@ -423,7 +423,7 @@ public interface ExpressionList<T> extends Serializable {
*
* }</pre>
*/
ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val);
ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object value);
/**
* Less than - for the given path in a JSON document.
@@ -434,7 +434,7 @@ public interface ExpressionList<T> extends Serializable {
*
* }</pre>
*/
ExpressionList<T> jsonLessThan(String propertyName, String path, Object val);
ExpressionList<T> jsonLessThan(String propertyName, String path, Object value);
/**
* Less than or equal to - for the given path in a JSON document.
@@ -445,7 +445,18 @@ public interface ExpressionList<T> extends Serializable {
*
* }</pre>
*/
ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val);
ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object value);
/**
* Between - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonBetween("content", "orderDate", lowerDateTime, upperDateTime)
*
* }</pre>
*/
ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
/**
* Add an Expression to the list.
+26 -1
View File
@@ -1,5 +1,6 @@
package com.avaje.ebean;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.util.ClassUtil;
import org.jetbrains.annotations.Nullable;
@@ -193,7 +194,31 @@ public abstract class Model {
public void markAsDirty() {
db().markAsDirty(this);
}
/**
* Mark the property as unset or 'not loaded'.
* <p>
* This would be used to specify a property that we did not wish to include in a stateless update.
* </p>
* <pre>{@code
*
* // populate an entity bean from JSON or whatever
* User user = ...;
*
* // mark the email property as 'unset' so that it is not
* // included in a 'stateless update'
* user.markPropertyUnset("email");
*
* user.update();
*
* }</pre>
*
* @param propertyName the name of the property on the bean to be marked as 'unset'
*/
public void markPropertyUnset(String propertyName) {
((EntityBean)this)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
}
/**
* Insert or update this entity depending on its state.
*
@@ -0,0 +1,17 @@
package com.avaje.ebean.annotation;
/**
* Specify the DB storage type used to with <code>@DbEnumValue</code>.
*/
public enum DbEnumType {
/**
* Store values as database INTEGER.
*/
INTEGER,
/**
* Store values as database VARCHAR.
*/
VARCHAR
}
@@ -0,0 +1,44 @@
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 a method on an Enum that returns the value that should be stored in the DB.
* <p>
* This is the preferred option for mapping Enum's to DB values (preferred over the JPA
* standard @Enumerated and Ebean's @EnumValue annotations).
* </p>
* <h3>Example:</h3>
* <pre>{@code
*
* public enum Status {
* NEW("N"),
* ACTIVE("A"),
* INACTIVE("I");
*
* String dbValue;
* Status(String dbValue) {
* this.dbValue = dbValue;
* }
*
* @DbEnumValue
* public String getValue() {
* return dbValue;
* }
* }
*
* }</pre>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DbEnumValue {
/**
* Specify the database type used to store the values (VARCHAR or INTEGER).
*/
DbEnumType storage() default DbEnumType.VARCHAR;
}
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
* }</pre>
* <p>
* This is an alternative to using the JPA standard approach or Ebean's
* {@link EnumMapping} annotation.
* {@link DbEnumValue} annotation.
* </p>
* <p>
* Note that if all the EnumValue values are parsable as Integers then Ebean
@@ -449,7 +449,18 @@ public final class EntityBeanIntercept implements Serializable {
public int getPropertyLength() {
return owner._ebean_getPropertyNames().length;
}
/**
* Set the loaded state of the property given it's name.
*/
public void setPropertyLoaded(String propertyName, boolean loaded) {
int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property "+propertyName+" not found");
}
loadedProps[position] = loaded;
}
/**
* Set the property to be treated as unloaded. Used for properties initialised in default
* constructor.
@@ -45,13 +45,29 @@ public class DbConstraintNaming {
protected String ckPrefix = "ck_";
protected String ckSuffix = "";
protected boolean lowerCaseNames = true;
protected MaxLength maxLength;
protected DbConstraintNormalise normalise = new DbConstraintNormalise();
protected DbConstraintNormalise normalise;
/**
* Construct using default of lower case for both table and column names.
*/
public DbConstraintNaming() {
this(true, true);
}
/**
* Construct specifying if lower case should be used (for both table and column names).
*/
public DbConstraintNaming(boolean lowerCase) {
this(lowerCase, lowerCase);
}
/**
* Construct specifying if lower case should be used for both table and column names.
*/
public DbConstraintNaming(boolean lowerCaseTableNames, boolean lowerCaseColumnNames) {
this.normalise = new DbConstraintNormalise(lowerCaseTableNames, lowerCaseColumnNames);
}
/**
@@ -167,7 +183,6 @@ public class DbConstraintNaming {
* quoted identifier characters (",',[,] etc).
*/
public String normaliseTable(String tableName) {
return normalise.normaliseTable(tableName);
}
@@ -175,18 +190,20 @@ public class DbConstraintNaming {
* Normalise the column name by removing any quoted identifier characters (",',[,] etc).
*/
public String normaliseColumn(String tableName) {
return normalise.normaliseColumn(tableName);
}
/**
* Lower case the table or column name checking for quoted identifiers.
* Lower case the table name checking for quoted identifiers.
*/
public String lowerName(String tableName) {
if (lowerCaseNames && normalise.notQuoted(tableName)) {
return tableName.toLowerCase();
}
return tableName;
public String lowerTableName(String tableName) {
return normalise.lowerTableName(tableName);
}
/**
* Lower case the column name checking for quoted identifiers.
*/
public String lowerColumnName(String name) {
return normalise.lowerColumnName(name);
}
}
@@ -8,11 +8,17 @@ public class DbConstraintNormalise {
protected final String[] quotedIdentifiers;
protected boolean lowerCaseTables = true;
protected final boolean lowerCaseTables;
protected boolean lowerCaseColumns = true;
protected final boolean lowerCaseColumns;
public DbConstraintNormalise() {
this(true, true);
}
public DbConstraintNormalise(boolean lowerCaseTables, boolean lowerCaseColumns) {
this.lowerCaseTables = lowerCaseTables;
this.lowerCaseColumns = lowerCaseColumns;
this.quotedIdentifiers = new String[]{"\"", "'", "[", "]", "`"};
}
@@ -46,6 +52,26 @@ public class DbConstraintNormalise {
return columnName;
}
/**
* Lower case the table name checking for quoted identifiers.
*/
public String lowerTableName(String tableName) {
if (lowerCaseTables && notQuoted(tableName)) {
return tableName.toLowerCase();
}
return tableName;
}
/**
* Lower case the column name checking for quoted identifiers.
*/
public String lowerColumnName(String name) {
if (lowerCaseColumns && notQuoted(name)) {
return name.toLowerCase();
}
return name;
}
/**
* Trim off the platform quoted identifier quotes like [ ' and ".
*/
@@ -18,6 +18,14 @@ public final class PropertyMap implements Serializable {
private final LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
/**
* Return properties loaded from test-ebean.properties.
*/
public static Properties testProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public static Properties defaultProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
@@ -14,17 +14,34 @@ final class PropertyMapLoader {
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
/**
* Load the <code>test-ebean.properties</code>.
*/
public static PropertyMap loadTestProperties() {
return load(null, "test-ebean.properties", false);
}
/**
* Load the ebean.properties (and test-ebean.properties if present).
*/
public static PropertyMap loadGlobalProperties() {
boolean loadTestProperties = false;
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
loadTestProperties = true;
fileName = "ebean.properties";
}
}
return load(null, fileName);
PropertyMap map = load(null, fileName, true);
if (loadTestProperties) {
// load test properties if present in classpath
load(map, "test-ebean.properties", false);
}
return map;
}
/**
@@ -35,11 +52,13 @@ final class PropertyMapLoader {
* @param fileName
* the name of the properties file to load.
*/
public static PropertyMap load(PropertyMap p, String fileName) {
public static PropertyMap load(PropertyMap p, String fileName, boolean errorOnNull) {
InputStream is = findInputStream(fileName);
if (is == null) {
logger.error(fileName + " not found");
if (errorOnNull) {
logger.error(fileName + " not found");
}
return p;
} else {
return load(p, is);
@@ -281,9 +281,9 @@ public class ServerConfig {
private boolean updatesDeleteMissingChildren = true;
/**
* Setting to indicate if UUID should be stored as binary(16) or varchar(40).
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
*/
private boolean uuidStoreAsBinary;
private DbUuid dbUuid = DbUuid.AUTO;
private List<BeanFindController> findControllers = new ArrayList<BeanFindController>();
@@ -1466,19 +1466,18 @@ public class ServerConfig {
this.dbEncrypt = dbEncrypt;
}
/**
* Return true if UUID should be stored as binary(16) (as opposed to varchar(40)).
* Return the DB type used to store UUID.
*/
public boolean isUuidStoreAsBinary() {
return uuidStoreAsBinary;
public DbUuid getDbUuid() {
return dbUuid;
}
/**
* Set to true if UUID should be stored as binary(16) (as opposed to varchar(40)).
* Set the DB type used to store UUID.
*/
public void setUuidStoreAsBinary(boolean uuidStoreAsBinary) {
this.uuidStoreAsBinary = uuidStoreAsBinary;
public void setDbUuid(DbUuid dbUuid) {
this.dbUuid = dbUuid;
}
/**
@@ -2041,6 +2040,21 @@ public class ServerConfig {
loadSettings(p);
}
/**
* Load settings from test-ebean.properties and do nothing if the properties is not found.
* <p>
* This is typically used when test-ebean.properties is put into the test class path and used
* to configure Ebean for running tests.
* </p>
*/
public void loadTestProperties() {
Properties properties = PropertyMap.testProperties();
if (!properties.isEmpty()) {
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
loadSettings(p);
}
}
/**
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
*/
@@ -2049,8 +2063,11 @@ public class ServerConfig {
}
@SuppressWarnings("unchecked")
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key) {
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key, T instance) {
if (instance != null) {
return instance;
}
String classname = p.get(key, null);
return classname == null ? null : (T) ClassUtil.newInstance(classname);
}
@@ -2098,15 +2115,15 @@ public class ServerConfig {
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider");
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager");
encryptor = createInstance(p, Encryptor.class, "encryptor");
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt");
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory");
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager");
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
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);
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager);
cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay);
classPathReaderClassName = p.get("classpathreader");
@@ -2152,7 +2169,10 @@ public class ServerConfig {
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", uuidStoreAsBinary);
dbUuid = p.getEnum(DbUuid.class, "dbuuid", dbUuid);
if (p.getBoolean("uuidStoreAsBinary", false)) {
dbUuid = DbUuid.BINARY;
}
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
@@ -2174,7 +2194,7 @@ public class ServerConfig {
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention");
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention", null);
return (nc != null) ? nc : namingConvention;
}
@@ -2274,4 +2294,25 @@ public class ServerConfig {
public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) {
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
}
/**
* Specify how UUID is stored.
*/
public enum DbUuid {
/**
* Store using native UUID in H2 and Postgres.
*/
AUTO,
/**
* Store using DB VARCHAR.
*/
VARCHAR,
/**
* Store using DB BINARY.
*/
BINARY
}
}
@@ -1,18 +1,16 @@
package com.avaje.ebean.config.dbplatform;
import java.sql.Types;
import javax.sql.DataSource;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.Query;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Database platform specific settings.
*/
@@ -54,28 +52,33 @@ public class DatabasePlatform {
*/
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
/**
* The open quote used by quoted identifiers.
/**
* The open quote used by quoted identifiers.
*/
protected String openQuote = "\"";
/**
* The close quote used by quoted identifiers.
* The close quote used by quoted identifiers.
*/
protected String closeQuote = "\"";
/**
* For limit/offset, row_number etc limiting of SQL queries.
/**
* For limit/offset, row_number etc limiting of SQL queries.
*/
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
/**
* Mapping of JDBC to Database types.
/**
* Mapping of JDBC to Database types.
*/
protected DbTypeMap dbTypeMap = new DbTypeMap();
/**
* Defines DB identity/sequence features.
* Set to true if the DB has native UUID type support.
*/
protected boolean nativeUuidType;
/**
* Defines DB identity/sequence features.
*/
protected DbIdentity dbIdentity = new DbIdentity();
@@ -84,28 +87,28 @@ public class DatabasePlatform {
*/
protected DbHistorySupport historySupport;
/**
* The JDBC type to map booleans to (by default).
/**
* The JDBC type to map booleans to (by default).
*/
protected int booleanDbType = Types.BOOLEAN;
/**
* The JDBC type to map Blob to.
/**
* The JDBC type to map Blob to.
*/
protected int blobDbType = Types.BLOB;
/**
* The JDBC type to map Clob to.
/**
* The JDBC type to map Clob to.
*/
protected int clobDbType = Types.CLOB;
/**
* For Oracle treat empty strings as null.
/**
* For Oracle treat empty strings as null.
*/
protected boolean treatEmptyStringsAsNull;
/**
* The database platform name.
/**
* The database platform name.
*/
protected String name = "generic";
@@ -124,7 +127,7 @@ public class DatabasePlatform {
* The like clause. Can be overridden to disable default escape character.
*/
protected String likeClause = "like ?";
protected DbEncrypt dbEncrypt;
protected boolean idInExpandedForm;
@@ -219,16 +222,12 @@ public class DatabasePlatform {
/**
* Return a DB Sequence based IdGenerator.
*
* @param be
* the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds
* the DataSource
* @param seqName
* the name of the sequence
* @param batchSize
* the number of sequences that should be loaded
*
* @param be the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds the DataSource
* @param seqName the name of the sequence
* @param batchSize the number of sequences that should be loaded
*/
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
return null;
@@ -276,9 +275,16 @@ public class DatabasePlatform {
this.historySupport = historySupport;
}
/**
* Return true if the DB supports native UUID.
*/
public boolean isNativeUuidType() {
return nativeUuidType;
}
/**
* Return the mapping of JDBC to DB types.
*
*
* @return the db type map
*/
public DbTypeMap getDbTypeMap() {
@@ -315,7 +321,7 @@ public class DatabasePlatform {
/**
* Return the close quote for quoted identifiers.
*
*
* @return the close quote
*/
public String getCloseQuote() {
@@ -324,7 +330,7 @@ public class DatabasePlatform {
/**
* Return the open quote for quoted identifiers.
*
*
* @return the open quote
*/
public String getOpenQuote() {
@@ -333,7 +339,7 @@ public class DatabasePlatform {
/**
* Return the JDBC type used to store booleans.
*
*
* @return the boolean db type
*/
public int getBooleanDbType() {
@@ -363,7 +369,7 @@ public class DatabasePlatform {
/**
* Return true if empty strings should be treated as null.
*
*
* @return true, if checks if is treat empty strings as null
*/
public boolean isTreatEmptyStringsAsNull() {
@@ -399,7 +405,7 @@ public class DatabasePlatform {
/**
* Return the DB identity/sequence features for this platform.
*
*
* @return the db identity
*/
public DbIdentity getDbIdentity() {
@@ -412,7 +418,7 @@ public class DatabasePlatform {
* <p>
* Basically add the clauses for limit/offset, rownum, row_number().
* </p>
*
*
* @return the sql limiter
*/
public SqlLimiter getSqlLimiter() {
@@ -421,15 +427,12 @@ public class DatabasePlatform {
/**
* Convert backticks to the platform specific open quote and close quote
*
* <p>
* Specific plugins may implement this method to cater for platform specific
* naming rules.
* </p>
*
* @param dbName
* the db name
*
*
* @param dbName the db name
* @return the string
*/
public String convertQuotedIdentifiers(String dbName) {
@@ -8,6 +8,11 @@ package com.avaje.ebean.config.dbplatform;
*/
public class DbType {
/**
* DB native UUID type (H2 and Postgres).
*/
public static final int UUID = 5010;
/**
* Type to map Map content to Postgres HSTORE.
*/
@@ -49,6 +49,8 @@ public class DbTypeMap {
lookup.put("TIME", Types.TIME);
lookup.put("TIMESTAMP", Types.TIMESTAMP);
lookup.put("UUID", DbType.UUID);
// Not standard java.sql.Types
// logical JSON storage types
lookup.put("JSON", DbType.JSON);
@@ -101,6 +103,9 @@ public class DbTypeMap {
put(Types.BLOB, new DbType("blob"));
put(Types.CLOB, new DbType("clob"));
// DB native UUID support (H2 and Postgres)
put(DbType.UUID, new DbType("uuid"));
if (logicalTypes) {
// keep it logical for 2 layer DDL generation
put(DbType.HSTORE, new DbType("hstore"));
@@ -16,6 +16,7 @@ public class H2Platform extends DatabasePlatform {
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
@@ -23,10 +23,10 @@ public class PostgresPlatform extends DatabasePlatform {
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
this.onQueryOnly = OnQueryOnly.CLOSE;
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
this.nativeUuidType = true;
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
@@ -98,7 +98,7 @@ public class BaseTableDdl implements TableDdl {
reset();
String tableName = lowerName(createTable.getName());
String tableName = lowerTableName(createTable.getName());
List<Column> columns = createTable.getColumn();
List<Column> pk = determinePrimaryKeyColumns(columns);
@@ -248,7 +248,7 @@ public class BaseTableDdl implements TableDdl {
protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException {
tableName = lowerName(tableName);
tableName = lowerTableName(tableName);
DdlBuffer fkeyBuffer = write.applyForeignKeys();
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
@@ -282,7 +282,7 @@ public class BaseTableDdl implements TableDdl {
if (i > 0) {
buffer.append(",");
}
buffer.append(lowerName(columns[i].trim()));
buffer.append(lowerColumnName(columns[i].trim()));
}
buffer.append(")");
}
@@ -367,7 +367,7 @@ public class BaseTableDdl implements TableDdl {
buffer.append(",").newLine();
buffer.append(" constraint ").append(uqName).append(" unique ");
buffer.append("(");
buffer.append(lowerName(column.getName()));
buffer.append(lowerColumnName(column.getName()));
buffer.append(")");
}
@@ -401,14 +401,17 @@ public class BaseTableDdl implements TableDdl {
}
/**
* Convert the table or column name to lower case.
* <p>
* This is passed up to the platformDdl to override as desired.
* Generally lower case with underscore is a good cross database
* choice for column/table names.
* Convert the table lower case.
*/
protected String lowerName(String name) {
return naming.lowerName(name);
protected String lowerTableName(String name) {
return naming.lowerTableName(name);
}
/**
* Convert the column name to lower case.
*/
protected String lowerColumnName(String name) {
return naming.lowerColumnName(name);
}
/**
@@ -176,7 +176,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String platformType = platformDdl.convert(type, false);
buffer.append(" ");
buffer.append(platformDdl.lowerName(columnName), 29);
buffer.append(platformDdl.lowerColumnName(columnName), 29);
buffer.append(platformType);
}
@@ -136,7 +136,7 @@ public class PlatformDdl {
String platformType = convert(column.getType(), identityColumn);
buffer.append(" ");
buffer.append(lowerName(column.getName()), 29);
buffer.append(lowerColumnName(column.getName()), 29);
buffer.append(platformType);
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
buffer.append(" not null");
@@ -254,7 +254,7 @@ public class PlatformDdl {
appendColumns(columns, buffer);
buffer
.append(" references ")
.append(lowerName(refTable));
.append(lowerTableName(refTable));
appendColumns(refColumns, buffer);
appendWithSpace(foreignKeyRestrict, buffer);
@@ -346,7 +346,7 @@ public class PlatformDdl {
if (i > 0) {
buffer.append(",");
}
buffer.append(lowerName(columns[i].trim()));
buffer.append(lowerColumnName(columns[i].trim()));
}
buffer.append(")");
}
@@ -358,16 +358,26 @@ public class PlatformDdl {
}
/**
* Convert the table or column name to lower case.
* Convert the table to lower case.
* <p>
* This is passed up to the platformDdl to override as desired.
* Generally lower case with underscore is a good cross database
* Override as desired. Generally lower case with underscore is a good cross database
* choice for column/table names.
*/
protected String lowerName(String name) {
return naming.lowerName(name);
protected String lowerTableName(String name) {
return naming.lowerTableName(name);
}
/**
* Convert the column name to lower case.
* <p>
* Override as desired. Generally lower case with underscore is a good cross database
* choice for column/table names.
*/
protected String lowerColumnName(String name) {
return naming.lowerColumnName(name);
}
/**
* Null safe Boolean true test.
*/
@@ -81,12 +81,12 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
.append(" return new;").newLine().newLine();
.append(" return new;").newLine();
apply
.append(" elsif (TG_OP = 'DELETE') then").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" return old;").newLine().newLine();
.append(" return old;").newLine();
apply
.append(" end if;").newLine()
.append("end;").newLine()
@@ -14,7 +14,7 @@ import java.util.List;
*/
public class MCompoundForeignKey {
private final String name;
private String name;
private final String referenceTable;
private final List<String> columns = new ArrayList<String>();
private final List<String> referenceColumns = new ArrayList<String>();
@@ -47,6 +47,28 @@ public class MCompoundForeignKey {
return fk;
}
/**
* Add a counter to the foreign key and index names to avoid duplication.
*/
public void addNameSuffix(int counter) {
this.name = name + "_" + counter;
this.indexName = indexName + "_" + counter;
}
/**
* Return the foreign key name.
*/
public String getName() {
return name;
}
/**
* Return the index name.
*/
public String getIndexName() {
return indexName;
}
/**
* Return the columns making up the foreign key in order.
*/
@@ -14,6 +14,7 @@ import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -482,4 +483,32 @@ public class MTable {
return (value == 0) ? null : BigInteger.valueOf(value);
}
/**
* Check if there are duplicate foreign keys.
* <p>
* This can occur when an ManyToMany relates back to itself.
* </p>
*/
public void checkDuplicateForeignKeys() {
if (hasDuplicateForeignKeys()) {
int counter = 1;
for (MCompoundForeignKey fk : compoundKeys) {
fk.addNameSuffix(counter++);
}
}
}
/**
* Return true if the foreign key names are not unique.
*/
private boolean hasDuplicateForeignKeys() {
Set<String> fkNames = new HashSet<String>();
for (MCompoundForeignKey fk : compoundKeys) {
if (!fkNames.add(fk.getName())) {
return true;
}
}
return false;
}
}
@@ -213,8 +213,8 @@ public class ModelContainer {
/**
* Add a table (typically from reading EbeanServer meta data).
*/
public void addTable(MTable table) {
tables.put(table.getName(), table);
public MTable addTable(MTable table) {
return tables.put(table.getName(), table);
}
/**
@@ -67,8 +67,8 @@ public class ModelBuildContext {
return maxLength(constraintNaming.checkConstraintName(tableName, columnName), checkCount);
}
public void addTable(MTable table) {
model.addTable(table);
public MTable addTable(MTable table) {
return model.addTable(table);
}
public void addIndex(String indexName, String tableName, String columnName) {
@@ -35,7 +35,11 @@ public class ModelBuildIntersectionTable {
public void build() {
intersectionTable = createTable();
ctx.addTable(intersectionTable);
MTable existingTable = ctx.addTable(intersectionTable);
if (existingTable != null) {
throw new IllegalStateException("Property " + manyProp.getFullBeanName() + " has duplicate ManyToMany intersection table " + intersectionTable.getName()
+ ". Please use @JoinTable to define unique table to use");
}
buildFkConstraints();
}
@@ -47,6 +51,8 @@ public class ModelBuildIntersectionTable {
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
buildFkConstraints(targetDesc, tableJoin.columns(), false);
intersectionTable.checkDuplicateForeignKeys();
}
@@ -0,0 +1,8 @@
/**
* Generation DDL migration scripts based on changes to the model.
* <p>
* You can use <code>DbMigration</code> to compare the entity bean model to its prior state
* and generate DDL for the differences - adding tables, columns etc.
* </p>
*/
package com.avaje.ebean.dbmigration;
@@ -0,0 +1,13 @@
/**
* Provides a built in change log mechanism and can audit changes.
* <p>
* Although you can build something similar using existing event adapters such as <code>BeanPersistController</code>
* this is built for purpose to reduce the effort and provide optimal performance for auditing or logging changes.
* </p>
* <p>
* By default you can annotate beans with <code>@ChangeLog</code> and associated change events are logged by default
* in a JSON form with appropriate auditing attributes such as who made the changes and ip address of the user etc
* via implementation of <code>ChangeLogPrepare</code>
* </p>
*/
package com.avaje.ebean.event.changelog;
@@ -0,0 +1,8 @@
/**
* Provides Auditing of read events including queries and L2 cache.
* <p>
* Provides a built support for supplied an audit of all the 'read events' for beans annotated
* with <code>@ReadAudit</code>
* </p>
*/
package com.avaje.ebean.event.readaudit;
@@ -0,0 +1,8 @@
/**
* Provides a API for plugins.
* <p>
* Plugins can get and read meta data about the entity beans and enhance or utilise the
* functionality of the EbeanServer.
* </p>
*/
package com.avaje.ebean.plugin;
@@ -59,11 +59,6 @@ public class ScopedTransaction implements SpiTransaction {
}
}
@Override
public void endQueryOnly() {
transaction.endQueryOnly();
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
@@ -19,11 +19,6 @@ import com.avaje.ebeaninternal.server.persist.BatchControl;
*/
public interface SpiTransaction extends Transaction {
/**
* End the transaction when had query only use.
*/
void endQueryOnly();
/**
* Return the string prefix with the transactin id and label used in logging.
*/
@@ -1,14 +1,14 @@
package com.avaje.ebeaninternal.server.core;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
import com.avaje.ebean.BeanState;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
/**
* Default implementation of BeanState.
*/
@@ -23,9 +23,13 @@ public class DefaultBeanState implements BeanState {
this.intercept = entityBean._ebean_getIntercept();
}
public boolean isReference() {
return intercept.isReference();
}
public void setPropertyLoaded(String propertyName, boolean loaded) {
intercept.setPropertyLoaded(propertyName, loaded);
}
public boolean isReference() {
return intercept.isReference();
}
public boolean isNew() {
return intercept.isNew();
@@ -1301,6 +1301,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.DELETE, query, t);
try {
request.initTransIfRequired();
request.markNotQueryOnly();
return request.delete();
} finally {
request.endTransIfRequired();
@@ -10,6 +10,7 @@ import javax.persistence.PersistenceException;
import com.avaje.ebean.*;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanQueryRequest;
@@ -71,6 +72,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
this.readOnly = query.isReadOnly();
}
/**
* Mark the transaction as not being query only.
*/
@Override
public void markNotQueryOnly() {
transaction.markNotQueryOnly();
}
/**
* Return the database platform like clause.
@@ -151,6 +159,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return persistenceContext;
}
/**
* Add the bean to the persistence context.
*/
public void persistenceContextAdd(EntityBean bean) {
Object id = beanDescriptor.getId(bean);
persistenceContext.put(id, bean);
}
/**
* This will create a local (readOnly) transaction if no current transaction
* exists.
@@ -210,7 +226,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
*/
public void endTransIfRequired() {
if (createdTransaction) {
transaction.endQueryOnly();
transaction.commit();
}
}
@@ -59,7 +59,7 @@ public final class RelationalQueryRequest {
*/
public void endTransIfRequired() {
if (createdTransaction) {
trans.endQueryOnly();
trans.commit();
}
}
@@ -112,4 +112,8 @@ public interface SpiOrmQueryRequest<T> {
*/
String getDBLikeClause();
/**
* Mark the underlying transaction as not being query only.
*/
void markNotQueryOnly();
}
@@ -73,7 +73,31 @@ public class DeployBeanPropertyLists {
this.propertyMap = new LinkedHashMap<String, BeanProperty>();
// see if there is a discriminator property we should add
String discriminatorColumn = null;
BeanProperty discProperty = null;
InheritInfo inheritInfo = deploy.getInheritInfo();
if (inheritInfo != null) {
// Create a BeanProperty for the discriminator column to support
// using RawSql queries with inheritance
discriminatorColumn = inheritInfo.getDiscriminatorColumn();
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
discDeployProp.setDiscriminator();
discDeployProp.setName(discriminatorColumn);
discDeployProp.setDbColumn(discriminatorColumn);
// only register it in the propertyMap. This might not be used if
// an explicit property is mapped to the discriminator on the bean
discProperty = new BeanProperty(desc, discDeployProp);
}
for (DeployBeanProperty prop : deploy.propertiesAll()) {
if (discriminatorColumn != null && discriminatorColumn.equals(prop.getDbColumn())) {
// we have an explicit property mapped to the discriminator column
prop.setDiscriminator();
discProperty = null;
}
BeanProperty beanProp = createBeanProperty(owner, prop);
propertyMap.put(beanProp.getName(), beanProp);
}
@@ -84,21 +108,12 @@ public class DeployBeanPropertyLists {
allocateToList(prop);
}
InheritInfo inheritInfo = deploy.getInheritInfo();
if (inheritInfo != null) {
// Create a BeanProperty for the discriminator column to support
// using RawSql queries with inheritance
String discriminatorColumn = inheritInfo.getDiscriminatorColumn();
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
discDeployProp.setDiscriminator();
discDeployProp.setName(discriminatorColumn);
discDeployProp.setDbColumn(discriminatorColumn);
// create the discriminator BeanProperty and only register it in the propertyMap
BeanProperty dprop = new BeanProperty(desc, discDeployProp);
propertyMap.put(dprop.getName(), dprop);
if (discProperty != null) {
// put the discriminator property into the property map only
// (after the real properties have been organised into their lists)
propertyMap.put(discProperty.getName(), discProperty);
}
List<DeployTableJoin> deployTableJoins = deploy.getTableJoins();
tableJoins = new TableJoin[deployTableJoins.size()];
for (int i = 0; i < deployTableJoins.size(); i++) {
@@ -76,6 +76,8 @@ public class AnnotationFields extends AnnotationParser {
*/
private void readAssocOne(DeployBeanProperty prop) {
readJsonAnnotations(prop);
Id id = get(prop, Id.class);
if (id != null) {
prop.setId();
@@ -117,25 +119,7 @@ public class AnnotationFields extends AnnotationParser {
readColumn(column, prop);
}
if (jacksonAnnotationsPresent) {
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
if (jsonIgnore != null) {
prop.setJsonSerialize(!jsonIgnore.value());
prop.setJsonDeserialize(!jsonIgnore.value());
}
}
Expose expose = get(prop, Expose.class);
if (expose != null) {
prop.setJsonSerialize(expose.serialize());
prop.setJsonDeserialize(expose.deserialize());
}
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
if (jsonIgnore != null) {
prop.setJsonSerialize(jsonIgnore.serialize());
prop.setJsonDeserialize(jsonIgnore.deserialize());
}
readJsonAnnotations(prop);
if (prop.getDbColumn() == null) {
// No @Column annotation or @Column.name() not set
@@ -291,6 +275,28 @@ public class AnnotationFields extends AnnotationParser {
}
}
private void readJsonAnnotations(DeployBeanProperty prop) {
if (jacksonAnnotationsPresent) {
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
if (jsonIgnore != null) {
prop.setJsonSerialize(!jsonIgnore.value());
prop.setJsonDeserialize(!jsonIgnore.value());
}
}
Expose expose = get(prop, Expose.class);
if (expose != null) {
prop.setJsonSerialize(expose.serialize());
prop.setJsonDeserialize(expose.deserialize());
}
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
if (jsonIgnore != null) {
prop.setJsonSerialize(jsonIgnore.serialize());
prop.setJsonDeserialize(jsonIgnore.deserialize());
}
}
private boolean hasRelationshipItem(DeployBeanProperty prop) {
return get(prop, OneToMany.class) != null ||
get(prop, ManyToOne.class) != null ||
@@ -111,8 +111,8 @@ public class DeployUtil {
}
ScalarType<?> scalarType = typeManager.getScalarType(enumType);
if (scalarType == null) {
// see if it has a Mapping in avaje.properties
scalarType = typeManager.createEnumScalarType(enumType);
// look for @DbEnumValue or @EnumValue annotations etc
scalarType = typeManager.createEnumScalarType((Class<? extends Enum<?>>)enumType);
if (scalarType == null) {
// use JPA normal Enum type (without mapping)
EnumType type = enumerated != null ? enumerated.value() : null;
@@ -44,28 +44,32 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new JsonPathExpression(propertyName, path, Op.NOT_EXISTS, null);
}
public Expression jsonEqualTo(String propertyName, String path, Object val) {
return new JsonPathExpression(propertyName, path, Op.EQ, val);
public Expression jsonEqualTo(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.EQ, value);
}
public Expression jsonNotEqualTo(String propertyName, String path, Object val) {
return new JsonPathExpression(propertyName, path, Op.NOT_EQ, val);
public Expression jsonNotEqualTo(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.NOT_EQ, value);
}
public Expression jsonGreaterThan(String propertyName, String path, Object val) {
return new JsonPathExpression(propertyName, path, Op.GT, val);
public Expression jsonGreaterThan(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.GT, value);
}
public Expression jsonGreaterOrEqual(String propertyName, String path, Object val) {
return new JsonPathExpression(propertyName, path, Op.GT_EQ, val);
public Expression jsonGreaterOrEqual(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.GT_EQ, value);
}
public Expression jsonLessThan(String propertyName, String path, Object val) {
return new JsonPathExpression(propertyName, path, Op.LT, val);
public Expression jsonLessThan(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.LT, value);
}
public Expression jsonLessOrEqualTo(String propertyName, String path, Object val) {
return new JsonPathExpression(propertyName, path, Op.LT_EQ, val);
public Expression jsonLessOrEqualTo(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.LT_EQ, value);
}
public Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
return new JsonPathExpression(propertyName, path, lowerValue, upperValue);
}
/**
@@ -30,11 +30,31 @@ class JsonPathExpression extends AbstractExpression {
*/
protected final Object value;
/**
* For Between this is the upper bind value.
*/
protected final Object upperValue;
/**
* Construct for Operator (not BETWEEN though).
*/
JsonPathExpression(String propertyName, String path, Op operator, Object value) {
super(propertyName);
this.path = path;
this.operator = operator;
this.value = value;
this.upperValue = null;
}
/**
* Construct for BETWEEN expression.
*/
JsonPathExpression(String propertyName, String path, Object value, Object upperValue) {
super(propertyName);
this.path = path;
this.operator = Op.BETWEEN;
this.value = value;
this.upperValue = upperValue;
}
@Override
@@ -49,7 +69,9 @@ class JsonPathExpression extends AbstractExpression {
@Override
public int queryBindHash() {
return (value == null) ? 0 : value.hashCode();
int hc = (value == null) ? 0 : value.hashCode();
hc = (upperValue == null) ? hc : hc * 31 + upperValue.hashCode();
return hc;
}
@Override
@@ -63,7 +85,12 @@ class JsonPathExpression extends AbstractExpression {
public void addBindValues(SpiExpressionRequest request) {
if (value != null) {
// value is null for EXISTS/NOT EXISTS
request.addBindValue(value);
}
if (upperValue != null) {
// upperValue only for BETWEEN operator
request.addBindValue(upperValue);
}
}
}
@@ -377,6 +377,14 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
return exprList.jsonLessOrEqualTo(propertyName, path, val);
}
/**
* Between - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){
return exprList.jsonBetween(propertyName, path, lowerValue, upperValue);
}
@Override
public ExpressionList<T> ge(String propertyName, Object value) {
return exprList.ge(propertyName, value);
@@ -15,6 +15,11 @@ public enum Op {
*/
NOT_EXISTS(" is null "),
/**
* Between (JSON).
*/
BETWEEN(" between ? and ? "),
/**
* Equal to
*/
@@ -64,12 +64,16 @@ public final class ShutdownManager {
/**
* Deregister the Shutdown hook.
* <p>
* In calling this method it is expected that application code will invoke
* the shutdown() method.
* </p>
* <p>
* For running in a Servlet Container a redeploy will cause a shutdown, and
* for that case we need to make sure the shutdown hook is deregistered.
* </p>
*/
protected static void deregisterShutdownHook() {
public static void deregisterShutdownHook() {
synchronized (servers) {
try {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
@@ -87,7 +91,10 @@ public final class ShutdownManager {
protected static void registerShutdownHook() {
synchronized (servers) {
try {
Runtime.getRuntime().addShutdownHook(shutdownHook);
String value = System.getProperty("ebean.registerShutdownHook");
if (value == null || !value.trim().equalsIgnoreCase("false")) {
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
} catch (IllegalStateException ex) {
if (!ex.getMessage().equals("Shutdown in progress")) {
throw ex;
@@ -9,6 +9,7 @@ import java.util.List;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
import com.avaje.ebeaninternal.server.core.Message;
@@ -307,6 +308,11 @@ public class Binder {
b.setBytes((byte[]) data);
break;
case DbType.UUID:
// native UUID support in H2 and Postgres
b.setObject(data);
break;
case java.sql.Types.OTHER:
b.setObject(data);
break;
@@ -643,6 +643,15 @@ public final class DefaultPersister implements Persister {
private boolean isCascade() {
return cascade;
}
public void modifyListenReset(BeanCollection<?> c) {
if (insertedParent) {
// after insert set the modify listening mode
// for private owned etc
c.setModifyListening(many.getModifyListenMode());
}
c.modifyReset();
}
}
private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) {
@@ -684,6 +693,7 @@ public final class DefaultPersister implements Persister {
BeanCollection<?> c = (BeanCollection<?>) details;
Set<?> modifyRemovals = c.getModifyRemovals();
saveMany.modifyListenReset(c);
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
SpiTransaction t = saveMany.getTransaction();
@@ -908,6 +918,10 @@ public final class DefaultPersister implements Persister {
t.depth(+1);
if (additions != null && !additions.isEmpty()) {
// ensure any cascade batch has been flushed prior
// to inserting into the intersection table
t.flushBatch();
for (Object other : additions) {
EntityBean otherBean = (EntityBean) other;
// the object from the 'other' side of the ManyToMany
@@ -933,6 +947,10 @@ public final class DefaultPersister implements Persister {
}
}
if (deletions != null && !deletions.isEmpty()) {
// ensure any cascade batch has been flushed prior
// to inserting into the intersection table
t.flushBatch();
for (Object other : deletions) {
EntityBean otherDelete = (EntityBean) other;
// the object from the 'other' side of the ManyToMany
@@ -433,6 +433,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
} else {
// nextBean set to previously read currentBean
nextBean = currentBean;
request.persistenceContextAdd(nextBean);
// check the current row we have just moved to
if (checkForDifferentBean()) {
return true;
@@ -484,6 +484,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
copy.profilingListener = profilingListener;
copy.query = query;
copy.rootTableAlias = rootTableAlias;
copy.additionalWhere = additionalWhere;
copy.additionalHaving = additionalHaving;
copy.distinct = distinct;
@@ -125,9 +125,12 @@ public class DJsonContext implements JsonContext {
List<T> list = new ArrayList<T>();
JsonToken event = src.nextToken();
if (event != JsonToken.START_ARRAY) {
throw new JsonParseException("Expecting start_array event but got " + event, src.getCurrentLocation());
JsonToken currentToken = src.getCurrentToken();
if (currentToken != JsonToken.START_ARRAY) {
JsonToken event = src.nextToken();
if (event != JsonToken.START_ARRAY) {
throw new JsonParseException("Expecting start_array event but got " + event, src.getCurrentLocation());
}
}
do {
@@ -849,23 +849,6 @@ public class JdbcTransaction implements SpiTransaction {
connection.commit();
}
/**
* End the transaction on a query only request.
*/
@Override
public void endQueryOnly() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
try {
connectionEndForQueryOnly();
} finally {
// these will not throw an exception
deactivate();
notifyQueryOnly();
}
}
/**
* Commit the transaction.
*/
@@ -1,5 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.annotation.DbEnumType;
import com.avaje.ebean.annotation.DbEnumValue;
import com.avaje.ebean.annotation.EnumMapping;
import com.avaje.ebean.annotation.EnumValue;
import com.avaje.ebean.config.*;
@@ -21,6 +23,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
@@ -419,8 +422,10 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
return value;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private boolean isIntegerType(String s) {
protected boolean isIntegerType(String s) {
if (isLeadingZeros(s)) {
return false;
}
try {
Integer.parseInt(s);
return true;
@@ -429,6 +434,13 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
}
}
/**
* Treat leading zeros as a non-integer for enum values.
*/
private boolean isLeadingZeros(String s) {
return s.length() > 1 && s.charAt(0) == '0';
}
/**
* Create the Mapping of Enum fields to DB values using EnumValue annotations.
* <p>
@@ -469,7 +481,16 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
* much shorter codes used in the DB.
* </p>
*/
public ScalarType<?> createEnumScalarType(Class<?> enumType) {
public ScalarType<?> createEnumScalarType(Class<? extends Enum<?>> enumType) {
Method[] methods = enumType.getMethods();
for (int i = 0; i <methods.length; i++) {
DbEnumValue dbValue = methods[i].getAnnotation(DbEnumValue.class);
if (dbValue != null) {
boolean integerValues = DbEnumType.INTEGER == dbValue.storage();
return createEnumScalarTypeDbValue(enumType, methods[i], integerValues);
}
}
// get the mapping information from EnumMapping
EnumMapping enumMapping = enumType.getAnnotation(EnumMapping.class);
@@ -487,6 +508,33 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
return createEnumScalarType(enumType, nameValueMap, integerType, dbColumnLength);
}
/**
* Create the Mapping of Enum fields to DB values using EnumValue annotations.
* <p>
* Return null if the EnumValue annotations are not present/used.
* </p>
*/
private ScalarType<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType) {
Map<String, String> nameValueMap = new HashMap<String, String>();
Enum<?>[] enumConstants = enumType.getEnumConstants();
for (int i = 0; i < enumConstants.length; i++) {
try {
Object value = method.invoke(enumConstants[i]);
nameValueMap.put(enumConstants[i].name(), value.toString());
} catch (Exception e) {
throw new IllegalArgumentException("Error trying to invoke DbEnumValue method on "+enumConstants[i], e);
}
}
if (nameValueMap.isEmpty()) {
// Not using EnumValue here
return null;
}
return createEnumScalarType(enumType, nameValueMap, integerType, 0);
}
/**
* Given the name value mapping and integer/string type and explicit DB column
* length create the ScalarType for the Enum.
@@ -768,7 +816,6 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
*/
protected void initialiseStandard(JsonConfig.DateTime mode, ServerConfig config) {
boolean binaryUUID = config.isUuidStoreAsBinary();
DatabasePlatform databasePlatform = config.getDatabasePlatform();
int platformClobType = databasePlatform.getClobDbType();
int platformBlobType = databasePlatform.getBlobDbType();
@@ -795,9 +842,17 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
nativeMap.put(Types.BIT, booleanType);
}
// Store UUID as binary(16) or varchar(40)
ScalarType<?> uuidType = (binaryUUID) ? new ScalarTypeUUIDBinary() : new ScalarTypeUUIDVarchar();
typeMap.put(UUID.class, uuidType);
boolean nativeUuidType = databasePlatform.isNativeUuidType();
ServerConfig.DbUuid dbUuid = config.getDbUuid();
if (nativeUuidType && dbUuid == ServerConfig.DbUuid.AUTO) {
// DB has native support for UUID
typeMap.put(UUID.class, new ScalarTypeUUIDNative());
} else {
// Store UUID as binary(16) or varchar(40)
ScalarType<?> uuidType = (ServerConfig.DbUuid.BINARY == dbUuid) ? new ScalarTypeUUIDBinary() : new ScalarTypeUUIDVarchar();
typeMap.put(UUID.class, uuidType);
}
typeMap.put(File.class, fileType);
typeMap.put(InetAddress.class, inetAddressType);
@@ -0,0 +1,120 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebean.text.json.JsonWriter;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.UUID;
/**
* Postgres Hstore type which maps Map<String,String> to a single 'HStore column' in the DB.
*/
@SuppressWarnings("rawtypes")
public class ScalarTypeUUIDNative extends ScalarTypeBase<UUID> {
public ScalarTypeUUIDNative() {
super(UUID.class, false, DbType.UUID);
}
@Override
public boolean isMutable() {
return false;
}
@Override
public boolean isDirty(Object value) {
return true;
}
@SuppressWarnings("unchecked")
@Override
public UUID read(DataReader dataReader) throws SQLException {
Object value = dataReader.getObject();
if (value == null) {
return null;
}
return (UUID)value;
}
@Override
public void bind(DataBind b, UUID value) throws SQLException {
b.setObject(value);
}
@Override
public Object toJdbcType(Object value) {
return value;
}
@Override
public UUID toBeanType(Object value) {
return (UUID) value;
}
@Override
public String formatValue(UUID v) {
return v.toString();
}
@Override
public UUID parse(String value) {
return UUID.fromString(value);
}
@Override
public UUID convertFromMillis(long dateTime) {
throw new RuntimeException("Should never be called");
}
@Override
public boolean isDateTimeCapable() {
return false;
}
@Override
public UUID readData(DataInput dataInput) throws IOException {
if (!dataInput.readBoolean()) {
return null;
} else {
String json = dataInput.readUTF();
return parse(json);
}
}
@Override
public void writeData(DataOutput dataOutput, UUID v) throws IOException {
if (v == null) {
dataOutput.writeBoolean(false);
} else {
dataOutput.writeBoolean(true);
String json = format(v);
dataOutput.writeUTF(json);
}
}
@Override
public void jsonWrite(JsonWriter writer, String name, UUID value) throws IOException {
// write the field name followed by the Map/JSON Object
if (value == null) {
writer.writeNullField(name);
} else {
writer.writeStringField(name, formatValue(value));
}
}
@Override
public UUID jsonRead(JsonParser parser, JsonToken event) throws IOException {
// at this point the BeanProperty has read the START_OBJECT token
// to check for a null value. Pass the START_OBJECT token through to
// the EJson parsing so that it knows the first token has been read
String strValue = parser.getValueAsString();
return strValue == null ? null : parse(strValue);
}
}
@@ -55,7 +55,7 @@ public interface TypeManager {
* Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal
* or String which has limitations).
*/
ScalarType<?> createEnumScalarType(Class<?> enumType);
ScalarType<?> createEnumScalarType(Class<? extends Enum<?>> enumType);
/**
* Return the ScalarType used to handle JSON content.
@@ -4,13 +4,24 @@ import com.avaje.ebeaninternal.api.ClassPathSearchService;
import com.avaje.ebeaninternal.api.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.loader.jar.JarEntryData;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
@@ -157,7 +168,7 @@ public class ClassPathSearch implements ClassPathSearchService {
} else if (element.isJarOrWar()) {
// search name including the ! offset if it is there
if (classPathSize == 1 || filter.isSearchJar(element.getJarNameWithOffset())) {
if (classPathSize == 1 || filter.isSearchJar(element.getJarNameWithOffset(), element.getJarOffset())) {
scanJar(element);
}
@@ -208,7 +219,7 @@ public class ClassPathSearch implements ClassPathSearchService {
private void scanDirectory(File directory) {
List<String> directoryFiles = getDirectoryFiles(directory);
searchFiles(Collections.enumeration(directoryFiles), null, null);
searchFiles(Collections.enumeration(directoryFiles), null, null, null);
}
private void scanUri(URI uri) throws IOException {
@@ -234,12 +245,14 @@ public class ClassPathSearch implements ClassPathSearchService {
File file = classPathEntry.classPath;
module = new JarFile(file);
logger.trace("scanJar file:{}", file);
List<URI> classPathFromManifest = getClassPathFromManifest(file, module.getManifest());
for (URI uri : classPathFromManifest) {
scanUri(uri);
}
searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset);
searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset, file);
} catch (MalformedURLException ex) {
throw new IOException("Bad classpath error: ", ex);
@@ -284,26 +297,30 @@ public class ClassPathSearch implements ClassPathSearchService {
/**
* Searches through the Java Archive (jar or war file) looking for classes
* that match our requirements.
*
* @param files - all of the files in the Java Archive, this is an enumeration
* @param entries - all of the entries in the Java Archive, this is an enumeration
* provided by the Jar file
* @param jarFileName - the name of the java archive
* @param jarOffset - an offset inside the archive to chop off the name of the class -
* this is used when we have bang path offsets (e.g.
* file:///myfile.war!/WEB-INF/classes)
* @param module the containing jar/war file (used for spring boot embedded jar scanning)
*/
private void searchFiles(Enumeration<?> files, String jarFileName, String jarOffset) {
private void searchFiles(Enumeration<?> entries, String jarFileName, String jarOffset, File module) {
if (files == null) {
if (entries == null) {
return;
}
/*
* Strips the first character off as all entries in a jar file have no /
* prefix. We want to come out with a name like WEB-INF/classes/ to ensure
* we filter the contents of the war/jar file by this.
*/
if (jarOffset != null) {
logger.debug("searchFiles jarFileName:{} jarOffset:{}", jarFileName, jarOffset);
// Strips the first character off as all entries in a jar file have no /
// prefix. We want to come out with a name like WEB-INF/classes/ to ensure
// we filter the contents of the war/jar file by this.
if ("/".equals(jarOffset)) {
// root level for runnable jar (spring boot etc)
jarOffset = null;
} else if (jarOffset != null) {
if (jarOffset.startsWith("/")) {
jarOffset = jarOffset.substring(1);
}
@@ -313,48 +330,90 @@ public class ClassPathSearch implements ClassPathSearchService {
}
}
while (files.hasMoreElements()) {
while (entries.hasMoreElements()) {
String fileName = files.nextElement().toString();
Object element = entries.nextElement();
String entryName = element.toString();
// we only want the class files
if (fileName.endsWith(".class") && (jarOffset == null || fileName.startsWith(jarOffset))) {
if (isEntryEmbeddedJar(module, entryName)) {
scanSpringBootEmbeddedJar(jarFileName, module, entryName);
}
if (jarOffset != null) {
// we got through here only if there is an offset and we
// matched it, so strip it off the file
// as we are trying to find the className
fileName = fileName.substring(jarOffset.length());
if (isEntryClass(jarOffset, entryName)) {
// check if it an 'interesting' class - entity etc
registerScannedClass(jarFileName, jarOffset, entryName);
}
}
}
/**
* Return true if this is an embedded jar that should be scanned.
*/
private boolean isEntryEmbeddedJar(File module, String entryName) {
return entryName.endsWith(".jar") && module != null && filter.isSearchJar(entryName, null);
}
/**
* Return true if this is a class that should be checked (for entity, interesting interface etc).
*/
private boolean isEntryClass(String jarOffset, String entryName) {
return entryName.endsWith(".class") && (jarOffset == null || entryName.startsWith(jarOffset));
}
private void scanSpringBootEmbeddedJar(String jarFileName, File module, String fileName) {
// spring boot embedded jar
logger.debug("spring boot embedded:{} : module:{}", fileName, module.getAbsoluteFile());
try {
org.springframework.boot.loader.jar.JarFile jarFile = new org.springframework.boot.loader.jar.JarFile(module);
org.springframework.boot.loader.jar.JarFile jarEntryFile = jarFile.getNestedJarFile(jarFile.getJarEntryData(fileName));
Iterator<JarEntryData> iterator = jarEntryFile.iterator();
while (iterator.hasNext()) {
JarEntryData jarEntryData = iterator.next();
if (jarEntryData.getName().toString().endsWith(".class")) {
logger.debug("... spring boot class entry:{}", jarEntryData.getName().toString());
registerScannedClass(jarFileName, null, jarEntryData.getName().toString());
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void registerScannedClass(String jarFileName, String jarOffset, String fileName) {
if (jarOffset != null) {
// we got through here only if there is an offset and we
// matched it, so strip it off the file
// as we are trying to find the className
fileName = fileName.substring(jarOffset.length());
}
String className = fileName.replace('/', '.').substring(0, fileName.length() - 6);
int lastPeriod = className.lastIndexOf(".");
String pckgName;
if (lastPeriod > 0) {
pckgName = className.substring(0, lastPeriod);
} else {
pckgName = "";
}
if (filter.isSearchPackage(pckgName)) {
// get the class for our class name
try {
Class<?> theClass = Class.forName(className, false, classLoader);
if (matcher.isMatch(theClass)) {
matchList.add(theClass);
registerHit(jarFileName, theClass);
}
String className = fileName.replace('/', '.').substring(0, fileName.length() - 6);
int lastPeriod = className.lastIndexOf(".");
String pckgName;
if (lastPeriod > 0) {
pckgName = className.substring(0, lastPeriod);
} else {
pckgName = "";
}
if (filter.isSearchPackage(pckgName)) {
// get the class for our class name
try {
Class<?> theClass = Class.forName(className, false, classLoader);
if (matcher.isMatch(theClass)) {
matchList.add(theClass);
registerHit(jarFileName, theClass);
}
} catch (ClassNotFoundException e) {
// expected to get this hence trace
logger.trace("Error searching classpath" + e.getMessage());
} catch (NoClassDefFoundError e) {
// expected to get this hence trace
logger.trace("Error searching classpath" + e.getMessage());
}
}
} catch (ClassNotFoundException e) {
// expected to get this hence trace
logger.trace("Error searching classpath" + e.getMessage());
} catch (NoClassDefFoundError e) {
// expected to get this hence trace
logger.trace("Error searching classpath" + e.getMessage());
}
}
}
@@ -498,6 +557,10 @@ public class ClassPathSearch implements ClassPathSearchService {
String getJarNameWithOffset() {
return (jarOffset == null) ? classPath.getName() : classPath.getName() + "!" + jarOffset;
}
String getJarOffset() {
return jarOffset;
}
}
}
@@ -122,17 +122,19 @@ public class ClassPathSearchFilter {
/**
* Return true if the jar should be included in the search.
*/
public boolean isSearchJar(String jarName) {
public boolean isSearchJar(String jarName, String jarOffset) {
if (containedIn(includeJarSet, jarName)) {
return true;
}
if (containedIn(includeJarSet, jarName)) {
return true;
}
if (containedIn(excludeJarSet, jarName)) {
return false;
}
return defaultJarMatch;
}
if (containedIn(excludeJarSet, jarName)) {
return false;
}
// by default scan the root level in runnable jar (spring boot etc)
return "/".equals(jarOffset) || defaultJarMatch;
}
/**
* Helper method to determine is a match is contained in the set.
@@ -435,6 +435,15 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return this;
}
/**
* Between - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){
add(expr.jsonBetween(propertyName, path, lowerValue, upperValue));
return this;
}
@Override
public ExpressionList<T> eq(String propertyName, Object value) {
add(expr.eq(propertyName, value));
@@ -0,0 +1,98 @@
package com.avaje.ebean;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.tests.model.converstation.User;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestUnsetLoadedProperties extends BaseTestCase {
@Test
public void testUnsetPropertyLoadedState() {
// populate db with a user
User seed = new User();
seed.setName("someName");
seed.setEmail("some@junk.com");
seed.save();
// our bean to perform stateless update
User user = new User();
user.setId(seed.getId());
user.setName("name mod");
user.setEmail("change@junk.com");
BeanState beanState = Ebean.getBeanState(user);
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
user.markPropertyUnset("email");
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
LoggedSqlCollector.start();
user.update();
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).doesNotContain("email");
}
@Test
public void testUnloadVia_EntityBeanIntercept_setPropertyLoaded() {
// our bean to perform stateless update
User user = new User();
user.setId(42L);
user.setName("name mod");
user.setEmail("change@junk.com");
BeanState beanState = Ebean.getBeanState(user);
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
// unset the loaded state for email
((EntityBean)user)._ebean_getIntercept().setPropertyLoaded("email", false);
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
}
@Test
public void testUnloadVia_Model_markPropertyUnset() {
// our bean to perform stateless update
User user = new User();
user.setId(42L);
user.setName("name mod");
user.setEmail("change@junk.com");
BeanState beanState = Ebean.getBeanState(user);
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
user.markPropertyUnset("email");
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
}
@Test
public void testUnloadVia_BeanState_setPropertyLoaded() {
// our bean to perform stateless update
User user = new User();
user.setId(42L);
user.setName("name mod");
user.setEmail("change@junk.com");
BeanState beanState = Ebean.getBeanState(user);
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
Ebean.getBeanState(user).setPropertyLoaded("email", false);
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
}
}
@@ -34,4 +34,40 @@ public class DbConstraintNamingTest {
assertThat(naming.normaliseTable("foo_bar]")).isEqualTo("foo_bar");
}
@Test
public void testDefaultToLower() throws Exception {
assertThat(naming.normaliseTable("SCH.FOO_BAR]")).isEqualTo("foo_bar");
assertThat(naming.lowerTableName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
assertThat(naming.lowerTableName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
assertThat(naming.lowerColumnName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
assertThat(naming.lowerColumnName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
}
@Test
public void testNoLowerCaseTable() throws Exception {
DbConstraintNaming naming = new DbConstraintNaming(false, true);
assertThat(naming.normaliseTable("SCH.FOO_BAR]")).isEqualTo("FOO_BAR");
assertThat(naming.normaliseColumn("SCH.FOO_BAR]")).isEqualTo("sch.foo_bar");
assertThat(naming.lowerTableName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
// table name not lowered
assertThat(naming.lowerTableName("SCH.FOO_BAR")).isEqualTo("SCH.FOO_BAR");
assertThat(naming.lowerColumnName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
assertThat(naming.lowerColumnName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
}
@Test
public void testNoLowerCaseColumn() throws Exception {
DbConstraintNaming naming = new DbConstraintNaming(true, false);
assertThat(naming.normaliseTable("SCH.FOO_BAR]")).isEqualTo("foo_bar");
assertThat(naming.normaliseColumn("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR");
assertThat(naming.lowerTableName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
assertThat(naming.lowerTableName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
assertThat(naming.lowerColumnName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
// column name not lowered
assertThat(naming.lowerColumnName("SCH.FOO_BAR")).isEqualTo("SCH.FOO_BAR");
}
}
@@ -31,11 +31,13 @@ public class ServerConfigTest {
Properties props = new Properties();
props.setProperty("persistBatch", "INSERT");
props.setProperty("persistBatchOnCascade", "INSERT");
props.setProperty("dbuuid","binary");
serverConfig.loadFromProperties(props);
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch());
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatchOnCascade());
assertEquals(ServerConfig.DbUuid.BINARY, serverConfig.getDbUuid());
serverConfig.setPersistBatch(PersistBatch.NONE);
serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
@@ -45,6 +47,7 @@ public class ServerConfigTest {
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
serverConfig.loadFromProperties(props1);
serverConfig.loadTestProperties();
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
@@ -0,0 +1,32 @@
package com.avaje.ebeaninternal.server.lib;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import org.junit.Ignore;
import org.junit.Test;
public class ShutdownManagerTest extends BaseTestCase {
/**
* Run this test manually.
*/
@Ignore
@Test
public void test_deregisterShutdownHook() {
Ebean.getDefaultServer();
ShutdownManager.deregisterShutdownHook();
}
/**
* Run this test manually.
*/
@Ignore
@Test
public void test_noShutdownHook() {
System.setProperty("ebean.registerShutdownHook","false");
Ebean.getDefaultServer();
}
}
@@ -0,0 +1,35 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.PostgresPlatform;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import org.junit.Test;
import static org.junit.Assert.*;
public class DefaultTypeManagerTest {
DefaultTypeManager typeManager;
public DefaultTypeManagerTest() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setDatabasePlatform(new PostgresPlatform());
BootupClasses bootupClasses = new BootupClasses();
typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
}
@Test
public void isIntegerType() {
assertTrue(typeManager.isIntegerType("1"));
assertTrue(typeManager.isIntegerType("0"));
assertFalse(typeManager.isIntegerType("A"));
assertFalse(typeManager.isIntegerType("01"));
assertFalse(typeManager.isIntegerType(" 01"));
assertFalse(typeManager.isIntegerType(" 0"));
assertFalse(typeManager.isIntegerType(" 1"));
assertFalse(typeManager.isIntegerType(" A"));
}
}
@@ -1,22 +1,22 @@
package com.avaje.ebeaninternal.server.type;
import java.util.List;
import java.util.UUID;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.SqlRow;
import com.avaje.tests.model.basic.UUOne;
import org.junit.Test;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class TestBinaryUUID extends BaseTestCase {
@Test
public void test() {
UUOne one0 = new UUOne();
one0.setName("first one");
@@ -27,26 +27,31 @@ public class TestBinaryUUID extends BaseTestCase {
Ebean.save(one0);
Ebean.save(one1);
UUOne fetch0 = Ebean.find(UUOne.class, one0.getId());
UUOne fetch1 = Ebean.find(UUOne.class, one1.getId());
Assert.assertEquals(one0.getId(), fetch0.getId());
Assert.assertEquals(one0.getName(), fetch0.getName());
Assert.assertEquals(one1.getId(), fetch1.getId());
Assert.assertEquals(one1.getName(), fetch1.getName());
assertEquals(one0.getId(), fetch0.getId());
assertEquals(one0.getName(), fetch0.getName());
assertEquals(one1.getId(), fetch1.getId());
assertEquals(one1.getName(), fetch1.getName());
String sql = "select id, name from uuone";
List<SqlRow> list = Ebean.createSqlQuery(sql).findList();
for (SqlRow sqlRow : list) {
Object sqlId = sqlRow.get("id");
Assert.assertNotNull(sqlId);
Assert.assertTrue(sqlId instanceof byte[]);
assertNotNull(sqlId);
UUID uuid = sqlRow.getUUID("id");
Assert.assertNotNull(uuid);
assertNotNull(uuid);
}
String asJson = Ebean.json().toJson(fetch0);
UUOne bean = Ebean.json().toBean(UUOne.class, asJson);
assertEquals(fetch0.getId(), bean.getId());
assertEquals(fetch0.getName(), bean.getName());
}
}
@@ -1,5 +1,6 @@
package com.avaje.tests.basic.type;
import com.avaje.tests.model.basic.EBasicEnumInt;
import org.junit.Assert;
import org.junit.Test;
@@ -11,6 +12,9 @@ import com.avaje.tests.model.basic.EBasic;
import com.avaje.tests.model.basic.EBasic.Status;
import com.avaje.tests.model.basic.EBasicEnumId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class TestEnumValueAnnotation extends BaseTestCase {
@Test
@@ -28,7 +32,7 @@ public class TestEnumValueAnnotation extends BaseTestCase {
SqlRow sqlRow = q.findUnique();
String strStatus = sqlRow.getString("status");
Assert.assertEquals("N", strStatus);
assertEquals("N", strStatus);
EBasic b2 = new EBasic();
b2.setName("Apple");
@@ -40,9 +44,9 @@ public class TestEnumValueAnnotation extends BaseTestCase {
b3.setName("Orange");
Ebean.save(b3);
}
@Test
public void testAsId() {
EBasicEnumId b = new EBasicEnumId();
b.setName("Banana");
@@ -56,7 +60,7 @@ public class TestEnumValueAnnotation extends BaseTestCase {
SqlRow sqlRow = q.findUnique();
String strStatus = sqlRow.getString("status");
Assert.assertEquals("N", strStatus);
assertEquals("N", strStatus);
try {
b = Ebean.find(EBasicEnumId.class, b.getStatus());
@@ -64,7 +68,32 @@ public class TestEnumValueAnnotation extends BaseTestCase {
Assert.fail("The use of an enum as id should work : " + iae.getLocalizedMessage());
}
Assert.assertEquals(EBasicEnumId.Status.NEW, b.getStatus());
assertEquals(EBasicEnumId.Status.NEW, b.getStatus());
}
@Test
public void testDbEnumValueInt() {
EBasicEnumInt b = new EBasicEnumInt();
b.setName("Banana");
b.setStatus(EBasicEnumInt.Status.NEW);
Ebean.save(b);
SqlQuery q = Ebean.createSqlQuery("select * from e_basic_eni where id = :id");
q.setParameter("id", b.getId());
SqlRow sqlRow = q.findUnique();
Integer intStatus = sqlRow.getInteger("status");
assertEquals(Integer.valueOf(1), intStatus);
EBasicEnumInt b2 = Ebean.find(EBasicEnumInt.class)
.where().eq("id", b.getId())
.eq("status", EBasicEnumInt.Status.NEW)
.findUnique();
assertNotNull(b2);
}
}
@@ -1,16 +1,19 @@
package com.avaje.tests.cascade;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.tests.model.basic.TSDetail;
import com.avaje.tests.model.basic.TSMaster;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class TestPrivateOwned extends BaseTestCase {
@@ -33,9 +36,9 @@ public class TestPrivateOwned extends BaseTestCase {
BeanCollection<?> bc = (BeanCollection<?>) details;
Set<?> modifyRemovals = bc.getModifyRemovals();
Assert.assertNotNull(modifyRemovals);
Assert.assertTrue(modifyRemovals.size() == 1);
Assert.assertTrue(modifyRemovals.contains(removedDetail));
assertNotNull(modifyRemovals);
assertTrue(modifyRemovals.size() == 1);
assertTrue(modifyRemovals.contains(removedDetail));
Ebean.save(master);
@@ -43,11 +46,42 @@ public class TestPrivateOwned extends BaseTestCase {
List<TSDetail> detailsReload = masterReload.getDetails();
// the removed bean has really been removed
Assert.assertTrue(detailsReload.size() == 1);
assertTrue(detailsReload.size() == 1);
TSMaster master3 = Ebean.find(TSMaster.class, m0.getId());
List<TSDetail> details3 = master3.getDetails();
details3.clear();
}
@Test
public void insertThenUpdate() {
TSMaster m0 = new TSMaster();
m0.setName("m2");
m0.addDetail(new TSDetail("m2 detail 1"));
m0.addDetail(new TSDetail("m2 detail 2"));
Ebean.save(m0);
assertThat(m0.getDetails()).hasSize(2);
LoggedSqlCollector.start();
m0.getDetails().remove(0);
Ebean.save(m0);
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from t_detail_with_other_namexxxyy where id=?");
TSMaster masterReload = Ebean.find(TSMaster.class, m0.getId());
assertThat(masterReload.getDetails()).hasSize(1);
LoggedSqlCollector.start();
Ebean.save(m0);
loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(0);
}
}
@@ -5,6 +5,8 @@ import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Contact;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
@@ -49,4 +51,29 @@ public class TestDeleteByQuery extends BaseTestCase {
assertThat(list).isEmpty();
}
@Test
public void testCommit() {
EbeanServer server = Ebean.getDefaultServer();
if (server.getName().equals("mysql")) {
// MySql does not the sub query selecting from the delete table
return;
}
ResetBasicData.reset();
List<Customer> all = Customer.find.all();
Contact contact = new Contact();
contact.setFirstName("DelByQueryFirstName");
contact.setLastName("deleteMe");
contact.setCustomer(all.get(0));
Ebean.save(contact);
Ebean.find(Contact.class).select("id").where().eq("firstName", "DelByQueryFirstName").delete();
Contact contactFind = Ebean.find(Contact.class, contact.getId());
assertThat(contactFind).isNull();
}
}
@@ -1,10 +1,5 @@
package com.avaje.tests.inheritance;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.bean.BeanCollection.ModifyListenMode;
@@ -13,6 +8,11 @@ import com.avaje.tests.model.basic.Animal;
import com.avaje.tests.model.basic.AnimalShelter;
import com.avaje.tests.model.basic.Cat;
import com.avaje.tests.model.basic.Dog;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertNotNull;
public class TestInheritanceOnMany extends BaseTestCase {
@@ -40,8 +40,9 @@ public class TestInheritanceOnMany extends BaseTestCase {
BeanList<?> beanList = (BeanList<?>)animals;
ModifyListenMode modifyListenMode = beanList.getModifyListenMode();
Assert.assertNotNull(modifyListenMode);
assertNotNull(modifyListenMode);
assertNotNull(Ebean.find(Animal.class).findList());
}
}
@@ -1,11 +1,15 @@
package com.avaje.tests.iud;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.info.InfoCompany;
import com.avaje.tests.model.info.InfoCustomer;
import org.junit.Assert;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TestInfoOneToOne extends BaseTestCase {
@Test
@@ -20,13 +24,17 @@ public class TestInfoOneToOne extends BaseTestCase {
customer.save();
// assert JsonIgnore working as expected
String asJson = Ebean.json().toJson(company);
assertThat(asJson).doesNotContain("contacts");
// assert both are inserted
Assert.assertNotNull(customer.getId());
Assert.assertNotNull(company.getId());
assertNotNull(customer.getId());
assertNotNull(company.getId());
// both can be fetched
Assert.assertNotNull(InfoCustomer.find.byId(customer.getId()));
Assert.assertNotNull(InfoCompany.find.byId(company.getId()));
assertNotNull(InfoCustomer.find.byId(customer.getId()));
assertNotNull(InfoCompany.find.byId(company.getId()));
// just update the customer
@@ -47,8 +55,8 @@ public class TestInfoOneToOne extends BaseTestCase {
// delete both customer and company
fetchedCustomer.delete();
Assert.assertNull(InfoCustomer.find.byId(customer.getId()));
Assert.assertNull(InfoCompany.find.byId(company.getId()));
assertNull(InfoCustomer.find.byId(customer.getId()));
assertNull(InfoCompany.find.byId(company.getId()));
}
@@ -0,0 +1,52 @@
package com.avaje.tests.m2m;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlRow;
import com.avaje.tests.model.basic.MnocRole;
import com.avaje.tests.model.basic.MnocUser;
import com.avaje.tests.model.m2m.MnyA;
import com.avaje.tests.model.m2m.MnyB;
import com.avaje.tests.model.m2m.MnyC;
import org.junit.Assert;
import org.junit.Test;
import javax.persistence.PersistenceException;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class TestM2MDeleteCascadeFromParent extends BaseTestCase {
@Test
public void test() {
MnyC c0 = new MnyC();
c0.setName("c0");
c0.save();
MnyA a0 = new MnyA();
a0.setName("a0");
a0.save();
MnyB b0 = new MnyB();
b0.setName("b0");
b0.setA(a0);
b0.save();
c0.getBs().add(b0);
Ebean.saveAssociation(c0, "bs");
SqlQuery sqlQuery = Ebean.createSqlQuery("select count(*) as count from mny_b_mny_c");
SqlRow unique = sqlQuery.findUnique();
assertEquals(Long.valueOf(1), unique.getLong("count"));
//Ebean.deleteManyToManyAssociations(b0, "cs");
a0.delete();
}
}
@@ -0,0 +1,31 @@
package com.avaje.tests.m2m;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.m2m.MailBox;
import com.avaje.tests.model.m2m.MailUser;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestM2MMultipleLists extends BaseTestCase {
@Test
public void test() {
MailUser u = new MailUser();
u.setName("mailUser1");
MailBox m1 = new MailBox();
m1.setName("mailBox1");
u.getInbox().add(m1);
Ebean.save(u);
u = Ebean.find(MailUser.class, u.getId());
assertEquals(1, u.getInbox().size());
assertEquals(0, u.getOutbox().size());
}
}
@@ -0,0 +1,29 @@
package com.avaje.tests.m2m;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.m2m.MnyTopic;
import org.junit.Test;
/**
* Added to test DDL generation for ManyToMany related back to itself.
*/
public class TestM2MSelfRelationship extends BaseTestCase {
@Test
public void test() {
Ebean.getDefaultServer();
// Create 2 roles r0 and r1
MnyTopic r0 = new MnyTopic("r0");
MnyTopic r1 = new MnyTopic("r1");
// Save r1 and r2
Ebean.save(r0);
Ebean.save(r1);
r0.getSubTopics().add(r1);
Ebean.save(r0);
}
}
@@ -68,4 +68,28 @@ public class TestM2mDeleteObject extends BaseTestCase {
Assert.assertEquals(2, permissionList2.size());
}
@Test
public void test_deleteCascading() {
Permission p1 = new Permission();
Permission p2 = new Permission();
p1.setName("p1");
p2.setName("p2");
Ebean.save(p1);
Ebean.save(p2);
Role role1 = new Role();
role1.setName("role");
Set<Permission> permissions = new HashSet<Permission>();
permissions.add(p2);
role1.setPermissions(permissions);
Ebean.save(role1);
// only deletes as not associated
Ebean.delete(p1);
// delete cascades
Ebean.delete(role1);
}
}
@@ -1,5 +1,6 @@
package com.avaje.tests.model.basic;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.Id;
@@ -21,6 +22,9 @@ public abstract class Animal {
@Version
Long version;
@Column(name="species", insertable = false, updatable = false, nullable = false)
String species;
@ManyToOne
AnimalShelter shelter;
@@ -40,6 +44,14 @@ public abstract class Animal {
this.version = version;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public AnimalShelter getShelter() {
return shelter;
}
@@ -2,7 +2,7 @@ package com.avaje.tests.model.basic;
import com.avaje.ebean.annotation.ChangeLog;
import com.avaje.ebean.annotation.ChangeLogInsertMode;
import com.avaje.ebean.annotation.EnumValue;
import com.avaje.ebean.annotation.DbEnumValue;
import com.avaje.ebean.annotation.JsonIgnore;
import com.avaje.ebean.annotation.Where;
import com.avaje.tests.model.basic.finder.CustomerFinder;
@@ -36,14 +36,19 @@ public class Customer extends BasicDomain {
* EnumValue is an Ebean specific mapping for enums.
*/
public enum Status {
@EnumValue("N")
NEW,
NEW("N"),
ACTIVE("A"),
INACTIVE("I");
@EnumValue("A")
ACTIVE,
String dbValue;
Status(String dbValue) {
this.dbValue = dbValue;
}
@EnumValue("I")
INACTIVE
@DbEnumValue
public String getValue() {
return dbValue;
}
}
@Transient
@@ -0,0 +1,81 @@
package com.avaje.tests.model.basic;
import com.avaje.ebean.annotation.DbEnumType;
import com.avaje.ebean.annotation.DbEnumValue;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.sql.Timestamp;
@Entity
@Table(name = "e_basic_eni")
public class EBasicEnumInt {
public enum Status {
NEW("1"),
ACTIVE("2"),
INACTIVE("3");
String value;
Status(String value) {
this.value = value;
}
@DbEnumValue(storage = DbEnumType.INTEGER)
public String getValue() {
return value;
}
}
@Id
Integer id;
Status status;
String name;
String description;
Timestamp someDate;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Timestamp getSomeDate() {
return someDate;
}
public void setSomeDate(Timestamp someDate) {
this.someDate = someDate;
}
}
@@ -1,14 +1,14 @@
package com.avaje.tests.model.basic;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.TxRunnable;
import com.avaje.tests.model.basic.Order.Status;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
public class ResetBasicData {
private static boolean runOnce;
@@ -135,6 +135,7 @@ public class ResetBasicData {
createOrder2(cust2);
createOrder3(cust1);
createOrder4(cust1);
createOrder5(cust2);
}
});
}
@@ -313,4 +314,13 @@ public class ResetBasicData {
Ebean.save(order);
}
private void createOrder5(Customer customer) {
Order order = new Order();
order.setCustomer(customer);
order.addShipment(new OrderShipment());
Ebean.save(order);
}
}
@@ -1,6 +1,7 @@
package com.avaje.tests.model.info;
import com.avaje.ebean.Model;
import com.avaje.ebean.annotation.JsonIgnore;
import javax.persistence.*;
import java.util.ArrayList;
@@ -19,6 +20,7 @@ public class InfoCompany extends Model {
String name;
@JsonIgnore
@OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
List<InfoContact> contacts = new ArrayList<InfoContact>();
@@ -2,7 +2,11 @@ package com.avaje.tests.model.info;
import com.avaje.ebean.Model;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Version;
@Entity
public class InfoCustomer extends Model {
@@ -0,0 +1,42 @@
package com.avaje.tests.model.m2m;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
@Entity
public class MailBox {
@Id
Long id;
@Version
Long version;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,70 @@
package com.avaje.tests.model.m2m;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Version;
import java.util.List;
@Entity
public class MailUser {
@Id
Long id;
@Version
Long version;
String name;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "mail_user_inbox")
List<MailBox> inbox;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "mail_user_outbox")
List<MailBox> outbox;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<MailBox> getInbox() {
return inbox;
}
public void setInbox(List<MailBox> inbox) {
this.inbox = inbox;
}
public List<MailBox> getOutbox() {
return outbox;
}
public void setOutbox(List<MailBox> outbox) {
this.outbox = outbox;
}
}
@@ -0,0 +1,34 @@
package com.avaje.tests.model.m2m;
import com.avaje.tests.model.BaseModel;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class MnyA extends BaseModel {
String name;
@OneToMany(mappedBy = "a", cascade = CascadeType.REMOVE)
List<MnyB> bs;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<MnyB> getBs() {
return bs;
}
public void setBs(List<MnyB> bs) {
this.bs = bs;
}
}
@@ -0,0 +1,45 @@
package com.avaje.tests.model.m2m;
import com.avaje.tests.model.BaseModel;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import java.util.List;
@Entity
public class MnyB extends BaseModel {
String name;
@ManyToOne
MnyA a;
@ManyToMany(cascade = CascadeType.REMOVE)
List<MnyC> cs;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public MnyA getA() {
return a;
}
public void setA(MnyA a) {
this.a = a;
}
public List<MnyC> getCs() {
return cs;
}
public void setCs(List<MnyC> cs) {
this.cs = cs;
}
}
@@ -0,0 +1,32 @@
package com.avaje.tests.model.m2m;
import com.avaje.tests.model.BaseModel;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import java.util.List;
@Entity
public class MnyC extends BaseModel {
String name;
@ManyToMany(mappedBy = "cs")
List<MnyB> bs;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<MnyB> getBs() {
return bs;
}
public void setBs(List<MnyB> bs) {
this.bs = bs;
}
}
@@ -0,0 +1,67 @@
package com.avaje.tests.model.m2m;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Version;
import java.util.List;
@Entity
public class MnyTopic {
@Id
Long id;
String name;
@Version
Long version;
@ManyToMany
@JoinTable(name = "subtopics",
joinColumns = @JoinColumn(name = "topic", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "subtopic", referencedColumnName = "id"))
List<MnyTopic> subTopics;
public MnyTopic() {
}
public MnyTopic(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public List<MnyTopic> getSubTopics() {
return subTopics;
}
public void setSubTopics(List<MnyTopic> subTopics) {
this.subTopics = subTopics;
}
}
@@ -0,0 +1,45 @@
package com.avaje.tests.query;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.ebean.QueryIterator;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestOneToManyCorrectGrouping extends BaseTestCase {
public static final int EXPECTED_ITERATIONS = 2;
@Test
public void test() {
ResetBasicData.reset();
Query<Customer> customerQuery = Ebean.find(Customer.class)
.fetch("orders")
.where().le("id", 2)
.query();
QueryIterator<Customer> customerQueryIterator = customerQuery.findIterate();
try {
int count = 0;
while (customerQueryIterator.hasNext()) {
Customer customer = customerQueryIterator.next();
System.out.println(String.format("Customer id %s", customer.getId()));
for (Order order : customer.getOrders()) {
System.out.println(String.format("--> Order id %s", order.getId()));
}
count++;
}
assertEquals(EXPECTED_ITERATIONS, count);
} finally {
customerQueryIterator.close();
}
}
}
@@ -110,4 +110,27 @@ public class TestQueryFindPagedList extends BaseTestCase {
Ebean.endTransaction();
}
}
@Test
public void test_usingAlias() throws ExecutionException, InterruptedException {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class)
.alias("b")
.where().raw("b.id > 0")
.findPagedList(0, 6);
LoggedSqlCollector.start();
pagedList.getTotalRowCount();
pagedList.getList();
List<String> loggedSql = LoggedSqlCollector.stop();
assertEquals(2, loggedSql.size());
assertThat(loggedSql.get(0)).contains("select count(*) from o_order b where b.id > 0");
assertThat(loggedSql.get(1)).contains("select b.id c0, b.status c1, b.order_date c2");
}
}
@@ -1,11 +1,13 @@
package com.avaje.tests.update;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlRow;
import com.avaje.tests.model.basic.Customer;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestUpdatePartial extends BaseTestCase {
@@ -18,6 +20,7 @@ public class TestUpdatePartial extends BaseTestCase {
c.setSmallnote("a note");
Ebean.save(c);
checkDbStatusValue(c.getId(), "A");
Customer c2 = Ebean.find(Customer.class)
.select("status, smallnote")
@@ -28,6 +31,7 @@ public class TestUpdatePartial extends BaseTestCase {
c2.setSmallnote("2nd note");
Ebean.save(c2);
checkDbStatusValue(c.getId(), "I");
Customer c3 = Ebean.find(Customer.class)
.select("status")
@@ -38,7 +42,15 @@ public class TestUpdatePartial extends BaseTestCase {
c3.setSmallnote("3rd note");
Ebean.save(c3);
checkDbStatusValue(c.getId(), "N");
}
private void checkDbStatusValue(Integer custId, String dbStatus) {
SqlQuery sqlQuery = Ebean.createSqlQuery("select id, status from o_customer where id = ?");
sqlQuery.setParameter(1, custId);
SqlRow sqlRow = sqlQuery.findUnique();
String status = sqlRow.getString("status");
assertEquals(dbStatus, status);
}
/**
@@ -57,6 +69,6 @@ public class TestUpdatePartial extends BaseTestCase {
Ebean.save(customerWithoutChanges);
// assert
Assert.assertEquals(customer.getUpdtime().getTime(), customerWithoutChanges.getUpdtime().getTime());
assertEquals(customer.getUpdtime().getTime(), customerWithoutChanges.getUpdtime().getTime());
}
}
+1
View File
@@ -0,0 +1 @@
datasource.h2.username=sa