blob: b3789b3d4e9c44475d4470a64aefcc7cdf1f58c7 [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.meecrowave.openwebbeans;
import org.apache.meecrowave.Meecrowave;
import org.apache.meecrowave.configuration.Configuration;
import org.apache.meecrowave.logging.tomcat.LogFacade;
import org.apache.tomcat.JarScanFilter;
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.spi.BdaScannerService;
import org.apache.webbeans.spi.BeanArchiveService;
import org.apache.webbeans.util.WebBeansUtil;
import org.apache.webbeans.web.scanner.WebScannerService;
import org.apache.xbean.finder.AnnotationFinder;
import org.apache.xbean.finder.filter.Filter;
import javax.servlet.ServletContext;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toSet;
import static org.apache.tomcat.JarScanType.PLUGGABILITY;
public class OWBTomcatWebScannerService extends WebScannerService {
private final LogFacade logger = new LogFacade(OWBTomcatWebScannerService.class.getName());
private final BdaScannerService delegate;
private final Supplier<OwbAnnotationFinder> finderAccessor;
protected JarScanFilter filter;
private String jreBase;
// just for logging (== temp)
private final Set<String> urls = new HashSet<>();
private String docBase;
private String shared;
private Consumer<File> fileVisitor;
public OWBTomcatWebScannerService() {
this(null, null);
}
public OWBTomcatWebScannerService(final BdaScannerService delegate, final Supplier<OwbAnnotationFinder> finderAccessor) {
this.delegate = delegate;
this.finderAccessor = finderAccessor;
}
@Override
public void init(final Object context) {
if (delegate != null) {
delegate.init(context);
}
}
@Override
public OwbAnnotationFinder getFinder() {
if (finderAccessor != null) {
return finderAccessor.get();
}
return super.getFinder();
}
@Override
public Map<BeanArchiveService.BeanArchiveInformation, Set<Class<?>>> getBeanClassesPerBda() {
if (delegate != null) {
return delegate.getBeanClassesPerBda();
}
return super.getBeanClassesPerBda();
}
@Override
public void release() {
if (delegate != null) {
delegate.release();
} else {
super.release();
}
}
@Override
public Set<Class<?>> getBeanClasses() {
if (delegate != null) {
return delegate.getBeanClasses();
}
return super.getBeanClasses();
}
@Override
public void scan() {
if (delegate != null) {
if (getFinder() == null) {
delegate.scan();
}
if (finder == null) {
finder = getFinder();
}
}
if (finder != null) {
return;
}
super.scan();
scanGroovy(WebBeansUtil.getCurrentClassLoader());
if (!urls.isEmpty()) {
logger.info("OpenWebBeans scanning:");
final String m2 = new File(System.getProperty("user.home", "."), ".m2/repository").getAbsolutePath();
final String base = ofNullable(docBase).orElse("$$$");
final String sharedBase = ofNullable(shared).orElse("$$$");
final String runnerBase = ofNullable(System.getProperty("meecrowave.base")).orElse("$$$");
final String runnerHome = ofNullable(System.getProperty("meecrowave.home")).orElse("$$$");
urls.stream().map(u -> {
String shownValue = u
// protocol
.replace("file://", "")
.replace("file:", "")
.replace("jar:", "")
// URL suffix
.replace("!/META-INF/beans.xml", "")
.replace("!/", "")
// beans.xml
.replace("META-INF/beans.xml", "");
// try to make it shorter
if (shownValue.startsWith(m2)) {
shownValue = "${maven}/" + shownValue.substring(shownValue.replace(File.separatorChar, '/').lastIndexOf('/') + 1);
} else if (shownValue.startsWith(base)) {
shownValue = "${app}" + shownValue.replace(base, "");
} else if (shownValue.startsWith(sharedBase)) {
shownValue = "${shared}" + shownValue.replace(sharedBase, "");
} else if (shownValue.startsWith(runnerBase)) {
shownValue = "${base}" + shownValue.replace(runnerBase, "");
} else if (shownValue.startsWith(runnerHome)) {
shownValue = "${home}" + shownValue.replace(runnerHome, "");
}
return shownValue;
}).sorted().forEach(v -> logger.info(" " + v));
if (fileVisitor != null) {
urls.stream()
.filter(this::isFile)
.map(this::toFile)
.filter(File::isDirectory)
.forEach(fileVisitor);
}
}
urls.clear(); // no more needed
filter = null;
docBase = null;
shared = null;
}
private File toFile(final String url) {
try {
return new File(new URL(url).getFile());
} catch (final MalformedURLException e) {
return new File(url.substring("file://".length(), url.length()));
}
}
private boolean isFile(final String url) {
return url.startsWith("file:") && !url.endsWith("!/") && !url.endsWith("!/META-INF/beans.xml");
}
private void scanGroovy(final ClassLoader currentClassLoader) {
if (currentClassLoader == null || !currentClassLoader.getClass().getName().equals("groovy.lang.GroovyClassLoader")) {
return;
}
try {
final Class<?>[] getLoadedClasses = Class[].class.cast(
currentClassLoader.getClass()
.getMethod("getLoadedClasses")
.invoke(currentClassLoader));
addClassesToDefault(getLoadedClasses);
} catch (final Exception e) {
new LogFacade(OWBTomcatWebScannerService.class.getName()).warn(e.getMessage());
}
}
private void addClassesToDefault(final Class<?>[] all) throws Exception {
if (all == null || all.length == 0) {
return;
}
final Field linking = AnnotationFinder.class.getDeclaredField("linking");
final Method readClassDef = AnnotationFinder.class.getDeclaredMethod("readClassDef", Class.class);
if (!readClassDef.isAccessible()) {
readClassDef.setAccessible(true);
}
if (!linking.isAccessible()) {
linking.setAccessible(true);
}
final URI uri = URI.create("jar:file://!/"); // we'll never find it during scanning and it avoids to create a custom handler
final URL url = uri.toURL();
final String key = uri.toASCIIString();
CdiArchive.FoundClasses foundClasses = archive.classesByUrl().get(key);
if (foundClasses == null) {
final BeanArchiveService beanArchiveService = webBeansContext().getBeanArchiveService();
foundClasses = CdiArchive.FoundClasses.class.cast(
CdiArchive.FoundClasses.class.getConstructor(CdiArchive.class, URL.class, Collection.class, BeanArchiveService.BeanArchiveInformation.class)
.newInstance(null, url, new HashSet<>(), beanArchiveService.getBeanArchiveInformation(url)));
archive.classesByUrl().put(key, foundClasses);
}
foundClasses.getClassNames().addAll(Stream.of(all).map(Class::getName).collect(toSet()));
try {
linking.set(finder, true);
Stream.of(all).forEach(c -> { // populate classInfos map to support annotated mode which relies on ClassInfo
try {
readClassDef.invoke(finder, c);
} catch (final IllegalAccessException e) {
throw new IllegalStateException(e);
} catch (final InvocationTargetException e) {
throw new IllegalStateException(e.getCause());
}
});
} finally {
try {
linking.set(finder, false);
} catch (final IllegalAccessException e) {
// no-op
}
}
}
@Override
protected void filterExcludedJars(final Set<URL> classPathUrls) {
String jreBaseTmp;
try {
jreBaseTmp = new File(System.getProperty("java.home")).toURI().toURL().toExternalForm();
} catch (MalformedURLException e) {
jreBaseTmp = System.getProperty("java.home");
}
jreBase = jreBaseTmp;
super.filterExcludedJars(classPathUrls);
}
@Override
protected int isExcludedJar(final String path) {
if (path.startsWith(jreBase) || (path.startsWith("jar:") && path.indexOf(jreBase) == 4)) {
return jreBase.length();
}
// jar:file:spring-boot-cdi-launcher-1.0-SNAPSHOT.jar!/BOOT-INF/lib/x.jar!/
if (path.startsWith("jar:file:") && path.endsWith(".jar!/")) {
final int lastSep = path.substring(0, path.length() - 2).lastIndexOf('/');
if (lastSep > 0) {
return filter != null && filter.check(PLUGGABILITY, path.substring(lastSep + 1, path.length() - 2)) ?
-1 : (path.indexOf(".jar") - 1 /*should be lastIndexOf but filterExcludedJar logic would be broken*/);
}
}
final int filenameIdx = path.replace(File.separatorChar, '/').replace("!/", "").lastIndexOf('/') + 1;
if (filenameIdx < 0 || filenameIdx >= path.length()) { // unlikely
return -1;
}
return filter!= null && filter.check(PLUGGABILITY, path.substring(filenameIdx)) ? -1 : (path.indexOf(".jar") - 1);
}
// replace init
public void setFilter(final JarScanFilter filter, final ServletContext ctx) {
this.filter = filter;
super.init(ctx);
final Configuration config = Configuration.class.cast(ServletContext.class.cast(ctx).getAttribute("meecrowave.configuration"));
if (this.filter == null) {
this.filter = new KnownJarsFilter(config);
}
final Filter userFilter = webBeansContext().getService(Filter.class);
if (KnownClassesFilter.class.isInstance(userFilter)) {
KnownClassesFilter.class.cast(userFilter).init(config);
}
}
@Override
protected void addWebBeansXmlLocation(final URL beanArchiveUrl) {
final String url = beanArchiveUrl.toExternalForm();
if (urls.add(of(url)
.map(s -> s.startsWith("jar:") && s.endsWith("!/META-INF/beans.xml") ? s.substring("jar:".length(), s.length() - "!/META-INF/beans.xml".length()) : s)
.get())) {
super.doAddWebBeansXmlLocation(beanArchiveUrl);
}
}
public void setShared(final String shared) {
this.shared = ofNullable(shared).map(File::new).filter(File::isDirectory).map(File::getAbsolutePath).orElse(null);
}
public void setDocBase(final String docBase) {
this.docBase = docBase;
}
public void setFileVisitor(final Consumer<File> fileVisitor) {
this.fileVisitor = fileVisitor;
}
//don't rename this method - in case of owb2 it overrides the method defined in AbstractMetaDataDiscovery
protected WebBeansContext webBeansContext() {
return WebBeansContext.getInstance(); //only way to be compatible with owb 1.7.x and 2.x (without reflection)
}
}