All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.web.loader.ServletContainerInitializerUtil Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2016] [Payara Foundation and/or its affiliates]

package org.glassfish.web.loader;

import org.glassfish.deployment.common.ClassDependencyBuilder;
import org.glassfish.hk2.classmodel.reflect.*;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.annotation.HandlesTypes;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Utility class - contains util methods used for implementation of
 * pluggable Shared Library features
 *
 *  @author Vijay Ramachandran
 */
public class ServletContainerInitializerUtil {

    private static final Logger log = LogFacade.getLogger();

    private static final ResourceBundle rb = log.getResourceBundle();

    /**
     * Given a class loader, check for ServletContainerInitializer
     * implementations in any JAR file in the classpath
     *
     * @param webFragmentMap
     * @param absoluteOrderingList
     * @param cl The ClassLoader to be used to find JAR files
     * @param hasOthers
     * @param servletInitializersEnabled
     *
     * @return Iterable over all ServletContainerInitializers that were found
     */
    public static Iterable getServletContainerInitializers(
            Map webFragmentMap, List absoluteOrderingList,
            boolean hasOthers, ClassLoader cl, boolean servletInitializersEnabled) {
        /*
         * If there is an absoluteOrderingList specified, then make sure that
         * any ServletContainerInitializers included in fragment JARs 
         * NOT listed in the absoluteOrderingList will be ignored.
         * For this, we remove any unwanted fragment JARs from the class
         * loader's URL
         */

        if (!servletInitializersEnabled) {
            try {
                final URLClassLoader webAppCl = (URLClassLoader) cl;
                cl = AccessController.doPrivileged(new PrivilegedAction() {
                    @Override
                    public URLClassLoader run() {
                        return new URLClassLoader(new URL[0], webAppCl.getParent());
                    }
                });
            } finally {
                /* intentionally left blank */
            }
        }

        if((absoluteOrderingList != null) && !hasOthers) {
            if(!(cl instanceof URLClassLoader)) {
                log.log(Level.WARNING,
                        LogFacade.WRONG_CLASSLOADER_TYPE,
                        cl.getClass().getCanonicalName());
                return null;
            }
            final URLClassLoader webAppCl = (URLClassLoader) cl;

            // Create a new List of URLs with missing fragments removed from
            // the currentUrls
            List newClassLoaderUrlList = new ArrayList();
            for (URL classLoaderUrl : webAppCl.getURLs()) {
                // Check that the URL is using file protocol, else ignore it
                if (!"file".equals(classLoaderUrl.getProtocol())) {
                    continue;
                }
                File file = new File(classLoaderUrl.getFile());
                try {
                    file = file.getCanonicalFile();
                } catch (IOException e) {
                    // Ignore
                }
                if (!file.exists()) {
                    continue;
                }
                String path = file.getAbsolutePath();
                if (!path.endsWith(".jar")) {
                    continue;
                }
                if (!isFragmentMissingFromAbsoluteOrdering(file.getName(),
                        webFragmentMap, absoluteOrderingList)) {
                    newClassLoaderUrlList.add(classLoaderUrl);
                }
            }

            // Create temporary classloader for ServiceLoader#load
            // TODO: Have temporary classloader honor delegate flag from
            // sun-web.xml
            URL[] urlsForNewClassLoader =
                new URL[newClassLoaderUrlList.size()];
            final URL[] urlArray = newClassLoaderUrlList.toArray(urlsForNewClassLoader);

            cl = AccessController.doPrivileged(new PrivilegedAction() {
                @Override
                public URLClassLoader run() {
                    return new URLClassLoader(urlArray, webAppCl.getParent());
                }
            });
        }

        return ServiceLoader.load(ServletContainerInitializer.class, cl);
    }


    /**
     * Builds a mapping of classes to the list of ServletContainerInitializers
     * interested in them
     *
     * @param initializers an Iterable over all ServletContainerInitializers
     * that need to be considered
     *
     * @return Mapping of classes to list of ServletContainerInitializers
     * interested in them
     */
    public static Map, List>> getInterestList(Iterable initializers) {

        if (null == initializers) {
            return null;
        }

        Map, List>> interestList = null;

        // Build a list of the classes / annotations in which the
        // initializers are interested
        for (ServletContainerInitializer sc : initializers) {
            if(interestList == null) {
                interestList = new HashMap, List>>();
            }
            Class sciClass = sc.getClass();
            HandlesTypes ann = (HandlesTypes) sciClass.getAnnotation(HandlesTypes.class);
            if(ann == null) {
                // This initializer does not contain @HandlesTypes
                // This means it should always be called for all web apps
                // So map it with a special token
                List> currentInitializerList =
                        interestList.get(ServletContainerInitializerUtil.class);
                if(currentInitializerList == null) {
                    List> arr =
                            new ArrayList>();
                    arr.add(sciClass);
                    interestList.put(ServletContainerInitializerUtil.class, arr);
                } else {
                    currentInitializerList.add(sciClass);
                }
            } else {
                Class[] interestedClasses = ann.value();
                if( (interestedClasses != null) && (interestedClasses.length != 0) ) {
                    for(Class c : interestedClasses) {
                        List> currentInitializerList =
                                interestList.get(c);
                        if(currentInitializerList == null) {
                            List> arr =
                                    new ArrayList>();
                            arr.add(sciClass);
                            interestList.put(c, arr);
                        } else {
                            currentInitializerList.add(sciClass);
                        }
                    }
                }
            }
        }

        return interestList;
    }

    /**
     * Given an interestlist that was built above, and a class loader, scan the entire web app's classes and libraries
     * looking for classes that extend/implement/use the annotations of a class present in the interest list
     *
     * @param initializers Iterable over all ServletContainerInitializers that
     * were discovered
     * @param interestList The interestList built by the previous util method
     * @param cl The classloader to be used to load classes in WAR
     * @return Map<Class<? extends ServletContainerInitializer>, Set<Class<?>>>
     *                          A Map of ServletContainerInitializer classes to be called and arguments to be passed
     *                          to them
     */
    public  static Map, Set>> getInitializerList(
            Iterable initializers,
            Map, List>> interestList,
            Types types,
            ClassLoader cl, boolean isStandalone) {

        if (interestList == null) {
            return null;
        }

        // This contains the final list of initializers and the set of
        // classes to be passed to them as arg
        Map, Set>> initializerList = null;

        // If an initializer was present without any @HandleTypes, it 
        // must be called with a null set of classes
        if(interestList.containsKey(ServletContainerInitializerUtil.class)) {
            initializerList = new HashMap, Set>>();
            List> initializersWithoutHandleTypes =
                    interestList.get(ServletContainerInitializerUtil.class);
            for(Class c : initializersWithoutHandleTypes) {
                initializerList.put(c, null);
            }
        }

        /*
         * Now scan every class in this app's WEB-INF/classes and WEB-INF/lib
         * to see if any class uses the annotation or extends/implements a
         * class in our interest list.
         * Do this scanning only if we have ServletContainerinitializers that
         * have expressed specific interest
         */
        if( (interestList.keySet().size() > 1) ||
            ((interestList.keySet().size() == 1) &&
                    (!interestList.containsKey(ServletContainerInitializerUtil.class)))) {
            /*
             * Create an instance of ClassDependencyBuilder that looks at the byte code and keeps
             * the information for every class in this app
             *
             */
            if (types==null || Boolean.getBoolean("org.glassfish.web.parsing")) {
                ClassDependencyBuilder classInfo = new ClassDependencyBuilder();
                if (cl instanceof URLClassLoader) {
                    URLClassLoader ucl = (URLClassLoader) cl;
                    for(URL u : ucl.getURLs()) {
                        String path = u.getPath();
                        try {
                            if(path.endsWith(".jar")) {
                                JarFile jf = new JarFile(path);
                                try {
                                    Enumeration entries = jf.entries();
                                    while(entries.hasMoreElements()) {
                                        JarEntry anEntry = entries.nextElement();
                                        if(anEntry.isDirectory())
                                            continue;
                                        if(!anEntry.getName().endsWith(".class"))
                                            continue;
                                        InputStream jarInputStream = null;
                                        try {
                                            jarInputStream = jf.getInputStream(anEntry);
                                            int size = (int) anEntry.getSize();
                                            byte[] classData = new byte[size];
                                            for(int bytesRead = 0; bytesRead < size;) {
                                                int r2 = jarInputStream.read(classData, bytesRead, size - bytesRead);
                                                bytesRead += r2;
                                            }
                                            classInfo.loadClassData(classData);
                                        } catch (Throwable t) {
                                            if (log.isLoggable(Level.FINE)) {
                                                log.log(Level.FINE,
                                                    LogFacade.CLASS_LOADING_ERROR,
                                                    new Object[] {
                                                        anEntry.getName(),
                                                        t.toString()});
                                            }
                                            continue;
                                        } finally {
                                            if(jarInputStream != null) {
                                                jarInputStream.close();
                                            }
                                        }
                                    }
                                } finally {
                                    jf.close();
                                }
                            } else {
                                File file = new File(path);
                                if (file.exists()) {
                                    if (file.isDirectory()) {
                                        scanDirectory(file, classInfo);
                                    } else {
                                        log.log(Level.WARNING,
                                            LogFacade.INVALID_URL_CLASS_LOADER_PATH,
                                            path);
                                    }
                                }
                            }
                        } catch(IOException ioex) {
                            String msg = rb.getString(LogFacade.IO_ERROR);
                            msg = MessageFormat.format(msg,
                                new Object[] { path });
                            log.log(Level.SEVERE, msg, ioex);
                            return null;
                        }
                    }
                }

                initializerList = checkAgainstInterestList(classInfo, interestList, initializerList, cl, isStandalone);
            } else {
                initializerList = checkAgainstInterestList(types, interestList, initializerList, cl, isStandalone);
            }
        }

        /*
         * If a ServletContainerInitializer was annotated with HandlesTypes,
         * but none of the application classes match, we must still invoke
         * it at its onStartup method, passing in a null Set of classes
         */ 
        for (ServletContainerInitializer initializer : initializers) {
            if (!initializerList.containsKey(initializer.getClass())) {
                initializerList.put(initializer.getClass(), null);
            }
        }

        return initializerList;
    }

    /**
     * Checks if a given JAR file is to be excluded while searching for ServletContainerInitializer implementations
     * @param jarName the JAR file
     * @param webFragmentMap fragment information from deployment desc
     * @param absoluteOrderingList give ordering list
     * @return true if the given JAR file is NOT present in the absolute ordering list
     */
    private static boolean isFragmentMissingFromAbsoluteOrdering(
           String jarName, Map webFragmentMap,
           List absoluteOrderingList) {
        return (webFragmentMap != null &&
            absoluteOrderingList != null &&
            !absoluteOrderingList.contains(
                webFragmentMap.get(jarName)));
   }

    /**
     * Given a directory, scan all sub directories looking for classes and
     * build the interest list
     *
     * @param dir the directory to be scanned
     * @param classInfo the ClassDependencyBuilder that holds info on all classes
     */
    private static void scanDirectory(File dir, ClassDependencyBuilder classInfo) {
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                String fileName = file.getPath();
                if (fileName.endsWith(".class")) {
                    try {
                        InputStream is = null;
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        try {
                            is = new BufferedInputStream(new FileInputStream(fileName));
                            byte[] bs = new byte[2048];
                            int size = -1;
                            while ((size = is.read(bs)) >= 0) {
                                baos.write(bs, 0, size);
                            } 
                            classInfo.loadClassData(baos.toByteArray());
                        } finally {
                            if (is != null) {
                                is.close();
                            }
                            baos.close();
                        }
                    } catch (Throwable t) {
                        if (log.isLoggable(Level.WARNING)) {
                            log.log(Level.WARNING,
                                LogFacade.CLASS_LOADING_ERROR,
                                new Object[] {fileName, t.toString()});
                        }
                        continue;
                    }
                }
            } else {
                scanDirectory(file, classInfo);
            }
        }
        return;
    }

    /**
     * Given the interestList, checks in the Types metadata if a given class
     * uses any of the annotations, subclasses any of the type; If so, builds
     * the initializer list
     *
     */
    private static Map, Set>> checkAgainstInterestList(
                                Types classInfo,
                                Map, List>> interestList,
                                Map, Set>> initializerList,
                                ClassLoader cl, boolean isStandalone) {

        if (classInfo==null) {
            return initializerList;
        }
        for (Map.Entry, List>> e:
                interestList.entrySet()) {

            Class c = e.getKey();
            Type type = classInfo.getBy(c.getName());
            if (type==null)
                continue;

            Set> resultSet = new HashSet>();
            if (type instanceof AnnotationType) {
                for (AnnotatedElement ae : ((AnnotationType) type).allAnnotatedTypes()) {
                    if (ae instanceof Member) {
                        ae = ((Member) ae).getDeclaringType();
                    } else if (ae instanceof Parameter) {
                        ae = ((Parameter) ae).getMethod().getDeclaringType();
                    }
                    if (ae instanceof Type) {
                        try {
                            resultSet.add(cl.loadClass(ae.getName()));
                        } catch (Throwable t) {
                            if (log.isLoggable(getStandaloneWarningLevel(isStandalone))) {
                                log.log(getStandaloneWarningLevel(isStandalone),
                                    LogFacade.CLASS_LOADING_ERROR,
                                    new Object[] {ae.getName(), t.toString()});
                            }
                        }     
                    }
                }
            } else {
                Collection classes;
                if (type instanceof InterfaceModel) {
                    classes = ((InterfaceModel) type).allImplementations();
                } else {
                    classes = ((ClassModel) type).allSubTypes();
                }
                for (ClassModel classModel : classes) {
                    try {
                        resultSet.add(cl.loadClass(classModel.getName()));
                    } catch (Throwable t) {
                        if (log.isLoggable(getStandaloneWarningLevel(isStandalone))) {
                            log.log(getStandaloneWarningLevel(isStandalone),
                                LogFacade.CLASS_LOADING_ERROR,
                                new Object[] {classModel.getName(), t.toString()});
                        }
                    }
                }
            }
            if(initializerList == null) {
                initializerList = new HashMap, Set>>();
            }
            List> containerInitializers = e.getValue();
            for(Class initializer : containerInitializers) {
                Set> classSet = initializerList.get(initializer);
                if(classSet == null) {
                    classSet = new HashSet>();
                }
                classSet.addAll(resultSet);
                initializerList.put(initializer, classSet);
            }
            
        }

        return initializerList;
    }
    /**
     * Given the interestList, checks if a given class uses any of the
     * annotations; If so, builds the initializer list
     *
     * @param classInfo the ClassDependencyBuilder instance that holds info on all classes
     * @param interestList the interestList built earlier
     * @param initializerList the initializerList built so far
     * @param cl the ClassLoader to be used to load the class
     * @return the updated initializer list
     */
    private static Map, Set>> checkAgainstInterestList(
                                ClassDependencyBuilder classInfo,
                                Map, List>> interestList,
                                Map, Set>> initializerList,
                                ClassLoader cl, boolean isStandalone) {
        for(Map.Entry, List>> e :
                interestList.entrySet()) {

            Class c = e.getKey();
            Set resultFromClassInfo = classInfo.computeResult(c.getName());
            if(resultFromClassInfo.isEmpty()) {
                continue;
            }
            Set> resultSet = new HashSet>();
            for(Iterator iter = resultFromClassInfo.iterator(); iter.hasNext();) {
                String className = iter.next().replace('/', '.');
                try {
                    Class aClass = cl.loadClass(className);
                    resultSet.add(aClass);
                } catch (Throwable t) {
                    if (log.isLoggable(getStandaloneWarningLevel(isStandalone))) {
                        log.log(getStandaloneWarningLevel(isStandalone),
                            LogFacade.CLASS_LOADING_ERROR,
                            new Object[] {className, t.toString()});
                    }
                }
            }
            if(initializerList == null) {
                initializerList = new HashMap, Set>>();
            }
            List> containerInitializers = e.getValue();
            for(Class initializer : containerInitializers) {
                Set> classSet = initializerList.get(initializer);
                if(classSet == null) {
                    classSet = new HashSet>();
                }
                classSet.addAll(resultSet);
                initializerList.put(initializer, classSet);
            }
        }
        return initializerList;
    }

    
    private static Level getStandaloneWarningLevel(boolean isStandalone) {
        return isStandalone? Level.WARNING : Level.FINE;
    }
}