From eee6aeb5905fbfdda97ee1bb340c3366fbe35172 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Thu, 8 Oct 2026 12:08:56 +0200 Subject: [PATCH 1/2] Fix enhancing single model classes and packages without classes The sbt plugin passed the configured models as they are to Ebean's enhancer, which treats every entry as a package directory. So single classes (e.g. "models.Task", as shown in the docs) and packages without compiled classes (e.g. not existing yet, or provided by a dependency) made it throw. The plugin swallowed that error, which also skipped all following entries, so entities silently stayed unenhanced and the application failed at runtime. Now single classes get enhanced via their package and packages without compiled classes get skipped. Other enhancement errors are not swallowed anymore. --- .../main/scala/play/ebean/sbt/PlayEbean.scala | 21 +++++++++--- .../app/models/Task.java | 29 ++++++++++++++++ .../enhancement-model-entries/build.sbt | 17 ++++++++++ .../conf/application.conf | 3 ++ .../project/plugins.sbt | 4 +++ .../sbt-ebean/enhancement-model-entries/test | 2 ++ .../tests/models/TestTask.java | 34 +++++++++++++++++++ 7 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/app/models/Task.java create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/build.sbt create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/conf/application.conf create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/project/plugins.sbt create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/test create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/tests/models/TestTask.java 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 4863f748..2902f8ed 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 @@ -23,7 +23,6 @@ import xsbti.compile.CompileResult import xsbti.compile.analysis.Stamp import sbt._ import xsbti.VirtualFileRef -import scala.util.control.NonFatal object PlayEbean extends AutoPlugin { @@ -64,6 +63,18 @@ object PlayEbean extends AutoPlugin { // This is replacement of old Stamp `Exists` representation private final val notPresent = "absent" + /** + * Turns the configured models, which can be packages (`models.*`) or single classes (`models.Task`), into the + * packages the enhancer processes. Packages without compiled classes (e.g. 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] = + models + .map(_.trim) + .map(model => if (model.endsWith(".*")) model.dropRight(2) else model.take(math.max(model.lastIndexOf('.'), 0))) + .distinct + .filter(pkg => new File(classes, pkg.replace('.', '/')).isDirectory) + def ebeanEnhance: Def.Initialize[Task[CompileResult]] = Def.task { @@ -85,10 +96,10 @@ object PlayEbean extends AutoPlugin { val transformer = new Transformer(classLoader, agentArgsString) val fileTransform = new OfflineFileTransform(transformer, classLoader, classes.getAbsolutePath) - try { - fileTransform.process(playEbeanModels.value.mkString(",")) - } catch { - case NonFatal(_) => + val packages = packagesToEnhance(playEbeanModels.value, classes) + // An empty list would make the enhancer process all classes + if (packages.nonEmpty) { + fileTransform.process(packages.mkString(",")) } } finally { 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..33e7fd70 --- /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") + +Test / sourceDirectory := baseDirectory.value / "tests" + +Test / scalaSource := baseDirectory.value / "tests" + +Test / javaSource := baseDirectory.value / "tests" + +resolvers ++= DefaultOptions.resolvers(snapshot = true) + +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..ffaa76e1 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-model-entries/project/plugins.sbt @@ -0,0 +1,4 @@ +// Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +resolvers ++= DefaultOptions.resolvers(snapshot = true) +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); + } + +} From 8c016b901f10f9633b40eff7d0b26da5224dcbe5 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Thu, 8 Oct 2026 12:39:01 +0200 Subject: [PATCH 2/2] Keep enhancing plain package names as packages playEbeanModels can also contain package names without a ".*" suffix, e.g. Seq("models"), which Ebean's enhancer handles just like "models.*". However, the previous commit took them for single classes and enhanced their parent package instead, which for "models" is the whole classes directory. Now only entries that are no package with compiled classes are treated as single classes. --- .../main/scala/play/ebean/sbt/PlayEbean.scala | 19 ++++++++---- .../app/models/Task.java | 29 +++++++++++++++++++ .../app/other/Other.java | 14 +++++++++ .../enhancement-plain-package/build.sbt | 20 +++++++++++++ .../project/plugins.sbt | 4 +++ .../sbt-ebean/enhancement-plain-package/test | 1 + .../tests/models/EnhancementTest.java | 20 +++++++++++++ 7 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/models/Task.java create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/app/other/Other.java create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/build.sbt create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/project/plugins.sbt create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/test create mode 100644 sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/tests/models/EnhancementTest.java 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 2902f8ed..7f98e550 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 @@ -64,16 +64,23 @@ object PlayEbean extends AutoPlugin { private final val notPresent = "absent" /** - * Turns the configured models, which can be packages (`models.*`) or single classes (`models.Task`), into the - * packages the enhancer processes. Packages without compiled classes (e.g. not existing yet, or provided by a - * dependency) are skipped, because the enhancer fails for them. + * 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] = + private def packagesToEnhance(models: Seq[String], classes: File): Seq[String] = { + def isPackage(name: String) = new File(classes, name.replace('.', '/')).isDirectory models .map(_.trim) - .map(model => if (model.endsWith(".*")) model.dropRight(2) else model.take(math.max(model.lastIndexOf('.'), 0))) + .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 - .filter(pkg => new File(classes, pkg.replace('.', '/')).isDirectory) + } def ebeanEnhance: Def.Initialize[Task[CompileResult]] = Def.task { 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..d58e4c6e --- /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") + +Test / sourceDirectory := baseDirectory.value / "tests" + +Test / scalaSource := baseDirectory.value / "tests" + +Test / javaSource := baseDirectory.value / "tests" + +resolvers ++= DefaultOptions.resolvers(snapshot = true) + +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..ffaa76e1 --- /dev/null +++ b/sbt-play-ebean/src/sbt-test/sbt-ebean/enhancement-plain-package/project/plugins.sbt @@ -0,0 +1,4 @@ +// Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc. + +resolvers ++= DefaultOptions.resolvers(snapshot = true) +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)); + } +}