io.apiman.common.plugin.PluginClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for io.apiman.common.plugin.PluginClassLoader.java

Source

/*
 * Copyright 2015 JBoss Inc
 *
 * 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.apiman.common.plugin;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.apache.commons.io.IOUtils;

/**
 * A classloader that is capable of loading classes from an apiman
 * plugin artifact.
 *
 * @author eric.wittmann@redhat.com
 */
@SuppressWarnings("nls")
public class PluginClassLoader extends ClassLoader {

    private ZipFile pluginArtifactZip;
    private List<ZipFile> dependencyZips;
    private File workDir;
    private boolean closed;

    /**
     * Constructor.
     * @param pluginArtifactFile plugin artifact
     * @throws IOException if an I/O error has occurred
     */
    public PluginClassLoader(File pluginArtifactFile) throws IOException {
        super();
        closed = false;
        this.pluginArtifactZip = new ZipFile(pluginArtifactFile);
        this.workDir = createWorkDir(pluginArtifactFile);
        indexPluginArtifact();
    }

    /**
     * Constructor.
     * @param pluginArtifactFile plugin artifact
     * @param parent parent classloader
     * @throws IOException if an I/O error has occurred
     */
    public PluginClassLoader(File pluginArtifactFile, ClassLoader parent) throws IOException {
        super(parent);
        this.pluginArtifactZip = new ZipFile(pluginArtifactFile);
        this.workDir = createWorkDir(pluginArtifactFile);
        indexPluginArtifact();
    }

    /**
     * Creates a work directory into which various resources discovered in the plugin
     * artifact can be extracted.
     * @param pluginArtifactFile plugin artifact
     * @throws IOException if an I/O error has occurred
     */
    protected File createWorkDir(File pluginArtifactFile) throws IOException {
        File tempDir = File.createTempFile(pluginArtifactFile.getName(), "");
        tempDir.delete();
        tempDir.mkdirs();
        return tempDir;
    }

    /**
     * Indexes the content of the plugin artifact.  This includes discovering all of the
     * dependency JARs as well as any configuration resources such as plugin definitions.
     * @throws IOException if an I/O error has occurred
     */
    private void indexPluginArtifact() throws IOException {
        dependencyZips = new ArrayList<>();
        Enumeration<? extends ZipEntry> entries = this.pluginArtifactZip.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            if (zipEntry.getName().startsWith("WEB-INF/lib/")
                    && zipEntry.getName().toLowerCase().endsWith(".jar")) {
                ZipFile dependencyZipFile = extractDependency(zipEntry);
                if (dependencyZipFile != null) {
                    dependencyZips.add(dependencyZipFile);
                }
            }
        }
    }

    /**
     * Extracts a dependency from the plugin artifact ZIP and saves it to the work
     * directory.  If the dependency has already been extracted (we're re-using the
     * work directory) then this simply returns what is already there.
     * @param zipEntry a ZIP file entry
     * @throws IOException if an I/O error has occurred
     */
    private ZipFile extractDependency(ZipEntry zipEntry) throws IOException {
        File dependencyWorkDir = new File(workDir, "lib");
        if (!dependencyWorkDir.exists()) {
            dependencyWorkDir.mkdirs();
        }
        String depFileName = new File(zipEntry.getName()).getName();
        File depFile = new File(dependencyWorkDir, depFileName);
        if (!depFile.isFile()) {
            InputStream input = null;
            OutputStream output = null;
            try {
                input = this.pluginArtifactZip.getInputStream(zipEntry);
                output = new FileOutputStream(depFile);
                IOUtils.copy(input, output);
                output.flush();
            } catch (IOException e) {
                throw e;
            } finally {
                IOUtils.closeQuietly(input);
                IOUtils.closeQuietly(output);
            }
        }
        return new ZipFile(depFile);
    }

    /**
     * Extracts a resource from the plugin artifact ZIP and saves it to the work
     * directory.  If the resource has already been extracted (we're re-using the
     * work directory) then this simply returns what is already there.
     * @param zipEntry a ZIP file entry
     * @throws IOException if an I/O error has occurred
     */
    private URL extractResource(ZipEntry zipEntry) throws IOException {
        File resourceWorkDir = new File(workDir, "resources");
        if (!resourceWorkDir.exists()) {
            resourceWorkDir.mkdirs();
        }
        File resourceFile = new File(resourceWorkDir, zipEntry.getName());
        if (!resourceFile.isFile()) {
            resourceFile.getParentFile().mkdirs();
            InputStream input = null;
            OutputStream output = null;
            try {
                input = this.pluginArtifactZip.getInputStream(zipEntry);
                output = new FileOutputStream(resourceFile);
                IOUtils.copy(input, output);
                output.flush();
            } catch (IOException e) {
                throw e;
            } finally {
                IOUtils.closeQuietly(input);
                IOUtils.closeQuietly(output);
            }
        }
        return resourceFile.toURI().toURL();
    }

    /**
     * @see java.lang.ClassLoader#findClass(java.lang.String)
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        InputStream inputStream = null;
        try {
            inputStream = findClassContent(name);
            if (inputStream == null) {
                return super.findClass(name);
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(inputStream, baos);
            byte[] bytes = baos.toByteArray();
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * Searches the plugin artifact ZIP and all dependency ZIPs for a zip entry for
     * the given fully qualified class name.
     * @param className name of class
     * @throws IOException if an I/O error has occurred
     */
    protected InputStream findClassContent(String className) throws IOException {
        String primaryArtifactEntryName = "WEB-INF/classes/" + className.replace('.', '/') + ".class";
        String dependencyEntryName = className.replace('.', '/') + ".class";
        ZipEntry entry = this.pluginArtifactZip.getEntry(primaryArtifactEntryName);
        if (entry != null) {
            return this.pluginArtifactZip.getInputStream(entry);
        }
        for (ZipFile zipFile : this.dependencyZips) {
            entry = zipFile.getEntry(dependencyEntryName);
            if (entry != null) {
                return zipFile.getInputStream(entry);
            }
        }
        return null;
    }

    /**
     * @see java.lang.ClassLoader#findResource(java.lang.String)
     */
    @Override
    protected URL findResource(String name) {
        Enumeration<? extends ZipEntry> entries = this.pluginArtifactZip.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            if (zipEntry.getName().equalsIgnoreCase(name)
                    || zipEntry.getName().equalsIgnoreCase("WEB-INF/classes/" + name)) {
                try {
                    return extractResource(zipEntry);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        ZipEntry entry;
        File file;
        for (ZipFile zipFile : this.dependencyZips) {
            entry = zipFile.getEntry(name);
            if (entry != null) {
                try {
                    file = new File(zipFile.getName());
                    URL zipUrl = file.toURI().toURL();
                    URL entryUrl = new URL("jar:" + zipUrl + "!/" + name);
                    return entryUrl;
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
            }
        }

        return super.findResource(name);
    }

    /**
     * @see java.lang.ClassLoader#findResources(java.lang.String)
     */
    @Override
    protected Enumeration<URL> findResources(String name) throws IOException {
        List<URL> resources = new ArrayList<>();

        // Search for the artifact in the plugin WAR itself
        Enumeration<? extends ZipEntry> entries = this.pluginArtifactZip.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            if (zipEntry.getName().equalsIgnoreCase(name)) {
                try {
                    resources.add(extractResource(zipEntry));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        // Now also add any resources found in dependencies
        ZipEntry entry;
        File file;
        for (ZipFile zipFile : this.dependencyZips) {
            entry = zipFile.getEntry(name);
            if (entry != null) {
                try {
                    file = new File(zipFile.getName());
                    URL zipUrl = file.toURI().toURL();
                    URL entryUrl = new URL("jar:" + zipUrl + "!/" + name);
                    resources.add(entryUrl);
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
            }
        }

        // Return the discovered resources as an Enumeration
        final Iterator<URL> iterator = resources.iterator();
        return new Enumeration<URL>() {
            @Override
            public boolean hasMoreElements() {
                return iterator.hasNext();
            }

            @Override
            public URL nextElement() {
                return iterator.next();
            }
        };
    }

    /**
     * @see java.lang.Object#finalize()
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        close();
    }

    /**
     * Closes any resources the plugin classloader is holding open.
     * @throws IOException if an I/O error has occurred
     */
    public void close() throws IOException {
        if (closed) {
            return;
        }
        this.pluginArtifactZip.close();
        for (ZipFile zipFile : this.dependencyZips) {
            zipFile.close();
        }
        closed = true;
    }

    /**
     * @return gets any policy definition resources from the plugin artifact (located in META-INF/apiman/policyDefs/*.json
     */
    public List<URL> getPolicyDefinitionResources() {
        List<URL> resources = new ArrayList<>();
        Enumeration<? extends ZipEntry> entries = this.pluginArtifactZip.entries();
        while (entries.hasMoreElements()) {
            ZipEntry zipEntry = entries.nextElement();
            if (zipEntry.getName().toLowerCase().startsWith("meta-inf/apiman/policydefs/")
                    && zipEntry.getName().toLowerCase().endsWith(".json")) {
                try {
                    resources.add(extractResource(zipEntry));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return resources;
    }
}