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}