de.micromata.genome.gwiki.plugin.GWikiPluginJavaClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for de.micromata.genome.gwiki.plugin.GWikiPluginJavaClassLoader.java

Source

//
// Copyright (C) 2010-2016 Roger Rene Kommer & Micromata GmbH
//
// 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 de.micromata.genome.gwiki.plugin;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections4.iterators.IteratorEnumeration;

import de.micromata.genome.gdbfs.FsObject;
import de.micromata.genome.gdbfs.FsObjectURLStreamHandler;
import de.micromata.genome.gdbfs.ZipRamFileSystem;
import de.micromata.genome.util.matcher.EveryMatcher;

/**
 * @author Roger Rene Kommer (r.kommer@micromata.de)
 * 
 */
public class GWikiPluginJavaClassLoader extends URLClassLoader {
    // private FsObject storage;

    /**
     * List of resource paths
     */
    private List<Map<String, FsObject>> resPaths = new ArrayList<Map<String, FsObject>>();

    private Map<String, Class<?>> loadedClasses = Collections.synchronizedMap(new HashMap<String, Class<?>>());

    private boolean enableCacheMissedClasses = true;

    /**
     * for performance reason cache missing class requests.
     */
    private Set<String> missedClasses = Collections.synchronizedSet(new HashSet<String>());

    /**
     * If true, don't call parent class loader
     */
    private boolean isolated = false;

    private String pluginName;

    public GWikiPluginJavaClassLoader() {
        super(new URL[0], Thread.currentThread().getContextClassLoader());
    }

    public GWikiPluginJavaClassLoader(boolean isolated) {
        super(new URL[0], Thread.currentThread().getContextClassLoader());
        this.isolated = isolated;
    }

    public GWikiPluginJavaClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
    }

    /**
     * copy constructor.
     * 
     * @param other must not null
     */
    public GWikiPluginJavaClassLoader(GWikiPluginJavaClassLoader other) {
        super(other.getURLs(), other.getParent());
        this.resPaths = other.resPaths;
        this.isolated = other.isolated;
    }

    @Override
    public String toString() {
        if (pluginName != null) {
            return pluginName;
        }
        return super.toString();
    }

    @Override
    public URL[] getURLs() {
        List<URL> ret = new ArrayList<URL>();
        try {

            for (Map<String, FsObject> rsm : resPaths) {
                for (Map.Entry<String, FsObject> me : rsm.entrySet()) {
                    ret.add(new URL("repository", "local", 0, me.getValue().getName(),
                            new FsObjectURLStreamHandler(me.getValue())));
                }
            }
        } catch (MalformedURLException ex) {
            throw new RuntimeException("Ooops", ex);
        }
        URL[] r = ret.toArray(new URL[0]);

        return r;
    }

    public void addJar(FsObject storage) {
        if (enableCacheMissedClasses == true) {
            missedClasses.clear();
        }
        try {
            Map<String, FsObject> fileMap = new HashMap<String, FsObject>();
            byte[] data = storage.getFileSystem().readBinaryFile(storage.getName());

            InputStream is = new ByteArrayInputStream(data);
            ZipRamFileSystem zfsys = new ZipRamFileSystem();
            zfsys.load(is);
            List<FsObject> files = zfsys.listFiles("", new EveryMatcher<String>(), 'F', true);
            for (FsObject fso : files) {
                fileMap.put(fso.getName(), fso);
            }
            resPaths.add(fileMap);
        } catch (Exception ex) {
            throw new RuntimeException("IO Failure while loading jar: " + storage.getName(), ex);
        }

    }

    /**
     * Add all Jars in given path inside the storage to classpath
     */
    public void addJarPath(FsObject storage) throws IOException {
        if (enableCacheMissedClasses == true) {
            missedClasses.clear();
        }
        // rw fixed: vorher stand ein "." vor "jar"; es wird jedoch nur die Endung erwartet.
        List<FsObject> files = storage.getFileSystem().listFilesByPattern(storage.getName(), "*.jar", 'F', false);
        for (FsObject lf : files) {
            addJar(lf);
        }
    }

    /**
     * Add all ressources from classpath out of the storage.
     *
     * @param storage Wher to find classPath
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public void addClassPath(FsObject storage) throws IOException {
        if (enableCacheMissedClasses == true) {
            missedClasses.clear();
        }
        Map<String, FsObject> fileMap = new HashMap<String, FsObject>();
        List<FsObject> files = storage.getFileSystem().listFiles("", new EveryMatcher<String>(), 'F', true);
        for (FsObject lf : files) {
            String name = lf.getName();
            if (name.startsWith("/") == true) {
                name = name.substring(1);
            }
            fileMap.put(name, lf);
        }
        resPaths.add(fileMap);
    }

    /**
     * 
     * Construiert aus byte[] eine Klasse. Klassenname format bitte beachten
     * 
     * @param className so: de.micromata.Foo
     * @param content
     * @return
     */
    public Class<?> loadDefine(String className, byte[] content) {
        Class<?> lcls = super.defineClass(className, content, 0, content.length);
        loadedClasses.put(className, lcls);
        return lcls;
    }

    @Override
    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        if (loadedClasses.containsKey(name) == true) {
            return loadedClasses.get(name);
        }

        if (enableCacheMissedClasses == true && missedClasses.contains(name) == true) {
            throw new ClassNotFoundException("Class " + name + " cannot be found (cached)");
        }

        String clsName = name.replace('.', '/') + ".class";
        for (Map<String, FsObject> rsm : resPaths) {
            if (rsm.containsKey(clsName) == false) {
                continue;
            }
            FsObject sclr = rsm.get(clsName);
            byte[] data = sclr.getFileSystem().readBinaryFile(sclr.getName());

            // TODO problem. If isolated, this needs the complete class loader.
            // super should not be the complete config repository, but only required.
            Class<?> cls = loadDefine(name, data);
            return cls;

        }
        if (isolated == true) {
            throw new ClassNotFoundException("Cannot find class: " + name);
        }
        try {
            Class<?> cls = super.loadClass(name, resolve);
            if (enableCacheMissedClasses == true && cls == null) {
                missedClasses.add(name);
            }
            return cls;
        } catch (ClassNotFoundException ex) {
            if (enableCacheMissedClasses == true) {
                missedClasses.add(name);
            }
            throw ex;
        }
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        return new IteratorEnumeration<URL>(getResourceList(name, false).iterator());
    }

    public List<URL> getResourceList(String name, boolean onlyFirst) throws IOException {
        List<URL> ressources = new ArrayList<URL>();
        try {
            for (Map<String, FsObject> rsm : resPaths) {
                if (rsm.containsKey(name) == false) {
                    continue;
                }
                FsObject rsp = rsm.get(name);
                ressources.add(new URL("repository", "local", 0, rsp.getName(), new FsObjectURLStreamHandler(rsp)));
            }
            if (isolated == true) {
                return ressources;
            }
            Enumeration<URL> suen = super.getResources(name);
            for (; suen.hasMoreElements();) {
                ressources.add(suen.nextElement());
                if (onlyFirst == true) {
                    return ressources;
                }
            }
            return ressources;
        } catch (MalformedURLException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public URL getResource(String name) {
        try {
            List<URL> l = getResourceList(name, true);
            if (l.size() > 0) {
                return l.get(0);
            }
            return null;
        } catch (IOException ex) {
            return null;
        }
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        if (name.startsWith("/") == true) {
            name = name.substring(1);
        }
        for (Map<String, FsObject> rsm : resPaths) {
            if (rsm.containsKey(name) == false) {
                continue;
            }
            FsObject rsp = rsm.get(name);
            byte[] data = rsp.getFileSystem().readBinaryFile(rsp.getName());
            return new ByteArrayInputStream(data);
        }
        if (isolated == true) {
            return null;
        }
        // if called super, in some case nullpointer occours
        return super.getResourceAsStream(name);
    }

    /**
     * Unfortunatelly this is needed as guard because default implementation of definePackage calls getPackage.
     * Additionally Package constructors all all private or protected.
     */
    private ThreadLocal<Boolean> inDefinePackage = new ThreadLocal<Boolean>();

    @Override
    protected Package getPackage(String name) {
        if (inDefinePackage.get() != null) {// don't recursive
            return null;
        }
        Package sp = super.getPackage(name);
        if (sp != null) {
            return sp;
        }
        try {
            inDefinePackage.set(Boolean.TRUE);
            return definePackage(name, "", "1.0", "Generic", "Generic", "1.0", "Generic", null);
        } finally {
            inDefinePackage.set(null);
        }
    }

    public void setLoadedClasses(Map<String, Class<?>> loadedClasses) {
        this.loadedClasses = loadedClasses;
    }

    public boolean isIsolated() {
        return isolated;
    }

    public void setIsolated(boolean isolated) {
        this.isolated = isolated;
    }

    public Map<String, Class<?>> getLoadedClasses() {
        return loadedClasses;
    }

    public boolean isEnableCacheMissedClasses() {
        return enableCacheMissedClasses;
    }

    public void setEnableCacheMissedClasses(boolean enableCacheMissedClasses) {
        this.enableCacheMissedClasses = enableCacheMissedClasses;
    }

    public String getPluginName() {
        return pluginName;
    }

    public void setPluginName(String pluginName) {
        this.pluginName = pluginName;
    }
}