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.List; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import dk.netarkivet.common.exceptions.ArgumentNotValid; 039import dk.netarkivet.common.exceptions.IOFailure; 040 041/** 042 * This implementation of FileBatchJob is a bridge to a jar file given as a File object. The given class will be loaded 043 * and used to perform the actions of the FileBatchJob class. 044 */ 045@SuppressWarnings({"unchecked", "rawtypes", "serial"}) 046public class LoadableJarBatchJob extends FileBatchJob { 047 048 /** The log. */ 049 private static final transient Logger log = LoggerFactory.getLogger(LoadableJarBatchJob.class); 050 051 /** The FileBatchJob that this LoadableJarBatchJob is a wrapper for. */ 052 transient FileBatchJob loadedJob; 053 054 /** The ClassLoader of type ByteJarLoader associated with this job. */ 055 private ClassLoader multipleClassLoader; 056 057 /** The name of the loaded Job. */ 058 private String jobClass; 059 060 /** The arguments for instantiating the batchjob. */ 061 private List<String> args; 062 063 /** 064 * Load a given class from a jar file. 065 * 066 * @param jarFiles The jar file(s) to load from. This file may also contain other classes required by the 067 * FileBatchJob class. 068 * @param arguments The arguments for the batchjob. 069 * @param jobClass The class to load initially. This must be a subclass of FileBatchJob. 070 * @throws ArgumentNotValid If any of the arguments are null. 071 */ 072 public LoadableJarBatchJob(String jobClass, List<String> arguments, File... jarFiles) throws ArgumentNotValid { 073 ArgumentNotValid.checkNotNull(jarFiles, "File jarFile"); 074 ArgumentNotValid.checkNotNullOrEmpty(jobClass, "String jobClass"); 075 ArgumentNotValid.checkNotNull(arguments, "List<String> arguments"); 076 this.jobClass = jobClass; 077 this.args = arguments; 078 StringBuffer res = new StringBuffer("Loading loadableJarBatchJob using jarfiles: "); 079 for (File jarFile : jarFiles) { 080 res.append(jarFile.getName()); 081 } 082 res.append(" and jobclass '" + jobClass); 083 if (!args.isEmpty()) { 084 res.append(", and arguments: '" + args + "'."); 085 } 086 log.info(res.toString()); 087 multipleClassLoader = new ByteJarLoader(jarFiles); 088 089 // Ensure that the batchjob can be loaded. 090 loadBatchJob(); 091 } 092 093 /** 094 * Method for initialising the batch job. 095 * 096 * @throws IOFailure If the job is not loaded correctly. 097 */ 098 private void loadBatchJob() throws IOFailure { 099 try { 100 Class batchClass = multipleClassLoader.loadClass(jobClass); 101 102 if (args.size() == 0) { 103 // just load if no arguments. 104 loadedJob = (FileBatchJob) batchClass.newInstance(); 105 } else { 106 // get argument classes (string only). 107 Class[] argClasses = new Class[args.size()]; 108 for (int i = 0; i < args.size(); i++) { 109 argClasses[i] = String.class; 110 } 111 112 // extract the constructor and instantiate the batchjob. 113 Constructor con = batchClass.getConstructor(argClasses); 114 loadedJob = (FileBatchJob) con.newInstance(args.toArray()); 115 log.debug("Loaded batchjob with arguments: '{}'.", args); 116 } 117 } catch (InvocationTargetException e) { 118 final String msg = "Not allowed to invoke the batchjob '" + jobClass + "'."; 119 log.warn(msg, e); 120 throw new IOFailure(msg, e); 121 } catch (NoSuchMethodException e) { 122 final String msg = "No constructor for the arguments '" + args + "' can be found for the batchjob '" 123 + jobClass + "'."; 124 log.warn(msg, e); 125 throw new IOFailure(msg, e); 126 } catch (InstantiationException e) { 127 final String msg = "Cannot instantiate loaded job class"; 128 log.warn(msg, e); 129 throw new IOFailure(msg, e); 130 } catch (IllegalAccessException e) { 131 final String msg = "Cannot access loaded job from byte array"; 132 log.warn(msg, e); 133 throw new IOFailure(msg, e); 134 } catch (ClassNotFoundException e) { 135 final String msg = "Cannot create job class from jar file"; 136 log.warn(msg, e); 137 throw new IOFailure(msg, e); 138 } 139 } 140 141 /** 142 * Initialize the job before running. This is called before the processFile() calls. 143 * 144 * @param os the OutputStream to which output should be written 145 */ 146 public void initialize(OutputStream os) { 147 ArgumentNotValid.checkNotNull(os, "os"); 148 149 // Initialise the loadedJob. 150 loadBatchJob(); 151 loadedJob.initialize(os); 152 } 153 154 /** 155 * Process one file stored in the bit archive. 156 * 157 * @param file the file to be processed. 158 * @param os the OutputStream to which output should be written 159 * @return true if the file was successfully processed, false otherwise 160 */ 161 public boolean processFile(File file, OutputStream os) { 162 ArgumentNotValid.checkNotNull(file, "File file"); 163 ArgumentNotValid.checkNotNull(os, "OutputStream os"); 164 return loadedJob.processFile(file, os); 165 } 166 167 /** 168 * Finish the job. This is called after the last process() call. 169 * 170 * @param os the OutputStream to which output should be written 171 */ 172 public void finish(OutputStream os) { 173 ArgumentNotValid.checkNotNull(os, "OutputStream os"); 174 loadedJob.finish(os); 175 } 176 177 /** 178 * Human readable representation of this object. Overrides FileBatchJob.toString to include name of loaded 179 * jar/class. 180 * 181 * @return a Human readable representation of this class 182 */ 183 public String toString() { 184 return this.getClass().getName() + " processing " + jobClass + " from " + multipleClassLoader.toString(); 185 } 186 187 /** 188 * Override of the default way to serialize this class. 189 * 190 * @param out Stream that the object will be written to. 191 * @throws IOException In case there is an error from the underlying stream, or this object cannot be serialized. 192 */ 193 private void writeObject(ObjectOutputStream out) throws IOException { 194 out.defaultWriteObject(); 195 } 196 197 /** 198 * Override of the default way to deserialize an object of this class. 199 * 200 * @param in Stream that the object can be read from. 201 * @throws IOException If there is an error reading from the stream, or the serialized object cannot be deserialized 202 * due to errors in the serialized form. 203 * @throws ClassNotFoundException If the class definition of the serialized object cannot be found. 204 */ 205 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 206 in.defaultReadObject(); 207 } 208 209 @Override 210 public boolean postProcess(InputStream input, OutputStream output) { 211 ArgumentNotValid.checkNotNull(input, "InputStream input"); 212 ArgumentNotValid.checkNotNull(output, "OutputStream output"); 213 214 // Let the loaded job handle the post processing. 215 log.debug("Post-processing in the loaded batchjob."); 216 loadBatchJob(); 217 return loadedJob.postProcess(input, output); 218 } 219 220 /** 221 * Method for retrieving the name of the loaded class. 222 * 223 * @return The name of the loaded class. 224 */ 225 public String getLoadedJobClass() { 226 return jobClass; 227 } 228 229}