io.personium.engine.extension.support.ExtensionJarLoader.java Source code

Java tutorial

Introduction

Here is the source code for io.personium.engine.extension.support.ExtensionJarLoader.java

Source

/**
 * Personium
 * Copyright 2014 - 2017 FUJITSU LIMITED
 *
 * Licensed 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 io.personium.engine.extension.support;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.personium.engine.PersoniumEngineException;

/**
 * Extension? jar??Rhino???.
 */
public class ExtensionJarLoader {

    /** . */
    private static Logger log = LoggerFactory.getLogger(ExtensionJarLoader.class);

    private static ExtensionJarLoader singleton = null;

    /** Extension jar??????.
     * ?????"/personium-engine/extensions"?? */
    public static final String ENGINE_EXTENSION_DIR_KEY = "io.personium.environment";
    /** ?????????? Extension jar??. */
    public static final String DEFAULT_EXTENSION_DIR = "/personium";

    private static final String JAR_SUFFIX = "jar";

    // ?
    private Path extensionJarDirectory = null;
    // ExtensionJarDirectory??/? jar?????
    private boolean searchDescendant = true;
    // jar?????????
    private ClassLoader classloader = null;
    /**
     * JavaScript?????Java?Set.
     */
    private Set<Class<? extends Scriptable>> scriptableClassSet = null;

    /**
     * ?Extensionjar????.
     * @param parentCl 
     * @param filter Extension
     * @return ExtensionLoader
     * @throws IOException Extension????
     * @throws PersoniumEngineException Extension????
     */
    public static ExtensionJarLoader getInstance(ClassLoader parentCl, ExtensionClassFilter filter)
            throws IOException, PersoniumEngineException {
        if (null == singleton) {
            String extensionDir = System.getProperty(ENGINE_EXTENSION_DIR_KEY, DEFAULT_EXTENSION_DIR)
                    + "/personium-engine/extensions";
            singleton = new ExtensionJarLoader(Paths.get(new File(extensionDir).toURI()), false, parentCl, filter);
        }
        return singleton;
    }

    /**
     * .
     * PCS????????????????? getInstance()???
     * @param extJarDir Extensionjar??
     * @param searchDescend true: ?, false: ????
     * @param parentCl 
     * @param filter Extension
     * @throws IOException Extension????
     * @throws PersoniumEngineException 
     */
    private ExtensionJarLoader(Path extJarDir, boolean searchDescend, ClassLoader parentCl,
            ExtensionClassFilter filter) throws IOException, PersoniumEngineException {
        extensionJarDirectory = extJarDir;
        searchDescendant = searchDescend;
        List<URL> jarPaths = getJarPaths(extensionJarDirectory, searchDescendant);
        classloader = new URLClassLoader(jarPaths.toArray(new URL[] {}), parentCl);

        scriptableClassSet = loadPrototypeClassSet(jarPaths, filter);
    }

    /**
     * Extension? jar?????.
     * @return 
     */
    public ClassLoader getClassLoader() {
        return classloader;
    }

    /**
     * JavaScript?????Java?Set??.
     * @return ????Java?Set
     */
    public Set<Class<? extends Scriptable>> getPrototypeClassSet() {
        return scriptableClassSet;
    }

    /**
     * ExtensionJarDirectory?? jar?URL??.
     * ???? "jar"????
     * @param extJarDir Extensionjar??
     * @param searchDescend true: ?, false: ????
     * @return jar? URL.
     */
    private List<URL> getJarPaths(Path extJarDir, boolean searchDescend) throws PersoniumEngineException {
        try {
            // ??
            List<URL> uriList = new ArrayList<URL>();
            // jar?????
            uriList.add(new URL("file", "", extJarDir.toFile().getAbsolutePath() + "/"));

            // jar?
            File[] jarFiles = extJarDir.toFile().listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    if (!pathname.exists() || !pathname.canRead() || pathname.isDirectory()) {
                        return false;
                    }
                    return FilenameUtils.isExtension(pathname.getName(), JAR_SUFFIX);
                }
            });

            if (null != jarFiles) {
                for (File jarFile : jarFiles) {
                    try {
                        uriList.add(new URL("file", "", jarFile.getCanonicalPath()));
                        log.info(String.format("Info: Adding extension jar file %s to classloader.",
                                jarFile.toURI()));
                    } catch (MalformedURLException e) {
                        // ############################################################################3
                        // ????????? jar???????? jar????
                        // ? Extension????????? Extension????? UserScript??
                        // ????????????
                        // ?? Extension?????Script???
                        // ############################################################################3
                        log.info(String.format("Warn: Some Extension jar file has malformed path. Ignoring: %s",
                                jarFile.toURI()));
                    } catch (IOException e) {
                        log.info(String.format("Warn: Some Extension jar file has malformed path. Ignoring: %s",
                                jarFile.toURI()));
                    }
                }
            }

            // ?
            File[] subDirs = extJarDir.toFile().listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.exists() && pathname.isDirectory() && pathname.canRead();
                }
            });

            if (null != subDirs) {
                for (File subDir : subDirs) {
                    //  jar?
                    uriList.addAll(getJarPaths(subDir.toPath(), searchDescend));
                }
            }
            return uriList;
        } catch (RuntimeException e) {
            e.printStackTrace();
            log.info(String.format("Warn: Error occured while loading Extension: %s", e.getMessage()));
            throw new PersoniumEngineException("Error occured while loading Extension.",
                    PersoniumEngineException.STATUSCODE_SERVER_ERROR, e);
        } catch (Exception e) {
            log.info(String.format("Warn: Error occured while loading Extension: %s", e.getMessage()));
            throw new PersoniumEngineException("Error occured while loading Extension.",
                    PersoniumEngineException.STATUSCODE_SERVER_ERROR, e);
        }
    }

    /**
     * jar???JavaScript??? Set??.
     * @param jarPaths  jar?
     * @param filter Extension
     * @return Javascript??????Java?
     * @throws IOException ?????/????
     */
    @SuppressWarnings("unchecked")
    private Set<Class<? extends Scriptable>> loadPrototypeClassSet(List<URL> jarPaths, ExtensionClassFilter filter)
            throws IOException, PersoniumEngineException {
        scriptableClassSet = new HashSet<Class<? extends Scriptable>>();

        for (URL jarUrl : jarPaths) {
            JarFile jar = null;
            try {
                File jarFile = new File(jarUrl.getPath());
                if (jarFile.isDirectory()) {
                    continue;
                }
                jar = new JarFile(jarFile);

                for (Enumeration<JarEntry> ent = jar.entries(); ent.hasMoreElements();) {
                    JarEntry entry = ent.nextElement();
                    String[] pathAndName = resolveJarEntry(entry.getName());
                    if (null == pathAndName) {
                        continue;
                    }
                    String entryPath = pathAndName[0];
                    String entryName = pathAndName[1];
                    if (null == entryPath || null == entryName) {
                        continue;
                    }
                    //   jar??"/" ????????
                    entryPath = entryPath.replaceAll("\\/", "\\.");
                    // ??? JavaScript????? filter??????
                    if (filter.accept(entryPath, entryName)) {
                        String className = entryPath + "." + entryName;
                        try {
                            Class<?> cl = classloader.loadClass(className);
                            if (ScriptableObject.class.isAssignableFrom(cl)
                                    || Scriptable.class.isAssignableFrom(cl)) {
                                scriptableClassSet.add((Class<? extends Scriptable>) cl);
                                log.info(String.format("Info: Extension class %s is revealed to JavaScript.",
                                        className));
                                // OK.
                                continue;
                            }
                            // ScriptableObject/Scriptable????????JavaScript??????
                            log.info(String.format("Info: Extension class %s is not derived from "
                                    + "ScriptableObject class or does not implment Scriptable interface. Ignored.",
                                    className));
                        } catch (ClassNotFoundException e) {
                            log.warn(String.format("Warn: Extension class %s is not found in classLoader: %s",
                                    className, e.getMessage()), e);
                        } catch (NoClassDefFoundError e) {
                            log.warn(String.format("Warn: Extension class %s is not found in classLoader: %s",
                                    className, e.getMessage()), e);
                        } catch (Exception e) {
                            log.warn(String.format("Warn: Extension class %s cannot be loaded into classLoader: %s "
                                    + "or the jar content is invalid.", className, e.getMessage()), e);
                        }
                    }
                }
            } catch (RuntimeException e) {
                log.warn(String.format("Warn: Failed to handle Extension jar file %s: %s", jarUrl.toString(),
                        e.getMessage()), e);
            } catch (Exception e) {
                log.warn(String.format("Warn: Failed to handle Extension jar file %s: %s", jarUrl.toString(),
                        e.getMessage()), e);
                continue;
            } finally {
                IOUtils.closeQuietly(jar);
            }
        }
        return scriptableClassSet;
    }

    /**
     * Jar????????.
     * @param jarEntryStr Jar?
     * @return  ? ?????????
     */
    private String[] resolveJarEntry(String jarEntryStr) {
        if (null == jarEntryStr || jarEntryStr.isEmpty()) {
            return null;
        }
        int dotClassPosition = jarEntryStr.lastIndexOf(".class");
        if (0 < dotClassPosition) {
            jarEntryStr = jarEntryStr.substring(0, jarEntryStr.lastIndexOf(".class"));
            int lastSeparator = jarEntryStr.lastIndexOf('/');
            if (-1 != lastSeparator) {
                return new String[] { jarEntryStr.substring(0, lastSeparator),
                        jarEntryStr.substring(lastSeparator + 1) };
            } else {
                return new String[] { "", jarEntryStr };
            }
        }
        return null;
    }
}