This is an automated email from the ASF dual-hosted git repository.
rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwebbeans.git
The following commit(s) were added to refs/heads/master by this push:
new 5e72019 OWB-1299 ensure jar with an extension can be skipped in terms
of scanning, org.apache.webbeans.scanExtensionJars = true new property, default
stays consistent with our previous behavior
5e72019 is described below
commit 5e72019d22bc49860ef33e9bf134d712b44221c1
Author: Romain Manni-Bucau <[email protected]>
AuthorDate: Sat Oct 5 17:53:02 2019 +0200
OWB-1299 ensure jar with an extension can be skipped in terms of scanning,
org.apache.webbeans.scanExtensionJars = true new property, default stays
consistent with our previous behavior
---
.../webbeans/config/OpenWebBeansConfiguration.java | 23 +++
.../corespi/scanner/AbstractMetaDataDiscovery.java | 22 ++-
.../webbeans/portable/events/ExtensionLoader.java | 74 ++++++++-
.../META-INF/openwebbeans/openwebbeans.properties | 7 +
.../scanner/AbstractMetaDataDiscoveryTest.java | 174 +++++++++++++++++++++
.../org/apache/webbeans/test/AbstractUnitTest.java | 14 +-
6 files changed, 306 insertions(+), 8 deletions(-)
diff --git
a/webbeans-impl/src/main/java/org/apache/webbeans/config/OpenWebBeansConfiguration.java
b/webbeans-impl/src/main/java/org/apache/webbeans/config/OpenWebBeansConfiguration.java
index 968afd0..187f665 100644
---
a/webbeans-impl/src/main/java/org/apache/webbeans/config/OpenWebBeansConfiguration.java
+++
b/webbeans-impl/src/main/java/org/apache/webbeans/config/OpenWebBeansConfiguration.java
@@ -181,6 +181,13 @@ public class OpenWebBeansConfiguration
public static final String IGNORED_EXTENSIONS =
"org.apache.webbeans.ignoredExtensions";
/**
+ * A boolean to enable CDI 1.1 behavior to not scan extension jar.
+ *
+ * IMPORTANT: this can break CDI 1.0 extensions.
+ */
+ public static final String SCAN_EXTENSION_JARS =
"org.apache.webbeans.scanExtensionJars";
+
+ /**
* By default we do _not_ force session creation in our
WebBeansConfigurationListener. We only create the
* Session if we really need the SessionContext. E.g. when we create a
Contextual Instance in it.
* Sometimes this creates a problem as the HttpSession can only be created
BEFORE anything got written back
@@ -226,6 +233,11 @@ public class OpenWebBeansConfiguration
private Set<String> ignoredExtensions;
/**
+ * @see #SCAN_EXTENSION_JARS
+ */
+ private Boolean scanExtensionJars;
+
+ /**
* All configured lists per key.
*
* For a single key the following configuration sources will get parsed:
@@ -450,6 +462,17 @@ public class OpenWebBeansConfiguration
return ignoredExtensions;
}
+ public synchronized boolean getScanExtensionJars()
+ {
+ if (scanExtensionJars == null)
+ {
+ final String property = getProperty(SCAN_EXTENSION_JARS);
+ // default must stay true for backward compatibility
+ scanExtensionJars = property == null ||
Boolean.parseBoolean(property.trim());
+ }
+ return scanExtensionJars;
+ }
+
private Set<String> getPropertyList(String configKey)
{
String configValue = getProperty(configKey);
diff --git
a/webbeans-impl/src/main/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscovery.java
b/webbeans-impl/src/main/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscovery.java
index 4fded1f..44febe9 100644
---
a/webbeans-impl/src/main/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscovery.java
+++
b/webbeans-impl/src/main/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscovery.java
@@ -19,6 +19,8 @@
package org.apache.webbeans.corespi.scanner;
+import static java.util.stream.Collectors.toMap;
+
import org.apache.webbeans.config.OWBLogConst;
import org.apache.webbeans.config.OpenWebBeansConfiguration;
import org.apache.webbeans.config.WebBeansContext;
@@ -105,13 +107,27 @@ public abstract class AbstractMetaDataDiscovery
implements BdaScannerService
return finder;
}
+ final WebBeansContext webBeansContext = webBeansContext();
if (beanArchiveService == null)
{
- beanArchiveService = webBeansContext().getBeanArchiveService();
+ beanArchiveService = webBeansContext.getBeanArchiveService();
}
- Filter userFilter = webBeansContext().getService(Filter.class);
- archive = new CdiArchive(beanArchiveService,
WebBeansUtil.getCurrentClassLoader(), getBeanDeploymentUrls(), userFilter,
getAdditionalArchive());
+ final Filter userFilter = webBeansContext.getService(Filter.class);
+ Map<String, URL> beanDeploymentUrls = getBeanDeploymentUrls();
+ if
(!webBeansContext.getOpenWebBeansConfiguration().getScanExtensionJars())
+ {
+ webBeansContext.getExtensionLoader().loadExtensionServices();
+
+ final Set<URL> extensionJars =
webBeansContext.getExtensionLoader().getExtensionJars();
+ beanDeploymentUrls = extensionJars.isEmpty() ? beanDeploymentUrls
: beanDeploymentUrls.entrySet().stream()
+ .filter(it -> !extensionJars.contains(it.getValue()))
+ .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+ extensionJars.clear(); // no more needed
+ }
+ archive = new CdiArchive(
+ beanArchiveService, WebBeansUtil.getCurrentClassLoader(),
+ beanDeploymentUrls, userFilter, getAdditionalArchive());
finder = new OwbAnnotationFinder(archive);
return finder;
diff --git
a/webbeans-impl/src/main/java/org/apache/webbeans/portable/events/ExtensionLoader.java
b/webbeans-impl/src/main/java/org/apache/webbeans/portable/events/ExtensionLoader.java
index e331baf..b3b9913 100644
---
a/webbeans-impl/src/main/java/org/apache/webbeans/portable/events/ExtensionLoader.java
+++
b/webbeans-impl/src/main/java/org/apache/webbeans/portable/events/ExtensionLoader.java
@@ -18,9 +18,16 @@
*/
package org.apache.webbeans.portable.events;
+import static java.util.stream.Collectors.toSet;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
@@ -37,6 +44,7 @@ import org.apache.webbeans.exception.WebBeansException;
import org.apache.webbeans.logger.WebBeansLoggerFacade;
import org.apache.webbeans.util.ExceptionUtil;
import org.apache.webbeans.util.WebBeansUtil;
+import org.apache.xbean.finder.archive.FileArchive;
/**
* Loads META-INF/services/javax.enterprise.inject.spi.Extension
@@ -56,6 +64,8 @@ public class ExtensionLoader
private final BeanManagerImpl manager;
private final WebBeansContext webBeansContext;
+ private final Set<URL> extensionJars = new HashSet<>();
+ private boolean loaded;
/**
* Creates a new loader instance.
@@ -71,9 +81,13 @@ public class ExtensionLoader
/**
* Load extension services.
*/
- public void loadExtensionServices()
+ public synchronized void loadExtensionServices()
{
- loadExtensionServices(WebBeansUtil.getCurrentClassLoader());
+ if (!loaded)
+ {
+ loadExtensionServices(WebBeansUtil.getCurrentClassLoader());
+ loaded = true;
+ }
}
/**
@@ -115,9 +129,61 @@ public class ExtensionLoader
throw new WebBeansException("Error occurred while reading
Extension service list", e);
}
}
- }
+ }
+
+ if
(!webBeansContext.getOpenWebBeansConfiguration().getScanExtensionJars())
+ {
+ extensionJars.addAll(extensionClasses.stream()
+ .map(clazz -> toJar(classLoader, clazz))
+ .filter(Objects::nonNull)
+ .collect(toSet()));
+ }
+ }
+
+ private URL toJar(final ClassLoader loader, final Class<?> clazz)
+ {
+ try
+ {
+ final String resource = clazz.getName().replace('.', '/') +
".class";
+ Enumeration<URL> urls = loader.getResources(resource);
+ while (urls.hasMoreElements())
+ {
+ URL url = urls.nextElement();
+ switch (url.getProtocol())
+ {
+ case "jar":
+ {
+ final String spec = url.getFile();
+ int separator = spec.indexOf('!');
+ if (separator == -1)
+ {
+ break;
+ }
+ url = new URL(spec.substring(0, separator));
+ return new
File(FileArchive.decode(url.getFile())).toURI().toURL();
+ }
+ case "file":
+ {
+ String path = url.getFile();
+ path = path.substring(0, path.length() -
resource.length());
+ return new
File(FileArchive.decode(path)).toURI().toURL();
+ }
+ default:
+ }
+ }
+ }
+ catch (final IOException ioe)
+ {
+ logger.warning(ioe.getMessage());
+ }
+ return null;
+ }
+
+ public Set<URL> getExtensionJars()
+ {
+ return extensionJars;
}
-
+
/**
* Returns service bean instance.
*
diff --git
a/webbeans-impl/src/main/resources/META-INF/openwebbeans/openwebbeans.properties
b/webbeans-impl/src/main/resources/META-INF/openwebbeans/openwebbeans.properties
index f2818e7..fec5259 100644
---
a/webbeans-impl/src/main/resources/META-INF/openwebbeans/openwebbeans.properties
+++
b/webbeans-impl/src/main/resources/META-INF/openwebbeans/openwebbeans.properties
@@ -153,6 +153,13 @@ org.apache.webbeans.web.eagerSessionInitialisation=false
# org.apache.webbeans.generator.javaVersion=1.6
################################################################################################
+############################# Are Extension jar scanned
################################
+# In CDI 1.0 it was done but no more in next versions.
+# To avoid any impacting breaking change we still scan by default these jars
+# but you can enforce the spec behavior setting that property to false.
+# org.apache.webbeans.scanExtensionJars = true
+################################################################################################
+
############################# Ignored CDI Extension class names
################################
# A comma separated list of CDI Extension class names which should get ignored.
# Each listed class name must be fully qualified.
diff --git
a/webbeans-impl/src/test/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscoveryTest.java
b/webbeans-impl/src/test/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscoveryTest.java
index 5a62e2f..ca86eb0 100644
---
a/webbeans-impl/src/test/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscoveryTest.java
+++
b/webbeans-impl/src/test/java/org/apache/webbeans/corespi/scanner/AbstractMetaDataDiscoveryTest.java
@@ -18,15 +18,55 @@
*/
package org.apache.webbeans.corespi.scanner;
+import static java.util.Collections.emptyEnumeration;
+import static java.util.Collections.emptyMap;
+import static org.apache.xbean.asm7.ClassWriter.COMPUTE_FRAMES;
+import static org.apache.xbean.asm7.Opcodes.ACC_PUBLIC;
+import static org.apache.xbean.asm7.Opcodes.ACC_SUPER;
+import static org.apache.xbean.asm7.Opcodes.ALOAD;
+import static org.apache.xbean.asm7.Opcodes.INVOKESPECIAL;
+import static org.apache.xbean.asm7.Opcodes.RETURN;
+import static org.apache.xbean.asm7.Opcodes.V1_8;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.Extension;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.config.WebBeansFinder;
+import org.apache.webbeans.corespi.DefaultSingletonService;
+import org.apache.webbeans.spi.ContainerLifecycle;
+import org.apache.xbean.asm7.ClassWriter;
+import org.apache.xbean.asm7.MethodVisitor;
+import org.apache.xbean.asm7.Type;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
public class AbstractMetaDataDiscoveryTest
{
+ @Rule
+ public final TemporaryFolder temp = new TemporaryFolder();
+
@Test
public void isAnonymous() throws Exception
{
@@ -45,4 +85,138 @@ public class AbstractMetaDataDiscoveryTest
assertTrue(Boolean.class.cast(mtd.invoke(mock,
AbstractMetaDataDiscoveryTest.class.getName() + "$1$2")));
assertTrue(Boolean.class.cast(mtd.invoke(mock,
AbstractMetaDataDiscoveryTest.class.getName() + "$15$222")));
}
+
+ @Test
+ public void skipExtensionJarScanning() throws Exception
+ {
+ // we create a module with some scanned beans
+ final URL scannedModule = createScannedModule();
+
+ // we create another module with some elligible beans and an extension
+ final URL extensionModule = createExtensionModule();
+
+ final Thread thread = Thread.currentThread();
+ final ClassLoader oldLoader = thread.getContextClassLoader();
+ final URL[] urls = {scannedModule, extensionModule};
+ try (final URLClassLoader loader = new URLClassLoader(urls, new
ClassLoader() {
+ @Override
+ public Class<?> loadClass(final String name) throws
ClassNotFoundException
+ {
+ return oldLoader.loadClass(name);
+ }
+
+ @Override
+ public URL getResource(final String name)
+ {
+ return oldLoader.getResource(name);
+ }
+
+ @Override
+ public Enumeration<URL> getResources(final String name) throws
IOException
+ {
+ if ("META-INF".equals(name) || "".equals(name)) // scanning
+ {
+ return emptyEnumeration();
+ }
+ return oldLoader.getResources(name);
+ }
+ })
+ {
+ @Override
+ public URL[] getURLs()
+ {
+ return urls;
+ }
+ })
+ {
+ thread.setContextClassLoader(loader);
+
+ // we disable extension jar scanning and start then
+ // we start the container and check we scanned only first module
+ final Properties config = new Properties();
+ config.setProperty("org.apache.webbeans.scanExtensionJars",
"false");
+ config.setProperty("org.apache.webbeans.scanExclusionPaths",
"/classes,/test-classes," +
+
"/xbean,/ham,/junit-,/junit5-,/debugger,/idea,/openwebbeans,/geronimo");
+ final WebBeansContext context = new WebBeansContext(emptyMap(),
config);
+ final DefaultSingletonService singletonService =
DefaultSingletonService.class.cast(
+ WebBeansFinder.getSingletonService());
+ singletonService.register(loader, context);
+ final ContainerLifecycle lifecycle =
context.getService(ContainerLifecycle.class);
+ lifecycle.startApplication(null);
+ try
+ {
+ final BeanManager manager = context.getBeanManagerImpl();
+
+ final Set<Bean<?>> foos = manager.getBeans(
+
loader.loadClass("org.apache.openwebbeans.generated.test.Foo"));
+ assertEquals(1, foos.size());
+
+ final Set<Bean<?>> bars = manager.getBeans(
+
loader.loadClass("org.apache.openwebbeans.generated.test.Bar"));
+ assertTrue(bars.isEmpty());
+
+ final Object myExtension = context.getExtensionLoader()
+
.getExtension(loader.loadClass("org.apache.openwebbeans.generated.test.MyExtension"));
+ assertNotNull(myExtension);
+ }
+ finally
+ {
+ lifecycle.stopApplication(null);
+ singletonService.clear(loader);
+ }
+ }
+ finally
+ {
+ thread.setContextClassLoader(oldLoader);
+ }
+ }
+
+ private URL createScannedModule() throws IOException
+ {
+ final File file = temp.newFile("test-scanned.jar");
+ try (final JarOutputStream outputStream = new JarOutputStream(new
FileOutputStream(file)))
+ {
+ createBean(outputStream,
"org/apache/openwebbeans/generated/test/Foo.class", null);
+ outputStream.putNextEntry(new JarEntry("META-INF/beans.xml"));
+ outputStream.closeEntry();
+ }
+ return file.toURI().toURL();
+ }
+
+ private URL createExtensionModule() throws IOException
+ {
+ final File file = temp.newFile("test-extension.jar");
+ try (final JarOutputStream outputStream = new JarOutputStream(new
FileOutputStream(file)))
+ {
+ createBean(outputStream,
"org/apache/openwebbeans/generated/test/Bar.class", null);
+ createBean(outputStream,
"org/apache/openwebbeans/generated/test/MyExtension.class", Extension.class);
+ outputStream.putNextEntry(new JarEntry("META-INF/services/" +
Extension.class.getName()));
+
outputStream.write("org.apache.openwebbeans.generated.test.MyExtension".getBytes(StandardCharsets.UTF_8));
+ outputStream.closeEntry();
+ }
+ return file.toURI().toURL();
+ }
+
+ private void createBean(final JarOutputStream outputStream, final String
resource, final Class<?> itf)
+ throws IOException
+ {
+ outputStream.putNextEntry(new JarEntry(resource));
+ final ClassWriter writer = new ClassWriter(COMPUTE_FRAMES);
+ // make it count for annotated mode
+ writer.visitAnnotation(Type.getDescriptor(ApplicationScoped.class),
true).visitEnd();
+ writer.visit(V1_8, ACC_PUBLIC + ACC_SUPER,
+ resource.substring(0, resource.length() - ".class".length()),
null,
+ Type.getInternalName(Object.class), itf == null ? null : new
String[]{ Type.getInternalName(itf) });
+ writer.visitSource(resource.replace(".class", ".java"), null);
+ final MethodVisitor constructor = writer.visitMethod(ACC_PUBLIC,
"<init>", "()V", null, null);
+ constructor.visitCode();
+ constructor.visitVarInsn(ALOAD, 0);
+ constructor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object",
"<init>", "()V", false);
+ constructor.visitInsn(RETURN);
+ constructor.visitMaxs(1, 1);
+ constructor.visitEnd();
+ writer.visitEnd();
+ outputStream.write(writer.toByteArray());
+ outputStream.closeEntry();
+ }
}
diff --git
a/webbeans-impl/src/test/java/org/apache/webbeans/test/AbstractUnitTest.java
b/webbeans-impl/src/test/java/org/apache/webbeans/test/AbstractUnitTest.java
index dee3a11..054f1af 100644
--- a/webbeans-impl/src/test/java/org/apache/webbeans/test/AbstractUnitTest.java
+++ b/webbeans-impl/src/test/java/org/apache/webbeans/test/AbstractUnitTest.java
@@ -59,6 +59,7 @@ public abstract class AbstractUnitTest
{
private StandaloneLifeCycle testLifecycle;
private Map<Class<?>, Object> services = new HashMap<>();
+ private Properties configuration = new Properties();
private List<Extension> extensions = new ArrayList<>();
private List<Class<?>> interceptors = new ArrayList<Class<?>>();
private List<Class<?>> decorators = new ArrayList<Class<?>>();
@@ -75,6 +76,8 @@ public abstract class AbstractUnitTest
extensions.clear();
interceptors.clear();
decorators.clear();
+ services.clear();
+ configuration.clear();
}
/**
@@ -148,7 +151,7 @@ public abstract class AbstractUnitTest
final Properties properties = wbcAwareServices.entrySet().stream()
.collect(Collector.of(
- Properties::new,
+ () -> configuration == null ? new Properties() :
configuration,
(p, e) -> p.setProperty(e.getKey().getName(),
Class.class.cast(e.getValue()).getName()),
(properties1, properties2) -> {
properties1.putAll(properties2);
@@ -322,6 +325,15 @@ public abstract class AbstractUnitTest
this.services.put(type, instance);
}
+ protected void addConfiguration(final String key, final String value)
+ {
+ if (configuration == null)
+ {
+ configuration = new Properties();
+ }
+ configuration.setProperty(key, value);
+ }
+
/**
* @param packageName package of the beans.xml file
* @param fileName name of the beans xml file, without '.xml'