/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.cdi; import org.apache.openejb.BeanContext; import org.apache.openejb.assembler.classic.AppInfo; import org.apache.openejb.assembler.classic.BeansInfo; import org.apache.openejb.assembler.classic.EjbJarInfo; import org.apache.openejb.cdi.transactional.MandatoryInterceptor; import org.apache.openejb.cdi.transactional.NeverInterceptor; import org.apache.openejb.cdi.transactional.NotSupportedInterceptor; import org.apache.openejb.cdi.transactional.RequiredInterceptor; import org.apache.openejb.cdi.transactional.RequiredNewInterceptor; import org.apache.openejb.cdi.transactional.SupportsInterceptor; import org.apache.openejb.core.ParentClassLoaderFinder; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.classloader.ClassLoaderComparator; import org.apache.openejb.util.classloader.DefaultClassLoaderComparator; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.container.BeanManagerImpl; import org.apache.webbeans.intercept.InterceptorsManager; import org.apache.webbeans.spi.BDABeansXmlScanner; import org.apache.webbeans.spi.BeanArchiveService; import org.apache.webbeans.spi.ScannerService; import javax.decorator.Decorator; import java.lang.annotation.Annotation; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static java.util.Arrays.asList; /** * @version $Rev:$ $Date:$ */ public class CdiScanner implements ScannerService { public static final String OPENEJB_CDI_FILTER_CLASSLOADER = "openejb.cdi.filter.classloader"; private static final Class<?>[] TRANSACTIONAL_INTERCEPTORS = new Class<?>[]{ MandatoryInterceptor.class, NeverInterceptor.class, NotSupportedInterceptor.class, RequiredInterceptor.class, RequiredNewInterceptor.class, SupportsInterceptor.class }; private final Set<Class<?>> classes = new HashSet<>(); private final Set<Class<?>> startupClasses = new HashSet<>(); private final Set<URL> beansXml = new HashSet<>(); private final boolean logDebug; private WebBeansContext webBeansContext; private ClassLoader containerLoader; public CdiScanner() { logDebug = "true".equals(SystemInstance.get().getProperty("openejb.cdi.noclassdeffound.log", "false")); } public void setContext(final WebBeansContext webBeansContext) { this.webBeansContext = webBeansContext; } @Override public void init(final Object object) { if (!StartupObject.class.isInstance (object)) { return; } containerLoader = ParentClassLoaderFinder.Helper.get(); final StartupObject startupObject = StartupObject.class.cast(object); final AppInfo appInfo = startupObject.getAppInfo(); final ClassLoader classLoader = startupObject.getClassLoader(); final ClassLoaderComparator comparator; if (classLoader instanceof ClassLoaderComparator) { comparator = (ClassLoaderComparator) classLoader; } else { comparator = new DefaultClassLoaderComparator(classLoader); } final WebBeansContext webBeansContext = startupObject.getWebBeansContext(); final InterceptorsManager interceptorsManager = webBeansContext.getInterceptorsManager(); // app beans for (final EjbJarInfo ejbJar : appInfo.ejbJars) { final BeansInfo beans = ejbJar.beans; if (beans == null || "false".equalsIgnoreCase(ejbJar.properties.getProperty("openejb.cdi.activated"))) { continue; } if (startupObject.isFromWebApp()) { // deploy only the related ejbmodule if (!ejbJar.moduleId.equals(startupObject.getWebContext().getId())) { continue; } } else if (ejbJar.webapp && !appInfo.webAppAlone) { continue; } if (appInfo.webAppAlone || !ejbJar.webapp) { // "manual" extension to avoid to add it through SPI mecanism classes.addAll(asList(TRANSACTIONAL_INTERCEPTORS)); for (final Class<?> interceptor : TRANSACTIONAL_INTERCEPTORS) { interceptorsManager.addEnabledInterceptorClass(interceptor); } } // here for ears we need to skip classes in the parent classloader final ClassLoader scl = ClassLoader.getSystemClassLoader(); final boolean filterByClassLoader = "true".equals( ejbJar.properties.getProperty(OPENEJB_CDI_FILTER_CLASSLOADER, SystemInstance.get().getProperty(OPENEJB_CDI_FILTER_CLASSLOADER, "true"))); final BeanArchiveService beanArchiveService = webBeansContext.getBeanArchiveService(); final boolean openejb = OpenEJBBeanInfoService.class.isInstance(beanArchiveService); final Map<BeansInfo.BDAInfo, BeanArchiveService.BeanArchiveInformation> infoByBda = new HashMap<>(); for (final BeansInfo.BDAInfo bda : beans.bdas) { if (bda.uri != null) { try { beansXml.add(bda.uri.toURL()); } catch (final MalformedURLException e) { // no-op } } infoByBda.put(bda, handleBda(startupObject, classLoader, comparator, beans, scl, filterByClassLoader, beanArchiveService, openejb, bda)); } for (final BeansInfo.BDAInfo bda : beans.noDescriptorBdas) { // infoByBda.put() not needed since we know it means annotated handleBda(startupObject, classLoader, comparator, beans, scl, filterByClassLoader, beanArchiveService, openejb, bda); } if (startupObject.getBeanContexts() != null) { for (final BeanContext bc : startupObject.getBeanContexts()) { final String name = bc.getBeanClass().getName(); if (BeanContext.Comp.class.getName().equals(name)) { continue; } boolean cdi = false; for (final BeansInfo.BDAInfo bda : beans.bdas) { final BeanArchiveService.BeanArchiveInformation info = infoByBda.get(bda); if (info.getBeanDiscoveryMode() == BeanArchiveService.BeanDiscoveryMode.NONE) { continue; } if (bda.managedClasses.contains(name)) { classes.add(load(name, classLoader)); cdi = true; break; } } if (!cdi) { for (final BeansInfo.BDAInfo bda : beans.noDescriptorBdas) { if (bda.managedClasses.contains(name)) { classes.add(load(name, classLoader)); break; } } } } } if ("true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.cdi.debug", "false"))) { final Logger logger = Logger.getInstance(LogCategory.OPENEJB, CdiScanner.class.getName()); logger.info("CDI beans for " + startupObject.getAppInfo().appId + (startupObject.getWebContext() != null ? " webcontext = " + startupObject.getWebContext().getContextRoot() : "")); final List<String> names = new ArrayList<>(classes.size()); for (final Class<?> c : classes) { names.add(c.getName()); } Collections.sort(names); for (final String c : names) { logger.info(" " + c); } } } } private void addClasses(final Collection<String> list, final ClassLoader loader) { for (final String s : list) { final Class<?> load = load(s, loader); if (load != null) { classes.add(load); } } } private BeanArchiveService.BeanArchiveInformation handleBda(final StartupObject startupObject, final ClassLoader classLoader,final ClassLoaderComparator comparator, final BeansInfo beans, final ClassLoader scl, final boolean filterByClassLoader, final BeanArchiveService beanArchiveService, final boolean openejb, final BeansInfo.BDAInfo bda) { BeanArchiveService.BeanArchiveInformation information; if (openejb) { final OpenEJBBeanInfoService beanInfoService = OpenEJBBeanInfoService.class.cast(beanArchiveService); information = beanInfoService.createBeanArchiveInformation(bda, beans, classLoader); // TODO: log a warn is discoveryModes.get(key) == null try { beanInfoService.getBeanArchiveInfo().put(bda.uri == null ? null : bda.uri.toURL(), information); } catch (final MalformedURLException e) { throw new IllegalStateException(e); } } else { try { information = beanArchiveService.getBeanArchiveInformation(bda.uri.toURL()); } catch (final MalformedURLException e) { throw new IllegalStateException(e); } } addClasses(information.getAlternativeClasses(), classLoader); addClasses(information.getDecorators(), classLoader); addClasses(information.getInterceptors(), classLoader); addClasses(information.getAlternativeStereotypes(), classLoader); final boolean scanModeAnnotated = BeanArchiveService.BeanDiscoveryMode.ANNOTATED.equals(information.getBeanDiscoveryMode()); final boolean noScan = BeanArchiveService.BeanDiscoveryMode.NONE.equals(information.getBeanDiscoveryMode()); final boolean isNotEarWebApp = startupObject.getWebContext() == null; if (!noScan) { if (scanModeAnnotated) { try { Logger.getInstance(LogCategory.OPENEJB, CdiScanner.class.getName()) .info("Using annotated mode for " + bda.uri.toASCIIString() + " looking all classes to find CDI beans, maybe think to add a beans.xml if not there or " + "add the jar to exclusions.list"); } catch (final Exception ex) { // no-op: not a big deal } } for (final String name : bda.managedClasses) { if (information.isClassExcluded(name)) { continue; } final Class clazz = load(name, classLoader); if (clazz == null) { continue; } if (scanModeAnnotated) { if (isBean(clazz)) { classes.add(clazz); if (beans.startupClasses.contains(name)) { startupClasses.add(clazz); } } } else { final ClassLoader loader = clazz.getClassLoader(); // main case it tries to filter is ear one ie lib classes shouldn't be in webapp classes // but embedded case should still work if (!filterByClassLoader || comparator.isSame(loader) || ((loader.equals(scl) || loader == containerLoader) && isNotEarWebApp)) { classes.add(clazz); if (beans.startupClasses.contains(name)) { startupClasses.add(clazz); } } } } } return information; } // TODO: reusing our finder would be a good idea to avoid reflection we already did! private boolean isBean(final Class clazz) { try { for (final Annotation a : clazz.getAnnotations()) { final Class<? extends Annotation> annotationType = a.annotationType(); final BeanManagerImpl beanManager = webBeansContext.getBeanManagerImpl(); if (beanManager.isScope(annotationType) || beanManager.isStereotype(annotationType) || beanManager.isInterceptorBinding(annotationType) || Decorator.class == a.annotationType()) { return true; } } } catch (final Throwable e) { // no-op } return false; } public boolean isBDABeansXmlScanningEnabled() { return false; } public BDABeansXmlScanner getBDABeansXmlScanner() { return null; } /** * @param className name of class to load * @param classLoader classloader to (try to) load it from * @return the loaded class if possible, or null if loading fails. */ private Class load(final String className, final ClassLoader classLoader) { try { final Class<?> loadClass = classLoader.loadClass(className); tryToMakeItFail(loadClass); return loadClass; } catch (final ClassNotFoundException e) { return null; } catch (final NoClassDefFoundError e) { if (logDebug) { Logger.getInstance(LogCategory.OPENEJB_CDI, CdiScanner.class).warning(className + " -> " + e); } return null; } } private void tryToMakeItFail(final Class<?> loadClass) { // we try to avoid later NoClassDefFoundError loadClass.getDeclaredFields(); loadClass.getDeclaredMethods(); } @Override public void scan() { // Unused } @Override public Set<URL> getBeanXmls() { return beansXml; } @Override public Set<Class<?>> getBeanClasses() { return classes; } @Override public void release() { classes.clear(); } public Set<Class<?>> getStartupClasses() { return startupClasses; } }