Package org.apache.tomee.loader

Source Code of org.apache.tomee.loader.TomEEJarScanner

/**
* 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.tomee.loader;

import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.startup.TldConfig;
import org.apache.catalina.startup.XmlErrorHandler;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.config.TldScanner;
import org.apache.openejb.loader.IO;
import org.apache.openejb.util.URLs;
import org.apache.openejb.util.reflection.Reflections;
import org.apache.tomcat.JarScannerCallback;
import org.apache.tomcat.util.file.Matcher;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.scan.Constants;
import org.apache.tomcat.util.scan.StandardJarScanner;

import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

public class TomEEJarScanner extends StandardJarScanner {

    private static final Log log = LogFactory.getLog(StandardJarScanner.class);

    protected static final Set<String[]> DEFAULT_JARS_TO_SKIP;

    /**
     * The string resources for this package.
     */
    private static final StringManager sm = StringManager.getManager(Constants.Package);

    private static final Method tldConfigScanStream;
    private static final Field tldConfig;
    private static final Method tldLocationScanStream;
    private static final Field tldLocationCache;

    // tld of the server
    private static final Set<URL> SERVER_URLS;

    // TldConfig of the server (common)
    private static final Set<String> TAG_LIB_URIS;
    private static final ArrayList<String> LISTENERS;

    // TldLocationCache of the server (common)
    private static final Hashtable<String, Object> MAPPINGS;

    private static final Thread SERVER_SCANNING_THREAD;

    static {
        final Set<String> defaultJarsToSkip = new HashSet<String>();
        final String jarList = System.getProperty(Constants.SKIP_JARS_PROPERTY);
        if (jarList != null) {
            final StringTokenizer tokenizer = new StringTokenizer(jarList, ",");
            while (tokenizer.hasMoreElements()) {
                defaultJarsToSkip.add(tokenizer.nextToken());
            }
        }

        final Set<String[]> ignoredJarsTokens = new HashSet<String[]>();
        for (final String pattern : defaultJarsToSkip) {
            ignoredJarsTokens.add(Matcher.tokenizePathAsArray(pattern));
        }
        DEFAULT_JARS_TO_SKIP = ignoredJarsTokens;

        try {
            final ClassLoader loader = TomEEJarScanner.class.getClassLoader();

            tldConfigScanStream = TldConfig.class.getDeclaredMethod("tldScanStream", InputStream.class);
            tldConfigScanStream.setAccessible(true);
            tldConfig = loader.loadClass("org.apache.catalina.startup.TldConfig$TldJarScannerCallback")
                            .getDeclaredFields()[0]; // there is a unique field and this way it is portable
                            //.getDeclaredField("this$0");
            tldConfig.setAccessible(true);

            final Class<?> tldLocationsCache = loader.loadClass("org.apache.jasper.compiler.TldLocationsCache");
            tldLocationScanStream = tldLocationsCache.getDeclaredMethod("tldScanStream", String.class, String.class, InputStream.class);
            tldLocationScanStream.setAccessible(true);
            tldLocationCache = loader.loadClass("org.apache.jasper.compiler.TldLocationsCache$TldJarScannerCallback")
                                    .getDeclaredFields()[0];
            tldLocationCache.setAccessible(true);

            // init server cache
            SERVER_URLS = TldScanner.scan(TomEEJarScanner.class.getClassLoader());

            final Context fakeWebApp = (Context) Proxy.newProxyInstance(loader, new Class<?>[]{Context.class},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                            if ("getTldNamespaceAware".equals(method.getName())) {
                                return Globals.STRICT_SERVLET_COMPLIANCE;
                            } else if ("getTldValidation".equals(method.getName())) {
                                return Globals.STRICT_SERVLET_COMPLIANCE;
                            }
                            return null;
                        }
                    });
            final TldConfig config = new TldConfig();
            config.lifecycleEvent(new LifecycleEvent(fakeWebApp, Lifecycle.AFTER_INIT_EVENT, null));

            final Object fakeSc = Proxy.newProxyInstance(loader, new Class<?>[]{ ServletContext.class }, new InvocationHandler() {
                @Override
                public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                    return null;
                }
            });
            final Object locationsCacheInstance = tldLocationsCache.getConstructor(ServletContext.class).newInstance(fakeSc);

            if (!SERVER_URLS.isEmpty()) {
                SERVER_SCANNING_THREAD = new Thread() {
                    @Override
                    public void run() {
                        for (URL current : SERVER_URLS) {
                            tldConfig(config, current);
                            if (tldLocationsCache != null) {
                                tldLocationCache(locationsCacheInstance, current);
                            }
                        }
                    }
                };
                SERVER_SCANNING_THREAD.setName("TomEE-server-tld-reading");
                SERVER_SCANNING_THREAD.setDaemon(true);
                SERVER_SCANNING_THREAD.start();
            } else {
                SERVER_SCANNING_THREAD = null;
            }

            TAG_LIB_URIS = (Set<String>) Reflections.get(config, "taglibUris");
            LISTENERS = (ArrayList<String>) Reflections.get(config, "listeners");
            MAPPINGS= (Hashtable<String, Object>) Reflections.get(locationsCacheInstance, "mappings");
        } catch (Exception e) {
            throw new OpenEJBRuntimeException(e);
        }
    }

    @Override
    public void scan(final ServletContext context, final ClassLoader classLoader, final JarScannerCallback callback, final Set<String> jarsToIgnore) {
        if ("FragmentJarScannerCallback".equals(callback.getClass().getSimpleName())) {
            final EmbeddedJarScanner embeddedJarScanner = new EmbeddedJarScanner();
            embeddedJarScanner.scan(context, classLoader, callback, jarsToIgnore);
        } else if ("TldJarScannerCallback".equals(callback.getClass().getSimpleName())) {

            final String cbName = callback.getClass().getName();
            if (cbName.equals(tldConfig.getDeclaringClass().getName())) {
                ensureServerTldsScanned();
                try {
                    final TldConfig config;
                    try {
                        config = (TldConfig) tldConfig.get(callback);
                    } catch (IllegalAccessException e) {
                        throw new OpenEJBException("scan with default algo");
                    }

                    final Set<URL> urls = TldScanner.scan(context.getClassLoader());
                    for (URL url : urls) {
                        if (!SERVER_URLS.contains(url)) {
                            tldConfig(config, url);
                        }
                    }

                    // add already scanned ones
                    for (String uri : TAG_LIB_URIS) {
                        config.addTaglibUri(uri);
                    }
                    for (String listener : LISTENERS) {
                        if (!"org.apache.myfaces.webapp.StartupServletContextListener".equals(listener)) { // done elsewhere
                            config.addApplicationListener(listener);
                        }
                    }

                    return; // done, next code is a fallback if scan() throw an exception
                } catch (OpenEJBException oe) {
                    // no-op
                }
            } else if (cbName.equals(tldLocationCache.getDeclaringClass().getName())) {
                ensureServerTldsScanned();
                try {
                    final Object tldLocationsCache;
                    try {
                        tldLocationsCache = tldLocationCache.get(callback);
                    } catch (IllegalAccessException e) {
                        throw new OpenEJBException("scan with default algo");
                    }

                    final Set<URL> urls = TldScanner.scan(context.getClassLoader());
                    for (URL url : urls) {
                        if (!SERVER_URLS.contains(url)) {
                            tldLocationCache(tldLocationsCache, url);
                        }
                    }

                    // add server ones
                    final Hashtable<String, Object> mappings = (Hashtable<String, Object>) Reflections.get(tldLocationsCache, "mappings");
                    mappings.putAll((Map<String, Object>) MAPPINGS.clone());

                    return;
                } catch (OpenEJBException oe) {
                    // no-op
                }
            } else { log.debug("This callback " + callback + " is not known and perf optim will not be available"); }

            // Scan WEB-INF/lib
            final Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
            if (dirList != null) {
                for (final String path : dirList) {
                    if (path.endsWith(Constants.JAR_EXT) &&
                            !Matcher.matchPath(DEFAULT_JARS_TO_SKIP,
                                    path.substring(path.lastIndexOf('/') + 1))) {
                        // Need to scan this JAR
                        URL url = null;
                        try {
                            // File URLs are always faster to work with so use them
                            // if available.
                            final String realPath = context.getRealPath(path);
                            if (realPath == null) {
                                url = context.getResource(path);
                            } else {
                                url = (new File(realPath)).toURI().toURL();
                            }
                            this.process(callback, url);
                        } catch (IOException e) {
                            log.warn(sm.getString("jarScan.webinflibFail", url), e);
                        }
                    } else {
                        if (log.isTraceEnabled()) {
                            log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
                        }
                    }
                }
            }

            // Scan the classpath
            if (this.isScanClassPath()) {
                if (log.isTraceEnabled()) {
                    log.trace(sm.getString("jarScan.classloaderStart"));
                }


                try {
                    final ClassLoader loader = Thread.currentThread().getContextClassLoader();
                    final Set<URL> tldFileUrls = TldScanner.scan(loader);

                    final Set<URL> jarUlrs = this.discardFilePaths(tldFileUrls);

                    for (final URL url : jarUlrs) {
                        final String jarName = this.getJarName(url);

                        // Skip JARs known not to be interesting and JARs
                        // in WEB-INF/lib we have already scanned
                        if (jarName != null && !(Matcher.matchPath(DEFAULT_JARS_TO_SKIP, jarName) || url.toString().contains(Constants.WEB_INF_LIB + jarName))) {

                            if (log.isDebugEnabled()) {
                                log.debug(sm.getString("jarScan.classloaderJarScan", url));
                            }
                            try {
                                this.process(callback, url);
                            } catch (IOException ioe) {
                                log.warn(sm.getString(
                                        "jarScan.classloaderFail", url), ioe);
                            }
                        } else {
                            if (log.isTraceEnabled()) {
                                log.trace(sm.getString("jarScan.classloaderJarNoScan", url));
                            }
                        }
                    }
                } catch (OpenEJBException e) {
                    log.warn("JarScan.TldScan Failed ", e);
                }
            }

        } else {
            super.scan(context, classLoader, callback, jarsToIgnore);
        }
    }

    private void ensureServerTldsScanned() {
        if (SERVER_SCANNING_THREAD == null) {
            return;
        }

        try {
            SERVER_SCANNING_THREAD.join();
        } catch (InterruptedException e) {
            throw new OpenEJBRuntimeException(e);
        }
    }

    private Set<URL> discardFilePaths(final Set<URL> tldFileUrls) {
        final Set<String> jarPaths = new HashSet<String>();

        for (final URL tldFileUrl : tldFileUrls) {
            jarPaths.add(URLs.toFilePath(tldFileUrl));
        }

        final Set<URL> jars = new HashSet<URL>();
        for (final String jarPath : jarPaths) {
            try {
                final URL url = new File(jarPath).toURI().toURL();
                jars.add(url);
            } catch (MalformedURLException e) {
                log.warn("Skipping JAR file " + jarPath, e);
            }
        }
        return jars;
    }

    private static void tldLocationCache(final Object tldLocationsCache, final URL url) {
        String resource = url.toString();
        String entry = null;

        if (resource.contains("!/")) {
            final String path = url.getPath();
            final int endIndex = path.indexOf("!/");
            resource = path.substring(0, endIndex);
            entry = path.substring(endIndex + 2, path.length());
        }

        InputStream is = null;
        try {
            is = url.openStream();
            tldLocationScanStream.invoke(tldLocationsCache, resource, entry, is);
        } catch (Exception e) {
            log.warn(sm.getString("jarScan.webinflibFail", url.toExternalForm()), e);
        } finally {
            IO.close(is);
        }
    }

    private static void tldConfig(final TldConfig config, final URL current) {
        InputStream is = null;
        try {
            is = current.openStream();
            final XmlErrorHandler handler = (XmlErrorHandler) tldConfigScanStream.invoke(config, is);
            handler.logFindings(log, current.toExternalForm());
        } catch (Exception e) {
            log.warn(sm.getString("jarScan.webinflibFail", current), e);
        } finally {
            IO.close(is);
        }
    }

    /*
    * Scan a URL for JARs with the optional extensions to look at all files
    * and all directories.
    */
    private void process(final JarScannerCallback callback, final URL url) throws IOException {
        final URLConnection conn = url.openConnection();
        if (conn instanceof JarURLConnection) {

            callback.scan((JarURLConnection) conn);

        } else {

            final String urlStr = url.toString();

            if (urlStr.startsWith("file:") || urlStr.startsWith("jndi:")) {

                if (urlStr.endsWith(Constants.JAR_EXT)) {

                    final URL jarURL = new URL("jar:" + urlStr + "!/");
                    callback.scan((JarURLConnection) jarURL.openConnection());

                } else {
                    try {

                        final File f = new File(url.toURI());

                        if (f.isFile() && this.isScanAllFiles()) {

                            // Treat this file as a JAR
                            final URL jarURL = new URL("jar:" + urlStr + "!/");
                            callback.scan((JarURLConnection) jarURL.openConnection());

                        } else if (f.isDirectory() && this.isScanAllDirectories()) {

                            final File metainf = new File(f.getAbsoluteFile() + File.separator + "META-INF");

                            if (metainf.isDirectory()) {
                                callback.scan(f);
                            }
                        }
                    } catch (URISyntaxException e) {
                        // Wrap the exception and re-throw
                        final IOException ioe = new IOException();
                        ioe.initCause(e);
                        throw ioe;
                    }
                }
            }
        }
    }

    /*
     * Extract the JAR name, if present, from a URL
     */
    private String getJarName(final URL url) {

        String name = null;

        final String path = url.getPath();
        final int end = path.indexOf(Constants.JAR_EXT);
        if (end != -1) {
            final int start = path.lastIndexOf('/', end);
            name = path.substring(start + 1, end + 4);
        } else if (this.isScanAllDirectories()) {
            final int start = path.lastIndexOf('/');
            name = path.substring(start + 1);
        }

        return name;
    }
}
TOP

Related Classes of org.apache.tomee.loader.TomEEJarScanner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.