blob: 9e79551ce1854bd8a9ecdf69d38eaeefcde4d04c [file] [log] [blame]
/*
* 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.webbeans.corespi.scanner;
import org.apache.webbeans.config.OWBLogConst;
import org.apache.webbeans.config.OpenWebBeansConfiguration;
import org.apache.webbeans.config.WebBeansContext;
import org.apache.webbeans.corespi.scanner.xbean.CdiArchive;
import org.apache.webbeans.corespi.scanner.xbean.OwbAnnotationFinder;
import org.apache.webbeans.exception.WebBeansDeploymentException;
import org.apache.webbeans.logger.WebBeansLoggerFacade;
import org.apache.webbeans.spi.BDABeansXmlScanner;
import org.apache.webbeans.spi.BdaScannerService;
import org.apache.webbeans.spi.BeanArchiveService;
import org.apache.webbeans.spi.BeanArchiveService.BeanDiscoveryMode;
import org.apache.webbeans.util.ClassUtil;
import org.apache.webbeans.util.UrlSet;
import org.apache.webbeans.util.WebBeansUtil;
import org.apache.xbean.finder.AnnotationFinder;
import org.apache.xbean.finder.ClassLoaders;
import org.apache.xbean.finder.filter.Filter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public abstract class AbstractMetaDataDiscovery implements BdaScannerService
{
protected static final Logger logger = WebBeansLoggerFacade.getLogger(AbstractMetaDataDiscovery.class);
public static final String META_INF_BEANS_XML = "META-INF/beans.xml";
private BeanArchiveService beanArchiveService;
/**
* Location of the beans.xml files.
* Since CDI-1.1 (OWB-2.0) this also includes 'implicit bean archives.
* Means URLs of JARs which do not have a beans.xml marker file.
*/
private final UrlSet beanArchiveLocations = new UrlSet();
/**
* This Map contains the corresponding deployment URL for each beans.xml locations.
*
* key: the beans.xml externalForm
* value: the corresponding base URL
*
* We store this information since not all containers and storages do support
* new URL(...).
*/
private final Map<String, URL> beanDeploymentUrls = new HashMap<String, URL>();
/**
* for having proper scan mode 'SCOPED' support we need to know which bean class
* has which beans.xml.
*/
private Map<BeanArchiveService.BeanArchiveInformation, Set<Class<?>>> beanClassesPerBda;
protected String[] scanningExcludes;
protected ClassLoader loader;
protected CdiArchive archive;
protected OwbAnnotationFinder finder;
protected boolean isBDAScannerEnabled = false;
protected BDABeansXmlScanner bdaBeansXmlScanner;
protected final WebBeansContext webBeansContext;
protected AbstractMetaDataDiscovery()
{
webBeansContext = WebBeansContext.getInstance();
}
protected AnnotationFinder initFinder()
{
if (finder != null)
{
return finder;
}
if (beanArchiveService == null)
{
beanArchiveService = webBeansContext.getBeanArchiveService();
}
final Filter userFilter = webBeansContext.getService(Filter.class);
archive = new CdiArchive(beanArchiveService, WebBeansUtil.getCurrentClassLoader(), getBeanDeploymentUrls(), userFilter);
finder = new OwbAnnotationFinder(archive);
return finder;
}
/**
* @return list of beans.xml locations or implicit bean archives
* @deprecated just here for backward compat reasons
*/
protected Iterable<URL> getBeanArchiveUrls()
{
return beanArchiveLocations;
}
/**
* @return URLs of all classpath entries which
*/
public Map<String, URL> getBeanDeploymentUrls()
{
return beanDeploymentUrls;
}
/**
* Configure the Web Beans Container with deployment information and fills
* annotation database and beans.xml stream database.
*
* @throws org.apache.webbeans.exception.WebBeansConfigurationException if any run time exception occurs
*/
@Override
public void scan() throws WebBeansDeploymentException
{
try
{
configure();
initFinder();
}
catch (Exception e)
{
throw new WebBeansDeploymentException(e);
}
}
protected abstract void configure();
/**
* Since CDI-1.1 this is actually more a 'findBdaBases' as it also
* picks up jars without marker file.
* This will register all 'explicit' Bean Archives, aka all
* META-INF/beans.xml resources on the classpath. Those will
* be added including the META-INF/beans.xml in the URL.
*
* We will also add all other classpath locations which do not
* have the beans.xml marker file, the 'implicit bean archives'.
* In this case the URL will point to the root of the classpath entry.
*
* @param loader the ClassLoader which should be used
*
* @see #getBeanArchiveUrls()
* @see #getBeanDeploymentUrls()
*/
protected void registerBeanArchives(ClassLoader loader)
{
this.loader = loader;
try
{
Set<URL> classPathUrls = ClassLoaders.findUrls(loader);
// first step: get all META-INF/beans.xml marker files
Enumeration<URL> beansXmlUrls = loader.getResources(META_INF_BEANS_XML);
while (beansXmlUrls.hasMoreElements())
{
URL beansXmlUrl = beansXmlUrls.nextElement();
addWebBeansXmlLocation(beansXmlUrl);
// second step: remove the corresponding classpath entry if we found an explicit beans.xml
String beansXml = beansXmlUrl.toExternalForm();
beansXml = stripProtocol(beansXml);
Iterator<URL> cpIt = classPathUrls.iterator(); // do not use Set<URL> remove as this would trigger hashCode -> DNS
while (cpIt.hasNext())
{
URL cpUrl = cpIt.next();
if (beansXml.startsWith(stripProtocol(cpUrl.toExternalForm())))
{
cpIt.remove();
addDeploymentUrl(beansXml, cpUrl);
break;
}
}
}
boolean onlyBeansXmlJars = webBeansContext.getOpenWebBeansConfiguration().scanOnlyBeansXmlJars();
if (!onlyBeansXmlJars)
{
// third step: remove all jars we know they do not contain any CDI beans
filterExcludedJars(classPathUrls);
// forth step: add all 'implicit bean archives'
for (URL url : classPathUrls)
{
if (isBdaUrlEnabled(url))
{
addWebBeansXmlLocation(url);
addDeploymentUrl(url.toExternalForm(), url);
}
}
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
/**
* Get rid of any protocol header from the url externalForm
* @param urlPath
*/
protected String stripProtocol(String urlPath)
{
int pos = urlPath.lastIndexOf(":/");
if (pos > 0)
{
return urlPath.substring(pos+1);
}
return urlPath;
}
protected void filterExcludedJars(Set<URL> classPathUrls)
{
final Iterator<URL> it = classPathUrls.iterator();
while (it.hasNext())
{
final URL url = it.next();
final String path = url.toExternalForm();
// TODO: should extract file path and test file.getName(), not the whole path
// + should be configurable
final int knownJarIdx = isExcludedJar(path);
// -Prun-its openwebbeans-tomcat7 in path but WEB-INF/classes
if (knownJarIdx > 0 && knownJarIdx < path.indexOf(".jar"))
{
//X TODO this should be much more actually
//X TODO we might need to configure it via files
it.remove();
}
}
}
private int isExcludedJar(final String path)
{
// lazy init - required when using DS CdiTestRunner
initScanningExcludes();
for (final String p : scanningExcludes)
{
final int i = path.indexOf(p);
if (i > 0)
{
return i;
}
}
return -1;
}
@Override
public void release()
{
finder = null;
archive = null;
loader = null;
}
/**
* Add an URL for a deployment later on
* @param beansXml
* @param cpUrl
*/
protected void addDeploymentUrl(String beansXml, URL cpUrl)
{
beanDeploymentUrls.put(beansXml, cpUrl);
}
/**
* This method could filter out known JARs or even JVM classpaths which
* shall not be considered bean archives.
*
* @return whether the URL is a bean archive or not
*/
protected boolean isBdaUrlEnabled(URL bdaUrl)
{
return true;
}
@Override
public void init(Object object)
{
// set per BDA beans.xml flag here because setting it in constructor
// occurs before
// properties are loaded.
String usage = WebBeansContext.currentInstance().getOpenWebBeansConfiguration().getProperty(OpenWebBeansConfiguration.USE_BDA_BEANSXML_SCANNER);
isBDAScannerEnabled = Boolean.parseBoolean(usage);
initScanningExcludes();
}
public void initScanningExcludes()
{
if (scanningExcludes == null)
{
OpenWebBeansConfiguration owbConfiguration = WebBeansContext.currentInstance().getOpenWebBeansConfiguration();
String scanningExcludesProperty = owbConfiguration.getProperty(OpenWebBeansConfiguration.SCAN_EXCLUSION_PATHS);
List<String> excludes = owbConfiguration.splitValues(scanningExcludesProperty);
scanningExcludes = excludes.toArray(new String[excludes.size()]);
}
}
/**
* add the given beans.xml path to the locations list
* @param beanArchiveUrl location path
*/
protected void addWebBeansXmlLocation(URL beanArchiveUrl)
{
if(logger.isLoggable(Level.INFO))
{
logger.info("added beans archive URL: " + beanArchiveUrl.toExternalForm());
}
beanArchiveLocations.add(beanArchiveUrl);
// and also scan the bean archive!
if (beanArchiveService == null)
{
beanArchiveService = webBeansContext.getBeanArchiveService();
}
// just to trigger the creation
beanArchiveService.getBeanArchiveInformation(beanArchiveUrl);
}
/**
* This method only gets called if the initialisation is done already.
* It will collect all the classes from all the BDAs it can find.
*/
public Map<BeanArchiveService.BeanArchiveInformation, Set<Class<?>>> getBeanClassesPerBda()
{
if (beanClassesPerBda == null)
{
beanClassesPerBda = new HashMap<BeanArchiveService.BeanArchiveInformation, Set<Class<?>>>();
for (CdiArchive.FoundClasses foundClasses : archive.classesByUrl().values())
{
Set<Class<?>> classSet = new HashSet<Class<?>>();
boolean scanModeAnnotated = BeanDiscoveryMode.ANNOTATED.equals(foundClasses.getBeanArchiveInfo().getBeanDiscoveryMode());
for (String className : foundClasses.getClassNames())
{
try
{
if (scanModeAnnotated)
{
// in this case we need to find out whether we should keep this class in the Archive
AnnotationFinder.ClassInfo classInfo = finder.getClassInfo(className);
if (classInfo == null || !isBeanAnnotatedClass(classInfo))
{
continue;
}
}
Class<?> clazz = ClassUtil.getClassFromName(className);
if (clazz != null)
{
// try to provoke a NoClassDefFoundError exception which is thrown
// if some dependencies of the class are missing
clazz.getDeclaredFields();
// we can add this class cause it has been loaded completely
classSet.add(clazz);
}
}
catch (NoClassDefFoundError e)
{
if (logger.isLoggable(Level.WARNING))
{
logger.log(Level.WARNING, OWBLogConst.WARN_0018, new Object[]{className, e.toString()});
}
}
}
beanClassesPerBda.put(foundClasses.getBeanArchiveInfo(), classSet);
}
}
return beanClassesPerBda;
}
/* (non-Javadoc)
* @see org.apache.webbeans.corespi.ScannerService#getBeanClasses()
*/
@Override
public Set<Class<?>> getBeanClasses()
{
// do nothing, getBeanClasses() should not get invoked anymore
return Collections.EMPTY_SET;
}
/**
* This method is called for classes from bean archives with
* bean-discovery-mode 'annotated'.
*
* This method is intended to be overwritten in integration scenarios and e.g.
* allows to add other criterias for keeping the class.
*
* @param classInfo
* @return true if this class should be kept and further get picked up as CDI Bean
*/
protected boolean isBeanAnnotatedClass(AnnotationFinder.ClassInfo classInfo)
{
// check whether this class has 'scope' annotations or a stereotype
for (AnnotationFinder.AnnotationInfo annotationInfo : classInfo.getAnnotations())
{
if (isBeanAnnotation(annotationInfo))
{
return true;
}
}
return false;
}
protected boolean isBeanAnnotation(AnnotationFinder.AnnotationInfo annotationInfo)
{
String annotationName = annotationInfo.getName();
// TODO add caches
try
{
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) WebBeansUtil.getCurrentClassLoader().loadClass(annotationName);
boolean isBeanAnnotation = webBeansContext.getBeanManagerImpl().isScope(annotationType);
isBeanAnnotation = isBeanAnnotation || webBeansContext.getBeanManagerImpl().isStereotype(annotationType);
return isBeanAnnotation;
}
catch (ClassNotFoundException e)
{
return false;
}
}
@Override
public Set<URL> getBeanXmls()
{
return Collections.unmodifiableSet(beanArchiveLocations);
}
@Override
public BDABeansXmlScanner getBDABeansXmlScanner()
{
return bdaBeansXmlScanner;
}
@Override
public boolean isBDABeansXmlScanningEnabled()
{
return isBDAScannerEnabled;
}
}