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.File;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.ObjectInputStream;
029import java.io.ObjectOutputStream;
030import java.io.OutputStream;
031import java.lang.reflect.Constructor;
032import java.lang.reflect.InvocationTargetException;
033import java.util.ArrayList;
034import java.util.List;
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.FileUtils;
042
043/**
044 * This implementation of FileBatchJob is a bridge to a class file given as a File object. The given class will be
045 * loaded and used to perform the actions of the FileBatchJob class.
046 */
047@SuppressWarnings({"unchecked", "rawtypes", "serial"})
048public class LoadableFileBatchJob extends FileBatchJob {
049
050    /** The class logger. */
051    private static final transient Logger log = LoggerFactory.getLogger(LoadableFileBatchJob.class);
052
053    /** The job loaded from file. */
054    transient FileBatchJob loadedJob;
055    /** The binary contents of the file before they are turned into a class. */
056    byte[] fileContents;
057    /** The name of the file before they are turned into a class. */
058    String fileName;
059    /** The arguments for instantiating the batchjob. */
060    private List<String> args;
061
062    /**
063     * Create a new batch job that runs the loaded class.
064     *
065     * @param classFile the classfile for the batch job we want to run.
066     * @param arguments The arguments for the batchjobs. This can be null.
067     * @throws ArgumentNotValid If the classfile is null.
068     */
069    public LoadableFileBatchJob(File classFile, List<String> arguments) throws ArgumentNotValid {
070        ArgumentNotValid.checkNotNull(classFile, "File classFile");
071        fileContents = FileUtils.readBinaryFile(classFile);
072        fileName = classFile.getName();
073        if (arguments == null) {
074            this.args = new ArrayList<String>();
075        } else {
076            this.args = arguments;
077        }
078
079        loadBatchJob();
080    }
081
082    /**
083     * Override of the default toString to include name of loaded class.
084     *
085     * @return string representation of this class.
086     */
087    public String toString() {
088        return this.getClass().getName() + " processing " + fileName;
089    }
090
091    /**
092     * Override of the default way to serialize this class.
093     *
094     * @param out Stream that the object will be written to.
095     * @throws IOException In case there is an error from the underlying stream, or this object cannot be serialized.
096     */
097    private void writeObject(ObjectOutputStream out) throws IOException {
098        out.defaultWriteObject();
099    }
100
101    /**
102     * Override of the default way to unserialize an object of this class.
103     *
104     * @param in Stream that the object can be read from.
105     * @throws IOException If there is an error reading from the stream, or the serialized object cannot be deserialized
106     * due to errors in the serialized form.
107     * @throws ClassNotFoundException If the class definition of the serialized object cannot be found.
108     */
109    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
110        in.defaultReadObject();
111    }
112
113    /**
114     * Initialize the job before runnning. This is called before the processFile() calls.
115     *
116     * @param os the OutputStream to which output should be written
117     */
118    public void initialize(OutputStream os) {
119        ArgumentNotValid.checkNotNull(os, "OutputStream os");
120        loadBatchJob();
121        loadedJob.initialize(os);
122    }
123
124    /**
125     * Method for initializing the loaded batchjob.
126     *
127     * @throws IOFailure If the batchjob cannot be loaded.
128     */
129    protected void loadBatchJob() throws IOFailure {
130        ByteClassLoader singleClassLoader = new ByteClassLoader(fileContents);
131        try {
132            Class batchClass = singleClassLoader.defineClass();
133            if (args.size() == 0) {
134                loadedJob = (FileBatchJob) batchClass.newInstance();
135            } else {
136                // get argument classes (string only).
137                Class[] argClasses = new Class[args.size()];
138                for (int i = 0; i < args.size(); i++) {
139                    argClasses[i] = String.class;
140                }
141
142                // extract the constructor and instantiate the batchjob.
143                Constructor con = batchClass.getConstructor(argClasses);
144                loadedJob = (FileBatchJob) con.newInstance(args.toArray());
145                log.debug("Loaded batchjob with arguments: '{}'.", args);
146            }
147        } catch (InvocationTargetException e) {
148            final String msg = "Not allowed to invoke the batchjob '" + fileName + "'.";
149            log.warn(msg, e);
150            throw new IOFailure(msg, e);
151        } catch (NoSuchMethodException e) {
152            final String msg = "No constructor for the arguments '" + args + "' can be found for the batchjob '"
153                    + fileName + "'.";
154            log.warn(msg, e);
155            throw new IOFailure(msg, e);
156        } catch (InstantiationException e) {
157            String errMsg = "Cannot instantiate batchjob from byte array";
158            log.warn(errMsg, e);
159            throw new IOFailure(errMsg, e);
160        } catch (IllegalAccessException e) {
161            String errMsg = "Cannot access loaded job from byte array";
162            log.warn(errMsg, e);
163            throw new IOFailure(errMsg, e);
164        }
165    }
166
167    /**
168     * Process one file stored in the bit archive.
169     *
170     * @param file the file to be processed.
171     * @param os the OutputStream to which output should be written
172     * @return true if the file was successfully processed, false otherwise
173     */
174    public boolean processFile(File file, OutputStream os) {
175        log.trace("Started processing of file '{}'.", file.getAbsolutePath());
176        ArgumentNotValid.checkNotNull(file, "File file");
177        ArgumentNotValid.checkNotNull(os, "OutputStream os");
178        return loadedJob.processFile(file, os);
179    }
180
181    /**
182     * Finish up the job. This is called after the last process() call.
183     *
184     * @param os the OutputStream to which output should be written
185     */
186    public void finish(OutputStream os) {
187        ArgumentNotValid.checkNotNull(os, "OutputStream os");
188        loadedJob.finish(os);
189    }
190
191    @Override
192    public boolean postProcess(InputStream input, OutputStream output) {
193        ArgumentNotValid.checkNotNull(input, "InputStream input");
194        ArgumentNotValid.checkNotNull(output, "OutputStream output");
195
196        // Let the loaded job handle the post processing.
197        loadBatchJob();
198        return loadedJob.postProcess(input, output);
199    }
200
201}