From 1304646ccef3aa8b70143288ed21e2f24be5a74f Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Thu, 2 Jun 2016 17:35:06 +1200 Subject: [PATCH] #731 - ENH: Add support for using ebean.mf manifest file for finding/defining packages to search for entities --- .../server/core/DefaultContainer.java | 2 + .../server/core/InternalConfiguration.java | 1 + .../{ => bootup}/BootupClassPathSearch.java | 13 ++- .../core/{ => bootup}/BootupClasses.java | 2 +- .../server/core/bootup/DistillPackages.java | 48 +++++++++++ .../server/core/bootup/ManifestReader.java | 84 +++++++++++++++++++ .../server/deploy/BeanDescriptorManager.java | 2 +- .../server/deploy/BeanFinderManager.java | 2 +- .../deploy/BeanQueryAdapterManager.java | 2 +- .../deploy/PersistControllerManager.java | 2 +- .../server/deploy/PersistListenerManager.java | 2 +- .../server/deploy/PostLoadManager.java | 2 +- .../server/deploy/parse/DeployInherit.java | 2 +- .../AutoCommitTransactionManager.java | 2 +- .../ExplicitTransactionManager.java | 2 +- .../transaction/TransactionManager.java | 2 +- .../server/type/DefaultTypeManager.java | 2 +- .../ebean/server/type/TestTypeManager.java | 2 +- .../core/bootup/DistillPackagesTest.java | 50 +++++++++++ .../core/bootup/ManifestReaderTest.java | 30 +++++++ .../server/type/DefaultTypeManagerTest.java | 2 +- src/test/resources/META-INF/test/test-one.mf | 1 + src/test/resources/META-INF/test/test-some.mf | 1 + 23 files changed, 241 insertions(+), 17 deletions(-) rename src/main/java/com/avaje/ebeaninternal/server/core/{ => bootup}/BootupClassPathSearch.java (78%) rename src/main/java/com/avaje/ebeaninternal/server/core/{ => bootup}/BootupClasses.java (99%) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/bootup/DistillPackages.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReader.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/core/bootup/DistillPackagesTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReaderTest.java create mode 100644 src/test/resources/META-INF/test/test-one.mf create mode 100644 src/test/resources/META-INF/test/test-some.mf 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 cb5839c34..8cdbb98ad 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java @@ -17,6 +17,8 @@ import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.server.cache.DefaultServerCachePlugin; import com.avaje.ebeaninternal.server.cache.DefaultServerCacheManager; import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.core.bootup.BootupClassPathSearch; +import com.avaje.ebeaninternal.server.core.bootup.BootupClasses; import com.avaje.ebeaninternal.server.lib.ShutdownManager; import org.avaje.datasource.DataSourceAlertFactory; import org.avaje.datasource.DataSourceConfig; 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 ec0be128b..4f8e62dab 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare; import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister; import com.avaje.ebeaninternal.server.cluster.ClusterManager; +import com.avaje.ebeaninternal.server.core.bootup.BootupClasses; import com.avaje.ebeaninternal.server.core.timezone.CloneDataTimeZone; import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone; import com.avaje.ebeaninternal.server.core.timezone.NoDataTimeZone; diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClassPathSearch.java similarity index 78% rename from src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java rename to src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClassPathSearch.java index 3d32f5fa6..b72314a08 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClassPathSearch.java @@ -1,6 +1,7 @@ -package com.avaje.ebeaninternal.server.core; +package com.avaje.ebeaninternal.server.core.bootup; import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebeaninternal.server.core.ClassPathScanners; import org.avaje.classpath.scanner.ClassPathScanner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,10 +11,12 @@ import java.util.*; /** * Searches for interesting classes such as Entities, Embedded and ScalarTypes. */ -class BootupClassPathSearch { +public class BootupClassPathSearch { private static final Logger logger = LoggerFactory.getLogger(BootupClassPathSearch.class); + private static final String EBEAN_MF = "META-INF/ebean.mf"; + private final List packages; private final List scanners; @@ -28,7 +31,11 @@ class BootupClassPathSearch { } private BootupClassPathSearch(ServerConfig serverConfig) { - this.packages = serverConfig.getPackages(); + + // find packages defined in META-INF/ebean.mf resources + Set mfPackages = ManifestReader.readManifests(serverConfig.getClassLoadConfig().getClassLoader(), EBEAN_MF); + + this.packages = DistillPackages.distill(serverConfig.getPackages(), mfPackages); this.scanners = ClassPathScanners.find(serverConfig); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java similarity index 99% rename from src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java rename to src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java index 2932af9e9..2413c6caa 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClasses.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/BootupClasses.java @@ -1,4 +1,4 @@ -package com.avaje.ebeaninternal.server.core; +package com.avaje.ebeaninternal.server.core.bootup; import com.avaje.ebean.config.CompoundType; import com.avaje.ebean.config.IdGenerator; diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/bootup/DistillPackages.java b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/DistillPackages.java new file mode 100644 index 000000000..a1b978d21 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/DistillPackages.java @@ -0,0 +1,48 @@ +package com.avaje.ebeaninternal.server.core.bootup; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.TreeSet; + +/** + * Distill packages into distinct top level packages for searching. + */ +class DistillPackages { + + + /** + * Distill the list of packages into distinct top level packages. + */ + static List distill(Collection packages, Collection mfPackages) { + + // sort into natural order + TreeSet treeSet = new TreeSet(); + treeSet.addAll(packages); + treeSet.addAll(mfPackages); + + List distilled = new ArrayList(); + + // build the distilled list + for (String pack : treeSet) { + if (notAlreadyContained(distilled, pack)) { + distilled.add(pack); + } + } + + return distilled; + } + + /** + * Return true if the package is not already contained in the distilled list. + */ + private static boolean notAlreadyContained(List distilled, String pack) { + + for (int i = 0; i < distilled.size(); i++) { + if (pack.startsWith(distilled.get(i))) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReader.java b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReader.java new file mode 100644 index 000000000..37b734bd9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReader.java @@ -0,0 +1,84 @@ +package com.avaje.ebeaninternal.server.core.bootup; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +/** + * Reads all the META-INF/ebean.mf resources with the package locations of entity beans. + */ +class ManifestReader { + + private static final Logger logger = LoggerFactory.getLogger(ManifestReader.class); + + private final Set packageSet = new HashSet(); + + /** + * Read the packages from ebean.mf manifest files found as resources. + */ + static Set readManifests(ClassLoader classLoader, String resourcePath) { + return new ManifestReader().read(classLoader, resourcePath); + } + + /** + * Read all the specific manifest files and return the set of packages containing type query beans. + */ + private Set read(ClassLoader classLoader, String resourcePath) { + + try { + Enumeration resources = classLoader.getResources(resourcePath); + while (resources.hasMoreElements()) { + InputStream is = resources.nextElement().openStream(); + try { + read(new Manifest(is)); + } finally { + close(is); + } + } + } catch (IOException e) { + logger.warn("Error reading META-INF/ebean.mf manifest resources", e); + } + return packageSet; + } + + private void close(InputStream is) { + try { + is.close(); + } catch (IOException e) { + logger.warn("Error closing manifest InputStream", e); + } + } + + /** + * Read the packages from the manifest. + */ + private void read(Manifest manifest) throws IOException { + + Attributes attributes = manifest.getMainAttributes(); + String packages = attributes.getValue("packages"); + if (packages != null) { + add(packages); + } + } + + /** + * Collect each individual package splitting by delimiters. + */ + private void add(String packages) { + String[] split = packages.split(",|;| "); + for (int i = 0; i distill = DistillPackages.distill(group("one", "two"), group("three")); + assertThat(distill).containsExactly("one", "three", "two"); + } + + @Test + public void when_sub_expect_distilled() throws Exception { + + List distill = DistillPackages.distill(group("one", "one.sub"), group("three")); + assertThat(distill).containsExactly("one", "three"); + } + + + @Test + public void when_sub_expect_distilled2() throws Exception { + + List distill = DistillPackages.distill(group("one", "one.sub"), group("one.foo")); + assertThat(distill).containsExactly("one"); + } + + @Test + public void when_subDotSub_expect_distilled2() throws Exception { + + List distill = DistillPackages.distill(group("one", "one.sub.me"), group("two")); + assertThat(distill).containsExactly("one", "two"); + } + + @Test + public void when_unordered_expect_naturalOrder() throws Exception { + + List distill = DistillPackages.distill(group("z.x.y","two"), group("one", "one.sub.me")); + assertThat(distill).containsExactly("one", "two", "z.x.y"); + } + + List group(String... packages) { + return Arrays.asList(packages); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReaderTest.java b/src/test/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReaderTest.java new file mode 100644 index 000000000..c8736a147 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/core/bootup/ManifestReaderTest.java @@ -0,0 +1,30 @@ +package com.avaje.ebeaninternal.server.core.bootup; + +import org.junit.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ManifestReaderTest { + + @Test + public void readOne() throws Exception { + + Set packageSet = readMf("META-INF/test/test-one.mf"); + assertThat(packageSet).contains("foo"); + } + + @Test + public void readSome() throws Exception { + + Set packageSet = readMf("META-INF/test/test-some.mf"); + assertThat(packageSet).contains("com.foo.domain", "com.bar.domain"); + } + + private Set readMf(String path) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + return ManifestReader.readManifests(classLoader, path); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/DefaultTypeManagerTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/DefaultTypeManagerTest.java index b61d8524d..cca2a3e9f 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/type/DefaultTypeManagerTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/type/DefaultTypeManagerTest.java @@ -2,7 +2,7 @@ package com.avaje.ebeaninternal.server.type; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.PostgresPlatform; -import com.avaje.ebeaninternal.server.core.BootupClasses; +import com.avaje.ebeaninternal.server.core.bootup.BootupClasses; import org.junit.Test; import static org.junit.Assert.*; diff --git a/src/test/resources/META-INF/test/test-one.mf b/src/test/resources/META-INF/test/test-one.mf new file mode 100644 index 000000000..450fb6ccf --- /dev/null +++ b/src/test/resources/META-INF/test/test-one.mf @@ -0,0 +1 @@ +packages: foo diff --git a/src/test/resources/META-INF/test/test-some.mf b/src/test/resources/META-INF/test/test-some.mf new file mode 100644 index 000000000..b2b4109e5 --- /dev/null +++ b/src/test/resources/META-INF/test/test-some.mf @@ -0,0 +1 @@ +packages: com.foo.domain, com.bar.domain