diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ade87e7f..37e5cf2d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -30,7 +30,7 @@ jobs: java: 25, 21, 17 # Symbolic lanes resolve to exact versions in project/Dependencies.scala. scala: 2.13.x, 3.3.x, 3.9.x, 3.next - cmd: cd docs && sbt -Dscala.version=$MATRIX_SCALA validateCode + cmd: cd docs && sbt -Dscala.version=$MATRIX_SCALA "validateCode; test" tests: name: Tests diff --git a/docs/build.sbt b/docs/build.sbt index d74409d6..cfd1a931 100644 --- a/docs/build.sbt +++ b/docs/build.sbt @@ -61,8 +61,9 @@ lazy val docs = project ) .settings(PlayEbean.unscopedSettings) .settings( + // The code samples are test sources, so enhance their models there inConfig(Test)( - Seq( + PlayEbean.scopedSettings ++ Seq( playEbeanModels := Seq("javaguide.ebean.*") ) ) diff --git a/docs/manual/working/javaGuide/main/sql/JavaEbean.md b/docs/manual/working/javaGuide/main/sql/JavaEbean.md index 8ecb2284..96da08e4 100644 --- a/docs/manual/working/javaGuide/main/sql/JavaEbean.md +++ b/docs/manual/working/javaGuide/main/sql/JavaEbean.md @@ -37,12 +37,14 @@ In this example, we have access to two Ebean servers - each using its own databa Each `ebean.` config line (as above) can map *any* classes that Ebean may be interested in registering (eg. `@Entity`/`Model` classes, `@Embeddable`s, custom `ScalarType`s and `CompoundType`s, `BeanPersistController`s, `BeanPersistListener`s, `BeanFinder`s, `ServerConfigStartup`s, etc). These can be individually listed separated by commas, and/or you can use the wildcard `.*`. For example, `models.*` registers with Ebean all classes within the models package that Ebean can make use of. -To customise the underlying Ebean Server configuration, you can either add a [`conf/application.yaml`](https://ebean.io/docs/intro/configuration/) file, or create an instance of the `ServerConfigStartup` interface to programmatically manipulate the Ebean `ServerConfig` before the server is initialised. +To customise the underlying Ebean Server configuration, you can either add a [`conf/application.yaml`](https://ebean.io/docs/intro/configuration/) file, or implement the `ServerConfigStartup` interface to programmatically manipulate the Ebean `DatabaseBuilder` before the server is initialised. As an example, the fairly common problem of reducing the sequence batch size in order to minimise sequence gaps, could be solved quite simply with a class like this: @[content](code/javaguide/ebean/MyServerConfigStartup.java) +Make sure that such a class is covered by an `ebean.` config line, for example by putting it into the `models` package. Play Ebean registers the configured classes with Ebean explicitly, which disables Ebean's own classpath scanning, so Ebean silently ignores any `ServerConfigStartup` that is not covered. + Note that Ebean will also make use of a `conf/orm.xml` file (if present), to configure ``. #### Using a read replica @@ -108,7 +110,7 @@ By default Ebean will use transactions. However these transactions will be creat @[transaction](code/javaguide/ebean/JavaEbeanTest.java) -So, if you want to do more than one action in the same transaction you can use TxRunnable and TxCallable: +So, if you want to do more than one action in the same transaction you can use `DB.execute` (or `DB.executeCall` to return a value): @[txrunnable](code/javaguide/ebean/JavaEbeanTest.java) diff --git a/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/JavaEbeanTest.java b/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/JavaEbeanTest.java index 2ac60307..5aa31174 100644 --- a/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/JavaEbeanTest.java +++ b/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/JavaEbeanTest.java @@ -8,7 +8,8 @@ import static org.junit.Assert.*; import static play.test.Helpers.*; -import io.ebean.Ebean; +import io.ebean.DB; +import io.ebean.Transaction; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -91,11 +92,11 @@ public void txRunnable() { // ###insert: ... - Ebean.execute( + DB.execute( () -> { // code running in "REQUIRED" transactional scope // ... as "REQUIRED" is the default TxType - System.out.println(Ebean.currentTransaction()); + System.out.println(DB.currentTransaction()); Task task = Task.find.byId(34L); task.setDone(true); @@ -131,16 +132,17 @@ public void traditional() { createTask(); // #traditional - Ebean.beginTransaction(); - try { + // ###insert: import io.ebean.*; + + // ###insert: ... + + try (Transaction transaction = DB.beginTransaction()) { Task task = Task.find.byId(34L); task.setDone(true); task.save(); - Ebean.commitTransaction(); - } finally { - Ebean.endTransaction(); + transaction.commit(); } // #traditional diff --git a/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/MyServerConfigStartup.java b/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/MyServerConfigStartup.java index 6c82a80c..4933165f 100644 --- a/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/MyServerConfigStartup.java +++ b/docs/manual/working/javaGuide/main/sql/code/javaguide/ebean/MyServerConfigStartup.java @@ -6,12 +6,13 @@ // ###replace: package models; package javaguide.ebean; -import io.ebean.config.ServerConfig; +import io.ebean.DatabaseBuilder; import io.ebean.event.ServerConfigStartup; public class MyServerConfigStartup implements ServerConfigStartup { - public void onStart(ServerConfig serverConfig) { - serverConfig.setDatabaseSequenceBatchSize(1); + @Override + public void onStart(DatabaseBuilder config) { + config.databaseSequenceBatchSize(1); } } // #content diff --git a/sbt-play-ebean/src/main/scala/play/ebean/sbt/PlayEbean.scala b/sbt-play-ebean/src/main/scala/play/ebean/sbt/PlayEbean.scala index c1a6c2dd..5eae7851 100644 --- a/sbt-play-ebean/src/main/scala/play/ebean/sbt/PlayEbean.scala +++ b/sbt-play-ebean/src/main/scala/play/ebean/sbt/PlayEbean.scala @@ -94,6 +94,25 @@ object PlayEbean extends AutoPlugin { // This is replacement of old Stamp `Exists` representation private final val notPresent = "absent" + /** + * Turns the configured models into the packages the enhancer processes: packages (`models.*` or `models`) as they + * are, and single classes (`models.Task`) via their package. Entries without compiled classes (e.g. packages not + * existing yet, or provided by a dependency) are skipped, because the enhancer fails for them. + */ + private def packagesToEnhance(models: Seq[String], classes: File): Seq[String] = { + def isPackage(name: String) = new File(classes, name.replace('.', '/')).isDirectory + models + .map(_.trim) + .flatMap { model => + // Like the enhancer, ignore a trailing ".*" or ".**" + val pkg = model.stripSuffix("*").stripSuffix("*").stripSuffix(".") + if (isPackage(pkg)) Some(pkg) + else if (pkg == model && model.contains('.')) Some(model.substring(0, model.lastIndexOf('.'))).filter(isPackage) + else None + } + .distinct + } + def ebeanEnhance: Def.Initialize[Task[CompileResult]] = Def.task { @@ -120,7 +139,11 @@ object PlayEbean extends AutoPlugin { val transformer = new Transformer(classLoader, agentArgsString) val fileTransform = new OfflineFileTransform(transformer, classLoader, classes.getAbsolutePath) - fileTransform.process(models.mkString(",")) + val packages = packagesToEnhance(models, classes) + // An empty list would make the enhancer process all classes + if (packages.nonEmpty) { + fileTransform.process(packages.mkString(",")) + } } finally { Thread.currentThread.setContextClassLoader(originalContextClassLoader) diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/app/models/Task.java b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/app/models/Task.java new file mode 100644 index 00000000..0af270d9 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/app/models/Task.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package models; + +import java.util.*; +import jakarta.persistence.*; + +import io.ebean.*; +import play.data.format.*; +import play.data.validation.*; + +@Entity +public class Task extends Model { + + public static final Finder find = new Finder<>(Task.class); + + @Id + public Long id; + + @Constraints.Required + public String name; + + public boolean done; + + @Formats.DateTime(pattern = "dd/MM/yyyy") + public Date dueDate = new Date(); +} diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/build.sbt b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/build.sbt new file mode 100644 index 00000000..c7ee09d6 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/build.sbt @@ -0,0 +1,17 @@ +// Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +lazy val root = project + .in(file(".")) + .enablePlugins(PlayJava, PlayEbean) + +scalaVersion := sys.props("scala.version") + +resolvers ++= Seq(Resolver.sonatypeCentralSnapshots, Resolver.ApacheMavenSnapshotsRepo) + +Test / sourceDirectory := baseDirectory.value / "tests" + +Test / scalaSource := baseDirectory.value / "tests" + +Test / javaSource := baseDirectory.value / "tests" + +libraryDependencies += "com.h2database" % "h2" % "2.5.252" diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/conf/application.conf b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/conf/application.conf new file mode 100644 index 00000000..62509a28 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/conf/application.conf @@ -0,0 +1,3 @@ +# A package without any compiled classes (listed first, so it must not stop the enhancement of the +# following entries) and a single model class instead of a package +ebean.default = [ "missing.*", "models.Task" ] diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/project/plugins.sbt b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/project/plugins.sbt new file mode 100644 index 00000000..d65f662c --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/project/plugins.sbt @@ -0,0 +1,5 @@ +// Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +resolvers += Resolver.sonatypeCentralSnapshots + +addSbtPlugin("org.playframework" % "sbt-play-ebean" % sys.props("project.version")) diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/test b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/test new file mode 100644 index 00000000..0f99eb3c --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/test @@ -0,0 +1,2 @@ +> test +$ exists conf/evolutions/default/1.sql \ No newline at end of file diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/tests/models/TestTask.java b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/tests/models/TestTask.java new file mode 100644 index 00000000..06dee9f3 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/tests/models/TestTask.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package models; + +import org.junit.*; + +import static org.junit.Assert.*; + +import play.test.*; +import play.Application; + +import static play.test.Helpers.*; + +public class TestTask extends WithApplication { + + protected Application provideApplication() { + return fakeApplication(inMemoryDatabase()); + } + + @Test + public void saveAndFind() { + Task task = new Task(); + task.id = 10L; + task.name = "Hello"; + task.done = false; + task.save(); + + Task saved = Task.find.byId(10L); + assertEquals("Hello", saved.name); + } + +} diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/models/Task.java b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/models/Task.java new file mode 100644 index 00000000..0af270d9 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/models/Task.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package models; + +import java.util.*; +import jakarta.persistence.*; + +import io.ebean.*; +import play.data.format.*; +import play.data.validation.*; + +@Entity +public class Task extends Model { + + public static final Finder find = new Finder<>(Task.class); + + @Id + public Long id; + + @Constraints.Required + public String name; + + public boolean done; + + @Formats.DateTime(pattern = "dd/MM/yyyy") + public Date dueDate = new Date(); +} diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/other/Other.java b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/other/Other.java new file mode 100644 index 00000000..468bf1b5 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/other/Other.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package other; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +/** An entity outside of the configured package, so it must not get enhanced. */ +@Entity +public class Other { + @Id public Long id; +} diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/build.sbt b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/build.sbt new file mode 100644 index 00000000..904fd58c --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/build.sbt @@ -0,0 +1,20 @@ +// Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +lazy val root = project + .in(file(".")) + .enablePlugins(PlayJava, PlayEbean) + +scalaVersion := sys.props("scala.version") + +resolvers ++= Seq(Resolver.sonatypeCentralSnapshots, Resolver.ApacheMavenSnapshotsRepo) + +Test / sourceDirectory := baseDirectory.value / "tests" + +Test / scalaSource := baseDirectory.value / "tests" + +Test / javaSource := baseDirectory.value / "tests" + +libraryDependencies += "com.h2database" % "h2" % "2.5.252" + +// A plain package name (without ".*"), which must not be taken for a class +Compile / playEbeanModels := Seq("models") diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/project/plugins.sbt b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/project/plugins.sbt new file mode 100644 index 00000000..d65f662c --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/project/plugins.sbt @@ -0,0 +1,5 @@ +// Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +resolvers += Resolver.sonatypeCentralSnapshots + +addSbtPlugin("org.playframework" % "sbt-play-ebean" % sys.props("project.version")) diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/test b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/test new file mode 100644 index 00000000..dfffb838 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/test @@ -0,0 +1 @@ +> test diff --git a/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/tests/models/EnhancementTest.java b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/tests/models/EnhancementTest.java new file mode 100644 index 00000000..7c752550 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/tests/models/EnhancementTest.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + */ + +package models; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import io.ebean.bean.EntityBean; +import org.junit.Test; + +public class EnhancementTest { + + @Test + public void enhancesOnlyTheConfiguredPackage() { + assertTrue(EntityBean.class.isAssignableFrom(models.Task.class)); + assertFalse(EntityBean.class.isAssignableFrom(other.Other.class)); + } +}