org.echocat.nodoodle.transport.HandlerUnpacker.java Source code

Java tutorial

Introduction

Here is the source code for org.echocat.nodoodle.transport.HandlerUnpacker.java

Source

/*****************************************************************************************
 * *** BEGIN LICENSE BLOCK *****
 *
 * Version: MPL 2.0
 *
 * echocat NoDoodle, Copyright (c) 2010-2012 echocat
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * *** END LICENSE BLOCK *****
 ****************************************************************************************/

package org.echocat.nodoodle.transport;

import org.echocat.nodoodle.Handler;
import org.echocat.nodoodle.classloading.FileClassLoader;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.jar.*;
import java.util.zip.ZipEntry;

import static org.echocat.nodoodle.transport.TransportConstants.*;

public class HandlerUnpacker {

    private static final Field ZIP_ENTRY_NAME_FIELD;

    static {
        try {
            ZIP_ENTRY_NAME_FIELD = ZipEntry.class.getDeclaredField("name");
            ZIP_ENTRY_NAME_FIELD.setAccessible(true);
            // Access check...
            ZIP_ENTRY_NAME_FIELD.set(new ZipEntry("test"), "test2");
        } catch (Exception e) {
            final UnsupportedClassVersionError toThrow = new UnsupportedClassVersionError(
                    "It was not possible to access the name field of '" + ZipEntry.class.getName() + "'.");
            toThrow.initCause(e);
            throw toThrow;
        }
    }

    public static class Result {

        private final String _id;
        private final File _targetDirectory;
        private final ClassLoader _classLoader;
        private final Handler<?> _handler;
        private final Date _created;

        private Result(String id, File targetDirectory, ClassLoader classLoader, Handler<?> handler) {
            if (id == null) {
                throw new NullPointerException();
            }
            if (targetDirectory == null) {
                throw new NullPointerException();
            }
            if (classLoader == null) {
                throw new NullPointerException();
            }
            if (handler == null) {
                throw new NullPointerException();
            }
            _id = id;
            _targetDirectory = targetDirectory;
            _classLoader = classLoader;
            _handler = handler;
            _created = new Date();
        }

        public String getId() {
            return _id;
        }

        public File getTargetDirectory() {
            return _targetDirectory;
        }

        public ClassLoader getClassLoader() {
            return _classLoader;
        }

        public Handler<?> getHandler() {
            return _handler;
        }

        public Date getCreated() {
            return _created;
        }
    }

    private final ClassLoader _classLoader;

    private File _workDirectory = new File(System.getProperty("java.io.tmpdir", "tmp"), "nodoodle");

    public HandlerUnpacker() {
        this(null);
    }

    public HandlerUnpacker(ClassLoader classLoader) {
        _classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    }

    public Result unpack(String id, InputStream inputStream) throws Exception {
        if (id == null) {
            throw new NullPointerException();
        }
        if (inputStream == null) {
            throw new NullPointerException();
        }
        final File targetDirectory = new File(_workDirectory, id).getCanonicalFile();
        if (!targetDirectory.mkdirs() && !targetDirectory.isDirectory()) {
            throw new IOException("Could not create " + targetDirectory + ".");
        }
        final SplitResult splitResult = splitMultipleJarIntoJars(inputStream, targetDirectory);
        final ClassLoader classLoader = new FileClassLoader(splitResult.jarFiles, _classLoader);
        final Handler<?> handler = loadInstance(splitResult, classLoader);
        return new Result(id, targetDirectory, classLoader, handler);
    }

    protected Handler<?> loadInstance(SplitResult splitResult, final ClassLoader classLoader) throws IOException {
        if (splitResult == null) {
            throw new NullPointerException();
        }
        if (classLoader == null) {
            throw new NullPointerException();
        }
        final Attributes extensionAttributes = splitResult.manifest.getAttributes(MANIFEST_EXTENSION_NAME);
        if (extensionAttributes == null) {
            throw new IOException("There is no valid manifest in the provided jar.");
        }
        final Object dataFile = extensionAttributes.get(MANIFEST_DATE_FILE);
        if (!(dataFile instanceof String)) {
            throw new IOException("There is no valid " + MANIFEST_DATE_FILE + " attribute defined in manifest.");
        }
        final InputStream inputStream = classLoader.getResourceAsStream((String) dataFile);
        if (inputStream == null) {
            throw new FileNotFoundException("Could not find the resource " + dataFile + " which was defined by "
                    + MANIFEST_DATE_FILE + " manifest entry of provided jar file.");
        }
        try {
            final ObjectInputStream ois = new ObjectInputStream(inputStream) {
                @Override
                protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
                    return classLoader.loadClass(desc.getName());
                }
            };
            final Object result;
            try {
                result = ois.readObject();
            } catch (Exception e) {
                throw new IOException("Could not parse the resource " + dataFile + " which was defined by "
                        + MANIFEST_DATE_FILE + " manifest entry of provided jar file.", e);
            }
            if (!(result instanceof Handler)) {
                throw new IOException("The resource " + dataFile + " which was defined by " + MANIFEST_DATE_FILE
                        + " manifest entry of provided jar file does contain " + result
                        + " and not an object of type " + Handler.class.getName() + ".");
            }
            return (Handler) result;
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    @SuppressWarnings({ "ProtectedInnerClass", "InstanceVariableNamingConvention", "ParameterHidesMemberVariable" })
    protected class SplitResult {
        public final Collection<File> jarFiles;
        public final Manifest manifest;

        protected SplitResult(Collection<File> jarFiles, Manifest manifest) {
            if (jarFiles == null) {
                throw new NullPointerException();
            }
            if (manifest == null) {
                throw new NullPointerException();
            }
            this.jarFiles = jarFiles;
            this.manifest = manifest;
        }
    }

    protected SplitResult splitMultipleJarIntoJars(InputStream inputStream, File targetDirectory)
            throws IOException {
        if (inputStream == null) {
            throw new NullPointerException();
        }
        if (targetDirectory == null) {
            throw new NullPointerException();
        }
        final Collection<File> jarFiles = new ArrayList<File>();
        final File mainJarFile = getMainJarFile(targetDirectory);
        jarFiles.add(mainJarFile);
        final JarInputStream jarInput = new JarInputStream(inputStream);
        final Manifest manifest = jarInput.getManifest();
        final FileOutputStream mainJarFileStream = new FileOutputStream(mainJarFile);
        try {
            final JarOutputStream jarOutput = new JarOutputStream(mainJarFileStream, manifest);
            try {
                JarEntry entry = jarInput.getNextJarEntry();
                while (entry != null) {
                    final String entryName = entry.getName();
                    if (!entry.isDirectory() && entryName.startsWith("lib/")) {
                        final File targetFile = new File(targetDirectory, entryName);
                        if (!targetFile.getParentFile().mkdirs()) {
                            throw new IOException("Could not create parent directory of " + targetFile + ".");
                        }
                        final OutputStream outputStream = new FileOutputStream(targetFile);
                        try {
                            IOUtils.copy(jarInput, outputStream);
                        } finally {
                            IOUtils.closeQuietly(outputStream);
                        }
                        jarFiles.add(targetFile);
                    } else {
                        if (entryName.startsWith(TransportConstants.CLASSES_PREFIX)
                                && entryName.length() > TransportConstants.CLASSES_PREFIX.length()) {
                            try {
                                ZIP_ENTRY_NAME_FIELD.set(entry, entryName.substring(CLASSES_PREFIX.length()));
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException("Could not set " + ZIP_ENTRY_NAME_FIELD + ".", e);
                            }
                        }
                        jarOutput.putNextEntry(entry);
                        IOUtils.copy(jarInput, jarOutput);
                        jarOutput.closeEntry();
                    }
                    entry = jarInput.getNextJarEntry();
                }
            } finally {
                IOUtils.closeQuietly(jarOutput);
            }
        } finally {
            IOUtils.closeQuietly(mainJarFileStream);
        }
        return new SplitResult(Collections.unmodifiableCollection(jarFiles), manifest);
    }

    protected File getMainJarFile(File targetDirectory) {
        return new File(targetDirectory, "main.jar");
    }

    public File getWorkDirectory() {
        return _workDirectory;
    }

    public void setWorkDirectory(File workDirectory) {
        if (workDirectory == null) {
            throw new NullPointerException();
        }
        _workDirectory = workDirectory;
    }
}