diff --git a/composites/ebean-clickhouse/pom.xml b/composites/ebean-clickhouse/pom.xml index 64358f2aa7..d317a4ca40 100644 --- a/composites/ebean-clickhouse/pom.xml +++ b/composites/ebean-clickhouse/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-clickhouse @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-clickhouse - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-cockroach/pom.xml b/composites/ebean-cockroach/pom.xml index 121335dfee..557b1549a7 100644 --- a/composites/ebean-cockroach/pom.xml +++ b/composites/ebean-cockroach/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-cockroach @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-postgres - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-db2/pom.xml b/composites/ebean-db2/pom.xml index 00c83f37c3..e27713ae4f 100644 --- a/composites/ebean-db2/pom.xml +++ b/composites/ebean-db2/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-db2 @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-db2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-h2/pom.xml b/composites/ebean-h2/pom.xml index d17de79131..405dd251c0 100644 --- a/composites/ebean-h2/pom.xml +++ b/composites/ebean-h2/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-h2 @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-hana/pom.xml b/composites/ebean-hana/pom.xml index 2149af31ae..cd28d3ef8c 100644 --- a/composites/ebean-hana/pom.xml +++ b/composites/ebean-hana/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-hana @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-hana - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-mariadb/pom.xml b/composites/ebean-mariadb/pom.xml index 8e7bc9ead4..ce04760d51 100644 --- a/composites/ebean-mariadb/pom.xml +++ b/composites/ebean-mariadb/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-mariadb @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-mariadb - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-mysql/pom.xml b/composites/ebean-mysql/pom.xml index fcd9742f76..fbbdfe7254 100644 --- a/composites/ebean-mysql/pom.xml +++ b/composites/ebean-mysql/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-mysql @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-mysql - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-nuodb/pom.xml b/composites/ebean-nuodb/pom.xml index 21fea586c1..a444eca161 100644 --- a/composites/ebean-nuodb/pom.xml +++ b/composites/ebean-nuodb/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-nuodb @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-nuodb - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-oracle/pom.xml b/composites/ebean-oracle/pom.xml index 32cc7030d1..db5653860f 100644 --- a/composites/ebean-oracle/pom.xml +++ b/composites/ebean-oracle/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-oracle @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-oracle - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-postgres/pom.xml b/composites/ebean-postgres/pom.xml index 289b1e5923..64804000fb 100644 --- a/composites/ebean-postgres/pom.xml +++ b/composites/ebean-postgres/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-postgres @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-postgres - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-sqlite/pom.xml b/composites/ebean-sqlite/pom.xml index b1f7a84835..fce5a66d24 100644 --- a/composites/ebean-sqlite/pom.xml +++ b/composites/ebean-sqlite/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-sqlite @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-sqlite - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-sqlserver/pom.xml b/composites/ebean-sqlserver/pom.xml index fc18293b29..5d84642095 100644 --- a/composites/ebean-sqlserver/pom.xml +++ b/composites/ebean-sqlserver/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-sqlserver @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-sqlserver - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean-yugabyte/pom.xml b/composites/ebean-yugabyte/pom.xml index 3c45e89e88..43c6727a93 100644 --- a/composites/ebean-yugabyte/pom.xml +++ b/composites/ebean-yugabyte/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-yugabyte @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-postgres - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/ebean/pom.xml b/composites/ebean/pom.xml index f503dd58af..5a32cf148a 100644 --- a/composites/ebean/pom.xml +++ b/composites/ebean/pom.xml @@ -4,7 +4,7 @@ composites io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean (all platforms) @@ -16,13 +16,13 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -35,14 +35,18 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-all - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/composites/pom.xml b/composites/pom.xml index 491da3e5b9..f96d9992d1 100644 --- a/composites/pom.xml +++ b/composites/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT composites diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index cf4ae1bcb6..a734d9beb3 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean api diff --git a/ebean-api/src/main/java/io/ebean/DB.java b/ebean-api/src/main/java/io/ebean/DB.java index 0b7d3fa38e..b89ccd4f27 100644 --- a/ebean-api/src/main/java/io/ebean/DB.java +++ b/ebean-api/src/main/java/io/ebean/DB.java @@ -448,6 +448,19 @@ public static int saveAll(Collection beans) throws OptimisticLockException { public static int saveAll(Object... beans) throws OptimisticLockException { return getDefault().saveAll(beans); } + + /** + * This will visit all beans in the persist graph on a given object + * start. It will call the visitor for each dirty bean that would + * be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can + * use this method to implement custom validations. + * + * @param start could be a bean, a list of beans or a map of beans. + * @param visitor the visitor + */ + public static void visitSave(Object start, PersistVisitor visitor) { + getDefault().visitSave(start, visitor); + } /** * This method checks the uniqueness of a bean. I.e. if the save will work. It will return the diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java index c98f885e45..f58b3a0288 100644 --- a/ebean-api/src/main/java/io/ebean/Database.java +++ b/ebean-api/src/main/java/io/ebean/Database.java @@ -905,6 +905,17 @@ public interface Database { * Save all the beans. */ int saveAll(Object... beans) throws OptimisticLockException; + + /** + * This will visit all beans in the persist graph on a given object + * start. It will call the visitor for each dirty bean that would + * be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can + * use this method to implement custom validations. + * + * @param start could be a bean, a list of beans or a map of beans. + * @param visitor the visitor + */ + void visitSave(Object start, PersistVisitor visitor); /** * Delete the bean. @@ -1613,4 +1624,14 @@ public interface Database { */ void truncate(Class... tables); + /** + * Starts the database and runs db-migration or ddl. Normally the server is + * started automatically when the teantMode supports ddl-generation and start is + * not skipped by server config. + * + * In a multi tenancy environment, you may have call start multiple times for + * each tenant to initialize the database. + */ + void start(); + } diff --git a/ebean-api/src/main/java/io/ebean/PersistVisitor.java b/ebean-api/src/main/java/io/ebean/PersistVisitor.java new file mode 100644 index 0000000000..b5f1398d09 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/PersistVisitor.java @@ -0,0 +1,29 @@ +package io.ebean; + +import java.util.Collection; +import java.util.Map; + +import io.ebean.bean.EntityBean; +import io.ebean.plugin.Property; + +@FunctionalInterface +public interface PersistVisitor { + + PersistVisitor visitBean(EntityBean bean); + + default PersistVisitor visitProperty(Property prop) { + return this; + }; + + default PersistVisitor visitCollection(Collection collection) { + return this; + }; + + default PersistVisitor visitMap(Map map) { + return this; + }; + + default void visitEnd() { + } + +} diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/EntityImplements.java b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityImplements.java new file mode 100644 index 0000000000..ee312bb72c --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityImplements.java @@ -0,0 +1,21 @@ +package io.ebean.annotation.ext; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation to define a list of interface for which this entity is target for. + * @author Roland Praml, FOCONIS AG + */ +@Documented +@Target({ FIELD, TYPE }) +@Retention(RUNTIME) +public @interface EntityImplements { + + Class[] value(); + +} diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/EntityOverride.java b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityOverride.java new file mode 100644 index 0000000000..2c166812a3 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityOverride.java @@ -0,0 +1,24 @@ +package io.ebean.annotation.ext; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation to define that an entity overrides the parent entity. + * @author Roland Praml, FOCONIS AG + */ +@Documented +@Target({ FIELD, TYPE }) +@Retention(RUNTIME) +public @interface EntityOverride { + + /** + * The priority of the statement. Lower priority wins. + */ + int priority() default 0; + +} diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/package.html b/ebean-api/src/main/java/io/ebean/annotation/ext/package.html new file mode 100644 index 0000000000..9505c002ca --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/annotation/ext/package.html @@ -0,0 +1,8 @@ + + + Ebean Annotations + + +This classes will be moved later to the ebean-annotation module. + + diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java index d47b5fbbf3..a9ad4599e8 100644 --- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java @@ -16,6 +16,7 @@ import io.ebean.event.changelog.ChangeLogRegister; import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; +import io.ebean.plugin.CustomDeployParser; import io.ebean.util.StringHelper; import javax.persistence.EnumType; @@ -301,6 +302,11 @@ public class DatabaseConfig { private ExternalTransactionManager externalTransactionManager; private boolean skipDataSourceCheck; + + /** + * Skip server start (i.e. run DDL/migration on creation). + */ + private boolean skipStart; /** * The data source (if programmatically provided). @@ -398,6 +404,8 @@ public class DatabaseConfig { */ private Clock clock = Clock.systemUTC(); + private TempFileProvider tempFileProvider = new WeakRefTempFileProvider(); + private List idGenerators = new ArrayList<>(); private List findControllers = new ArrayList<>(); private List persistControllers = new ArrayList<>(); @@ -407,6 +415,7 @@ public class DatabaseConfig { private List queryAdapters = new ArrayList<>(); private final List bulkTableEventListeners = new ArrayList<>(); private final List configStartupListeners = new ArrayList<>(); + private final List customDeployParsers = new ArrayList<>(); /** * By default inserts are included in the change log. @@ -566,6 +575,14 @@ public void setClock(final Clock clock) { this.clock = clock; } + public TempFileProvider getTempFileProvider() { + return tempFileProvider; + } + + public void setTempFileProvider(final TempFileProvider tempFileProvider) { + this.tempFileProvider = tempFileProvider; + } + /** * Return the slow query time in millis. */ @@ -1671,6 +1688,20 @@ public boolean skipDataSourceCheck() { public void setSkipDataSourceCheck(boolean skipDataSourceCheck) { this.skipDataSourceCheck = skipDataSourceCheck; } + + /** + * Return true if the server start should be skipped. + */ + public boolean skipStart() { + return skipStart; + } + + /** + * Set to true to skip the server start. + */ + public void setSkipStart(boolean skipStart) { + this.skipStart = skipStart; + } /** * Return the DataSource. @@ -2687,6 +2718,17 @@ public List getServerConfigStartupListeners() { return configStartupListeners; } + /** + * Add a CustomDeployParser. + */ + public void addCustomDeployParser(CustomDeployParser customDeployParser) { + customDeployParsers.add(customDeployParser); + } + + public List getCustomDeployParsers() { + return customDeployParsers; + } + /** * Register all the BeanPersistListener instances. *

@@ -2937,6 +2979,7 @@ protected void loadSettings(PropertiesWrapper p) { jsonMutationDetection = p.getEnum(MutationDetection.class, "jsonMutationDetection", jsonMutationDetection); skipDataSourceCheck = p.getBoolean("skipDataSourceCheck", skipDataSourceCheck); + skipStart = p.getBoolean("skipStart", skipStart); runMigration = p.getBoolean("migration.run", runMigration); ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate); ddlRun = p.getBoolean("ddl.run", ddlRun); diff --git a/ebean-api/src/main/java/io/ebean/config/DeleteOnShutdownTempFileProvider.java b/ebean-api/src/main/java/io/ebean/config/DeleteOnShutdownTempFileProvider.java new file mode 100644 index 0000000000..ad257c98ee --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/DeleteOnShutdownTempFileProvider.java @@ -0,0 +1,68 @@ +package io.ebean.config; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TempFileProvider implementation, which deletes all temp files on shutdown. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class DeleteOnShutdownTempFileProvider implements TempFileProvider { + + private static final Logger logger = LoggerFactory.getLogger(DeleteOnShutdownTempFileProvider.class); + + List tempFiles = new ArrayList<>(); + private final String prefix; + private final String suffix; + private final File directory; + + /** + * Creates the TempFileProvider with default prefix "db-". + */ + public DeleteOnShutdownTempFileProvider() { + this("db-", null, null); + } + + /** + * Creates the TempFileProvider. + */ + public DeleteOnShutdownTempFileProvider(String prefix, String suffix, File directory) { + this.prefix = prefix; + this.suffix = suffix; + this.directory = directory; + } + + @Override + public File createTempFile() throws IOException { + File file = File.createTempFile(prefix, suffix, directory); + synchronized (tempFiles) { + tempFiles.add(file.getAbsolutePath()); + } + return file; + } + + /** + * Deletes all created files on shutdown. + */ + @Override + public void shutdown() { + synchronized (tempFiles) { + for (String path : tempFiles) { + if (new File(path).delete()) { + logger.trace("deleted {}", path); + } else { + logger.warn("could not delete {}", path); + } + } + tempFiles.clear(); + } + } + +} diff --git a/ebean-api/src/main/java/io/ebean/config/TempFileProvider.java b/ebean-api/src/main/java/io/ebean/config/TempFileProvider.java new file mode 100644 index 0000000000..4658b46c28 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/TempFileProvider.java @@ -0,0 +1,23 @@ +package io.ebean.config; + +import java.io.File; +import java.io.IOException; + +/** + * Creates a temp file for the ScalarTypeFile datatype. + * + * @author Roland Praml, FOCONIS AG + * + */ +public interface TempFileProvider { + + /** + * Creates a tempFile. + */ + File createTempFile() throws IOException; + + /** + * Shutdown the tempFileProvider. + */ + void shutdown(); +} diff --git a/ebean-api/src/main/java/io/ebean/config/WeakRefTempFileProvider.java b/ebean-api/src/main/java/io/ebean/config/WeakRefTempFileProvider.java new file mode 100644 index 0000000000..aa3ae82905 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/WeakRefTempFileProvider.java @@ -0,0 +1,145 @@ +package io.ebean.config; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * WeakRefTempFileProvider will delete the tempFile if all references to the returned File + * object are collected by the garbage collection. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class WeakRefTempFileProvider implements TempFileProvider { + + private static final Logger logger = LoggerFactory.getLogger(WeakRefTempFileProvider.class); + + private final ReferenceQueue tempFiles = new ReferenceQueue<>(); + + private WeakFileReference root; + + private final String prefix; + private final String suffix; + private final File directory; + + /** + * We hold a linkedList of weak references. So we can remove stale files in O(1) + * + * @author Roland Praml, FOCONIS AG + */ + private static class WeakFileReference extends WeakReference { + + String path; + WeakFileReference prev; + WeakFileReference next; + + WeakFileReference(File referent, ReferenceQueue q) { + super(referent, q); + path = referent.getAbsolutePath(); + } + + boolean delete(boolean shutdown) { + File file = new File(path); + if (!file.exists()) { + logger.trace("already deleted {}", path); + return true; + } else if (file.delete()) { + logger.trace("deleted {}", path); + return true; + } else { + if (shutdown) { + logger.warn("could not delete {}", path); + } else { + logger.info("could not delete {} - will delete on shutdown", path); + } + return false; + } + } + } + + + /** + * Creates the TempFileProvider with default prefix "db-". + */ + public WeakRefTempFileProvider() { + this("db-", null, null); + } + + /** + * Creates the TempFileProvider. + */ + public WeakRefTempFileProvider(String prefix, String suffix, File directory) { + this.prefix = prefix; + this.suffix = suffix; + this.directory = directory; + } + + @Override + public File createTempFile() throws IOException { + File tempFile = File.createTempFile(prefix, suffix, directory); + logger.trace("createTempFile: {}", tempFile); + synchronized (this) { + add(new WeakFileReference(tempFile, tempFiles)); + } + return tempFile; + } + + /** + * Will delete stale files. + * This is public to use in tests. + */ + public void deleteStaleTempFiles() { + synchronized (this) { + deleteStaleTempFilesInternal(); + } + } + + private void deleteStaleTempFilesInternal() { + WeakFileReference ref; + while ((ref = (WeakFileReference) tempFiles.poll()) != null) { + if (ref.delete(false)) { + remove(ref); // remove from linkedList only, if delete was successful. + } + } + } + + private void add(WeakFileReference ref) { + deleteStaleTempFilesInternal(); + + if (root == null) { + root = ref; + } else { + ref.next = root; + root.prev = ref; + root = ref; + } + } + + private void remove(WeakFileReference ref) { + if (ref.next != null) { + ref.next.prev = ref.prev; + } + if (ref.prev != null) { + ref.prev.next = ref.next; + } else { + root = ref.next; + } + } + + /** + * Deletes all created files on shutdown. + */ + @Override + public void shutdown() { + while (root != null) { + root.delete(true); + root = root.next; + } + } + +} diff --git a/ebean-api/src/main/java/io/ebean/plugin/CustomDeployParser.java b/ebean-api/src/main/java/io/ebean/plugin/CustomDeployParser.java new file mode 100644 index 0000000000..4c854f2f1e --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/plugin/CustomDeployParser.java @@ -0,0 +1,15 @@ +package io.ebean.plugin; + +import io.ebean.config.dbplatform.DatabasePlatform; + +/** + * Fired after all beans are parsed. You may implement own parsers to handle custom annotations. + * (See test case for example) + * + * @author Roland Praml, FOCONIS AG + */ +@FunctionalInterface +public interface CustomDeployParser { + + void parse(DeployBeanDescriptorMeta descriptor, DatabasePlatform databasePlatform); +} diff --git a/ebean-api/src/main/java/io/ebean/plugin/DeployBeanDescriptorMeta.java b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanDescriptorMeta.java new file mode 100644 index 0000000000..4347a5057c --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanDescriptorMeta.java @@ -0,0 +1,36 @@ +package io.ebean.plugin; + +import java.util.Collection; + +/** + * General deployment information. This is used in {@link CustomDeployParser}. + * + * @author Roland Praml, FOCONIS AG + */ +public interface DeployBeanDescriptorMeta { + + /** + * Return a collection of all BeanProperty deployment information. + */ + public Collection propertiesAll(); + + /** + * Get a BeanProperty by its name. + */ + public DeployBeanPropertyMeta getBeanProperty(String secondaryBeanName); + + /** + * Return the DeployBeanDescriptorMeta for the given bean class. + */ + public DeployBeanDescriptorMeta getDeployBeanDescriptorMeta(Class propertyType); + + /** + * Returns the discriminator column, if any. + * @return + */ + public String getDiscriminatorColumn(); + + public String getBaseTable(); + + DeployBeanPropertyMeta idProperty(); +} diff --git a/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyAssocMeta.java b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyAssocMeta.java new file mode 100644 index 0000000000..d7d1d637a3 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyAssocMeta.java @@ -0,0 +1,23 @@ +package io.ebean.plugin; + +public interface DeployBeanPropertyAssocMeta extends DeployBeanPropertyMeta { + + /** + * Return the mappedBy deployment attribute. + *

+ * This is the name of the property in the 'detail' bean that maps back to + * this 'master' bean. + *

+ */ + String getMappedBy(); + + /** + * Return the base table for this association. + *

+ * This has the table name which is used to determine the relationship for + * this association. + *

+ */ + String getBaseTable(); + +} diff --git a/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyMeta.java b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyMeta.java new file mode 100644 index 0000000000..4ba2d3e2a1 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyMeta.java @@ -0,0 +1,37 @@ +package io.ebean.plugin; + +import java.lang.reflect.Field; + +public interface DeployBeanPropertyMeta { + + /** + * Return the name of the property. + */ + String getName(); + + /** + * The database column name this is mapped to. + */ + String getDbColumn(); + + /** + * Return the bean Field associated with this property. + */ + Field getField(); + + /** + * The property is based on a formula. + */ + void setSqlFormula(String sqlSelect, String sqlJoin); + + /** + * Return the bean type. + */ + Class getOwningType(); + + /** + * Return the property type. + */ + Class getPropertyType(); + +} diff --git a/ebean-api/src/main/java/io/ebean/plugin/Plugin.java b/ebean-api/src/main/java/io/ebean/plugin/Plugin.java index 359adce38b..3314f9353c 100644 --- a/ebean-api/src/main/java/io/ebean/plugin/Plugin.java +++ b/ebean-api/src/main/java/io/ebean/plugin/Plugin.java @@ -14,6 +14,13 @@ public interface Plugin { * Called just before the server starts indicating if it is coming up in online mode. */ void online(boolean online); + + /** + * Called when server.start is invoked. This may be executed for each tenant. + */ + default void start() { + + }; /** * Called when the server is shutting down. diff --git a/ebean-api/src/main/java/module-info.java b/ebean-api/src/main/java/module-info.java index 7c8453cea6..b7fda66b0a 100644 --- a/ebean-api/src/main/java/module-info.java +++ b/ebean-api/src/main/java/module-info.java @@ -41,5 +41,5 @@ exports io.ebean.text.json; exports io.ebean.text.csv; exports io.ebean.util; - + exports io.ebean.annotation.ext; } diff --git a/ebean-api/src/test/java/io/ebean/TestWeakRefTempFileProvider.java b/ebean-api/src/test/java/io/ebean/TestWeakRefTempFileProvider.java new file mode 100644 index 0000000000..dc69cc6876 --- /dev/null +++ b/ebean-api/src/test/java/io/ebean/TestWeakRefTempFileProvider.java @@ -0,0 +1,171 @@ +package io.ebean; + + +import io.ebean.config.WeakRefTempFileProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.nio.channels.FileLock; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the WeakRefTempFileProvider. (Note: this test relies on an aggressive garbage collection. + * if GC implementation will change, the test may fail) + * + * @author Roland Praml, FOCONIS AG + */ +public class TestWeakRefTempFileProvider { + + WeakRefTempFileProvider prov = new WeakRefTempFileProvider(); + + @AfterEach + public void shutdown() { + prov.shutdown(); + } + + /** + * Run the garbage collection and delete stale files. + */ + private void gc() throws InterruptedException { + System.gc(); + Thread.sleep(100); + prov.deleteStaleTempFiles(); + } + + @Test + public void testStaleEntries() throws Exception { + File tempFile = prov.createTempFile(); + String fileName = tempFile.getAbsolutePath(); + + gc(); + + assertThat(new File(fileName)).exists(); + + tempFile = null; // give up reference + + gc(); + + assertThat(new File(fileName)).doesNotExist(); + + + } + + @Test + public void testLinkedListForward() throws Exception { + File tempFile1 = prov.createTempFile(); + String fileName1 = tempFile1.getAbsolutePath(); + File tempFile2 = prov.createTempFile(); + String fileName2 = tempFile2.getAbsolutePath(); + File tempFile3 = prov.createTempFile(); + String fileName3 = tempFile3.getAbsolutePath(); + + assertThat(new File(fileName1)).exists(); + assertThat(new File(fileName2)).exists(); + assertThat(new File(fileName3)).exists(); + + gc(); + + // give up first ref + tempFile1 = null; + + gc(); + + assertThat(new File(fileName1)).doesNotExist(); + assertThat(new File(fileName2)).exists(); + assertThat(new File(fileName3)).exists(); + + // give up second ref + tempFile2 = null; + + gc(); + + assertThat(new File(fileName1)).doesNotExist(); + assertThat(new File(fileName2)).doesNotExist(); + assertThat(new File(fileName3)).exists(); + + // give up third ref + tempFile3 = null; + + gc(); + + assertThat(new File(fileName1)).doesNotExist(); + assertThat(new File(fileName2)).doesNotExist(); + assertThat(new File(fileName3)).doesNotExist(); + + } + + + @Test + public void testLinkedListReverse() throws Exception { + File tempFile1 = prov.createTempFile(); + String fileName1 = tempFile1.getAbsolutePath(); + File tempFile2 = prov.createTempFile(); + String fileName2 = tempFile2.getAbsolutePath(); + File tempFile3 = prov.createTempFile(); + String fileName3 = tempFile3.getAbsolutePath(); + + assertThat(new File(fileName1)).exists(); + assertThat(new File(fileName2)).exists(); + assertThat(new File(fileName3)).exists(); + + gc(); + + // give up third ref + tempFile3 = null; + + gc(); + + assertThat(new File(fileName1)).exists(); + assertThat(new File(fileName2)).exists(); + assertThat(new File(fileName3)).doesNotExist(); + + // give up second ref + tempFile2 = null; + + gc(); + + assertThat(new File(fileName1)).exists(); + assertThat(new File(fileName2)).doesNotExist(); + assertThat(new File(fileName3)).doesNotExist(); + + // give up first ref + tempFile1 = null; + + gc(); + + assertThat(new File(fileName1)).doesNotExist(); + assertThat(new File(fileName2)).doesNotExist(); + assertThat(new File(fileName3)).doesNotExist(); + + } + + @Test + @Disabled("Runs on Windows only") + public void testFileLocked() throws Exception { + File tempFile = prov.createTempFile(); + String fileName = tempFile.getAbsolutePath(); + + try (FileOutputStream os = new FileOutputStream(fileName)) { + FileLock lock = os.getChannel().lock(); + try { + os.write(42); + + tempFile = null; + gc(); + } finally { + lock.release(); + } + + } + + assertThat(new File(fileName)).exists(); + + prov.shutdown(); + + assertThat(new File(fileName)).doesNotExist(); + } +} diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml index d87da836bd..85c66a71a1 100644 --- a/ebean-autotune/pom.xml +++ b/ebean-autotune/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -13,8 +13,8 @@ - scm:git:git@github.com:ebean-orm/ebean.git - HEAD + scm:git:git@github.com:FOCONIS/ebean.git + ebean-parent-13.6.4-FOC1 ebean autotune @@ -26,7 +26,7 @@ io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided @@ -55,7 +55,7 @@ io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index ebdcea3a70..a0281a6530 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean bom @@ -71,88 +71,94 @@ io.ebean ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core-type - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + + io.ebean + ebean-dbmigration-runner + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-ddl-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-externalmapping-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-externalmapping-xml - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-autotune - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean querybean-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided io.ebean kotlin-querybean-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-postgis - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-redis - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml index f7da6a2568..5bbe71999b 100644 --- a/ebean-core-type/pom.xml +++ b/ebean-core-type/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-core-type @@ -16,7 +16,7 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 20a71700a1..5bd0d8faad 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -3,7 +3,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-core @@ -14,8 +14,8 @@ https://ebean.io/ - scm:git:git@github.com:ebean-orm/ebean.git - HEAD + scm:git:git@github.com:FOCONIS/ebean.git + ebean-parent-13.6.4-FOC1 @@ -32,28 +32,22 @@ 7.1 - - io.ebean - ebean-migration-auto - ${ebean-migration-auto.version} - - io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core-type - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-externalmapping-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -144,21 +138,21 @@ io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-platform-postgres - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-platform-sqlserver - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java deleted file mode 100644 index b3c4858139..0000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.ebeaninternal.api; - -/** - * DDL generate and run for drop all/create all. - */ -public interface SpiDdlGenerator { - - /** - * Generate and run the DDL for drop-all and create-all scripts. - *

- * Run based on on property settings for ebean.ddl.generate and ebean.ddl.run etc. - */ - void execute(boolean online); - -} diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGeneratorProvider.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGeneratorProvider.java deleted file mode 100644 index d162679573..0000000000 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGeneratorProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.ebeaninternal.api; - -/** - * Provides the DDL Generator for create-all/drop-all. - */ -public interface SpiDdlGeneratorProvider { - - /** - * Provide the DDL generator. - */ - SpiDdlGenerator generator(SpiEbeanServer server); - -} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java index 70c0e0cf71..fc84373b02 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java @@ -109,8 +109,12 @@ public SpiEbeanServer createServer(DatabaseConfig config) { InternalConfiguration c = new InternalConfiguration(online, clusterManager, executor, config, bootupClasses); DefaultServer server = new DefaultServer(c, c.cacheManager()); // generate and run DDL if required plus other plugins + if (!DbOffline.isGenerateMigration()) { - startServer(online, server); + initServer(online, server); + if (online && config.getTenantMode().isDdlEnabled() && !config.skipStart()) { + server.start(); + } } DbOffline.reset(); log.info("Started database[{}] platform[{}] in {}ms", config.getName(), config.getDatabasePlatform().getPlatform(), System.currentTimeMillis() - start); @@ -146,7 +150,7 @@ private void checkMissingModulePathProvides() { } } - private void startServer(boolean online, DefaultServer server) { + private void initServer(boolean online, DefaultServer server) { server.executePlugins(online); // initialise prior to registering with clusterManager server.initialise(); @@ -155,8 +159,6 @@ private void startServer(boolean online, DefaultServer server) { clusterManager.registerServer(server); } } - // start any services after registering with clusterManager - server.start(); } /** @@ -173,6 +175,7 @@ private BootupClasses bootupClasses(DatabaseConfig config) { bootup.addPersistListeners(config.getPersistListeners()); bootup.addQueryAdapters(config.getQueryAdapters()); bootup.addServerConfigStartup(config.getServerConfigStartupListeners()); + bootup.addCustomDeployParser(config.getCustomDeployParsers()); bootup.addChangeLogInstances(config); bootup.runServerConfigStartup(config); return bootup; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 13e4365fa7..3b96eb9644 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -18,6 +18,7 @@ import io.ebean.MergeOptions; import io.ebean.MergeOptionsBuilder; import io.ebean.PagedList; +import io.ebean.PersistVisitor; import io.ebean.PersistenceContextScope; import io.ebean.ProfileLocation; import io.ebean.Query; @@ -47,7 +48,14 @@ import io.ebean.bean.SingleBeanLoader; import io.ebean.cache.ServerCacheManager; import io.ebean.common.CopyOnFirstWriteList; -import io.ebean.config.*; +import io.ebean.config.CurrentTenantProvider; +import io.ebean.config.DatabaseConfig; +import io.ebean.config.EncryptKeyManager; +import io.ebean.config.QueryPlanCapture; +import io.ebean.config.QueryPlanListener; +import io.ebean.config.SlowQueryEvent; +import io.ebean.config.SlowQueryListener; +import io.ebean.config.TempFileProvider; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.event.BeanPersistController; import io.ebean.event.ShutdownManager; @@ -58,7 +66,6 @@ import io.ebean.meta.MetricVisitor; import io.ebean.meta.QueryPlanInit; import io.ebean.meta.QueryPlanRequest; -import io.ebean.migration.auto.AutoMigrationRunner; import io.ebean.plugin.BeanType; import io.ebean.plugin.Plugin; import io.ebean.plugin.Property; @@ -136,6 +143,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final String serverName; private final DatabasePlatform databasePlatform; private final TransactionManager transactionManager; + private final TempFileProvider tempFileProvider; private final QueryPlanManager queryPlanManager; private final ExtraMetrics extraMetrics; private final DataTimeZone dataTimeZone; @@ -153,7 +161,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final ReadAuditLogger readAuditLogger; private final CQueryEngine cqueryEngine; private final List serverPlugins; - private final SpiDdlGenerator ddlGenerator; private final ScriptRunner scriptRunner; private final ExpressionFactory expressionFactory; private final SpiBackgroundExecutor backgroundExecutor; @@ -215,7 +222,7 @@ public DefaultServer(InternalConfiguration config, ServerCacheManager cache) { this.queryPlanManager = config.initQueryPlanManager(transactionManager); this.metaInfoManager = new DefaultMetaInfoManager(this); this.serverPlugins = config.getPlugins(); - this.ddlGenerator = config.initDdlGenerator(this); + this.tempFileProvider = config.getConfig().getTempFileProvider(); this.scriptRunner = new DScriptRunner(this); configureServerPlugins(); @@ -245,9 +252,6 @@ private void configureServerPlugins() { * Execute all the plugins with an online flag indicating the DB is up or not. */ public void executePlugins(boolean online) { - if (!config.isDocStoreOnly()) { - ddlGenerator.execute(online); - } for (Plugin plugin : serverPlugins) { plugin.online(online); } @@ -357,29 +361,15 @@ public void initialise() { encryptKeyManager.initialise(); } serverCacheManager.enabledRegions(config.getEnabledL2Regions()); + startQueryPlanCapture(); } - /** - * Start any services after registering with the ClusterManager. - */ + + @Override public void start() { - if (config.isRunMigration() && TenantMode.DB != config.getTenantMode()) { - final AutoMigrationRunner migrationRunner = ServiceUtil.service(AutoMigrationRunner.class); - if (migrationRunner == null) { - throw new IllegalStateException("No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?"); - } - final String dbSchema = config.getDbSchema(); - if (dbSchema != null) { - migrationRunner.setDefaultDbSchema(dbSchema); - } - migrationRunner.setName(config.getName()); - Platform platform = config.getDatabasePlatform().getPlatform(); - migrationRunner.setBasePlatform(platform.base().name().toLowerCase()); - migrationRunner.setPlatform(platform.name().toLowerCase()); - migrationRunner.loadProperties(config.getProperties()); - migrationRunner.run(config.getDataSource()); + for (Plugin plugin : serverPlugins) { + plugin.start(); } - startQueryPlanCapture(); } private void startQueryPlanCapture() { @@ -445,6 +435,8 @@ private void shutdownInternal(boolean shutdownDataSource, boolean deregisterDriv backgroundExecutor.shutdown(); // shutdown DataSource (if its an Ebean one) transactionManager.shutdown(shutdownDataSource, deregisterDriver); + + tempFileProvider.shutdown(); dumpMetrics(); shutdown = true; if (shutdownDataSource) { @@ -655,7 +647,11 @@ public void clearQueryStatistics() { */ @Override public T createEntityBean(Class type) { - return descriptor(type).createBean(); + final BeanDescriptor desc = descriptor(type); + if (desc == null) { + throw new IllegalArgumentException("No bean type " + type.getName() + " registered"); + } + return desc.createBean(); } /** @@ -1638,6 +1634,11 @@ public void save(Object bean, @Nullable Transaction transaction) { persister.save(checkEntityBean(bean), transaction); } + @Override + public void visitSave(Object start, PersistVisitor visitor) { + new VisitHandler(descriptorManager).visit(start, visitor); + } + @Override public void markAsDirty(Object bean) { if (!(bean instanceof EntityBean)) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index e6944a9e96..d31c91e4b4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -8,6 +8,7 @@ import io.ebean.config.ExternalTransactionManager; import io.ebean.config.ProfilingConfig; import io.ebean.config.SlowQueryListener; +import io.ebean.config.TempFileProvider; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbHistorySupport; import io.ebean.event.changelog.ChangeLogListener; @@ -75,6 +76,7 @@ public final class InternalConfiguration { private final DatabasePlatform databasePlatform; private final DeployInherit deployInherit; private final TypeManager typeManager; + private final TempFileProvider tempFileProvider; private final DtoBeanManager dtoBeanManager; private final ClockService clockService; private final DataTimeZone dataTimeZone; @@ -115,6 +117,7 @@ public final class InternalConfiguration { this.databasePlatform = config.getDatabasePlatform(); this.expressionFactory = initExpressionFactory(config); this.typeManager = new DefaultTypeManager(config, bootupClasses); + this.tempFileProvider = config.getTempFileProvider(); this.multiValueBind = createMultiValueBind(databasePlatform.getPlatform()); this.deployInherit = new DeployInherit(bootupClasses); this.deployCreateProperties = new DeployCreateProperties(typeManager); @@ -511,6 +514,10 @@ SpiLogManager getLogManager() { return logManager; } + public TempFileProvider getTempFileProvider() { + return tempFileProvider; + } + private ServerCachePlugin initServerCachePlugin() { if (config.isLocalOnlyL2Cache()) { localL2Caching = true; @@ -590,30 +597,10 @@ QueryPlanLogger queryPlanLogger(Platform platform) { return new QueryPlanLoggerSqlServer(); case ORACLE: return new QueryPlanLoggerOracle(); + case DB2: + return new QueryPlanLoggerDb2(); default: return new QueryPlanLoggerExplain(); } } - - /** - * Return the DDL generator. - */ - public SpiDdlGenerator initDdlGenerator(SpiEbeanServer server) { - final SpiDdlGeneratorProvider service = service(SpiDdlGeneratorProvider.class); - return service == null ? new NoopDdl(server.config().isDdlRun()) : service.generator(server); - } - - private static class NoopDdl implements SpiDdlGenerator { - private final boolean ddlRun; - NoopDdl(boolean ddlRun) { - this.ddlRun = ddlRun; - } - - @Override - public void execute(boolean online) { - if (online && ddlRun) { - CoreLog.log.error("Configured to run DDL but ebean-ddl-generator is not in the classpath (or ebean-test in the test classpath?)"); - } - } - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java index d3ca32b6df..518b3e7cd7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java @@ -34,7 +34,7 @@ public interface OrmQueryEngine { */ > A findSingleAttributeCollection(OrmQueryRequest request, A collection); - /** + /** * Execute the findVersions query. */ List> findVersions(OrmQueryRequest request); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java index c3f98854b6..1591cf4c67 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -121,7 +121,7 @@ public interface SpiOrmQueryRequest extends BeanQueryRequest, DocQueryRequ Map findMap(); /** - * Execute the findSingleAttributeList query. + * Execute the findSingleAttributeCollection query. */ > A findSingleAttributeCollection(A collection); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java new file mode 100644 index 0000000000..0ab8d9c880 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java @@ -0,0 +1,123 @@ +package io.ebeaninternal.server.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import io.ebean.PersistVisitor; +import io.ebean.bean.EntityBean; +import io.ebean.bean.EntityBeanIntercept; +import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.deploy.BeanDescriptorManager; +import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Handler to process persist graphs. It will allow you to visit a persist + * action (insert/update) and get information about all beans that will be + * affected by that action. This allows you to do validation and other things on + * a set of beans. + * + * @author Roland Praml, FOCONIS AG + * + */ +class VisitHandler { + + private final Set seen = Collections.newSetFromMap(new IdentityHashMap()); + private final BeanDescriptorManager beanDescriptorManager; + + public VisitHandler(BeanDescriptorManager beanDescriptorManager) { + this.beanDescriptorManager = beanDescriptorManager; + } + + public void visit(Object start, PersistVisitor visitor) { + if (start != null) { + if (start instanceof EntityBean) { + visitBean((EntityBean) start, visitor); + } else { + visitMany(start, visitor); + } + } + visitor.visitEnd(); + } + + private void visitBean(EntityBean bean, PersistVisitor visitor) { + if (!seen.add(bean)) { + return; + } + visitor = visitor.visitBean(bean); + if (visitor != null) { + BeanDescriptor desc = beanDescriptorManager.descriptor(bean.getClass()); + visitOnes(bean, visitor, desc, desc.propertiesOneImportedSave()); + visitOnes(bean, visitor, desc, desc.propertiesOneExportedSave()); + visitManys(bean, visitor, desc, desc.propertiesManySave()); + visitor.visitEnd(); + } + } + + private void visitManys(EntityBean bean, PersistVisitor visitor, BeanDescriptor desc, + BeanPropertyAssocMany[] manys) { + EntityBeanIntercept ebi = bean._ebean_getIntercept(); + for (BeanPropertyAssocMany many : manys) { + // check that property is loaded and collection should be cascaded to + if (ebi.isLoadedProperty(many.propertyIndex()) + && !many.isSkipSaveBeanCollection(bean, ebi.isNew()) + && !many.isElementCollection()) { + Object manyValue = many.getValue(bean); + if (manyValue != null) { + PersistVisitor propertyVisitor = visitor.visitProperty(many); + if (propertyVisitor != null) { + visitMany(manyValue, propertyVisitor); + propertyVisitor.visitEnd(); + } + } + } + } + } + + private void visitMany(Object many, PersistVisitor visitor) { + if (!seen.add(many)) { + return; + } + if (many instanceof Collection) { + Collection coll = (Collection) many; + PersistVisitor collectionVisitor = visitor.visitCollection(coll); + if (collectionVisitor != null) { + for (Object elem : coll) { + visitBean((EntityBean) elem, collectionVisitor); + } + collectionVisitor.visitEnd(); + } + } else if (many instanceof Map) { + Map map = (Map) many; + PersistVisitor mapVisitor = visitor.visitMap(map); + if (mapVisitor != null) { + for (Object elem : ((Map) many).values()) { + visitBean((EntityBean) elem, mapVisitor); + } + mapVisitor.visitEnd(); + } + } else { + throw new IllegalArgumentException("Object " + many + " cannot be visited in persist graph"); + } + } + + private void visitOnes(EntityBean bean, PersistVisitor visitor, BeanDescriptor desc, + BeanPropertyAssocOne[] ones) { + EntityBeanIntercept ebi = bean._ebean_getIntercept(); + for (BeanPropertyAssocOne prop : ones) { + if (ebi.isLoadedProperty(prop.propertyIndex())) { + EntityBean detailBean = prop.getValueAsEntityBean(bean); + if (detailBean != null && !prop.isSaveRecurseSkippable(detailBean) && !prop.isReference(detailBean)) { + PersistVisitor propertyVisitor = visitor.visitProperty(prop); + if (propertyVisitor != null) { + visitBean(detailBean, propertyVisitor); + propertyVisitor.visitEnd(); + } + } + } + } + } +} \ No newline at end of file diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java index 3fc4bbb950..afa8f61195 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java @@ -11,6 +11,7 @@ import io.ebean.event.changelog.ChangeLogRegister; import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; +import io.ebean.plugin.CustomDeployParser; import io.ebean.util.AnnotationUtil; import io.ebeaninternal.api.CoreLog; import org.slf4j.Logger; @@ -51,6 +52,7 @@ public class BootupClasses implements Predicate> { private final List> beanPersistListenerCandidates = new ArrayList<>(); private final List> beanQueryAdapterCandidates = new ArrayList<>(); private final List> serverConfigStartupCandidates = new ArrayList<>(); + private final List> customDeployParserCandidates = new ArrayList<>(); private final List idGeneratorInstances = new ArrayList<>(); private final List beanPersistControllerInstances = new ArrayList<>(); @@ -60,6 +62,7 @@ public class BootupClasses implements Predicate> { private final List beanPersistListenerInstances = new ArrayList<>(); private final List beanQueryAdapterInstances = new ArrayList<>(); private final List serverConfigStartupInstances = new ArrayList<>(); + private final List customDeployParserInstances = new ArrayList<>(); // single objects private Class changeLogPrepareClass; @@ -169,6 +172,10 @@ public void addServerConfigStartup(List startupInstances) { add(startupInstances, serverConfigStartupInstances, serverConfigStartupCandidates); } + public void addCustomDeployParser(List customDeployParser) { + add(customDeployParser, customDeployParserInstances, customDeployParserCandidates); + } + public void addChangeLogInstances(DatabaseConfig config) { readAuditPrepare = config.getReadAuditPrepare(); readAuditLogger = config.getReadAuditLogger(); @@ -285,6 +292,10 @@ public List getBeanQueryAdapters() { return createAdd(beanQueryAdapterInstances, beanQueryAdapterCandidates); } + public List getCustomDeployParsers() { + return createAdd(customDeployParserInstances, customDeployParserCandidates); + } + /** * Return the list of Embeddable classes. */ @@ -404,6 +415,11 @@ private boolean isInterestingInterface(Class cls) { interesting = true; } + if (CustomDeployParser.class.isAssignableFrom(cls)) { + customDeployParserCandidates.add((Class) cls); + interesting = true; + } + // single instances, last assigned wins if (ChangeLogListener.class.isAssignableFrom(cls)) { changeLogListenerClass = (Class) cls; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index af0edfef1f..1cd5c6b527 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -74,6 +74,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private final BeanFinderManager beanFinderManager; private final PersistListenerManager persistListenerManager; private final BeanQueryAdapterManager beanQueryAdapterManager; + private final CustomDeployParserManager customDeployParserManager; private final NamingConvention namingConvention; private final DeployCreateProperties createProperties; private final BeanManagerFactory beanManagerFactory; @@ -87,10 +88,11 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private final BootupClasses bootupClasses; private final String serverName; private final List> elementDescriptors = new ArrayList<>(); - private final Map, BeanTable> beanTableMap = new HashMap<>(); + private final Map beanTableMap = new HashMap<>(); private final Map> descMap = new HashMap<>(); private final Map> descQueueMap = new HashMap<>(); private final Map> beanManagerMap = new HashMap<>(); + private final Map descAliases = new HashMap<>(); private final Map>> tableToDescMap = new HashMap<>(); private final Map>> tableToViewDescMap = new HashMap<>(); private final DbIdentity dbIdentity; @@ -116,7 +118,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy private final Map draftTableMap = new HashMap<>(); // temporary collections used during startup and then cleared - private Map, DeployBeanInfo> deployInfoMap = new HashMap<>(); + private Map> deployInfoMap = new HashMap<>(); private Set> embeddedIdTypes = new HashSet<>(); private List> embeddedBeans = new ArrayList<>(); @@ -153,6 +155,7 @@ public BeanDescriptorManager(InternalConfiguration config) { this.persistListenerManager = new PersistListenerManager(bootupClasses); this.beanQueryAdapterManager = new BeanQueryAdapterManager(bootupClasses); this.beanFinderManager = new BeanFinderManager(bootupClasses); + this.customDeployParserManager = new CustomDeployParserManager(bootupClasses); this.transientProperties = new TransientProperties(); this.changeLogPrepare = config.changeLogPrepare(bootupClasses.getChangeLogPrepare()); this.changeLogListener = config.changeLogListener(bootupClasses.getChangeLogListener()); @@ -234,14 +237,22 @@ public SpiBeanType beanType(Class entityType) { } @Override - @SuppressWarnings("unchecked") public BeanDescriptor descriptor(Class entityType) { - return (BeanDescriptor) descMap.get(entityType.getName()); + BeanDescriptor ret = descriptorByClassName(entityType.getName()); + if (ret == null) { + for (Class iface : entityType.getInterfaces()) { + ret = descriptorByClassName(iface.getName()); + if (ret != null) { + return ret; + } + } + } + return ret; } @SuppressWarnings("unchecked") public BeanDescriptor descriptorByClassName(String entityClassName) { - return (BeanDescriptor) descMap.get(entityClassName); + return (BeanDescriptor) descMap.get(resolveAlias(entityClassName)); } @Override @@ -287,10 +298,12 @@ public Map deploy(List mappings) { try { createListeners(); readEntityDeploymentInitial(); + readOverridesAndAliases(); readXmlMapping(mappings); readEntityBeanTable(); readEntityDeploymentAssociations(); readInheritedIdGenerators(); + deployInfoMap.values().forEach(customDeployParserManager::parse); // creates the BeanDescriptors readEntityRelationships(); List> list = new ArrayList<>(descMap.values()); @@ -336,7 +349,7 @@ private void readEntityMapping(ClassLoader classLoader, XmapEntity entityDeploy) return; } - DeployBeanInfo info = deployInfoMap.get(entityClass); + DeployBeanInfo info = deploy(entityClass); if (info == null) { log.error("No entity bean for ebean.xml entry " + entityClassName); @@ -550,7 +563,7 @@ public List> descriptorList() { } public BeanTable beanTable(Class type) { - return beanTableMap.get(type); + return beanTableMap.get(resolveAlias(type.getName())); } /** @@ -588,7 +601,12 @@ private String errNotRegistered(Class beanClass) { } private BeanManager beanManager(String beanClassName) { - return beanManagerMap.get(beanClassName); + return beanManagerMap.get(resolveAlias(beanClassName)); + } + + private String resolveAlias(String name) { + String altName = descAliases.get(name); + return altName == null ? name : altName; } /** @@ -613,7 +631,7 @@ private void logStatus() { */ @SuppressWarnings("unchecked") public DeployBeanInfo deploy(Class cls) { - return (DeployBeanInfo) deployInfoMap.get(cls); + return (DeployBeanInfo) deployInfoMap.get(resolveAlias(cls.getName())); } private void registerDescriptor(DeployBeanInfo info) { @@ -638,7 +656,7 @@ private void registerDescriptor(DeployBeanInfo info) { private void readEntityDeploymentInitial() { for (Class entityClass : bootupClasses.getEntities()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); - deployInfoMap.put(entityClass, info); + deployInfoMap.put(entityClass.getName(), info); Class embeddedIdType = info.getEmbeddedIdType(); if (embeddedIdType != null) { embeddedIdTypes.add(embeddedIdType); @@ -646,7 +664,7 @@ private void readEntityDeploymentInitial() { } for (Class entityClass : bootupClasses.getEmbeddables()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); - deployInfoMap.put(entityClass, info); + deployInfoMap.put(entityClass.getName(), info); if (embeddedIdTypes.contains(entityClass)) { // register embeddedId types early - scalar properties only // and needed for creating BeanTables (id properties) @@ -672,7 +690,7 @@ private void registerEmbeddedBean(DeployBeanInfo info) { private void readEntityBeanTable() { for (DeployBeanInfo info : deployInfoMap.values()) { BeanTable beanTable = createBeanTable(info); - beanTableMap.put(beanTable.getBeanType(), beanTable); + beanTableMap.put(beanTable.getBeanType().getName(), beanTable); } // register non-id embedded beans (after bean tables are created) for (DeployBeanInfo info : embeddedBeans) { @@ -696,7 +714,7 @@ private void readInheritedIdGenerators() { DeployBeanDescriptor descriptor = info.getDescriptor(); InheritInfo inheritInfo = descriptor.getInheritInfo(); if (inheritInfo != null && !inheritInfo.isRoot()) { - DeployBeanInfo rootBeanInfo = deployInfoMap.get(inheritInfo.getRoot().getType()); + DeployBeanInfo rootBeanInfo = deploy(inheritInfo.getRoot().getType()); PlatformIdGenerator rootIdGen = rootBeanInfo.getDescriptor().getIdGenerator(); if (rootIdGen != null) { descriptor.setIdGenerator(rootIdGen); @@ -737,13 +755,79 @@ private void readEntityRelationships() { } } + /** + * Finds the overrides by priority. Computes a map with the base class as key + * and the descriptor, that shoud replace that class. Modifies the deployInfoMap + * and descAliases accordingly. + */ + private void readOverridesAndAliases() { + Map, DeployBeanInfo> overrides = new HashMap<>(); + + for (DeployBeanInfo info : deployInfoMap.values()) { + DeployBeanDescriptor desc = info.getDescriptor(); + Integer prio = desc.getOverridePriority(); + if (prio != null) { + Class base = desc.getBaseBeanType(); + Class child = desc.getBeanType(); + log.debug("{} overridden by {} with priority {}", base.getName(), child.getName(), prio); + + // check, if we already have found an override for this base and take the one + // with + // lowest prio + DeployBeanInfo override = overrides.get(base); + if (override == null) { + overrides.put(base, info); + } else { + int delta = override.getDescriptor().getOverridePriority().compareTo(prio); + if (delta > 0) { + overrides.put(base, info); + } else if (delta == 0) { + throw new IllegalStateException("There are two or more implementations for " + base.getName() + + " with priority " + prio + ". Conflicting entity: " + desc.getBeanType().getName() + " - " + + override.getDescriptor().getBeanType().getName()); + } + } + } + } + + overrides.forEach((key, value) -> { + deployInfoMap.remove(key.getName()); // remove the original descriptor + + DeployBeanDescriptor newDesc = value.getDescriptor(); + newDesc.setOverridePriority(null); // clear priority (so no discard will occur) + + deployInfoMap.put(newDesc.getBeanType().getName(), value); + descAliases.put(key.getName(), newDesc.getBeanType().getName()); + }); + + + // remove all left overrides classes (they are discarded) + deployInfoMap.values().removeIf(info -> info.getDescriptor().getOverridePriority() != null); + + for (DeployBeanInfo info : deployInfoMap.values()) { + for (Class iface : info.getDescriptor().getInterfaces()) { + String conflict = descAliases.put(iface.getName(), info.getDescriptor().getBeanType().getName()); + if (conflict != null) { + throw new IllegalStateException("There are two or more implementations for " + iface + + ". Conflicting entity: " + conflict + " - " + info.getDescriptor().getBeanType().getName()); + } + } + } + + // output alias map + if (log.isInfoEnabled()) { + descAliases.forEach((key, value) -> log.info("{} alias for {}", key, value)); + } + } + + /** * Sets the inheritance info. */ private void setInheritanceInfo(DeployBeanInfo info) { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { - DeployBeanInfo assoc = deployInfoMap.get(oneProp.getTargetType()); + DeployBeanInfo assoc = deploy(oneProp.getTargetType()); if (assoc != null) { oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } @@ -751,7 +835,7 @@ private void setInheritanceInfo(DeployBeanInfo info) { } for (DeployBeanPropertyAssocMany manyProp : info.getDescriptor().propertiesAssocMany()) { if (!manyProp.isTransient()) { - DeployBeanInfo assoc = deployInfoMap.get(manyProp.getTargetType()); + DeployBeanInfo assoc = deploy(manyProp.getTargetType()); if (assoc != null) { manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo()); } @@ -808,7 +892,7 @@ private void checkMappedBy(DeployBeanInfo info, List targetDescriptor(DeployBeanPropertyAssoc prop) { Class targetType = prop.getTargetType(); - DeployBeanInfo info = deployInfoMap.get(targetType); + DeployBeanInfo info = deploy(targetType); if (info == null) { throw new PersistenceException("Can not find descriptor [" + targetType + "] for " + prop.getFullBeanName()); } @@ -900,7 +984,7 @@ private void makeOrderColumn(DeployBeanPropertyAssocMany oneToMany) { final InheritInfo targetInheritInfo = targetDesc.getInheritInfo(); if (targetInheritInfo != null) { for (InheritInfo child : targetInheritInfo.getChildren()) { - final DeployBeanDescriptor childDescriptor = deployInfoMap.get(child.getType()).getDescriptor(); + final DeployBeanDescriptor childDescriptor = deploy(child.getType()).getDescriptor(); childDescriptor.setOrderColumn(orderProperty); } } @@ -1165,11 +1249,12 @@ private DeployBeanInfo createDeployBeanInfo(Class beanClass) { // set bean controller, finder and listener setBeanControllerFinderListener(desc); deplyInherit.process(desc); - desc.checkInheritanceMapping(); createProperties.createProperties(desc); DeployBeanInfo info = new DeployBeanInfo<>(deployUtil, desc); readAnnotations.readInitial(info); + + desc.checkInheritanceMapping(); return info; } @@ -1446,7 +1531,7 @@ private void addPrimaryKeyJoin(DeployBeanPropertyAssocOne prop) { String baseTable = prop.getDesc().getBaseTable(); DeployTableJoin inverse = prop.getTableJoin().createInverse(baseTable); TableJoin inverseJoin = new TableJoin(inverse, prop.getForeignKey()); - DeployBeanInfo target = deployInfoMap.get(prop.getTargetType()); + DeployBeanInfo target = deploy(prop.getTargetType()); target.setPrimaryKeyJoin(inverseJoin); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index 7ab4afe3e4..4fa11fce56 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -1017,7 +1017,7 @@ public List dbMigrationInfos() { /** * Return the bean Field associated with this property. */ - private Field field() { + public Field field() { return field; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/CustomDeployParserManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/CustomDeployParserManager.java new file mode 100644 index 0000000000..77fd807c4b --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/CustomDeployParserManager.java @@ -0,0 +1,23 @@ +package io.ebeaninternal.server.deploy; + +import java.util.List; + +import io.ebean.plugin.CustomDeployParser; +import io.ebeaninternal.server.core.bootup.BootupClasses; +import io.ebeaninternal.server.deploy.parse.DeployBeanInfo; + +public class CustomDeployParserManager { + + private final List parsers; + + public CustomDeployParserManager(BootupClasses bootupClasses) { + parsers = bootupClasses.getCustomDeployParsers(); + } + + public void parse(DeployBeanInfo value) { + for (CustomDeployParser parser : parsers) { + parser.parse(value.getDescriptor(), value.getUtil().getDbPlatform()); + } + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 49767439f1..a69d111cae 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -3,6 +3,7 @@ import io.ebean.annotation.Cache; import io.ebean.annotation.DocStore; import io.ebean.annotation.DocStoreMode; +import io.ebean.annotation.ext.EntityOverride; import io.ebean.annotation.Identity; import io.ebean.config.DatabaseConfig; import io.ebean.config.TableName; @@ -15,7 +16,9 @@ import io.ebean.event.BeanPostLoad; import io.ebean.event.BeanQueryAdapter; import io.ebean.event.changelog.ChangeLogFilter; +import io.ebean.plugin.DeployBeanDescriptorMeta; import io.ebean.text.PathProperties; +import io.ebean.util.AnnotationUtil; import io.ebean.util.SplitName; import io.ebeaninternal.api.ConcurrencyMode; import io.ebeaninternal.server.core.CacheOptions; @@ -45,14 +48,16 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * Describes Beans including their deployment information. */ -public class DeployBeanDescriptor { +public class DeployBeanDescriptor implements DeployBeanDescriptorMeta { private static final Map EMPTY_NAMED_QUERY = new HashMap<>(); @@ -154,6 +159,10 @@ public int compare(DeployBeanProperty o1, DeployBeanProperty o2) { private TableJoin primaryKeyJoin; private Object jacksonAnnotatedClass; + private final Set> interfaces = new HashSet<>(); + + private Integer overridePriority; + /** * Construct the BeanDescriptor. */ @@ -193,7 +202,7 @@ public TableJoin getPrimaryKeyJoin() { /** * Return the DeployBeanInfo for the given bean class. */ - DeployBeanInfo getDeploy(Class cls) { + public DeployBeanInfo getDeploy(Class cls) { return manager.deploy(cls); } @@ -621,6 +630,7 @@ public String[] getDependentTables() { * Return the base table. Only properties mapped to the base table are by * default persisted. */ + @Override public String getBaseTable() { return baseTable; } @@ -698,6 +708,7 @@ public Collection properties() { /** * Get a BeanProperty by its name. */ + @Override public DeployBeanProperty getBeanProperty(String propName) { return propMap.get(propName); } @@ -817,6 +828,7 @@ public String toString() { /** * Return a collection of all BeanProperty deployment information. */ + @Override public Collection propertiesAll() { return propMap.values(); } @@ -886,6 +898,7 @@ public String getSinglePrimaryKeyColumn() { /** * Return the BeanProperty that is the Id. */ + @Override public DeployBeanProperty idProperty() { if (idProperty != null) { return idProperty; @@ -967,7 +980,7 @@ public List propertiesBase() { * Check the mapping for class inheritance */ public void checkInheritanceMapping() { - if (inheritInfo == null) { + if (inheritInfo == null && overridePriority == null) { checkInheritance(getBeanType()); } } @@ -1140,4 +1153,56 @@ private String getDeployWord(String expression) { } return jacksonAnnotatedClass; } + + /** + * Adds a concrete interface, that will be registered as alias for a concrete implementation. + */ + public void addInterface(Class iface) { + if (!iface.isAssignableFrom(beanType)) { + throw new ClassCastException("Cannot cast " + iface.getName() + " to " + beanType.getName()); + } + interfaces.add(iface); + } + + /** + * Returns the interfaces. + */ + public Set> getInterfaces() { + return interfaces; + } + + /** + * Sets the override priority. + */ + public void setOverridePriority(Integer overridePriority) { + this.overridePriority = overridePriority; + } + + /** + * returns the override priority, or null if this is a base entity. + */ + public Integer getOverridePriority() { + return overridePriority; + } + + /** + * Returns the base bean type, if {@link EntityOverride} was used. + */ + public Class getBaseBeanType() { + Class base = getBeanType(); + while (AnnotationUtil.has(base, EntityOverride.class)) { + base = base.getSuperclass(); + } + return base; + } + + @Override + public String getDiscriminatorColumn() { + return inheritInfo == null ? null : inheritInfo.getDiscriminatorColumn(); + } + + @Override + public DeployBeanDescriptorMeta getDeployBeanDescriptorMeta(Class propertyType) { + return getDeploy(propertyType).getDescriptor(); + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index 655f4c1014..954182f7a3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -1,11 +1,23 @@ package io.ebeaninternal.server.deploy.meta; -import io.ebean.annotation.*; +import io.ebean.annotation.DocCode; +import io.ebean.annotation.DocProperty; +import io.ebean.annotation.DocSortable; +import io.ebean.annotation.Formula; +import io.ebean.annotation.MutationDetection; +import io.ebean.annotation.Platform; +import io.ebean.annotation.SoftDelete; +import io.ebean.annotation.WhenCreated; +import io.ebean.annotation.WhenModified; +import io.ebean.annotation.Where; +import io.ebean.annotation.WhoCreated; +import io.ebean.annotation.WhoModified; import io.ebean.config.ScalarTypeConverter; import io.ebean.config.dbplatform.DbDefaultValue; import io.ebean.config.dbplatform.DbEncrypt; import io.ebean.config.dbplatform.DbEncryptFunction; import io.ebean.core.type.ScalarType; +import io.ebean.plugin.DeployBeanPropertyMeta; import io.ebean.util.AnnotationUtil; import io.ebeaninternal.server.core.InternString; import io.ebeaninternal.server.deploy.BeanProperty; @@ -35,7 +47,7 @@ * Description of a property of a bean. Includes its deployment information such * as database column mapping information. */ -public class DeployBeanProperty { +public class DeployBeanProperty implements DeployBeanPropertyMeta { private static final int ID_ORDER = 1000000; private static final int UNIDIRECTIONAL_ORDER = 100000; @@ -404,6 +416,7 @@ public void setOwningType(Class owningType) { this.owningType = owningType; } + @Override public Class getOwningType() { return owningType; } @@ -432,6 +445,7 @@ public void setSetter(BeanPropertySetter setter) { /** * Return the name of the property. */ + @Override public String getName() { return name; } @@ -446,6 +460,7 @@ public void setName(String name) { /** * Return the bean Field associated with this property. */ + @Override public Field getField() { return field; } @@ -549,6 +564,7 @@ public String getSqlFormulaJoin() { /** * The property is based on a formula. */ + @Override public void setSqlFormula(String formulaSelect, String formulaJoin) { this.sqlFormulaSelect = formulaSelect; this.sqlFormulaJoin = formulaJoin.isEmpty() ? null : formulaJoin; @@ -652,6 +668,7 @@ public String getElPlaceHolder() { /** * The database column name this is mapped to. */ + @Override public String getDbColumn() { if (sqlFormulaSelect != null) { return sqlFormulaSelect; @@ -845,6 +862,7 @@ public void setTransient() { /** * Return the property type. */ + @Override public Class getPropertyType() { return propertyType; } @@ -1137,7 +1155,7 @@ public Where getMetaAnnotationWhere(Platform platform) { private boolean matchPlatform(Platform[] platforms, Platform match) { for (Platform platform : platforms) { - if (platform == match) { + if (platform == match || platform == match.base()) { return true; } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index 3461be3bd8..123972cb0d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.deploy.meta; +import io.ebean.plugin.DeployBeanPropertyAssocMeta; import io.ebeaninternal.server.deploy.BeanCascadeInfo; import io.ebeaninternal.server.deploy.BeanTable; import io.ebeaninternal.server.deploy.PropertyForeignKey; @@ -7,7 +8,7 @@ /** * Abstract base for properties mapped to an associated bean, list, set or map. */ -public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { +public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty implements DeployBeanPropertyAssocMeta { /** * The type of the joined bean. @@ -129,6 +130,7 @@ public PropertyForeignKey getForeignKey() { * this 'master' bean. *

*/ + @Override public String getMappedBy() { return mappedBy; } @@ -173,4 +175,9 @@ public void setFetchPreference(int fetchPreference) { public void setTargetType(Class targetType) { this.targetType = (Class)targetType; } + + @Override + public String getBaseTable() { + return getBeanTable().getBaseTable(); + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java index 956fa66c7b..356973c767 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -6,6 +6,8 @@ import io.ebean.annotation.DocStore; import io.ebean.annotation.Draftable; import io.ebean.annotation.DraftableElement; +import io.ebean.annotation.ext.EntityImplements; +import io.ebean.annotation.ext.EntityOverride; import io.ebean.annotation.History; import io.ebean.annotation.Identity; import io.ebean.annotation.Index; @@ -15,6 +17,7 @@ import io.ebean.annotation.Tablespace; import io.ebean.annotation.View; import io.ebean.config.TableName; +import io.ebean.util.AnnotationUtil; import io.ebeaninternal.api.CoreLog; import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType; import io.ebeaninternal.server.deploy.IndexDefinition; @@ -31,6 +34,7 @@ import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.UniqueConstraint; +import java.util.Set; import static io.ebean.util.AnnotationUtil.typeGet; @@ -86,7 +90,7 @@ public void parse() { */ private void setTableName() { if (descriptor.isBaseTableType()) { - Class beanType = descriptor.getBeanType(); + Class beanType = descriptor.getBaseBeanType(); InheritInfo inheritInfo = descriptor.getInheritInfo(); if (inheritInfo != null) { beanType = inheritInfo.getRoot().getType(); @@ -202,6 +206,18 @@ private void read(Class cls) { for (NamedQuery namedQuery : annotationClassNamedQuery(cls)) { descriptor.addNamedQuery(namedQuery.name(), namedQuery.query()); } + + Set entityImplements = AnnotationUtil.typeGetAll(cls, EntityImplements.class); + for (EntityImplements ann : entityImplements) { + for (Class iface : ann.value()) { + descriptor.addInterface(iface); + } + } + + EntityOverride entityOverride = AnnotationUtil.typeGet(cls, EntityOverride.class); + if (entityOverride != null) { + descriptor.setOverridePriority(entityOverride.priority()); + } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java index 043f210aa2..c781fe502d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java @@ -4,6 +4,7 @@ import io.ebean.util.StringHelper; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.persist.BeanPersister; +import io.ebeaninternal.server.type.DataBindCapture; import java.sql.SQLException; @@ -70,7 +71,7 @@ private int execute(PersistRequestBean request, PersistHandler handler) { } } catch (SQLException e) { // log the error to the transaction log - String msg = "Error[" + StringHelper.removeNewLines(e.getMessage()) + "]"; + String msg = "Error[" + StringHelper.removeNewLines(e.getMessage()) + "] " + handler; if (request.transaction().isLogSummary()) { request.transaction().logSummary(msg); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java index 7b82e7faa0..a594159872 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java @@ -259,4 +259,14 @@ PreparedStatement getPstmtBatch(SpiTransaction t, String sql, PersistRequestBean return stmt; } + @Override + public String toString() { + if (sql == null) { + return "not yet initialized"; + } else if (bindLog == null || bindLog.length() == 0) { + return sql; + } else { + return Str.add(sql, " -- bind(", bindLog.toString(), ")"); + } + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index 416da3c311..12f56f2de6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -118,6 +118,7 @@ private > A findAttributeCollection(OrmQueryRequest r collection = (A) new LinkedHashSet<>(collection); } } + } return collection; } catch (SQLException e) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/QueryPlanLoggerDb2.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/QueryPlanLoggerDb2.java new file mode 100644 index 0000000000..4b892f987e --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/QueryPlanLoggerDb2.java @@ -0,0 +1,55 @@ + +package io.ebeaninternal.server.query; + +import io.ebeaninternal.api.CoreLog; +import io.ebeaninternal.api.SpiDbQueryPlan; +import io.ebeaninternal.api.SpiQueryPlan; +import io.ebeaninternal.server.type.bindcapture.BindCapture; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +/** + * A QueryPlanLogger for DB2. + * + * To use query plan capturing, you have to install the explain tables with + * SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA ). + * To do this in a repeatable script, you may use this statement: + * + *
+ * BEGIN    
+ * IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN 
+ *    call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA );
+ * END IF;                                                        
+ * END
+ * 
+ * + * @author Roland Praml, FOCONIS AG + */ +public final class QueryPlanLoggerDb2 extends QueryPlanLogger { + + private Random rnd = new Random(); + @Override + public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) { + try (Statement stmt = conn.createStatement()) { + int queryNo = rnd.nextInt(Integer.MAX_VALUE); + try (PreparedStatement explainStmt = conn + .prepareStatement("EXPLAIN PLAN SET QUERYNO=" + queryNo + " FOR " + plan.getSql())) { + bind.prepare(explainStmt, conn); + explainStmt.execute(); + } + + try (ResultSet rset = stmt.executeQuery("select * from EXPLAIN_STATEMENT where QUERYNO=" + queryNo)) { + return readQueryPlan(plan, bind, rset); + } + } catch (SQLException e) { + CoreLog.log.warn("Could not log query plan", e); + return null; + } + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java index 17c9e9372b..9a5ed94ad5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/text/json/WriteJson.java @@ -506,6 +506,9 @@ private boolean isIncludeProperty(BeanProperty prop) { return true; if (currentIncludeProps != null) { // explicitly controlled by pathProperties + if (prop.isId() && currentIncludeProps.contains("${identifier}")) { + return true; + } return currentIncludeProps.contains(prop.name()); } else { // include only loaded properties diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index 4fb74db18e..89761e8093 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -992,6 +992,9 @@ public final void postCommit() { public final void preCommit() { internalBatchFlush(); firePreCommit(); + // we must flush the batch queue again, because the callback can + // modify current transaction + internalBatchFlush(); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 165d86c5a1..af652be120 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -2,7 +2,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.ebean.annotation.*; +import io.ebean.annotation.DbEnumType; +import io.ebean.annotation.DbEnumValue; +import io.ebean.annotation.EnumValue; +import io.ebean.annotation.MutationDetection; +import io.ebean.annotation.Platform; import io.ebean.config.DatabaseConfig; import io.ebean.config.JsonConfig; import io.ebean.config.PlatformConfig; @@ -61,7 +65,7 @@ public final class DefaultTypeManager implements TypeManager { private final DefaultTypeFactory extraTypeFactory; private final ScalarType hstoreType = new ScalarTypePostgresHstore(); - private final ScalarTypeFile fileType = new ScalarTypeFile(); + private final ScalarTypeFile fileType; private final ScalarType charType = new ScalarTypeChar(); private final ScalarType charArrayType = new ScalarTypeCharArray(); private final ScalarType longVarcharType = new ScalarTypeLongVarchar(); @@ -143,6 +147,7 @@ public DefaultTypeManager(DatabaseConfig config, BootupClasses bootupClasses) { this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform()); this.offlineMigrationGeneration = DbOffline.isGenerateMigration(); this.defaultEnumType = config.getDefaultEnumType(); + this.fileType = new ScalarTypeFile(config.getTempFileProvider()); initialiseStandard(config); initialiseJavaTimeTypes(config); @@ -269,6 +274,7 @@ public ScalarType getScalarType(Class type) { return found != ScalarTypeNotFound.INSTANCE ? found : null; // Do not return ScalarTypeNotFound, otherwise checks will fail } + /** * Checks the typeMap for inherited types. * diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java index 9028523b4d..ccc315eb81 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java @@ -60,7 +60,7 @@ public Calendar convertFromInstant(Instant ts) { @Override protected String toJsonNanos(Calendar value) { - return String.valueOf(value.getTime()); + return String.valueOf(value.getTime().getTime()); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java index be712b6591..b10b206de3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import io.ebean.config.TempFileProvider; import io.ebean.core.type.DataBinder; import io.ebean.core.type.DataReader; import io.ebean.core.type.DocPropertyType; @@ -17,26 +18,22 @@ */ final class ScalarTypeFile extends ScalarTypeBase { - private final String prefix; - private final String suffix; - private final File directory; + private final TempFileProvider tempFileProvider; private final int bufferSize; /** - * Construct with reasonable defaults of Blob and 8096 buffer size. + * Construct with reasonable defaults of Blob and 8192 buffer size. */ - ScalarTypeFile() { - this(Types.LONGVARBINARY, "db-", null, null, 8096); + ScalarTypeFile(TempFileProvider tempFileProvider) { + this(Types.LONGVARBINARY, tempFileProvider, 8192); } /** * Create the ScalarTypeFile. */ - ScalarTypeFile(int jdbcType, String prefix, String suffix, File directory, int bufferSize) { + ScalarTypeFile(int jdbcType, TempFileProvider tempFileProvider, int bufferSize) { super(File.class, false, jdbcType); - this.prefix = prefix; - this.suffix = suffix; - this.directory = directory; + this.tempFileProvider = tempFileProvider; this.bufferSize = bufferSize; } @@ -63,7 +60,7 @@ public File read(DataReader reader) throws SQLException { } try { // stream from db into our temp file - File tempFile = File.createTempFile(prefix, suffix, directory); + File tempFile = tempFileProvider.createTempFile(); OutputStream os = getOutputStream(tempFile); pump(is, os); return tempFile; @@ -106,7 +103,7 @@ public void jsonWrite(JsonGenerator writer, File value) throws IOException { @Override public File jsonRead(JsonParser parser) throws IOException { - File tempFile = File.createTempFile(prefix, suffix, directory); + File tempFile = tempFileProvider.createTempFile(); try (OutputStream os = getOutputStream(tempFile)) { parser.readBinaryValue(os); os.flush(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java index 9a7990a6d7..9b5a194095 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaDateMidnight.java @@ -22,7 +22,7 @@ final class ScalarTypeJodaDateMidnight extends ScalarTypeBaseDate + + 4.0.0 + + ebean-parent + io.ebean + 13.6.4-FOC2-SNAPSHOT + + + ebean dbmigration runner + DB Migration runner plugin + ebean-dbmigration-runner + + + + + io.ebean + ebean-migration-auto + ${ebean-migration-auto.version} + + + + io.ebean + ebean-annotation + 8.0 + provided + + + + io.ebean + ebean-core + 13.6.4-FOC2-SNAPSHOT + provided + + + + io.ebean + ebean-migration + ${ebean-migration.version} + + + + diff --git a/ebean-dbmigration-runner/src/main/java/io/ebeaninternal/dbmigration/run/DbRunMigrationPlugin.java b/ebean-dbmigration-runner/src/main/java/io/ebeaninternal/dbmigration/run/DbRunMigrationPlugin.java new file mode 100644 index 0000000000..8b994fd436 --- /dev/null +++ b/ebean-dbmigration-runner/src/main/java/io/ebeaninternal/dbmigration/run/DbRunMigrationPlugin.java @@ -0,0 +1,51 @@ +package io.ebeaninternal.dbmigration.run; + +import io.ebean.annotation.Platform; +import io.ebean.config.DatabaseConfig; +import io.ebean.migration.MigrationConfig; +import io.ebean.migration.MigrationRunner; +import io.ebean.plugin.Plugin; +import io.ebean.plugin.SpiServer; + +public class DbRunMigrationPlugin implements Plugin { + + private SpiServer server; + private MigrationConfig migrationConfig; + + @Override + public void configure(SpiServer server) { + this.server = server; + DatabaseConfig config = server.config(); + if (config.isRunMigration()) { + migrationConfig = new MigrationConfig(); + final String dbSchema = config.getDbSchema(); + if (dbSchema != null) { + migrationConfig.setSetCurrentSchema(false); + migrationConfig.setDbSchema(dbSchema); + } + migrationConfig.setName(config.getName()); + Platform platform = config.getDatabasePlatform().getPlatform(); + migrationConfig.setBasePlatform(platform.base().name().toLowerCase()); + migrationConfig.setPlatform(platform.name().toLowerCase()); + migrationConfig.load(config.getProperties()); + migrationConfig.setRunPlaceholderMap(config.getDdlPlaceholderMap()); + } + } + + @Override + public void online(boolean online) { + + } + + @Override + public void shutdown() { + + } + + @Override + public void start() { + if (migrationConfig != null) { + new MigrationRunner(migrationConfig).run(server.dataSource()); + } + } +} diff --git a/ebean-dbmigration-runner/src/main/java/module-info.java b/ebean-dbmigration-runner/src/main/java/module-info.java new file mode 100644 index 0000000000..2841013a31 --- /dev/null +++ b/ebean-dbmigration-runner/src/main/java/module-info.java @@ -0,0 +1,10 @@ +module io.ebean.dbmigration.runner { + + provides io.ebean.plugin.Plugin with io.ebeaninternal.dbmigration.run.DbRunMigrationPlugin; + + /*requires transitive io.ebean.ddl.runner; + requires transitive io.ebean.core;*/ + requires transitive io.ebean.annotation; + requires io.ebean.api; + requires io.ebean.migration; +} diff --git a/ebean-dbmigration-runner/src/main/resources/META-INF/services/io.ebean.plugin.Plugin b/ebean-dbmigration-runner/src/main/resources/META-INF/services/io.ebean.plugin.Plugin new file mode 100644 index 0000000000..873033a797 --- /dev/null +++ b/ebean-dbmigration-runner/src/main/resources/META-INF/services/io.ebean.plugin.Plugin @@ -0,0 +1,2 @@ + +io.ebeaninternal.dbmigration.run.DbRunMigrationPlugin diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 5745e1c164..a93475bc36 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean ddl generation @@ -28,14 +28,14 @@ io.ebean ebean-core-type - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided @@ -55,10 +55,17 @@ test + + io.ebean + ebean-dbmigration-runner + 13.6.4-FOC2-SNAPSHOT + test + + io.ebean ebean-platform-all - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DbMigrationPlugin.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DbMigrationPlugin.java new file mode 100644 index 0000000000..1839cf4917 --- /dev/null +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DbMigrationPlugin.java @@ -0,0 +1,56 @@ +package io.ebeaninternal.dbmigration; + +import java.io.IOException; + +import io.ebean.plugin.Plugin; +import io.ebean.plugin.SpiServer; + +/** + * Plugin to generate db-migration scripts automatically. + * @author Roland Praml, FOCONIS AG + */ +public class DbMigrationPlugin implements Plugin { + + private DefaultDbMigration dbMigration; + + private static String lastMigration; + private static String lastInit; + + @Override + public void configure(SpiServer server) { + dbMigration = new DefaultDbMigration(); + dbMigration.setServer(server); + } + + @Override + public void online(boolean online) { + try { + lastInit = null; + lastMigration = null; + if (dbMigration.generate) { + String tmp = lastMigration = dbMigration.generateMigration(); + if (tmp == null) { + return; + } + } + if (dbMigration.generateInit) { + lastInit = dbMigration.generateInitMigration(); + } + } catch (IOException e) { + throw new RuntimeException("Error while generating migration", e); + } + } + + @Override + public void shutdown() { + dbMigration = null; + } + + public static String getLastInit() { + return lastInit; + } + + public static String getLastMigration() { + return lastMigration; + } +} diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGeneratorProvider.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGeneratorProvider.java deleted file mode 100644 index b551fa6a05..0000000000 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGeneratorProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.ebeaninternal.dbmigration; - -import io.ebeaninternal.api.SpiDdlGenerator; -import io.ebeaninternal.api.SpiDdlGeneratorProvider; -import io.ebeaninternal.api.SpiEbeanServer; - -public class DdlGeneratorProvider implements SpiDdlGeneratorProvider { - - @Override - public SpiDdlGenerator generator(SpiEbeanServer server) { - return new DdlGenerator(server); - } -} diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlPlugin.java similarity index 83% rename from ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java rename to ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlPlugin.java index 74ae5a64c8..1811ffa1f4 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlPlugin.java @@ -5,9 +5,10 @@ import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.ddlrunner.DdlRunner; import io.ebean.ddlrunner.ScriptTransform; +import io.ebean.plugin.Plugin; +import io.ebean.plugin.SpiServer; import io.ebean.util.IOUtils; import io.ebean.util.JdbcClose; -import io.ebeaninternal.api.SpiDdlGenerator; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.dbmigration.model.CurrentModel; import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader; @@ -25,74 +26,54 @@ * Typically the "Create All" DDL is executed for running tests etc and has nothing to do * with DB Migration (diff based) DDL. */ -public class DdlGenerator implements SpiDdlGenerator { +public class DdlPlugin implements Plugin { - private static final Logger log = LoggerFactory.getLogger(DdlGenerator.class); + private static final Logger log = LoggerFactory.getLogger(DdlPlugin.class); private static final String[] BUILD_DIRS = {"target", "build"}; - private final SpiEbeanServer server; + private SpiEbeanServer server; - private final boolean generateDdl; - private final boolean runDdl; - private final boolean extraDdl; - private final boolean createOnly; - private final boolean jaxbPresent; - private final String dbSchema; - private final ScriptTransform scriptTransform; - private final Platform platform; - private final String platformName; - private final boolean useMigrationStoredProcedures; + private boolean generateDdl; + private boolean runDdl; + private boolean extraDdl; + private boolean createOnly; + private boolean jaxbPresent; + private String dbSchema; + private ScriptTransform scriptTransform; + private Platform platform; + private String platformName; + private boolean useMigrationStoredProcedures; private CurrentModel currentModel; private String dropAllContent; private String createAllContent; - private final File baseDir; - - public DdlGenerator(SpiEbeanServer server) { - this.server = server; + private File baseDir; + + @Override + public void configure(SpiServer server) { + this.server = (SpiEbeanServer) server; final DatabaseConfig config = server.config(); - this.jaxbPresent = Detect.isJAXBPresent(config); this.generateDdl = config.isDdlGenerate(); - this.extraDdl = config.isDdlExtra(); - this.createOnly = config.isDdlCreateOnly(); - this.dbSchema = config.getDbSchema(); - final DatabasePlatform databasePlatform = server.databasePlatform(); - this.platform = databasePlatform.getPlatform(); - this.platformName = platform.base().name(); - if (!config.getTenantMode().isDdlEnabled() && config.isDdlRun()) { - log.warn("DDL can't be run on startup with TenantMode " + config.getTenantMode()); - this.runDdl = false; - this.useMigrationStoredProcedures = false; - } else { - this.runDdl = config.isDdlRun(); + this.runDdl = config.isDdlRun() && !config.isDocStoreOnly(); + if (generateDdl || runDdl) { + this.jaxbPresent = Detect.isJAXBPresent(config); + this.extraDdl = config.isDdlExtra(); + this.createOnly = config.isDdlCreateOnly(); + this.dbSchema = config.getDbSchema(); + final DatabasePlatform databasePlatform = server.databasePlatform(); + this.platform = databasePlatform.getPlatform(); + this.platformName = platform.base().name(); this.useMigrationStoredProcedures = config.getDatabasePlatform().isUseMigrationStoredProcedures(); - } - this.scriptTransform = createScriptTransform(config); - this.baseDir = initBaseDir(); - } - - private File initBaseDir() { - for (String buildDir : BUILD_DIRS) { - File dir = new File(buildDir); - if (dir.exists() && dir.isDirectory()) { - return dir; - } - } - return new File("."); - } - - @Override - public void execute(boolean online) { - generateDdl(); - if (online) { - runDdl(); - } + this.scriptTransform = createScriptTransform(config); + this.baseDir = initBaseDir(); + } } /** * Generate the DDL drop and create scripts if the properties have been set. */ - protected void generateDdl() { + @Override + public void online(boolean online) { if (generateDdl) { if (!createOnly) { writeDrop(getDropFileName()); @@ -100,11 +81,12 @@ protected void generateDdl() { writeCreate(getCreateFileName()); } } - + /** * Run the DDL drop and DDL create scripts if properties have been set. */ - protected void runDdl() { + @Override + public void start() { if (runDdl) { Connection connection = null; try { @@ -117,6 +99,20 @@ protected void runDdl() { } } + @Override + public void shutdown() {} + + + private File initBaseDir() { + for (String buildDir : BUILD_DIRS) { + File dir = new File(buildDir); + if (dir.exists() && dir.isDirectory()) { + return dir; + } + } + return new File("."); + } + private void runDdlWith(Connection connection) { try { if (dbSchema != null) { @@ -201,7 +197,7 @@ protected void runCreateSql(Connection connection) throws IOException { if (extraDdl && jaxbPresent) { if (currentModel().isTablePartitioning()) { String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(platform); - if (extraPartitioning != null && !extraPartitioning.isEmpty()) { + if (extraPartitioning != null && !extraPartitioning.isEmpty() && useMigrationStoredProcedures) { runScript(connection, false, extraPartitioning, "builtin-partitioning-ddl"); } } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java index ed592e9d2e..eb36766905 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java @@ -1,5 +1,6 @@ package io.ebeaninternal.dbmigration; +import io.avaje.classpath.scanner.core.Location; import io.ebean.DB; import io.ebean.Database; import io.ebean.annotation.Platform; @@ -11,6 +12,7 @@ import io.ebean.config.dbplatform.DatabasePlatformProvider; import io.ebean.dbmigration.DbMigration; import io.ebean.util.IOUtils; +import io.ebean.util.StringHelper; import io.ebeaninternal.api.DbOffline; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions; @@ -31,6 +33,8 @@ import java.util.List; import java.util.Properties; import java.util.ServiceLoader; +import java.util.StringJoiner; + import static io.ebeaninternal.api.PlatformMatch.matchPlatform; @@ -61,8 +65,8 @@ public class DefaultDbMigration implements DbMigration { private static final String initialVersion = "1.0"; private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY"; - private final List platformProviders = new ArrayList<>(); - protected final boolean online; + private List platformProviders = new ArrayList<>(); + protected boolean online; private boolean logToSystemOut = true; protected SpiEbeanServer server; protected String pathToResources = "src/main/resources"; @@ -77,8 +81,10 @@ public class DefaultDbMigration implements DbMigration { protected List platforms = new ArrayList<>(); protected DatabaseConfig databaseConfig; protected DbConstraintNaming constraintNaming; + @Deprecated protected Boolean strictMode; - protected Boolean includeGeneratedFileComment; + protected boolean includeGeneratedFileComment; + @Deprecated protected String header; protected String applyPrefix = ""; protected String version; @@ -88,6 +94,9 @@ public class DefaultDbMigration implements DbMigration { private int lockTimeoutSeconds; protected boolean includeBuiltInPartitioning = true; protected boolean includeIndex; + protected boolean generate = false; + protected boolean generateInit = false; + private boolean keepLastInit = true; /** * Create for offline migration generation. @@ -123,12 +132,66 @@ public void setServerConfig(DatabaseConfig config) { if (constraintNaming == null) { this.constraintNaming = databaseConfig.getConstraintNaming(); } + if (databasePlatform == null) { + this.databasePlatform = databaseConfig.getDatabasePlatform(); + } Properties properties = config.getProperties(); if (properties != null) { - PropertiesWrapper props = new PropertiesWrapper("ebean", config.getName(), properties, null); + PropertiesWrapper props = new PropertiesWrapper("ebean", config.getName(), properties, config.getClassLoadConfig()); migrationPath = props.get("migration.migrationPath", migrationPath); migrationInitPath = props.get("migration.migrationInitPath", migrationInitPath); pathToResources = props.get("migration.pathToResources", pathToResources); + addForeignKeySkipCheck = props.getBoolean("migration.addForeignKeySkipCheck", addForeignKeySkipCheck); + applyPrefix = props.get("migration.applyPrefix",applyPrefix); + databasePlatform = props.createInstance(DatabasePlatform.class, "migration.databasePlatform", databasePlatform); + generatePendingDrop = props.get("migration.generatePendingDrop", generatePendingDrop); + includeBuiltInPartitioning = props.getBoolean("migration.includeBuiltInPartitioning", includeBuiltInPartitioning); + includeGeneratedFileComment = props.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment); + includeIndex = props.getBoolean("migration.includeIndex", includeIndex); + lockTimeoutSeconds = props.getInt("migration.lockTimeoutSeconds", lockTimeoutSeconds); + logToSystemOut = props.getBoolean("migration.logToSystemOut", logToSystemOut); + modelPath = props.get("migration.modelPath", modelPath); + modelSuffix = props.get("migration.modelSuffix", modelSuffix); + name = props.get("migration.name", name); + online = props.getBoolean("migration.online", online); + vanillaPlatform = props.getBoolean("migration.vanillaPlatform", vanillaPlatform); + version = props.get("migration.version", version); + generate = props.getBoolean("migration.generate", generate); + generateInit = props.getBoolean("migration.generateInit", generateInit); + // header & strictMode must be configured at DatabaseConfig level + parsePlatforms(props, config); + } + } + + protected void parsePlatforms(PropertiesWrapper props, DatabaseConfig config) { + String platforms = props.get("migration.platforms"); + if (platforms == null || platforms.isEmpty()) { + return; + } + String[] tmp = StringHelper.splitNames(platforms); + for (String plat : tmp) { + DatabasePlatform dbPlatform; + String platformName = plat; + String platformPrefix = null; + int pos = plat.indexOf('='); + if (pos != -1) { + platformName = plat.substring(0, pos); + platformPrefix = plat.substring(pos + 1); + } + + if (platformName.indexOf('.') == -1) { + // parse platform as enum value + Platform platform = Enum.valueOf(Platform.class, platformName.toUpperCase()); + dbPlatform = platform(platform); + } else { + // parse platform as class + dbPlatform = (DatabasePlatform) config.getClassLoadConfig().newInstance(platformName); + } + if (platformPrefix == null) { + platformPrefix = dbPlatform.getPlatform().name().toLowerCase(); + } + + addDatabasePlatform(dbPlatform, platformPrefix); } } @@ -319,7 +382,18 @@ private String generateMigrationFor(boolean initMigration) throws IOException { } String pendingVersion = generatePendingDrop(); - if (pendingVersion != null) { + if ("auto".equals(pendingVersion)) { + StringJoiner sj = new StringJoiner(","); + String diff = generateDiff(request); + if (diff != null) { + sj.add(diff); + request = createRequest(initMigration); + } + for (String pendingDrop : request.getPendingDrops()) { + sj.add(generatePendingDrop(request, pendingDrop)); + } + return sj.length() == 0 ? null : sj.toString(); + } else if (pendingVersion != null) { return generatePendingDrop(request, pendingVersion); } else { return generateDiff(request); @@ -376,6 +450,7 @@ private void configurePlatforms() { private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException { if (dbPlatform != null) { if (tablePartitioning && includeBuiltInPartitioning) { + generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning(), false); } // skip built-in migration stored procedures based on isUseMigrationStoredProcedures @@ -384,6 +459,7 @@ private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, bo } } + private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl, boolean checkSkip) throws IOException { if (extraDdl != null) { List ddlScript = extraDdl.getDdlScript(); @@ -554,7 +630,7 @@ private String generateMigration(Request request, Migration dbMigration, String return null; } else { if (!platforms.isEmpty()) { - writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir); + writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir, request.initMigration && keepLastInit); } else if (databasePlatform != null) { // writer needs the current model to provide table/column details for @@ -634,12 +710,17 @@ private String toUnderScore(String name) { /** * Write any extra platform ddl. */ - private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException { + private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath, boolean clear) throws IOException { DdlOptions options = new DdlOptions(addForeignKeySkipCheck); for (Pair pair : platforms) { DdlWrite writer = new DdlWrite(new MConfiguration(), currentModel.read(), options); PlatformDdlWriter platformWriter = createDdlWriter(pair.platform); File subPath = platformWriter.subPath(writePath, pair.prefix); + if (clear) { + for (File existing : subPath.listFiles()) { + existing.delete(); + } + } platformWriter.processMigration(dbMigration, writer, subPath, fullVersion); } } @@ -657,7 +738,7 @@ private boolean writeMigrationXml(Migration dbMigration, File resourcePath, Stri if (file.exists()) { return false; } - String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null; + String comment = includeGeneratedFileComment ? GENERATED_COMMENT : null; MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment); xmlWriter.write(dbMigration, file); return true; @@ -675,6 +756,7 @@ private void setDefaults() { databasePlatform = server.databasePlatform(); } if (databaseConfig != null) { + // FIXME: Copy header and StrictMode to databaseConfig if (strictMode != null) { databaseConfig.setDdlStrictMode(strictMode); } @@ -749,15 +831,20 @@ public File migrationDirectory() { * Return the file path to write the xml and sql to. */ File migrationDirectory(boolean initMigration) { - // path to src/main/resources in typical maven project - File resourceRootDir = new File(pathToResources); - if (!resourceRootDir.exists()) { - String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath()); - throw new UnknownResourcePathException(msg); - } - String resourcePath = migrationPath(initMigration); + Location resourcePath = migrationPath(initMigration); // expect to be a path to something like - src/main/resources/dbmigration - File path = new File(resourceRootDir, resourcePath); + File path; + if (resourcePath.isClassPath()) { + // path to src/main/resources in typical maven project + File resourceRootDir = new File(pathToResources); + if (!resourceRootDir.exists()) { + String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath()); + throw new UnknownResourcePathException(msg); + } + path = new File(resourceRootDir, resourcePath.path()); + } else { + path = new File(resourcePath.path()); + } if (!path.exists()) { if (!path.mkdirs()) { logInfo("Warning - Unable to ensure migration directory exists at %s", path.getAbsolutePath()); @@ -766,8 +853,9 @@ File migrationDirectory(boolean initMigration) { return path; } - private String migrationPath(boolean initMigration) { - return initMigration ? migrationInitPath : migrationPath; + private Location migrationPath(boolean initMigration) { + // remove classpath: or filesystem: prefix + return new Location(initMigration ? migrationInitPath : migrationPath); } /** diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java index f7e8035490..17e6a3b26c 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java @@ -141,7 +141,7 @@ private String translate(String ddl, String tableName, String columnName, String private void handleStrictError(String tableName, String columnName) { if (strictMode) { - String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or specify dbMigration.setStrictMode(false)"; + String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or disable strict mode for migration"; throw new IllegalArgumentException(message); } } @@ -207,6 +207,7 @@ public void generate(DdlWrite writer, CreateTable createTable) { writeInlineForeignKeys(apply, createTable); } apply.newLine().append(")"); + addTableTableSpaces(apply, createTable); addTableStorageEngine(apply, createTable); addTableCommentInline(apply, createTable); diff --git a/ebean-ddl-generator/src/main/java/module-info.java b/ebean-ddl-generator/src/main/java/module-info.java index 1a7ef332e4..e22d066a08 100644 --- a/ebean-ddl-generator/src/main/java/module-info.java +++ b/ebean-ddl-generator/src/main/java/module-info.java @@ -1,9 +1,15 @@ module io.ebean.ddl.generator { + + uses io.ebean.dbmigration.DbMigration; + uses io.ebean.plugin.Plugin; + uses io.ebean.config.dbplatform.DatabasePlatformProvider; + + exports io.ebean.dbmigration; + provides io.ebean.plugin.Plugin with io.ebeaninternal.dbmigration.DbMigrationPlugin,io.ebeaninternal.dbmigration.DdlPlugin; provides io.ebean.dbmigration.DbMigration with io.ebeaninternal.dbmigration.DefaultDbMigration; - provides io.ebeaninternal.api.SpiDdlGeneratorProvider with io.ebeaninternal.dbmigration.DdlGeneratorProvider; requires transitive io.ebean.ddl.runner; requires transitive io.ebean.core; @@ -11,8 +17,9 @@ requires io.ebean.core.type; requires io.ebean.migration; - uses io.ebean.dbmigration.DbMigration; // support existing tests exports io.ebeaninternal.extraddl.model to io.ebean.test; + opens io.ebeaninternal.extraddl.model to java.xml.bind; + opens io.ebeaninternal.dbmigration.migration to java.xml.bind; } diff --git a/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebean.plugin.Plugin b/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebean.plugin.Plugin new file mode 100644 index 0000000000..8a340c377a --- /dev/null +++ b/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebean.plugin.Plugin @@ -0,0 +1,2 @@ +io.ebeaninternal.dbmigration.DdlPlugin +io.ebeaninternal.dbmigration.DbMigrationPlugin diff --git a/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebeaninternal.api.SpiDdlGeneratorProvider b/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebeaninternal.api.SpiDdlGeneratorProvider deleted file mode 100644 index bfef4384b4..0000000000 --- a/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebeaninternal.api.SpiDdlGeneratorProvider +++ /dev/null @@ -1 +0,0 @@ -io.ebeaninternal.dbmigration.DdlGeneratorProvider diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java index 3bd677a7ef..035b905830 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java @@ -10,10 +10,10 @@ public class MChecksumTest { @Test public void calculate() { - File file = new File("src/test/resources/dbmigration/index/1.0__hello.sql"); + File file = new File("src/test/resources/dbmigration/index-special-chars/1.0__hello.sql"); assertThat(file).exists(); - assertThat(MChecksum.calculate(file)).isEqualTo(907060870); + assertThat(MChecksum.calculate(file)).isEqualTo(1859426839); } @Test diff --git a/ebean-ddl-generator/src/test/resources/application-test.properties b/ebean-ddl-generator/src/test/resources/application-test.properties index dfcb052ab0..af973bb3a5 100644 --- a/ebean-ddl-generator/src/test/resources/application-test.properties +++ b/ebean-ddl-generator/src/test/resources/application-test.properties @@ -5,32 +5,12 @@ datasource.default=h2 datasource.h2.username=sa datasource.h2.password= -datasource.h2.url=jdbc:h2:mem:h2AutoTune +datasource.h2.url=jdbc:h2:mem:h2AutoTune;NON_KEYWORDS=KEY,VALUE + +datasource.db2.username=migtest +datasource.db2.password=migtest +datasource.db2.url=jdbc:db2://localhost:50005/migtest datasource.pg.username=sa datasource.pg.password= datasource.pg.url=jdbc:h2:mem:h2AutoTune - -# parameters for migration test -datasource.migrationtest.username=SA -datasource.migrationtest.password=SA -datasource.migrationtest.url=jdbc:h2:mem:migration -ebean.migrationtest.applyPrefix=V -ebean.migrationtest.ddl.generate=false -ebean.migrationtest.ddl.run=false -ebean.migrationtest.ddl.header=-- Migrationscripts for ebean unittest -ebean.migrationtest.migration.appName=migrationtest -ebean.migrationtest.migration.migrationPath=dbmigration/migrationtest -ebean.migrationtest.migration.strict=true - -# parameters for migration test -datasource.migrationtest-history.username=SA -datasource.migrationtest-history.password=SA -datasource.migrationtest-history.url=jdbc:h2:mem:migration -ebean.migrationtest-history.applyPrefix=V -ebean.migrationtest-history.ddl.generate=false -ebean.migrationtest-history.ddl.run=false -ebean.migrationtest-history.ddl.header=-- Migrationscripts for ebean unittest DbMigrationDropHistoryTest -ebean.migrationtest-history.migration.appName=migrationtest-history -ebean.migrationtest-history.migration.migrationPath=dbmigration/migrationtest-history -ebean.migrationtest-history.migration.strict=true diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml index 8c60176730..265859aa09 100644 --- a/ebean-externalmapping-api/pom.xml +++ b/ebean-externalmapping-api/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean external mapping api diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index dff67f11b3..929bf882a5 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -4,12 +4,12 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT - scm:git:git@github.com:ebean-orm/ebean.git - HEAD + scm:git:git@github.com:FOCONIS/ebean.git + ebean-parent-13.6.4-FOC1 ebean external mapping xml @@ -28,7 +28,7 @@ io.ebean ebean-externalmapping-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -61,21 +61,21 @@ io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-ddl-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-kotlin/pom.xml b/ebean-kotlin/pom.xml index 72fce9834a..ac25402bd4 100644 --- a/ebean-kotlin/pom.xml +++ b/ebean-kotlin/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-kotlin @@ -28,7 +26,7 @@ io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided @@ -50,7 +48,7 @@ io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index 0117b699a5..2f73275211 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean postgis @@ -22,14 +22,14 @@ io.ebean ebean-platform-postgres - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided @@ -73,7 +73,7 @@ io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 9f531ac998..6def789a13 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean querybean @@ -17,7 +17,7 @@ io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided @@ -63,21 +63,21 @@ io.ebean ebean-ddl-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean querybean-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index eb8a2f6c55..e17a465717 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-redis @@ -22,35 +22,35 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean querybean-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index bf0d252364..ecd26ae8bb 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean test @@ -29,20 +29,20 @@ io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT provided io.ebean ebean-ddl-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -106,14 +106,14 @@ io.ebean ebean-platform-all - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean - ebean-migration - 12.13.0 + ebean-dbmigration-runner + 13.6.4-FOC2-SNAPSHOT test diff --git a/ebean-test/src/main/java/module-info.java b/ebean-test/src/main/java/module-info.java index a23d1e65b8..5d86859fb6 100644 --- a/ebean-test/src/main/java/module-info.java +++ b/ebean-test/src/main/java/module-info.java @@ -1,5 +1,5 @@ - -module io.ebean.test { +// module must be open, so tests will pass +open module io.ebean.test { exports io.ebean.test; exports io.ebean.test.config; diff --git a/ebean-test/src/test/java/io/ebean/test/config/TestServerOffline.java b/ebean-test/src/test/java/io/ebean/test/config/TestServerOffline.java new file mode 100644 index 0000000000..c778c870a1 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/test/config/TestServerOffline.java @@ -0,0 +1,154 @@ +package io.ebean.test.config; + + +import io.ebean.Database; +import io.ebean.DatabaseFactory; +import io.ebean.annotation.Platform; +import io.ebean.config.DatabaseConfig; +import io.ebean.datasource.DataSourceAlert; +import io.ebean.datasource.DataSourceInitialiseException; +import io.ebean.xtest.ForPlatform; + +import io.ebean.xtest.base.PlatformCondition; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.tests.model.basic.EBasicVer; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ExtendWith(PlatformCondition.class) +public class TestServerOffline { + + @Test + @ForPlatform({Platform.H2}) + public void testOffline_default() throws SQLException { + + String url = "jdbc:h2:mem:testoffline1"; + try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) { + Properties props = props(url); + DatabaseConfig config = config(props); + + assertThatThrownBy(() -> DatabaseFactory.create(config)) + .isInstanceOf(DataSourceInitialiseException.class); + } + + } + + private static class LazyDatasourceInitializer implements DataSourceAlert { + + public Database server; + + private boolean initialized; + + @Override + public void dataSourceUp(DataSource dataSource) { + if (!initialized) { + initDatabase(); + } + } + + public synchronized void initDatabase() { + if (!initialized) { + server.start(); + initialized = true; + } + } + + @Override + public void dataSourceDown(DataSource dataSource, SQLException reason) {} + + @Override + public void dataSourceWarning(DataSource dataSource, String msg) {} + + } + + @Test + @ForPlatform({Platform.H2}) + public void testOffline_recovery() throws SQLException { + + String url = "jdbc:h2:mem:testoffline3"; + try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) { + + Properties props = props(url); + + // to bring up ebean without a database, we must disable various things + // that happen on startup + props.setProperty("datasource.h2_offline.failOnStart", "false"); + props.setProperty("ebean.h2_offline.skipDataSourceCheck", "true"); + props.setProperty("ebean.h2_offline.skipStart", "true"); + DatabaseConfig config = config(props); + + LazyDatasourceInitializer alert = new LazyDatasourceInitializer() ; + config.getDataSourceConfig().setAlert(alert); + config.getDataSourceConfig().setHeartbeatFreqSecs(1); + + Database h2Offline = DatabaseFactory.create(config); + alert.server = h2Offline; + assertThat(h2Offline).isNotNull(); + // DB is online now in offline mode + + // Accessing the DB will throw a PE + assertThatThrownBy(() -> alert.initDatabase()) + .isInstanceOf(PersistenceException.class) + .hasMessageContaining("Failed to obtain connection to run DDL"); + + assertThatThrownBy(() -> h2Offline.find(EBasicVer.class).findCount()).isInstanceOf(PersistenceException.class); + + // so - reset the password so that the server can reconnect + try (Statement stmt = bootup.createStatement()) { + stmt.execute("alter user sa set password 'sa'"); + } + + assertThat(alert.initialized).isFalse(); + + // next access to ebean should bring DS online + h2Offline.find(EBasicVer.class).findCount(); + assertThat(alert.initialized).isTrue(); + + // check if server is working (ie ddl was run) + EBasicVer bean = new EBasicVer("foo"); + h2Offline.save(bean); + assertThat(h2Offline.find(EBasicVer.class).findCount()).isEqualTo(1); + h2Offline.delete(bean); + } + } + + private Properties props(String url) { + + Properties props = new Properties(); + + props.setProperty("datasource.h2_offline.username", "sa"); + props.setProperty("datasource.h2_offline.password", "sa"); + props.setProperty("datasource.h2_offline.url", url); + props.setProperty("datasource.h2_offline.driver", "org.h2.Driver"); + + props.setProperty("ebean.h2_offline.databasePlatformName", "h2"); + props.setProperty("ebean.h2_offline.ddl.extra", "false"); + + props.setProperty("ebean.h2_offline.ddl.generate", "true"); + props.setProperty("ebean.h2_offline.ddl.run", "true"); + + return props; + } + + private DatabaseConfig config(Properties props) { + DatabaseConfig config = new DatabaseConfig(); + config.setName("h2_offline"); + config.loadFromProperties(props); + config.setDefaultServer(false); + config.setRegister(false); + config.getClasses().add(EBasicVer.class); + return config; + } + +} diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java index 0dcc4afc28..2915657281 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java +++ b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java @@ -49,6 +49,39 @@ public void create_new_server_with_multi_tenancy_db() { + /** + * Tests using multi tenancy per database + */ + @Test + public void create_new_server_with_multi_tenancy_db_with_master() { + + String tenant = "customer"; + CurrentTenantProvider tenantProvider = Mockito.mock(CurrentTenantProvider.class); + Mockito.doReturn(tenant).when(tenantProvider).currentId(); + + TenantDataSourceProvider dataSourceProvider = Mockito.mock(TenantDataSourceProvider.class); + + DatabaseConfig config = new DatabaseConfig(); + + config.setName("h2"); + config.loadFromProperties(); + config.setRegister(false); + config.setDefaultServer(false); + config.setDdlGenerate(false); + config.setDdlRun(false); + + config.setTenantMode(TenantMode.DB_WITH_MASTER); + config.setCurrentTenantProvider(tenantProvider); + config.setTenantDataSourceProvider(dataSourceProvider); + + Mockito.doReturn(config.getDataSource()).when(dataSourceProvider).dataSource(tenant); + + config.setDatabasePlatform(new PostgresPlatform()); + + final Database database = DatabaseFactory.create(config); + database.shutdown(); + } + /** * Tests using multi tenancy per schema */ diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/ServerStartTest.java b/ebean-test/src/test/java/io/ebean/xtest/base/ServerStartTest.java new file mode 100644 index 0000000000..b0dfe787b9 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/xtest/base/ServerStartTest.java @@ -0,0 +1,15 @@ +package io.ebean.xtest.base; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import io.ebean.DatabaseFactory; + +public class ServerStartTest { + + @Test + @Disabled("run manually") + void testServerStartAndMigrateDb2() throws Exception { + DatabaseFactory.create("db2-migration").shutdown(); + } +} diff --git a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java index 4f5471c62c..df9850d57f 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java @@ -79,13 +79,11 @@ public static void main(String[] args) throws IOException { List pendingDrops = migration.getPendingDrops(); assertThat(pendingDrops).contains("1.1"); - //System.setProperty("ddl.migration.pendingDropsFor", "1.1"); migration.setGeneratePendingDrop("1.1"); assertThat(migration.generateMigration()).isEqualTo("1.2__dropsFor_1.1"); assertThatThrownBy(()->migration.generateMigration()) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("No 'pendingDrops'"); // subsequent call - System.clearProperty("ddl.migration.pendingDropsFor"); server.shutdown(); logger.info("end"); diff --git a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java index df70742162..bf217cb20c 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java @@ -4,6 +4,8 @@ import io.ebean.DatabaseFactory; import io.ebean.annotation.Platform; import io.ebean.config.DatabaseConfig; +import io.ebeaninternal.api.DbOffline; + import io.ebean.dbmigration.DbMigration; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -17,7 +19,6 @@ import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; /** @@ -96,7 +97,7 @@ public static void run(String pathToResources) throws IOException { config.getProperties().put("ebean.hana.generateUniqueDdl", "true"); // need to generate unique statements to prevent them from being filtered out as duplicates by the DdlRunner config.setPackages(Arrays.asList("misc.migration.v1_0")); - Database server = DatabaseFactory.create(config); + Database server = createServer(config); migration.setServer(server); // then we generate migration scripts for v1_0 @@ -107,43 +108,29 @@ public static void run(String pathToResources) throws IOException { // and now for v1_1 config.setPackages(Arrays.asList("misc.migration.v1_1")); server.shutdown(); - server = DatabaseFactory.create(config); + server = createServer(config); migration.setServer(server); - assertThat(migration.generateMigration()).isEqualTo("1.1"); - assertThat(migration.generateMigration()).isNull(); // subsequent call - - - - System.setProperty("ddl.migration.pendingDropsFor", "1.1"); - assertThat(migration.generateMigration()).isEqualTo("1.2__dropsFor_1.1"); - - assertThatThrownBy(()->migration.generateMigration()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("No 'pendingDrops'"); // subsequent call - - System.clearProperty("ddl.migration.pendingDropsFor"); + assertThat(migration.generateMigration()).isEqualTo("1.1,1.2__dropsFor_1.1"); assertThat(migration.generateMigration()).isNull(); // subsequent call // and now for v1_2 with config.setPackages(Arrays.asList("misc.migration.v1_2")); server.shutdown(); - server = DatabaseFactory.create(config); + server = createServer(config); migration.setServer(server); - assertThat(migration.generateMigration()).isEqualTo("1.3"); - assertThat(migration.generateMigration()).isNull(); // subsequent call - - - System.setProperty("ddl.migration.pendingDropsFor", "1.3"); - assertThat(migration.generateMigration()).isEqualTo("1.4__dropsFor_1.3"); - assertThatThrownBy(migration::generateMigration) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("No 'pendingDrops'"); // subsequent call - - System.clearProperty("ddl.migration.pendingDropsFor"); + assertThat(migration.generateMigration()).isEqualTo("1.3,1.4__dropsFor_1.3"); assertThat(migration.generateMigration()).isNull(); // subsequent call server.shutdown(); logger.info("end"); } + private static Database createServer(DatabaseConfig config) { + DbOffline.setGenerateMigration(); + Database server = DatabaseFactory.create(config); + DbOffline.reset(); + server.start(); + return server; + } + } diff --git a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java index 29da0d4190..a337fc1b3d 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java +++ b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java @@ -92,6 +92,10 @@ public void testRunMigration() throws IOException, SQLException { if (isSqlServer() || isMariaDB() || isMySql() || isHana()) { runScript("I__create_procs.sql"); } + + if(isDb2()) { + runScript("I__create_tablespaces.sql"); + } if(isDb2()) { runScript("I__create_tablespaces.sql"); diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java index 08f2bea9e8..cd6f2cc40e 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java @@ -283,6 +283,11 @@ public int saveAll(Object... beans) throws OptimisticLockException { return 0; } + @Override + public void visitSave(Object start, PersistVisitor visitor) { + + } + @Override public boolean delete(Object bean) throws OptimisticLockException { return false; @@ -632,4 +637,9 @@ public void loadBeanL2(EntityBeanIntercept ebi) { public void loadBean(EntityBeanIntercept ebi) { } + + @Override + public void start() { + + } } diff --git a/ebean-test/src/test/java/org/tests/basic/TPersistVisitor.java b/ebean-test/src/test/java/org/tests/basic/TPersistVisitor.java new file mode 100644 index 0000000000..0d61a13f8a --- /dev/null +++ b/ebean-test/src/test/java/org/tests/basic/TPersistVisitor.java @@ -0,0 +1,69 @@ +package org.tests.basic; + +import java.util.Collection; + +import io.ebean.DB; +import io.ebean.PersistVisitor; +import io.ebean.bean.EntityBean; +import io.ebean.plugin.Property; +/** + * Sample persist visitor that converts the visited beans in a XML-like structure + */ +public class TPersistVisitor implements PersistVisitor { + + private final StringBuilder sb; + private final String indent; + private final String tag; + private boolean empty = true; + + public TPersistVisitor() { + this(new StringBuilder(), "", "root"); + } + private TPersistVisitor(StringBuilder sb, String indent, String tag) { + this.sb = sb; + this.indent = indent; + this.tag = tag; + this.sb.append(indent).append('<').append(tag); + } + + TPersistVisitor newVisitor(String tag) { + if (empty) { + sb.append(">\n"); + empty = false; + } + return new TPersistVisitor(sb, indent + " ", tag); + } + + @Override + public void visitEnd() { + if (empty) { + sb.append("/>\n"); + } else { + this.sb.append(indent).append("\n"); + } + } + + TPersistVisitor attr(String attr, Object value) { + sb.append(' ').append(attr).append('=').append('\'').append(value).append('\''); + return this; + } + + public TPersistVisitor visitBean(EntityBean bean) { + return newVisitor("bean").attr("type", bean.getClass().getSimpleName()).attr("newOrDirty", DB.beanState(bean).isNewOrDirty()); + } + + @Override + public PersistVisitor visitProperty(Property prop) { + return newVisitor("property").attr("name", prop.name()); + } + + @Override + public PersistVisitor visitCollection(Collection collection) { + return newVisitor("collection").attr("size", collection.size()); + } + + @Override + public String toString() { + return sb.toString(); + } + } \ No newline at end of file diff --git a/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java b/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java index f3dc340dc0..ea35c50f40 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java +++ b/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java @@ -3,12 +3,15 @@ import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Query; + +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; import org.tests.model.basic.MRole; import org.tests.model.basic.MUser; public class TestM2MCascadeOne extends BaseTestCase { - + @Test public void test() { @@ -30,8 +33,31 @@ public void test() { u1.addRole(r0); u1.addRole(r1); + TPersistVisitor tv = new TPersistVisitor(); + DB.visitSave(u1, tv); + assertThat(tv.toString()).isEqualTo("\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"); + DB.save(u1); - + + u1 = DB.find(MUser.class, u.getUserid()); + + tv = new TPersistVisitor(); + DB.visitSave(u1, tv); + // collection is unloaded + assertThat(tv.toString()).isEqualTo("\n" + + " \n" + + "\n"); + + } @Test diff --git a/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java b/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java index c8a374302f..f4b35ae275 100644 --- a/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java +++ b/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java @@ -5,27 +5,51 @@ import org.junit.jupiter.api.Test; import org.tests.model.basic.ResetBasicData; import org.tests.model.interfaces.Address; +import org.tests.model.interfaces.ExtPerson1and2; import org.tests.model.interfaces.IAddress; +import org.tests.model.interfaces.IExtPerson1; +import org.tests.model.interfaces.IExtPerson2; import org.tests.model.interfaces.IPerson; import org.tests.model.interfaces.Person; +import static org.assertj.core.api.Assertions.assertThat; + public class TestManyOneInterface extends BaseTestCase { @Test - public void test() { - + public void testEntityOverrideEntityImplements() { ResetBasicData.reset(); - IAddress a = new Address("hello"); + IAddress a = DB.getDefault().createEntityBean(IAddress.class); + assertThat(a).isInstanceOf(Address.class); - IPerson p = new Person(); + IPerson p = DB.getDefault().createEntityBean(Person.class); + assertThat(p).isInstanceOf(ExtPerson1and2.class); + p = DB.getDefault().pluginApi().createEntityBean(IPerson.class); + assertThat(p).isInstanceOf(ExtPerson1and2.class); p.setDefaultAddress(a); - DB.save(a); + IAddress ea1 = DB.getDefault().createEntityBean(IAddress.class); + IAddress ea2 = DB.getDefault().createEntityBean(IAddress.class); + + p.getExtraAddresses().add(ea1); + p.getExtraAddresses().add(ea2); + DB.save(p); - //Assert.assertTrue(); + IAddress a2 = DB.find(IAddress.class, a.getOid()); + IPerson p2 = DB.find(IPerson.class, p.getOid()); + + assertThat(a2).isInstanceOf(Address.class); + assertThat(p2).isNotNull().isInstanceOf(ExtPerson1and2.class); + assertThat(p2.getDefaultAddress()).isInstanceOf(Address.class); + + // some more checks + IExtPerson1 pe1 = DB.getDefault().pluginApi().createEntityBean(IExtPerson1.class); + IExtPerson2 pe2 = DB.getDefault().pluginApi().createEntityBean(IExtPerson2.class); + assertThat(pe1).isInstanceOf(ExtPerson1and2.class); + assertThat(pe2).isInstanceOf(ExtPerson1and2.class); } } diff --git a/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java b/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java index feff29a687..7024dddbf5 100644 --- a/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java +++ b/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java @@ -78,6 +78,7 @@ public void findSingleAttributeList() { assertThat(colA_Second).isSameAs(colA_first); + List colA_NotDistinct = DB .find(EColAB.class) .setUseQueryCache(true) @@ -91,12 +92,12 @@ public void findSingleAttributeList() { // ensure that findCount & findSingleAttribute use different // slots in cache. If not a "Cannot cast List to int" should happen. int count = DB - .find(EColAB.class) - .setUseQueryCache(true) - .select("columnA") - .where() - .eq("columnB", "SingleAttribute") - .findCount(); + .find(EColAB.class) + .setUseQueryCache(true) + .select("columnA") + .where() + .eq("columnB", "SingleAttribute") + .findCount(); assertThat(count).isEqualTo(2); } @@ -305,7 +306,7 @@ public void testReadOnlyFind() { assertSame(list, list2B); List list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where() - .ilike("name", "Rob").findList(); + .ilike("name", "Rob").findList(); assertNotSame(list, list3); BeanCollection bc3 = (BeanCollection) list3; @@ -349,10 +350,10 @@ public void findIds() { // and now, ensure that we hit the database LoggedSql.start(); colA_second = DB.find(EColAB.class) - .setUseQueryCache(CacheMode.PUT) - .where() - .eq("columnB", "someId") - .findIds(); + .setUseQueryCache(CacheMode.PUT) + .where() + .eq("columnB", "someId") + .findIds(); sql = LoggedSql.stop(); assertThat(sql).hasSize(1); @@ -361,15 +362,15 @@ public void findIds() { @Test public void findCountDifferentQueriesBit() { DB.getDefault().pluginApi().cacheManager().clearAll(); - differentFindCount(q->q.bitwiseAny("id",1), q->q.bitwiseAny("id",0)); - differentFindCount(q->q.bitwiseAll("id",1), q->q.bitwiseAll("id",0)); + differentFindCount(q -> q.bitwiseAny("id", 1), q -> q.bitwiseAny("id", 0)); + differentFindCount(q -> q.bitwiseAll("id", 1), q -> q.bitwiseAll("id", 0)); // differentFindCount(q->q.bitwiseNot("id",1), q->q.bitwiseNot("id",0)); NOT 1 == AND 1 = 0 - differentFindCount(q->q.bitwiseAnd("id",1, 0), q->q.bitwiseAnd("id",1, 1)); + differentFindCount(q -> q.bitwiseAnd("id", 1, 0), q -> q.bitwiseAnd("id", 1, 1)); - differentFindCount(q->q.bitwiseAnd("id",2, 0), q->q.bitwiseAnd("id",4, 0)); - differentFindCount(q->q.bitwiseAnd("id",2, 1), q->q.bitwiseAnd("id",4, 1)); + differentFindCount(q -> q.bitwiseAnd("id", 2, 0), q -> q.bitwiseAnd("id", 4, 0)); + differentFindCount(q -> q.bitwiseAnd("id", 2, 1), q -> q.bitwiseAnd("id", 4, 1)); // Will produce hash collision - differentFindCount(q->q.bitwiseAnd("id",10, 0), q->q.bitwiseAnd("id",0, 928210)); + differentFindCount(q -> q.bitwiseAnd("id", 10, 0), q -> q.bitwiseAnd("id", 0, 928210)); } diff --git a/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java b/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java index 35f0bf271e..ce37db6dfd 100644 --- a/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java +++ b/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java @@ -5,11 +5,13 @@ import io.ebean.DataIntegrityException; import io.ebean.xtest.IgnorePlatform; import io.ebean.annotation.Platform; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertThrows; public class TestInsertDataIntegrityException extends BaseTestCase { @@ -26,7 +28,10 @@ public void insert_invalidForeignKey() { Order order = new Order(); order.setStatus(Order.Status.NEW); order.setCustomer(invalidCustomer); + assertThatThrownBy(() -> DB.save(order)) + .isInstanceOf(DataIntegrityException.class) + .hasMessageContaining("insert into o_order") + .hasMessageContaining(",900000"); - assertThrows(DataIntegrityException.class, () -> DB.save(order)); } } diff --git a/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java b/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java index 27434601ef..0e6c7d60fb 100644 --- a/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java +++ b/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java @@ -2,19 +2,33 @@ import io.ebean.xtest.BaseTestCase; import io.ebean.DB; +import io.ebean.Database; +import io.ebean.DatabaseFactory; +import io.ebean.ValuePair; import io.ebean.xtest.ForPlatform; +import io.ebean.annotation.MutationDetection; import io.ebean.annotation.Platform; +import io.ebean.config.DatabaseConfig; import io.ebean.test.LoggedSql; import io.ebean.text.TextException; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.tests.model.json.EBasicJsonList; import org.tests.model.json.PlainBean; import javax.persistence.PersistenceException; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class TestDbJson_List extends BaseTestCase { @@ -231,4 +245,43 @@ public void testNullToEmpty() { assertThat(bean.getTags()).isEmpty(); assertThat(bean.getBeanMap()).isEmpty(); } + + @Test + @ForPlatform(Platform.H2) + @Disabled("breaks everything") + public void testDirtyValues() { + DatabaseConfig config = new DatabaseConfig(); + config.loadFromProperties(); + config.setDefaultServer(true); + config.setRegister(true); + config.setDdlRun(false); + config.setJsonMutationDetection(MutationDetection.SOURCE); + Database db = DatabaseFactory.create(config); + try { + assertThat(db).isNotNull(); + + EBasicJsonList bean = new EBasicJsonList(); + bean.getTags().add("aa"); + bean.getTags().add("bb"); + + db.save(bean); + bean = db.find(EBasicJsonList.class, bean.getId()); + + bean.getTags().add("cc"); + final Map dirtyValues = db.beanState(bean).dirtyValues(); + assertThat(dirtyValues).containsOnlyKeys("tags"); + + final ValuePair diff = dirtyValues.get("tags"); + assertThat(diff.getOldValue()).isInstanceOf(List.class).asList() + .containsExactly("aa", "bb"); + assertThat(diff.getNewValue()).isInstanceOf(List.class).asList() + .containsExactly("aa", "bb", "cc"); + } finally { + if (db != null) { + db.shutdown(); + } + } + + + } } diff --git a/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java b/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java new file mode 100644 index 0000000000..010d378603 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java @@ -0,0 +1,88 @@ +package org.tests.model.basic; + +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZonedDateTime; +import java.util.Calendar; + +import javax.annotation.Nullable; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class MDateTime { + + @Id + private Integer id; + + @Nullable + private LocalTime localTime; + + @Nullable + private LocalDateTime localDateTime; + + @Nullable + private LocalDate localDate; + + @Nullable + private OffsetDateTime offsetDateTime; + + @Nullable + private ZonedDateTime zonedDateTime; + + @Nullable + private YearMonth propYearMonth; + + @Nullable + private MonthDay propMonthDay; + + @Nullable + private Year propYear; + + @Nullable + private Instant propInstant; + + @Nullable + private Calendar propCalendar; + + @Nullable + private Timestamp propTimestamp; + + @Nullable + private java.sql.Date sqlDate; + + @Nullable + private java.sql.Time sqlTime; + + @Nullable + private java.util.Date utilDate; + + @Nullable + private org.joda.time.DateTime jodaDateTime; + + @Nullable + private org.joda.time.LocalDateTime jodaLocalDateTime; + + @Nullable + private org.joda.time.LocalDate jodaLocalDate; + + @Nullable + private org.joda.time.LocalTime jodaLocalTime; + + @Nullable + private org.joda.time.DateMidnight jodaDateMidnight; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/Address.java b/ebean-test/src/test/java/org/tests/model/interfaces/Address.java index 052ef63f87..454616feac 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/Address.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/Address.java @@ -1,10 +1,14 @@ package org.tests.model.interfaces; +import io.ebean.annotation.ext.EntityImplements; + import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.ManyToOne; import javax.persistence.Version; @Entity +@EntityImplements(IAddress.class) public class Address implements IAddress { @Id @@ -15,6 +19,9 @@ public class Address implements IAddress { private String street; + @ManyToOne + private Person extraAddress; + public Address(String street) { this.street = street; } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1.java b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1.java new file mode 100644 index 0000000000..dc9fac9e09 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1.java @@ -0,0 +1,25 @@ +package org.tests.model.interfaces; + +import io.ebean.annotation.ext.EntityImplements; +import io.ebean.annotation.ext.EntityOverride; + +import javax.persistence.Entity; + +@Entity() +@EntityImplements(IExtPerson1.class) +@EntityOverride(priority = 30) +public class ExtPerson1 extends Person implements IExtPerson1 { + + private int myField1; + + @Override + public int getMyField1() { + return myField1; + } + + @Override + public void setMyField1(int myField1) { + this.myField1 = myField1; + } + +} diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1and2.java b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1and2.java new file mode 100644 index 0000000000..a6d2661fd8 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1and2.java @@ -0,0 +1,26 @@ +package org.tests.model.interfaces; + +import io.ebean.annotation.ext.EntityImplements; +import io.ebean.annotation.ext.EntityOverride; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity() +@Table(name = "person") +@EntityImplements(IExtPerson2.class) +@EntityOverride(priority = -30) +public class ExtPerson1and2 extends ExtPerson1 implements IExtPerson2 { + + private int myField2; + + @Override + public int getMyField2() { + return myField2; + } + + @Override + public void setMyField2(int myField2) { + this.myField2 = myField2; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson2.java b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson2.java new file mode 100644 index 0000000000..670f73ad36 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson2.java @@ -0,0 +1,27 @@ +package org.tests.model.interfaces; + +import io.ebean.annotation.ext.EntityImplements; +import io.ebean.annotation.ext.EntityOverride; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@EntityOverride(priority = 20) +@EntityImplements(IExtPerson2.class) +@Table(name = "person") +public class ExtPerson2 extends Person implements IExtPerson2 { + + private int myField2; + + @Override + public int getMyField2() { + return myField2; + } + + @Override + public void setMyField2(int myField2) { + this.myField2 = myField2; + } + +} diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java b/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java index 78b6d20050..9097d23d76 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java @@ -1,6 +1,9 @@ package org.tests.model.interfaces; public interface IAddress { + + long getOid(); + String getStreet(); void setStreet(String s); diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson1.java b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson1.java new file mode 100644 index 0000000000..b81a5266d6 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson1.java @@ -0,0 +1,7 @@ +package org.tests.model.interfaces; + +public interface IExtPerson1 extends IPerson { + int getMyField1(); + + void setMyField1(int myField1); +} diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson2.java b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson2.java new file mode 100644 index 0000000000..49bf601339 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson2.java @@ -0,0 +1,7 @@ +package org.tests.model.interfaces; + +public interface IExtPerson2 extends IPerson { + int getMyField2(); + + void setMyField2(int myField2); +} diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java b/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java index 0d07ecc6b1..3c1366185d 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java @@ -1,7 +1,16 @@ package org.tests.model.interfaces; +import java.util.List; + public interface IPerson { + + long getOid(); + IAddress getDefaultAddress(); void setDefaultAddress(IAddress address); + + List getExtraAddresses(); + + List getAddressLinks(); } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/Person.java b/ebean-test/src/test/java/org/tests/model/interfaces/Person.java index 5edbb68c7c..a2e5e8dafd 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/Person.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/Person.java @@ -1,11 +1,19 @@ package org.tests.model.interfaces; +import io.ebean.annotation.ext.EntityImplements; + +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Version; +import java.util.ArrayList; +import java.util.List; @Entity +@EntityImplements(IPerson.class) public class Person implements IPerson { @Id private long oid; @@ -13,9 +21,15 @@ public class Person implements IPerson { @Version private int version; - @ManyToOne(targetEntity = Address.class) + @ManyToOne(cascade = CascadeType.PERSIST) private IAddress defaultAddress; + @OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true) + private List extraAddresses = new ArrayList<>(); + + @ManyToMany(cascade = CascadeType.PERSIST) + private List addressLinks = new ArrayList<>(); + @Override public IAddress getDefaultAddress() { return defaultAddress; @@ -42,4 +56,14 @@ public void setVersion(int version) { this.version = version; } + @Override + public List getExtraAddresses() { + return extraAddresses; + } + + @Override + public List getAddressLinks() { + return addressLinks; + } + } diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java b/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java index aa7030ef1d..f2ed05c573 100644 --- a/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java +++ b/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java @@ -30,7 +30,7 @@ public void test() { private Person setup() { Address address = new Address("street"); DB.save(address); - Person person = new Person(); + Person person = DB.getDefault().createEntityBean(Person.class); person.setDefaultAddress(address); DB.save(person); return person; diff --git a/ebean-test/src/test/java/org/tests/model/tevent/CustomFormulaAnnotationParser.java b/ebean-test/src/test/java/org/tests/model/tevent/CustomFormulaAnnotationParser.java new file mode 100644 index 0000000000..3f392aed06 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/tevent/CustomFormulaAnnotationParser.java @@ -0,0 +1,61 @@ +package org.tests.model.tevent; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import io.ebean.annotation.Formula; +import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.plugin.CustomDeployParser; +import io.ebean.plugin.DeployBeanDescriptorMeta; +import io.ebean.plugin.DeployBeanPropertyMeta; +import io.ebean.util.AnnotationUtil; +import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; + +/** + * Custom Annotation parser which parses @Count annotation + * + * @author Roland Praml, FOCONIS AG + */ +public class CustomFormulaAnnotationParser implements CustomDeployParser { + + private int counter; + + + @Target(FIELD) + @Retention(RUNTIME) + @Formula(select="TODO", join = "TODO") // meta-formula + public @interface Count { + String value(); + } + + + + @Override + public void parse(final DeployBeanDescriptorMeta descriptor, final DatabasePlatform databasePlatform) { + for (DeployBeanPropertyMeta prop : descriptor.propertiesAll()) { + readField(descriptor, prop); + } + } + + private void readField(DeployBeanDescriptorMeta descriptor, DeployBeanPropertyMeta prop) { + Count countAnnot = AnnotationUtil.get(prop.getField(), Count.class); + if (countAnnot != null) { + // @Count found, so build the (complex) count formula + DeployBeanPropertyAssocMany countProp = (DeployBeanPropertyAssocMany) descriptor.getBeanProperty(countAnnot.value()); + counter++; + String tmpTable = "f"+counter; + String sqlSelect = "coalesce(" + tmpTable + ".child_count, 0)"; + String parentId = countProp.getMappedBy() + "_id"; + String tableName = countProp.getBeanTable().getBaseTable(); + String sqlJoin = "left join (select " + parentId +", count(*) as child_count from " + tableName + " GROUP BY " + parentId + " )" + + " " + tmpTable + " on " + tmpTable + "." +parentId + " = ${ta}." + descriptor.idProperty().getDbColumn(); + prop.setSqlFormula(sqlSelect, sqlJoin); +// prop.setSqlFormula("f1.child_count", +// "join (select parent_id, count(*) as child_count from child_entity GROUP BY parent_id) f1 on f1.parent_id = ${ta}.id"); + } + } + +} diff --git a/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java b/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java index bcbad8ef2e..678ca9677c 100644 --- a/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java +++ b/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java @@ -42,6 +42,11 @@ public enum Status { @OneToMany(mappedBy = "event", cascade = CascadeType.ALL) List logs; + @CustomFormulaAnnotationParser.Count("logs") + //@Formula(select = "f1.child_count", + //join = "left join (select event_id, count(*) as child_count from tevent_many GROUP BY event_id ) as f1 on f1.event_id = ${ta}.id") + Long customFormula; + public TEventOne(String name, Status status) { this.name = name; this.status = status; @@ -64,6 +69,10 @@ public Long getCount() { return count; } + public Long getCustomFormula() { + return customFormula; + } + public BigDecimal getTotalUnits() { return totalUnits; } diff --git a/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java b/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java index 8636d48d6f..9dde14d07f 100644 --- a/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java +++ b/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java @@ -49,7 +49,7 @@ public void testBaseSelect() { List list = query.findList(); String sql = sqlOf(query, 5); - assertThat(sql).contains("select t0.id, t0.name, t0.status, t0.version, t0.event_id from tevent_one t0"); + assertThat(sql).contains("select t0.id, t0.name, t0.status, coalesce(f1.child_count, 0), t0.version, t0.event_id from tevent_one t0"); for (TEventOne eventOne : list) { // lazy loading on Aggregation properties diff --git a/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java b/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java index df9d0f3b06..9d492127b3 100644 --- a/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java +++ b/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java @@ -82,6 +82,112 @@ public void exampleUsage() { } } + @Test + public void oneToMany_normal() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .findSingleAttributeList(); + + assertThat(statusList).hasSize(7); + } + + @Test + public void oneToMany_notNull() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .where() + .isNotNull("orders.status") + .findSingleAttributeList(); + + assertThat(statusList).hasSize(5); + } + + @Test + public void oneToMany_orderBy() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .orderBy("orders.status") + .findSingleAttributeList(); + + assertThat(statusList).hasSize(7); + assertThat(statusList.get(0)).isEqualTo(null); + assertThat(statusList.get(6)).isEqualTo(Order.Status.COMPLETE); + } + + @Test + public void oneToMany_distinct() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .setDistinct(true) + .findSingleAttributeList(); + + assertThat(statusList).hasSize(4); + } + + @Test + public void oneToMany_distinct_orderBy() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .setDistinct(true) + .orderBy("orders.status") + .findSingleAttributeList(); + + assertThat(statusList).hasSize(4); + assertThat(statusList.get(0)).isEqualTo(null); + assertThat(statusList.get(3)).isEqualTo(Order.Status.COMPLETE); + } + + @Test + public void oneToMany_maxRows() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .setMaxRows(2) + .findSingleAttributeList(); + + assertThat(statusList).hasSize(2); + } + + @Test + public void oneToMany_maxRows_orderBy() { + + ResetBasicData.reset(); + + List statusList = + DB.find(Customer.class) + .fetch("orders", "status") + .setMaxRows(2) + .orderBy("orders.status") + .findSingleAttributeList(); + + assertThat(statusList).hasSize(2); + assertThat(statusList.get(0)).isEqualTo(null); + assertThat(statusList.get(1)).isEqualTo(Order.Status.NEW); + } + @Test public void exampleUsage_otherType() { diff --git a/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java b/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java index 17cf86356e..9f37c010ff 100644 --- a/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java +++ b/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java @@ -44,9 +44,12 @@ public void testMySqlColonEquals() throws SQLException { Transaction transaction = DB.beginTransaction(); + System.out.println(transaction.connection().getMetaData().getDriverName()); try { - if ("MariaDB connector/J".equals(transaction.connection().getMetaData().getDriverName())) { - return; // MariaDb only supports callable statements in the form "? = call function x(?)" + if ("MariaDB Connector/J".equals(transaction.connection().getMetaData().getDriverName())) { + // CHECKME: Should we report/fail if we are running mysql-tests with mariadb driver + // BTW: This happens with java 8 - the drivers in drivermanager are in different order + return; // MariaDB Connector/J only supports callable statements in the form "? = call function x(?)" } CallableSql callableSql = DB.createCallableSql("set @total = 0"); DB.getDefault().execute(callableSql); diff --git a/ebean-test/src/test/resources/ebean.properties b/ebean-test/src/test/resources/ebean.properties index 91c0d0c290..0a64c98084 100644 --- a/ebean-test/src/test/resources/ebean.properties +++ b/ebean-test/src/test/resources/ebean.properties @@ -196,6 +196,7 @@ datasource.hana.username=EBEAN_TEST datasource.hana.password=Eb3an_test datasource.hana.url=jdbc:sap://hxehost:39013/?databaseName=HXE #datasource.hana.driver=com.sap.db.jdbc.Driver +# # parameters for migration test datasource.migrationtest.username=SA @@ -207,6 +208,21 @@ ebean.migrationtest.ddl.run=false ebean.migrationtest.ddl.header=-- Migrationscripts for ebean unittest ebean.migrationtest.migration.appName=migrationtest ebean.migrationtest.migration.migrationPath=migrationtest/dbmigration +ebean.migrationtest.migration.migrationInitPath=migrationtest/dbinit +ebean.migrationtest.migration.strict=true +ebean.migrationtest.migration.generate=true +ebean.migrationtest.migration.run=false +ebean.migrationtest.migration.includeIndex=true +ebean.migrationtest.migration.generateInit=true +ebean.migrationtest.migration.generatePendingDrop=auto +ebean.migrationtest.migration.platforms=db2luw,h2,hsqldb,mysql,mysql55,mariadb,postgres,oracle,sqlite,sqlserver17,hana,yugabyte +#migration.migrationtest.db2luw.prefix=db2 +#migration.migrationtest.sqlserver17.prefix=sqlserver +dbmigration.platform.mariadb.useMigrationStoredProcedures=true +dbmigration.platform.mysql.useMigrationStoredProcedures=true + + + ebean.migrationtest.migration.strict=true # enable stored procedures f dbmigration.platform.mariadb.useMigrationStoredProcedures=true @@ -222,4 +238,17 @@ ebean.migrationtest-history.ddl.run=false ebean.migrationtest-history.ddl.header=-- Migrationscripts for ebean unittest DbMigrationDropHistoryTest ebean.migrationtest-history.migration.appName=migrationtest-history ebean.migrationtest-history.migration.migrationPath=migrationtest-history/dbmigration +ebean.migrationtest-history.migration.migrationInitPath=migrationtest-history/dbinit ebean.migrationtest-history.migration.strict=true + +# ServerStartTest - can we run the migrations and do we find the correct ones! +datasource.db2-migration.username=unit +datasource.db2-migration.password=test +datasource.db2-migration.url=jdbc:db2://127.0.0.1:50000/unit +ebean.db2-migration.ddl.generate=false +ebean.db2-migration.ddl.run=false +ebean.db2-migration.migration.run=true +ebean.db2-migration.databasePlatformName=db2luw +# workaround for https://github.com/ebean-orm/ebean-migration/issues/102 +ebean.db2-migration.migration.migrationPath=migrationtest/dbmigration/db2 +ebean.db2-migration.migration.migrationInitPath=migrationtest/dbinit/db2 \ No newline at end of file diff --git a/ebean-test/src/test/resources/extra-ddl.xml b/ebean-test/src/test/resources/extra-ddl.xml index 130de7fac6..44d49d1589 100644 --- a/ebean-test/src/test/resources/extra-ddl.xml +++ b/ebean-test/src/test/resources/extra-ddl.xml @@ -64,6 +64,15 @@ create index ix_ebasic_jmjb_gin2 on ebasic_json_map_json_b using gin(content jsonb_path_ops); + +delimiter $$ +BEGIN +IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN +call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA ); +END IF; +END;$$ + + delimiter $$ BEGIN @@ -101,4 +110,5 @@ END IF; END $$ + diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/R__db2_explain_tables.sql b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/R__db2_explain_tables.sql new file mode 100644 index 0000000000..b2539a5ea9 --- /dev/null +++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/R__db2_explain_tables.sql @@ -0,0 +1,8 @@ + +delimiter $$ +BEGIN +IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN +call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA ); +END IF; +END;$$ + \ No newline at end of file diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations index ad7785b1b8..7aa514bb3c 100644 --- a/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations +++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations @@ -3,5 +3,6 @@ -1187336846, 1.2__dropsFor_1.1.sql 2066110925, 1.3.sql -1713819679, 1.4__dropsFor_1.3.sql +-133543359, R__db2_explain_tables.sql 561281075, R__order_views.sql diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/R__db2_explain_tables.sql b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/R__db2_explain_tables.sql new file mode 100644 index 0000000000..b2539a5ea9 --- /dev/null +++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/R__db2_explain_tables.sql @@ -0,0 +1,8 @@ + +delimiter $$ +BEGIN +IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN +call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA ); +END IF; +END;$$ + \ No newline at end of file diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations index 1de746cf38..946e5f5866 100644 --- a/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations +++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations @@ -4,5 +4,6 @@ -1187336846, 1.2__dropsFor_1.1.sql -1088179698, 1.3.sql -1713819679, 1.4__dropsFor_1.3.sql +-133543359, R__db2_explain_tables.sql 561281075, R__order_views.sql diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/R__db2_explain_tables.sql b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/R__db2_explain_tables.sql new file mode 100644 index 0000000000..b2539a5ea9 --- /dev/null +++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/R__db2_explain_tables.sql @@ -0,0 +1,8 @@ + +delimiter $$ +BEGIN +IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN +call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA ); +END IF; +END;$$ + \ No newline at end of file diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations index 5eba36e5d0..e89edc651f 100644 --- a/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations +++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations @@ -3,5 +3,6 @@ -1187336846, 1.2__dropsFor_1.1.sql -1088179698, 1.3.sql -1713819679, 1.4__dropsFor_1.3.sql +-133543359, R__db2_explain_tables.sql 561281075, R__order_views.sql diff --git a/ebean-test/testconfig/ebean-oracle.properties b/ebean-test/testconfig/ebean-oracle.properties index 32b3af2491..24764943b2 100644 --- a/ebean-test/testconfig/ebean-oracle.properties +++ b/ebean-test/testconfig/ebean-oracle.properties @@ -1,3 +1,4 @@ ebean.test.platform=oracle ebean.test.dbName=test_eb +ebean.test.dbPassword=test datasource.default=oracle diff --git a/ebean-test/testplatforms.sh b/ebean-test/testplatforms.sh new file mode 100644 index 0000000000..1e19717c0e --- /dev/null +++ b/ebean-test/testplatforms.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# A small script, to run a certain test on all platforms +# invoke with ./testplatforms.sh -Dtest=DbMigrationTest +# Hint: in case of DbMigrationTest, you may disable ddl.run temporary + +# default H2 platform +set -e +mvn test "$@" + +mvn surefire:test -Dprops.file=testconfig/ebean-mysql.properties "$@" + +mvn surefire:test -Dprops.file=testconfig/ebean-mariadb.properties "$@" +mvn surefire:test -Dprops.file=testconfig/ebean-mariadb-10.3.properties "$@" + +mvn surefire:test -Dprops.file=testconfig/ebean-sqlserver17.properties "$@" +mvn surefire:test -Dprops.file=testconfig/ebean-sqlserver19.properties "$@" + +mvn surefire:test -Dprops.file=testconfig/ebean-postgres.properties "$@" + +#mvn surefire:test -Dprops.file=testconfig/ebean-oracle.properties "$@" + +#mvn surefire:test -Dprops.file=testconfig/ebean-sqlite.properties "$@" + +mvn surefire:test -Dprops.file=testconfig/ebean-hana.properties "$@" + +mvn surefire:test -Dprops.file=testconfig/ebean-db2.properties "$@" + +## Test ignored +## mvn surefire:test -Dprops.file=testconfig/ebean-yugabyte.properties "$@" + +## Scripts are not correct +## mvn surefire:test -Dprops.file=testconfig/ebean-cockroach.properties "$@" + +## Transactions are not supported +## mvn surefire:test -Dprops.file=testconfig/ebean-clickhouse.properties "$@" + +## I cannot start nuodb +## mvn surefire:test -Dprops.file=testconfig/ebean-nuodb.properties.properties "$@" + + diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml index c36475b2e7..686377f170 100644 --- a/kotlin-querybean-generator/pom.xml +++ b/kotlin-querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT kotlin querybean generator @@ -29,7 +29,7 @@ io.ebean ebean-querybean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test @@ -43,7 +43,7 @@ io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test @@ -64,14 +64,14 @@ io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-ddl-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test diff --git a/platforms/all/pom.xml b/platforms/all/pom.xml index 96858a7b21..f1d0c70f25 100644 --- a/platforms/all/pom.xml +++ b/platforms/all/pom.xml @@ -4,7 +4,7 @@ platforms io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-platform-all @@ -14,68 +14,72 @@ io.ebean ebean-platform-h2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-clickhouse - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-db2 - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-hana - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-hsqldb - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-mysql - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-mariadb - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-nuodb - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-oracle - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-postgres - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-sqlanywhere - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-sqlite - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT io.ebean ebean-platform-sqlserver - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/platforms/clickhouse/pom.xml b/platforms/clickhouse/pom.xml index b0e35711ec..8b0ca2c2e3 100644 --- a/platforms/clickhouse/pom.xml +++ b/platforms/clickhouse/pom.xml @@ -4,7 +4,7 @@ platforms io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-platform-clickhouse @@ -14,8 +14,12 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/platforms/db2/pom.xml b/platforms/db2/pom.xml index 3be2c4055d..da5501654b 100644 --- a/platforms/db2/pom.xml +++ b/platforms/db2/pom.xml @@ -4,7 +4,7 @@ platforms io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-platform-db2 @@ -14,8 +14,12 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + ebean-parent-13.6.4-FOC1 + diff --git a/platforms/h2/pom.xml b/platforms/h2/pom.xml index fe9b57fd2c..31d9f7e18e 100644 --- a/platforms/h2/pom.xml +++ b/platforms/h2/pom.xml @@ -4,7 +4,7 @@ platforms io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT ebean-platform-h2 @@ -14,7 +14,7 @@ io.ebean ebean-api - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT + + + foconis-release + FOCONIS Release Repository + https://mvnrepo.foconis.de/repository/release/ + + + foconis-snapshot + FOCONIS Snapshot Repository + https://mvnrepo.foconis.de/repository/snapshot/ + + diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index e3e4e05e80..502092acc2 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -4,7 +4,7 @@ ebean-parent io.ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT querybean generator diff --git a/release.md b/release.md new file mode 100644 index 0000000000..423bdb9ba6 --- /dev/null +++ b/release.md @@ -0,0 +1,18 @@ +## Release command + +We @foconis use this command to release. + + mvn versions:set -DgenerateBackupPoms=false -DnewVersion=13.6.0-FOC2-SNAPSHOT + mvn release:prepare release:perform -Darguments="-Dgpg.skip -DskipTests" -PfoconisRelease + + # RELEASE klappt nun, sollte es failen, ist wie folgt vorzugehen: + # um bei einen Fehler zu release ist dann ins target/checkout Verzeichnis zu gehen und + mvn clean source:jar install org.apache.maven.plugins:maven-deploy-plugin:deploy -DskipTests + # auszuführen. Aber auch das failed bei Kotlin. + + # nach dem Release müssen die Versionen in ebean-kotlin/pom.xml, tests/test-java16/pom.xml und tests/test-kotlin/pom.xml manuell angepasst werden + +generate Java classes from .xsd: + + export JAVA_TOOL_OPTIONS="-Duser.language=en -Duser.country=US -Dfile.encoding=UTF-8" + /c/Program\ Files/Java/jdk1.8.0_201/bin/xjc.exe src/main/resources/ebean-dbmigration-1.0.xsd -d src/main/java -p io.ebeaninternal.dbmigration.migration diff --git a/tests/pom.xml b/tests/pom.xml index ac7678298b..1942c9065a 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -5,7 +5,7 @@ org.avaje java8-oss 3.3 - + io.ebean @@ -41,4 +41,9 @@ + + + ebean-parent-13.3.1-FOC1 + scm:git:git@github.com:FOCONIS/ebean.git + diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml index 737e1c5274..69b806c045 100644 --- a/tests/test-java16/pom.xml +++ b/tests/test-java16/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 io.ebean @@ -15,7 +14,7 @@ io.ebean ebean - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT @@ -27,7 +26,7 @@ io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test @@ -53,7 +52,7 @@ io.ebean querybean-generator - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT diff --git a/tests/test-kotlin/pom.xml b/tests/test-kotlin/pom.xml index 8c8dbbe5d2..18cb56296b 100644 --- a/tests/test-kotlin/pom.xml +++ b/tests/test-kotlin/pom.xml @@ -1,13 +1,11 @@ - + 4.0.0 org.avaje java11-oss 3.7 - + test-kotlin @@ -35,14 +33,14 @@ io.ebean ebean-test - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test io.ebean ebean-core - 13.6.5-SNAPSHOT + 13.6.4-FOC2-SNAPSHOT test @@ -71,6 +69,11 @@ src/test/kotlin + + org.sonatype.plugins + nexus-staging-maven-plugin + false + org.jetbrains.kotlin kotlin-maven-plugin @@ -107,5 +110,4 @@ -