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 <rmannibu...@apache.org> 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'