blob: 31652ad8f94f011bdaf6def9fe5ffa4cfc541491 [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.openejb.config;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.DaemonThreadFactory;
import org.apache.openejb.util.URLs;
import org.apache.xbean.finder.UrlSet;
import org.apache.xbean.finder.filter.Filters;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static java.util.Arrays.asList;
import static org.apache.openejb.config.NewLoaderLogic.applyBuiltinExcludes;
import static org.apache.openejb.util.URLs.toFile;
/**
* TLD file urls cached on a per classloader basis. Helps with sharing TLD
* files between webapps by placing them in a parent classloader.
* <p/>
* Each webapp will be able to retrieve the cached version of the URLs and
* therefore only needs to scan its own libraries, the parent libraries will
* already have been scanned.
* <p/>
* For a tiny bit of performance, we will scan the StandardClassloader at boot
* in a separate thread so it should be primed in advance of any deployment.
*
* @version $Rev$ $Date$
*/
public class TldScanner {
// first cache, it is the faster one but not relevant between temp and runtime phases
private static final Map<ClassLoader, Set<URL>> cache = new WeakHashMap<ClassLoader, Set<URL>>();
// tld by classloader identified by hash on urls (same hash for temp and runtime classloaders)
// a bit longer to compute but let scanning be reused over temp and runtime classloaders
private static final Map<Integer, Set<URL>> cacheByhashCode = new WeakHashMap<Integer, Set<URL>>();
public static Set<URL> scan(final ClassLoader classLoader) throws OpenEJBException {
if (skip()) {
return Collections.emptySet();
}
if (classLoader == null) {
return Collections.emptySet();
}
final Set<URL> urls = cache.get(classLoader);
if (urls != null) {
return urls;
}
final Set<URL> result = scanClassLoaderForTagLibs(classLoader);
cache.put(classLoader, result);
return result;
}
private static boolean skip() {
return !"true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.taglib.scan", "true"));
}
public static Set<URL> scanClassLoaderForTagLibs(final ClassLoader classLoader) throws OpenEJBException {
if (skip()) {
return Collections.emptySet();
}
final Set<URL> tldUrls = new HashSet<URL>();
if (classLoader == null) {
return tldUrls;
}
if (classLoader == Object.class.getClassLoader()) {
return tldUrls;
}
final List<URL> urls = urls(classLoader);
final int hashCodeForUrls = hash(urls);
final Set<URL> cachedSet = cacheByhashCode.get(hashCodeForUrls);
if (cachedSet != null) {
return cachedSet;
}
tldUrls.addAll(scan(classLoader.getParent()));
if (urls.size() > 0) {
final ExecutorService es = Executors.newFixedThreadPool(
Math.min(urls.size(), 2 * Runtime.getRuntime().availableProcessors() + 1),
new DaemonThreadFactory("OpenEJB-tld-server-scanning"));
final Collection<Future<Set<URL>>> futures = new ArrayList<Future<Set<URL>>>(urls.size());
for (URL url : urls) {
if (url.getProtocol().equals("jar")) {
try {
String path = url.getPath();
if (path.endsWith("!/")) {
path = path.substring(0, path.length() - 2);
}
url = new URL(path);
} catch (final MalformedURLException e) {
DeploymentLoader.logger.warning("JSP tag library location bad: " + url.toExternalForm(), e);
continue;
}
}
if (!url.getProtocol().equals("file")) {
continue;
}
final File file;
try {
file = toFile(url).getCanonicalFile().getAbsoluteFile();
} catch (final IOException e) {
DeploymentLoader.logger.warning("JSP tag library location bad: " + url.toExternalForm(), e);
continue;
}
futures.add(es.submit(new Callable<Set<URL>>() {
@Override
public Set<URL> call() throws Exception {
return scanForTagLibs(file);
}
}));
}
es.shutdown();
for (final Future<Set<URL>> set : futures) {
try {
tldUrls.addAll(set.get());
} catch (final Exception e) {
// no-op
}
}
}
cacheByhashCode.put(hashCodeForUrls, tldUrls);
return tldUrls;
}
static Set<URL> scanWarForTagLibs(final File war) {
final Set<URL> urls = new HashSet<URL>();
final File webInfDir = new File(war, "WEB-INF");
if (!webInfDir.isDirectory()) {
return urls;
}
// skip the lib and classes dir in WEB-INF
final LinkedList<File> files = new LinkedList<File>();
final File[] list = webInfDir.listFiles();
if (list != null) {
for (final File file : list) {
if ("lib".equals(file.getName()) || "classes".equals(file.getName())) {
continue;
}
files.add(file);
}
}
final File webInfMetaInf = new File(webInfDir, "classes/META-INF");
if (webInfMetaInf.exists()) {
// filter directly to let it be faster in next loop
files.addAll(asList(webInfMetaInf.listFiles(new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith(".tld");
}
})));
}
if (files.isEmpty()) {
return urls;
}
// recursively scan the directories
while (!files.isEmpty()) {
File file = files.removeFirst();
if (file.isDirectory()) {
final File[] a = file.listFiles();
if (a != null) {
files.addAll(asList(a));
}
} else if (file.getName().endsWith(".tld")) {
try {
file = file.getCanonicalFile().getAbsoluteFile();
urls.add(file.toURI().toURL());
} catch (final IOException e) {
DeploymentLoader.logger.warning("JSP tag library location bad: " + file.getAbsolutePath(), e);
}
}
}
return urls;
}
static Set<URL> scanForTagLibs(final File file) {
final Set<URL> tldLocations = new HashSet<URL>();
try {
final String location = file.toURI().toURL().toExternalForm();
if (location.endsWith(".jar")) {
final Set<URL> urls = scanJarForTagLibs(file);
tldLocations.addAll(urls);
} else if (file.getName().endsWith(".tld")) {
final URL url = file.toURI().toURL();
tldLocations.add(url);
}
} catch (final IOException e) {
DeploymentLoader.logger.warning("Error scanning for JSP tag libraries: " + file.getAbsolutePath(), e);
}
return tldLocations;
}
static Set<URL> scanJarForTagLibs(final File file) {
final Set<URL> urls = new HashSet<URL>();
if (!file.isFile()) {
return urls;
}
JarFile jarFile = null;
try {
jarFile = new JarFile(file);
final URL jarFileUrl = new URL("jar", "", -1, file.toURI().toURL().toExternalForm() + "!/");
for (final JarEntry entry : Collections.list(jarFile.entries())) {
final String name = entry.getName();
if (!name.startsWith("META-INF/") || !name.endsWith(".tld")) {
continue;
}
final URL url = new URL(jarFileUrl, name);
urls.add(url);
}
} catch (final IOException e) {
DeploymentLoader.logger.warning("Error scanning jar for JSP tag libraries: " + file.getAbsolutePath(), e);
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (final IOException e) {
// exception ignored
}
}
}
return urls;
}
// mainly used to forget a classloader (temp one generally) but keep scanning info from classloader urls
public static void quickClean(final ClassLoader loader) {
if (loader == null) {
return;
}
cache.remove(loader);
if (loader.getParent() != TldScanner.class.getClassLoader()) { // for ears
quickClean(loader.getParent());
}
}
// this method clean the cacheByhash too
public static void forceCompleteClean(final ClassLoader loader) {
if (loader == null) {
return;
}
quickClean(loader);
cacheByhashCode.remove(hash(urls(loader)));
if (loader.getParent() != TldScanner.class.getClassLoader()) { // for ears
forceCompleteClean(loader.getParent());
}
}
private static List<URL> urls(final ClassLoader classLoader) {
UrlSet urlSet = new UrlSet();
if (classLoader instanceof URLClassLoader) {
final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
try {
urlSet = new UrlSet(urlClassLoader.getURLs());
} catch (final NullPointerException npe) { // happen for closeable classloaders like WebappClassLoader when already clean up
return Collections.emptyList();
}
} else {
try {
urlSet = new UrlSet(classLoader);
} catch (final IOException e) {
DeploymentLoader.logger.warning("Error scanning class loader for JSP tag libraries", e);
}
}
try {
urlSet = URLs.cullSystemJars(urlSet);
urlSet = applyBuiltinExcludes(
urlSet,
Filters.tokens("taglibs-standard-impl", "taglibs-standard-jstlel", "javax.faces-2.", "spring-security-taglibs", "spring-webmvc"),
Filters.prefixes("commons-jcs-", "myfaces-", "tomcat-websocket.jar")); // myfaces is hardcoded in tomee
} catch (final IOException e) {
DeploymentLoader.logger.warning("Error scanning class loader for JSP tag libraries", e);
}
return urlSet.getUrls();
}
private static int hash(final List<URL> urls) {
int hash = 0;
for (final URL u : urls) {
hash *= 31;
if (u != null) {
hash += u.toExternalForm().hashCode(); // url.hashCode() can be slow offline
}
}
return hash;
}
}