diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
index 74c009314ac..6f800f82d92 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
@@ -3906,6 +3906,15 @@ public class PersistenceUnitProperties {
*/
public static final String NAMING_INTO_INDEXED = "eclipselink.jpa.naming_into_indexed";
+ /**
+ * The "eclipselink.packages-to-scan
" property
+ * allows you to specify a comma separated list of packages you allow to be scanned.
+ * This could help reducing startup time.
+ *
+ * By default, no restriction will be applied and classes from every packages will be considered.
+ */
+ public static final String PACKAGES_TO_SCAN = "eclipselink.packages-to-scan";
+
/**
* INTERNAL: The following properties will not be displayed through logging
* but instead have an alternate value shown in the log.
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/PersistenceUnitProcessorTest.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/PersistenceUnitProcessorTest.java
index a1fe8967ba1..089aefbfd5f 100644
--- a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/PersistenceUnitProcessorTest.java
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/PersistenceUnitProcessorTest.java
@@ -20,6 +20,7 @@
import junit.framework.Test;
import junit.framework.TestSuite;
+import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.SystemProperties;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.jpa.deployment.ArchiveFactoryImpl;
@@ -32,8 +33,7 @@
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import jakarta.persistence.EntityManagerFactory;
@@ -222,4 +222,35 @@ public void testGetArchiveFactoryOverride() {
public static class AF1 extends ArchiveFactoryImpl {}
public static class AF2 extends ArchiveFactoryImpl {}
+ public void testIsEligibleToScan() {
+ Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(Collections.emptyList(), "foo/bar/MyClass.class"));
+
+ List pathsToScan = new ArrayList<>();
+ pathsToScan.add("foo/bar/");
+ pathsToScan.add("com/test/");
+
+ Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/bar/MyClass.class"));
+ Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/bar/inner/MyClass.class"));
+ Assert.assertFalse(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/MyClass.class"));
+ Assert.assertFalse(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "foo/barMyClass.class"));
+
+ Assert.assertTrue(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "com/test/MyClass.class"));
+
+ Assert.assertFalse(PersistenceUnitProcessor.isEligibleToScan(pathsToScan, "org/apache/SomeClass.class"));
+ }
+
+ public void testPathsToScan() {
+ Assert.assertTrue(PersistenceUnitProcessor.pathsToScan(null).isEmpty());
+ Assert.assertTrue(PersistenceUnitProcessor.pathsToScan("").isEmpty());
+ Assert.assertTrue(PersistenceUnitProcessor.pathsToScan(" ").isEmpty());
+
+ List pathsToScan = PersistenceUnitProcessor.pathsToScan("foo.bar,com.test");
+ Assert.assertEquals(2, pathsToScan.size());
+ Assert.assertTrue(pathsToScan.contains("foo/bar/"));
+ Assert.assertTrue(pathsToScan.contains("com/test/"));
+
+ List pathsToScan2 = PersistenceUnitProcessor.pathsToScan("foo.bar, ");
+ Assert.assertEquals(1, pathsToScan2.size());
+ Assert.assertTrue(pathsToScan2.contains("foo/bar/"));
+ }
}
diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/deployment/PersistenceUnitProcessor.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/deployment/PersistenceUnitProcessor.java
index 024a6511aa7..d273ce8a589 100644
--- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/deployment/PersistenceUnitProcessor.java
+++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/deployment/PersistenceUnitProcessor.java
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
+ * Copyright (c) 1998, 2020 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -42,6 +42,7 @@
import java.security.PrivilegedActionException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
@@ -479,22 +480,22 @@ public static ArchiveFactory getArchiveFactory(ClassLoader loader, Map propertie
}
public static Set getClassNamesFromURL(URL url, ClassLoader loader, Map properties) {
- Set classNames = new HashSet();
+ Set classNames = new HashSet<>();
Archive archive = null;
try {
archive = PersistenceUnitProcessor.getArchiveFactory(loader, properties).createArchive(url, properties);
+ List pathsToScan = pathsToScan(properties != null ? (String) properties.get(PersistenceUnitProperties.PACKAGES_TO_SCAN) : null);
+
if (archive != null) {
for (Iterator entries = archive.getEntries(); entries.hasNext();) {
String entry = entries.next();
- if (entry.endsWith(".class")){ // NOI18N
+ if (entry.endsWith(".class") && isEligibleToScan(pathsToScan, entry)) { // NOI18N
classNames.add(buildClassNameFromEntryString(entry));
}
}
}
- } catch (URISyntaxException e) {
- throw new RuntimeException("url = [" + url + "]", e); // NOI18N
- } catch (IOException e) {
+ } catch (IOException | URISyntaxException e) {
throw new RuntimeException("url = [" + url + "]", e); // NOI18N
} finally {
if (archive != null) {
@@ -504,6 +505,55 @@ public static Set getClassNamesFromURL(URL url, ClassLoader loader, Map
return classNames;
}
+ /**
+ * Returns true if a class entry is eligible to scan.
+ * A class entry is eligible if:
+ * - pathsToScan is empty (not configured)
+ * - it resides in one of {@code pathsToScan} list
+ *
+ * @param pathsToScan list of paths where the class entry must be. Can be empty
+ * @param classEntry path of the class we want to check in the form : foo/bar/MyClass.class
+ * @return true is the class entry is eligible
+ */
+ public static boolean isEligibleToScan(List pathsToScan, String classEntry) {
+ if (pathsToScan.isEmpty()) {
+ return true;
+ }
+
+ for (String pathToScan : pathsToScan) {
+ if (classEntry.startsWith(pathToScan)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Takes a comma separated list of packages to scan and returns a list of
+ * paths.
+ *
+ * @param packagesToScanProperty list of packages to scan (comma-separated)
+ * @return list of paths to scans
+ */
+ public static List pathsToScan(String packagesToScanProperty) {
+ if (packagesToScanProperty == null || packagesToScanProperty.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List packagesToScan = new ArrayList<>();
+
+ for (String packageToScan : packagesToScanProperty.split(",")) {
+ String trimmedPackageToScan = packageToScan.trim();
+
+ if (!trimmedPackageToScan.isEmpty()) {
+ packagesToScan.add(trimmedPackageToScan.replace('.', '/') + "/");
+ }
+ }
+
+ return packagesToScan;
+ }
+
/**
* Return if a given class is annotated with @Embeddable.
*/