diff --git a/pom.xml b/pom.xml index df4173a6c..04d8cd29e 100644 --- a/pom.xml +++ b/pom.xml @@ -211,6 +211,13 @@ test + + org.avaje.moduuid + avaje-moduuid + 1.2 + test + + diff --git a/src/main/java/com/avaje/ebean/config/IdGenerator.java b/src/main/java/com/avaje/ebean/config/IdGenerator.java new file mode 100644 index 000000000..6a5b68a0d --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/IdGenerator.java @@ -0,0 +1,22 @@ +package com.avaje.ebean.config; + +/** + * A customer Id generator that can be registered with Ebean and + * assigned to @Id properties using the name attribute of @GeneratedValue. + */ +public interface IdGenerator { + + /** + * Return the next Id value. + */ + Object nextValue(); + + /** + * Return the name of the IdGenerator. + *

+ * The name is used to assign the IdGenerator to a property using + * @GeneratedValue(name="myGeneratorName") + *

+ */ + String getName(); +} diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index 32bf47913..e4f8d33c8 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -3,7 +3,6 @@ package com.avaje.ebean.config; import com.avaje.ebean.EbeanServerFactory; import com.avaje.ebean.PersistenceContextScope; import com.avaje.ebean.annotation.Encrypted; -import com.avaje.ebean.cache.ServerCacheFactory; import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.cache.ServerCachePlugin; import com.avaje.ebean.config.dbplatform.DatabasePlatform; @@ -46,37 +45,36 @@ import java.util.ServiceLoader; * includes searching the class path and automatically registering any entity * classes and listeners etc. *

- * + * *
{@code
  * ServerConfig c = new ServerConfig();
  * c.setName("ordh2");
- * 
+ *
  * // read the ebean.properties and load
  * // those settings into this serverConfig object
  * c.loadFromProperties();
- * 
+ *
  * // generate DDL and run it
  * c.setDdlGenerate(true);
  * c.setDdlRun(true);
- * 
+ *
  * // add any classes found in the app.data package
  * c.addPackage("app.data");
- * 
+ *
  * // add the names of Jars that contain entities
  * c.addJar("myJarContainingEntities.jar");
  * c.addJar("someOtherJarContainingEntities.jar");
- * 
+ *
  * // register as the 'Default' server
  * c.setDefaultServer(true);
- * 
+ *
  * EbeanServer server = EbeanServerFactory.create(c);
- * 
+ *
  * }
- * - * @see EbeanServerFactory - * + * * @author emcgreal * @author rbygrave + * @see EbeanServerFactory */ public class ServerConfig { @@ -89,7 +87,7 @@ public class ServerConfig { * Typically configuration type objects that are passed by this ServerConfig * to plugins. For example - IgniteConfiguration passed to Ignite plugin. */ - private Map serviceObject = new HashMap(); + private Map serviceObject = new HashMap(); private ContainerConfig containerConfig; @@ -152,7 +150,7 @@ public class ServerConfig { */ private CurrentUserProvider currentUserProvider; - /** + /** * Config controlling the AutoTune behaviour. */ private AutoTuneConfig autoTuneConfig = new AutoTuneConfig(); @@ -168,13 +166,13 @@ public class ServerConfig { */ private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL; - /** - * The database platform name. Used to imply a DatabasePlatform to use. + /** + * The database platform name. Used to imply a DatabasePlatform to use. */ private String databasePlatformName; - /** - * The database platform. + /** + * The database platform. */ private DatabasePlatform databasePlatform; @@ -213,12 +211,12 @@ public class ServerConfig { private int persistBatchSize = 20; - /** - * The default batch size for lazy loading + /** + * The default batch size for lazy loading */ private int lazyLoadBatchSize = 10; - /** + /** * The default batch size for 'query joins'. */ private int queryBatchSize = 100; @@ -248,12 +246,12 @@ public class ServerConfig { private ExternalTransactionManager externalTransactionManager; /** - * The data source (if programmatically provided). + * The data source (if programmatically provided). */ private DataSource dataSource; - /** - * The data source config. + /** + * The data source config. */ private DataSourceConfig dataSourceConfig = new DataSourceConfig(); @@ -279,23 +277,23 @@ public class ServerConfig { */ private boolean explicitTransactionBeginMode; - /** - * The data source JNDI name if using a JNDI DataSource. + /** + * The data source JNDI name if using a JNDI DataSource. */ private String dataSourceJndiName; - /** + /** * The database boolean true value (typically either 1, T, or Y). */ private String databaseBooleanTrue; - /** - * The database boolean false value (typically either 0, F or N). + /** + * The database boolean false value (typically either 0, F or N). */ private String databaseBooleanFalse; - /** - * The naming convention. + /** + * The naming convention. */ private NamingConvention namingConvention = new UnderscoreNamingConvention(); @@ -304,7 +302,7 @@ public class ServerConfig { */ private DbConstraintNaming constraintNaming = new DbConstraintNaming(); - /** + /** * Behaviour of update to include on the change properties. */ private boolean updateChangesOnly = true; @@ -318,13 +316,14 @@ public class ServerConfig { * Default behaviour for updates when cascade save on a O2M or M2M to delete any missing children. */ private boolean updatesDeleteMissingChildren = true; - + /** * Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres). */ private DbUuid dbUuid = DbUuid.AUTO; + private List idGenerators = new ArrayList(); private List findControllers = new ArrayList(); private List persistControllers = new ArrayList(); private List postLoaders = new ArrayList(); @@ -395,13 +394,13 @@ public class ServerConfig { private int cacheWarmingDelay = 30; private int cacheMaxSize = 10000; private int cacheMaxIdleTime = 600; - private int cacheMaxTimeToLive = 60*60*6; + private int cacheMaxTimeToLive = 60 * 60 * 6; // defaults for the L2 query caching private int queryCacheMaxSize = 1000; private int queryCacheMaxIdleTime = 600; - private int queryCacheMaxTimeToLive = 60*60*6; + private int queryCacheMaxTimeToLive = 60 * 60 * 6; private Object objectMapper; /** @@ -444,7 +443,7 @@ public class ServerConfig { public Object getServiceObject(String key) { return serviceObject.get(key); } - + /** * Return the Jackson JsonFactory to use. *

@@ -671,6 +670,7 @@ public class ServerConfig { *

* You can also set the batch size on the transaction. *

+ * * @see com.avaje.ebean.Transaction#setBatchSize(int) */ public void setPersistBatchSize(int persistBatchSize) { @@ -679,7 +679,7 @@ public class ServerConfig { /** * Gets the query batch size. This defaults to 100. - * + * * @return the query batch size */ public int getQueryBatchSize() { @@ -688,9 +688,8 @@ public class ServerConfig { /** * Sets the query batch size. This defaults to 100. - * - * @param queryBatchSize - * the new query batch size + * + * @param queryBatchSize the new query batch size */ public void setQueryBatchSize(int queryBatchSize) { this.queryBatchSize = queryBatchSize; @@ -736,8 +735,8 @@ public class ServerConfig { /** * Return the ChangeLogPrepare. *

- * This is used to set user context information to the ChangeSet in the - * foreground thread prior to the logging occurring in a background thread. + * This is used to set user context information to the ChangeSet in the + * foreground thread prior to the logging occurring in a background thread. *

*/ public ChangeLogPrepare getChangeLogPrepare() { @@ -747,8 +746,8 @@ public class ServerConfig { /** * Set the ChangeLogPrepare. *

- * This is used to set user context information to the ChangeSet in the - * foreground thread prior to the logging occurring in a background thread. + * This is used to set user context information to the ChangeSet in the + * foreground thread prior to the logging occurring in a background thread. *

*/ public void setChangeLogPrepare(ChangeLogPrepare changeLogPrepare) { @@ -1309,7 +1308,7 @@ public class ServerConfig { public void setDataSourceJndiName(String dataSourceJndiName) { this.dataSourceJndiName = dataSourceJndiName; } - + /** * Return true if autoCommit mode is on. This indicates to Ebean to use autoCommit friendly Transactions and TransactionManager. */ @@ -1488,12 +1487,11 @@ public class ServerConfig { *

* You can also set this in ebean.proprerties: *

- * - *
+   *
+   * 
{@code
    * # set via ebean.properties
-   * 
    * ebean.encryptKeyManager=com.avaje.tests.basic.encrypt.BasicEncyptKeyManager
-   * 
+ * }
*/ public void setEncryptKeyManager(EncryptKeyManager encryptKeyManager) { this.encryptKeyManager = encryptKeyManager; @@ -1616,7 +1614,7 @@ public class ServerConfig { /** * Set to true to generate the "create all" DDL on startup. - * + *

* Typically we want this on when we are running tests locally (and often using H2) * and we want to create the full DB schema from scratch to run tests. */ @@ -1626,7 +1624,7 @@ public class ServerConfig { /** * Set to true to run the generated "create all DDL" on startup. - * + *

* Typically we want this on when we are running tests locally (and often using H2) * and we want to create the full DB schema from scratch to run tests. */ @@ -1636,7 +1634,7 @@ public class ServerConfig { /** * Return true if the "drop all ddl" should be skipped. - * + *

* Typically we want to do this when using H2 (in memory) as our test database and the drop statements * are not required so skipping the drop table statements etc makes it faster with less noise in the logs. */ @@ -1646,7 +1644,7 @@ public class ServerConfig { /** * Set to true if the "drop all ddl" should be skipped. - * + *

* Typically we want to do this when using H2 (in memory) as our test database and the drop statements * are not required so skipping the drop table statements etc makes it faster with less noise in the logs. */ @@ -1657,8 +1655,8 @@ public class ServerConfig { /** * Return SQL script to execute after the "create all" DDL has been run. *

- * Typically this is a sql script that inserts test seed data when running tests. - * Place a sql script in src/test/resources that inserts test seed data. + * Typically this is a sql script that inserts test seed data when running tests. + * Place a sql script in src/test/resources that inserts test seed data. *

*/ public String getDdlSeedSql() { @@ -1668,8 +1666,8 @@ public class ServerConfig { /** * Set a SQL script to execute after the "create all" DDL has been run. *

- * Typically this is a sql script that inserts test seed data when running tests. - * Place a sql script in src/test/resources that inserts test seed data. + * Typically this is a sql script that inserts test seed data when running tests. + * Place a sql script in src/test/resources that inserts test seed data. *

*/ public void setDdlSeedSql(String ddlSeedSql) { @@ -1748,10 +1746,9 @@ public class ServerConfig { *

* Alternatively the classes can be added via {@link #setClasses(List)}. *

- * - * @param cls - * the entity type (or other type) that should be registered by this - * server. + * + * @param cls the entity type (or other type) that should be registered by this + * server. */ public void addClass(Class cls) { if (classes == null) { @@ -1802,13 +1799,13 @@ public class ServerConfig { * If you are using ebean.properties you can specify jars to search by setting * a ebean.search.jars property. *

- * - *
-   * # EBean will search through classes for entities, but will not search jar files 
-   * # unless you tell it to do so, for performance reasons.  Set this value to a 
+   *
+   * 
{@code
+   * # EBean will search through classes for entities, but will not search jar files
+   * # unless you tell it to do so, for performance reasons.  Set this value to a
    * # comma-delimited list of jar files you want ebean to search.
    * ebean.search.jars=example.jar
-   * 
+ * }
*/ public void addJar(String jarName) { if (searchJars == null) { @@ -1846,7 +1843,7 @@ public class ServerConfig { /** * Set the class name of a classPathReader implementation. - * + *

* Refer to server.util.ClassPathReader, this should really by a plugin but doing this for now * to be relatively compatible with current implementation. */ @@ -1901,7 +1898,7 @@ public class ServerConfig { /** * Set to false if by default updates in JDBC batch should not include all properties. *

- * This mode can be explicitly set per transaction. + * This mode can be explicitly set per transaction. *

* * @see com.avaje.ebean.Transaction#setUpdateAllLoadedProperties(boolean) @@ -1944,6 +1941,7 @@ public class ServerConfig { *

* This information can be later retrieved via {@link MetaInfoManager}. *

+ * * @see MetaInfoManager */ public void setCollectQueryStatsByNode(boolean collectQueryStatsByNode) { @@ -1964,6 +1962,7 @@ public class ServerConfig { *

* This information can be later retrieved via {@link MetaInfoManager}. *

+ * * @see MetaInfoManager */ public void setCollectQueryOrigins(boolean collectQueryOrigins) { @@ -2013,6 +2012,27 @@ public class ServerConfig { this.queryAdapters = queryAdapters; } + /** + * Return the custom IdGenerator instances. + */ + public List getIdGenerators() { + return idGenerators; + } + + /** + * Set the custom IdGenerator instances. + */ + public void setIdGenerators(List idGenerators) { + this.idGenerators = idGenerators; + } + + /** + * Register a customer IdGenerator instance. + */ + public void add(IdGenerator idGenerator) { + idGenerators.add(idGenerator); + } + /** * Register a BeanPersistController instance. *

@@ -2277,7 +2297,7 @@ public class ServerConfig { * @param instance existing instance */ @SuppressWarnings("unchecked") - protected T createInstance(PropertiesWrapper properties, Class pluginType, String key, T instance) { + protected T createInstance(PropertiesWrapper properties, Class pluginType, String key, T instance) { if (instance != null) { return instance; @@ -2288,8 +2308,9 @@ public class ServerConfig { /** * Return the instance to use (can be null) for the given plugin. + * * @param pluginType the type of plugin - * @param classname the implementation class as per properties + * @param classname the implementation class as per properties */ protected T createInstance(Class pluginType, String classname) { return classname == null ? null : (T) classLoadConfig.newInstance(classname); @@ -2362,7 +2383,7 @@ public class ServerConfig { serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager); cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay); classPathReaderClassName = p.get("classpathreader"); - + String jarsProp = p.get("search.jars", p.get("jars", null)); if (jarsProp != null) { searchJars = getSearchJarsPackages(jarsProp); @@ -2378,7 +2399,7 @@ public class ServerConfig { updateAllPropertiesInBatch = p.getBoolean("updateAllPropertiesInBatch", updateAllPropertiesInBatch); updateChangesOnly = p.getBoolean("updateChangesOnly", updateChangesOnly); - + boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren); updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren); @@ -2442,10 +2463,8 @@ public class ServerConfig { /** * Build the list of classes from the comma delimited string. - * - * @param properties - * the properties - * + * + * @param properties the properties * @return the classes */ private List> getClasses(PropertiesWrapper properties) { @@ -2528,9 +2547,9 @@ public class ServerConfig { /** * Set to true if you want eq("someProperty", null) to generate "1=1" rather than "is null" sql expression. *

- * Setting this to true has the effect that eq(propertyName, value), ieq(propertyName, value) and - * ne(propertyName, value) have no effect when the value is null. The expression factory adds a NoopExpression - * which will add "1=1" into the SQL rather than "is null". + * Setting this to true has the effect that eq(propertyName, value), ieq(propertyName, value) and + * ne(propertyName, value) have no effect when the value is null. The expression factory adds a NoopExpression + * which will add "1=1" into the SQL rather than "is null". *

*/ public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java index 3b6e40e7e..b8134c138 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.core; import com.avaje.ebean.config.CompoundType; +import com.avaje.ebean.config.IdGenerator; import com.avaje.ebean.config.ScalarTypeConverter; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.event.BeanFindController; @@ -48,6 +49,8 @@ public class BootupClasses implements ClassPathSearchMatcher, ClassFilter { private final List> compoundTypeList = new ArrayList>(); + private final List> idGeneratorList = new ArrayList>(); + private final List> beanControllerList = new ArrayList>(); private final List> beanPostLoadList = new ArrayList>(); @@ -62,6 +65,7 @@ public class BootupClasses implements ClassPathSearchMatcher, ClassFilter { private final List> serverConfigStartupList = new ArrayList>(); private final List serverConfigStartupInstances = new ArrayList(); + private final List idGeneratorInstances = new ArrayList(); private final List findControllerInstances = new ArrayList(); private final List persistControllerInstances = new ArrayList(); private final List beanPostLoadInstances = new ArrayList(); @@ -116,6 +120,19 @@ public class BootupClasses implements ClassPathSearchMatcher, ClassFilter { } } + /** + * Add IdGenerator instances (registered explicitly with the ServerConfig). + */ + public void addIdGenerators(List idGenerators) { + if (idGenerators != null) { + for (IdGenerator c : idGenerators) { + this.idGeneratorInstances.add(c); + // don't automatically instantiate + this.idGeneratorList.remove(c.getClass()); + } + } + } + public void addQueryAdapters(List queryAdapterInstances) { if (queryAdapterInstances != null) { for (BeanQueryAdapter a : queryAdapterInstances) { @@ -328,6 +345,13 @@ public class BootupClasses implements ClassPathSearchMatcher, ClassFilter { return beanPostLoadInstances; } + public List getIdGenerators() { + for (Class cls : idGeneratorList) { + createAdd(cls, idGeneratorInstances); + } + return idGeneratorInstances; + } + public List getTransactionEventListeners() { // add class registered TransactionEventListener to the already created instances for (Class cls : transactionEventListenerList) { @@ -402,6 +426,11 @@ public class BootupClasses implements ClassPathSearchMatcher, ClassFilter { } boolean interesting = false; + if (IdGenerator.class.isAssignableFrom(cls)) { + idGeneratorList.add(cls); + interesting = true; + } + if (BeanPersistController.class.isAssignableFrom(cls)) { beanControllerList.add(cls); interesting = true; diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java index d757568d2..284864e87 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java @@ -194,6 +194,7 @@ public class DefaultContainer implements SpiContainer { private BootupClasses getBootupClasses(ServerConfig serverConfig) { BootupClasses bootupClasses = getBootupClasses1(serverConfig); + bootupClasses.addIdGenerators(serverConfig.getIdGenerators()); bootupClasses.addPersistControllers(serverConfig.getPersistControllers()); bootupClasses.addPostLoaders(serverConfig.getPostLoaders()); bootupClasses.addFindControllers(serverConfig.getFindControllers()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index 6cd87afd8..ed061bb83 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -337,7 +337,7 @@ public class InternalConfiguration { } public GeneratedPropertyFactory getGeneratedPropertyFactory() { - return new GeneratedPropertyFactory(serverConfig); + return new GeneratedPropertyFactory(serverConfig, bootupClasses.getIdGenerators()); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index 51db0c4b7..75b859f29 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -153,8 +153,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final DatabasePlatform databasePlatform; - private final UuidIdGenerator uuidIdGenerator = new UuidIdGenerator(); - private final ServerCacheManager cacheManager; private final BackgroundExecutor backgroundExecutor; @@ -1177,6 +1175,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap { */ private void setIdGeneration(DeployBeanDescriptor desc) { + if (desc.getIdGenerator() != null) { + // already assigned (So custom or UUID) + return; + } if (desc.propertiesId().size() == 0) { // bean doesn't have an Id property if (desc.isBaseTableType() && desc.getBeanFinder() == null) { @@ -1209,14 +1211,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { desc.setIdTypePlatformDefault(); } - if (IdType.GENERATOR.equals(desc.getIdType())) { - String genName = desc.getIdGeneratorName(); - if (UuidIdGenerator.AUTO_UUID.equals(genName)) { - desc.setIdGenerator(uuidIdGenerator); - return; - } - } - if (desc.getBaseTable() == null) { // no base table so not going to set Identity // of sequence information @@ -1240,8 +1234,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { } // create the sequence based IdGenerator - PlatformIdGenerator seqIdGen = createSequenceIdGenerator(seqName); - desc.setIdGenerator(seqIdGen); + desc.setIdGenerator(createSequenceIdGenerator(seqName)); } private PlatformIdGenerator createSequenceIdGenerator(String seqName) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java index 77cbceb02..890b6581f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/generatedproperty/GeneratedPropertyFactory.java @@ -1,12 +1,18 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty; +import com.avaje.ebean.Transaction; import com.avaje.ebean.config.ClassLoadConfig; import com.avaje.ebean.config.CurrentUserProvider; +import com.avaje.ebean.config.IdGenerator; import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.config.dbplatform.PlatformIdGenerator; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; import java.math.BigDecimal; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; /** * Default implementation of GeneratedPropertyFactory. @@ -27,7 +33,9 @@ public class GeneratedPropertyFactory { private final ClassLoadConfig classLoadConfig; - public GeneratedPropertyFactory(ServerConfig serverConfig) { + private final Map idGeneratorMap = new HashMap(); + + public GeneratedPropertyFactory(ServerConfig serverConfig, List idGenerators) { this.classLoadConfig = serverConfig.getClassLoadConfig(); this.insertFactory = new InsertTimestampFactory(classLoadConfig); @@ -51,6 +59,12 @@ public class GeneratedPropertyFactory { numberTypes.add(Double.class.getName()); numberTypes.add(double.class.getName()); numberTypes.add(BigDecimal.class.getName()); + + if (idGenerators != null) { + for (IdGenerator idGenerator : idGenerators) { + idGeneratorMap.put(idGenerator.getName(), new CustomIdGenerator(idGenerator)); + } + } } public ClassLoadConfig getClassLoadConfig() { @@ -98,4 +112,42 @@ public class GeneratedPropertyFactory { property.setGeneratedProperty(generatedWhoModified); } + /** + * Return the named custom IdGenerator (wrapped as a PlatformIdGenerator). + */ + public PlatformIdGenerator getIdGenerator(String generatorName) { + return idGeneratorMap.get(generatorName); + } + + /** + * Wraps the custom IdGenerator to implement PlatformIdGenerator. + */ + private static class CustomIdGenerator implements PlatformIdGenerator { + + private final IdGenerator generator; + + CustomIdGenerator(IdGenerator generator) { + this.generator = generator; + } + + @Override + public String getName() { + return generator.getName(); + } + + @Override + public boolean isDbSequence() { + return false; + } + + @Override + public Object nextId(Transaction transaction) { + return generator.nextValue(); + } + + @Override + public void preAllocateIds(int allocateSize) { + // do nothing + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 08c6504df..f4967557d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -29,6 +29,7 @@ import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery; import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate; import com.avaje.ebeaninternal.server.deploy.InheritInfo; import com.avaje.ebeaninternal.server.deploy.parse.DeployBeanInfo; +import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; @@ -830,6 +831,24 @@ public class DeployBeanDescriptor { } } + /** + * Assign the standard UUID generator. + */ + public void setUuidGenerator() { + this.idType = IdType.EXTERNAL; + this.idGeneratorName = UuidIdGenerator.AUTO_UUID; + this.idGenerator = UuidIdGenerator.INSTANCE; + } + + /** + * Assign a custom external IdGenerator. + */ + public void setCustomIdGenerator(PlatformIdGenerator idGenerator) { + this.idType = IdType.EXTERNAL; + this.idGeneratorName = idGenerator.getName(); + this.idGenerator = idGenerator; + } + /** * Summary description. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index 3d7a8cfd5..8b76eb7f6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -6,12 +6,12 @@ import com.avaje.ebean.config.EncryptDeploy.Mode; import com.avaje.ebean.config.dbplatform.DbEncrypt; import com.avaje.ebean.config.dbplatform.DbEncryptFunction; import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebean.config.dbplatform.PlatformIdGenerator; import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; -import com.avaje.ebeaninternal.server.idgen.UuidIdGenerator; import com.avaje.ebeaninternal.server.lib.util.StringHelper; import com.avaje.ebeaninternal.server.type.CtCompoundType; import com.avaje.ebeaninternal.server.type.DataEncryptSupport; @@ -419,12 +419,8 @@ public class AnnotationFields extends AnnotationParser { prop.setNullable(false); if (prop.getPropertyType().equals(UUID.class)) { - // An Id of type UUID if (descriptor.getIdGeneratorName() == null) { - // Without a generator explicitly specified - // so will use the default one AUTO_UUID - descriptor.setIdGeneratorName(UuidIdGenerator.AUTO_UUID); - descriptor.setIdType(IdType.GENERATOR); + descriptor.setUuidGenerator(); } } } @@ -449,14 +445,20 @@ public class AnnotationFields extends AnnotationParser { } else if (strategy == GenerationType.SEQUENCE) { descriptor.setIdType(IdType.SEQUENCE); - if (genName != null && genName.length() > 0) { + if (!genName.equals("")) { descriptor.setIdGeneratorName(genName); } } else if (strategy == GenerationType.AUTO) { - if (prop.getPropertyType().equals(UUID.class)) { - descriptor.setIdGeneratorName(UuidIdGenerator.AUTO_UUID); - descriptor.setIdType(IdType.GENERATOR); + if (!genName.equals("")) { + // use a custom IdGenerator + PlatformIdGenerator idGenerator = generatedPropFactory.getIdGenerator(genName); + if (idGenerator == null) { + throw new IllegalStateException("No custom IdGenerator registered with name "+genName); + } + descriptor.setCustomIdGenerator(idGenerator); + } else if (prop.getPropertyType().equals(UUID.class)) { + descriptor.setUuidGenerator(); } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java b/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java index bd3070a76..58700925b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java +++ b/src/main/java/com/avaje/ebeaninternal/server/idgen/UuidIdGenerator.java @@ -1,41 +1,43 @@ package com.avaje.ebeaninternal.server.idgen; -import java.util.UUID; - import com.avaje.ebean.Transaction; import com.avaje.ebean.config.dbplatform.PlatformIdGenerator; +import java.util.UUID; + /** * IdGenerator for java util UUID. */ public class UuidIdGenerator implements PlatformIdGenerator { - /** - * Return UUID from UUID.randomUUID(); - */ - public Object nextId(Transaction t) { - return UUID.randomUUID(); - } + public static UuidIdGenerator INSTANCE = new UuidIdGenerator(); - /** - * Returns "uuid". - */ - public String getName() { - return "uuid"; - } + /** + * Return UUID from UUID.randomUUID(); + */ + public Object nextId(Transaction t) { + return UUID.randomUUID(); + } - /** - * Returns false. - */ - public boolean isDbSequence() { - return false; - } + /** + * Returns "uuid". + */ + public String getName() { + return "uuid"; + } - /** - * Ignored for UUID as not required as a performance optimisation. - */ - public void preAllocateIds(int allocateSize) { - // ignored - } + /** + * Returns false. + */ + public boolean isDbSequence() { + return false; + } + + /** + * Ignored for UUID as not required as a performance optimisation. + */ + public void preAllocateIds(int allocateSize) { + // ignored + } } diff --git a/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java b/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java index 0eaef65df..7476b7c22 100644 --- a/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java +++ b/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java @@ -7,12 +7,15 @@ import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.common.BeanList; import com.avaje.ebean.config.ServerConfig; import com.avaje.tests.model.basic.EBasic; +import com.avaje.tests.model.basic.ECustomId; +import org.example.ModUuidGenerator; import org.junit.Test; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class BeanFindControllerTest extends BaseTestCase { @@ -30,7 +33,9 @@ public class BeanFindControllerTest extends BaseTestCase { config.setDdlRun(true); config.setRegister(false); config.setDefaultServer(false); + config.add(new ModUuidGenerator()); config.getClasses().add(EBasic.class); + config.getClasses().add(ECustomId.class); EBasicFindController findController = new EBasicFindController(); config.getFindControllers().add(findController); @@ -61,6 +66,10 @@ public class BeanFindControllerTest extends BaseTestCase { eBasic = list.get(0); assertEquals(Integer.valueOf(47), eBasic.getId()); assertEquals("47", eBasic.getName()); + + ECustomId bean = new ECustomId("check"); + ebeanServer.save(bean); + assertNotNull(bean.getId()); } static class EBasicFindController implements BeanFindController { diff --git a/src/test/java/com/avaje/tests/idkeys/TestCustomId.java b/src/test/java/com/avaje/tests/idkeys/TestCustomId.java new file mode 100644 index 000000000..f98e9b1fb --- /dev/null +++ b/src/test/java/com/avaje/tests/idkeys/TestCustomId.java @@ -0,0 +1,33 @@ +package com.avaje.tests.idkeys; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.tests.model.basic.ECustomId; +import org.avaje.moduuid.ModUUID; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TestCustomId extends BaseTestCase { + + @Test + public void insert() { + + ECustomId bean = new ECustomId("fred"); + Ebean.save(bean); + assertNotNull(bean.getId()); + } + + @Test + public void insert_when_hasValue() { + + ECustomId bean = new ECustomId("gotId"); + String explicitId = ModUUID.newShortId(); + bean.setId(explicitId); + Ebean.save(bean); + + ECustomId found = Ebean.find(ECustomId.class, explicitId); + assertEquals(found.getName(), bean.getName()); + } +} diff --git a/src/test/java/com/avaje/tests/model/basic/ECustomId.java b/src/test/java/com/avaje/tests/model/basic/ECustomId.java new file mode 100644 index 000000000..0bd0cb2dc --- /dev/null +++ b/src/test/java/com/avaje/tests/model/basic/ECustomId.java @@ -0,0 +1,34 @@ +package com.avaje.tests.model.basic; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class ECustomId { + + @Id @GeneratedValue(generator = "shortUid") + String id; + + String name; + + public ECustomId(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/org/example/ModUuidGenerator.java b/src/test/java/org/example/ModUuidGenerator.java new file mode 100644 index 000000000..b34b26b61 --- /dev/null +++ b/src/test/java/org/example/ModUuidGenerator.java @@ -0,0 +1,20 @@ +package org.example; + +import com.avaje.ebean.config.IdGenerator; +import org.avaje.moduuid.ModUUID; + +/** + * A customer Id Generator that can be assigned by @GeneratedValue(generator="shortUid") + */ +public class ModUuidGenerator implements IdGenerator { + + @Override + public Object nextValue() { + return ModUUID.newShortId(); + } + + @Override + public String getName() { + return "shortUid"; + } +}