001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023package dk.netarkivet.common.utils.batch;
024
025import java.io.ByteArrayOutputStream;
026import java.io.File;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.Serializable;
030import java.util.Enumeration;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.jar.JarEntry;
034import java.util.jar.JarFile;
035
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import dk.netarkivet.common.exceptions.ArgumentNotValid;
040import dk.netarkivet.common.exceptions.IOFailure;
041import dk.netarkivet.common.utils.StreamUtils;
042
043/**
044 * ByteJarLoader is a ClassLoader that stores java classes in a map where the key to the map is the class name, and the
045 * value is the class stored as a byte array.
046 */
047@SuppressWarnings("serial")
048public class ByteJarLoader extends ClassLoader implements Serializable {
049
050    /** The log. */
051    private static final transient Logger log = LoggerFactory.getLogger(ByteJarLoader.class);
052
053    /** The map, that holds the class data. */
054    Map<String, byte[]> binaryData = new HashMap<String, byte[]>();
055
056    /** Java package separator. */
057    private static final String JAVA_PACKAGE_SEPARATOR = ".";
058
059    /** Directory separator. */
060    private static final String DIRECTOR_SEPARATOR = "/";
061
062    /**
063     * Constructor for the ByteLoader.
064     *
065     * @param files An array of files, which are assumed to be jar-files, but they need not have the extension .jar
066     */
067    public ByteJarLoader(File... files) {
068        ArgumentNotValid.checkNotNull(files, "File ... files");
069        ArgumentNotValid.checkTrue(files.length != 0, "Should not be empty array");
070        for (File file : files) {
071            try {
072                JarFile jarFile = new JarFile(file);
073                for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
074                    JarEntry entry = e.nextElement();
075                    String name = entry.getName();
076                    InputStream in = jarFile.getInputStream(entry);
077                    ByteArrayOutputStream out = new ByteArrayOutputStream((int) entry.getSize());
078                    StreamUtils.copyInputStreamToOutputStream(in, out);
079                    log.trace("Entering data for class '{}'", name);
080                    binaryData.put(name, out.toByteArray());
081                }
082            } catch (IOException e) {
083                throw new IOFailure("Failed to load jar file '" + file.getAbsolutePath() + "': " + e);
084            }
085        }
086    }
087
088    /**
089     * Lookup and return the Class with the given className. This method overrides the ClassLoader.findClass method.
090     *
091     * @param className The name of the class to lookup
092     * @return the Class with the given className.
093     * @throws ClassNotFoundException If the class could not be found
094     */
095    @SuppressWarnings({"rawtypes", "unchecked"})
096    public Class findClass(String className) throws ClassNotFoundException {
097        ArgumentNotValid.checkNotNullOrEmpty(className, "String className");
098        // replace all dots with '/' in the className before looking it up
099        // in the
100        // hashmap
101        // Note: The class is stored in the hashmap with a .class extension
102        String realClassName = className.replace(JAVA_PACKAGE_SEPARATOR, DIRECTOR_SEPARATOR) + ".class";
103
104        if (binaryData.isEmpty()) {
105            log.warn("No data loaded for class with name '{}'", className);
106        }
107        if (binaryData.containsKey(realClassName)) {
108            final byte[] bytes = binaryData.get(realClassName);
109            return defineClass(className, bytes, 0, bytes.length);
110        } else {
111            return super.findClass(className);
112        }
113    }
114
115}