View Javadoc
1   package org.kuali.ole.sys.context;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.FileNotFoundException;
6   import java.io.FileReader;
7   import java.io.FilenameFilter;
8   import java.io.IOException;
9   import java.io.PrintStream;
10  import java.util.ArrayList;
11  import java.util.List;
12  
13  import org.apache.log4j.Logger;
14  
15  /**
16   * BatchContainerDirectory knows the path to the directory for the BatchContainerStep and BatchStepTrigger semaphore files.
17   * It also handles much of the logic for writing, reading, and removing those files.  
18   * 
19   * BatchContainerDirectory adds a ConsoleAppender to its Logger if one hasn't been configured.
20   *
21   */
22  public class BatchContainerDirectory {
23      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchContainerDirectory.class);
24  
25  	private static final String BATCH_CONTAINER_SEMAPHORE_EXTENSION = "runlock";
26  	
27  	private File directory;
28  	
29  	/**
30  	 * Creates a reference for the path to the batch container. Verifies that the directory exists and throws an error if it does not.
31  	 * 
32  	 * @param batchContainerPath the path to the directory that contains the semaphore files
33  	 */
34  	public BatchContainerDirectory(String batchContainerPath) {
35  	    BatchLogger.addConsoleAppender(LOG);
36  	    
37  		directory = new File(batchContainerPath);
38  		
39  		if(!directory.exists()) {
40  		    throw new RuntimeException(batchContainerPath + " does not exist. Please verify and run again");
41  		}
42  		else {			
43  			LOG.info("batchContainerDirectory=" + batchContainerPath);
44  		}
45  	}
46  	
47  	/**
48  	 * Writes the semaphore file which indicates that the batch container is running
49  	 * 
50  	 * @param jobName the name of the job that is starting the batch container
51  	 * @param stepName the name of the step which starts the batch container
52  	 */
53  	public void writeBatchContainerSemaphore(String jobName, String stepName) {
54  		if (!isExistsBatchContainerSemaphore()) {			
55  			BatchStepFileDescriptor batchStepFile = new BatchStepFileDescriptor(jobName, stepName, BATCH_CONTAINER_SEMAPHORE_EXTENSION);				
56  			writeBatchStepFileToSystem(batchStepFile, null);
57  		}
58  	}	
59  	
60  	/**
61  	 * Removes the semaphore file which indicates that the batch container is running
62  	 */
63  	public void removeBatchContainerSemaphore() {
64  		FilenameFilter semaphoreExtensionFilter = new FileExtensionFileFilter(BATCH_CONTAINER_SEMAPHORE_EXTENSION);		
65  		File[] filteredFiles = directory.listFiles(semaphoreExtensionFilter);
66  
67  		for(File semaphore: filteredFiles) {			
68  			semaphore.delete();
69  		}
70  	}
71  	
72  	/**
73  	 * Checks whether the batch container is currently running (by checking for the presence of the batch container semaphore file)
74  	 * 
75  	 * @return true if the batch container is running (if the semaphore file exists), false otherwise
76  	 */
77  	public boolean isBatchContainerRunning() {
78  		return isExistsBatchContainerSemaphore();
79  	}
80  	
81  	/** 
82  	 * @return true if the batch container semaphore file exists in the batch container, false otherwise
83  	 */
84  	private boolean isExistsBatchContainerSemaphore() {
85  		FilenameFilter semaphoreExtensionFilter = new FileExtensionFileFilter(BATCH_CONTAINER_SEMAPHORE_EXTENSION);		
86  		File[] filteredFiles = directory.listFiles(semaphoreExtensionFilter);
87  		
88  		if (filteredFiles != null && filteredFiles.length > 0) {
89  			return true;
90  		}
91  		else {
92  			return false;
93  		}
94  	}
95  	
96  	/**
97  	 * Returns a list of files in the batch container with the run extension
98  	 * 
99  	 * @return a list of step .run files
100 	 */
101 	public File[] getStepRunFiles() { 
102 		// Find all the step .run files for all the jobs. 
103 		FilenameFilter stepStartFilter = new FileExtensionFileFilter(BatchStepFileDescriptor.getFileExtensionRun());		
104 		File[] stepStartFiles = directory.listFiles(stepStartFilter);
105 		
106 		// No .run files found 
107 		if (stepStartFiles == null || stepStartFiles.length == 0) {
108 			return null;
109 		}
110 		
111 		return stepStartFiles;
112 	}	
113 	
114 	/**
115 	 * Writes the run file for the Step specified in the descriptor
116 	 * 
117 	 * @param batchStepFile the step for which a .run file will be written
118 	 * @param stepIndex the index of the step in the job
119 	 */
120     public void writeBatchStepRunFile(BatchStepFileDescriptor batchStepFile, int stepIndex) {
121     	BatchStepFileDescriptor runFile = getCopyWithNewExtension(batchStepFile, BatchStepFileDescriptor.getFileExtensionRun());
122     	writeBatchStepFileToSystem(runFile, new Integer(stepIndex));
123     }        
124     	
125     /**
126      * Writes the success file for the Step specified in the descriptor
127      * 
128      * @param batchStepFile the step for which a .success file will be written
129      */
130     public void writeBatchStepSuccessfulResultFile(BatchStepFileDescriptor batchStepFile) {
131     	BatchStepFileDescriptor successFile = getCopyWithNewExtension(batchStepFile, BatchStepFileDescriptor.getFileExtensionSuccess());
132     	writeBatchStepFileToSystem(successFile, null);
133     }
134     
135     /**
136      * Writes the error file for the Step specified in the descriptor
137      * 
138      * @param batchStepFile the step for which a .error file will be written
139      */
140     public void writeBatchStepErrorResultFile(BatchStepFileDescriptor batchStepFile) {
141     	BatchStepFileDescriptor errorFile = getCopyWithNewExtension(batchStepFile, BatchStepFileDescriptor.getFileExtensionError());
142     	writeBatchStepFileToSystem(errorFile, null);
143     }
144     
145     /**
146      * Writes the error file for the Step specified in the descriptor. The stack trace in the Throwable will be written to the file.
147      * 
148      * @param batchStepFile the step for which a .error file will be written
149      * @param error the error to write in the error file
150      */
151     public void writeBatchStepErrorResultFile(BatchStepFileDescriptor batchStepFile, Throwable error) {
152     	BatchStepFileDescriptor errorFile = getCopyWithNewExtension(batchStepFile, BatchStepFileDescriptor.getFileExtensionError());
153     	writeBatchStepFileToSystem(errorFile, error);
154     }
155 	
156     /**
157      * Returns a copy of the descriptor passed into the method, but with the extension specified. 
158      * 
159      * @param batchStepFile the Step for which a new descriptor is needed
160      * @param extension the extension to use for the new descriptor
161      * @return a copy of the descriptor passed in, but with the specified extension
162      */
163 	private BatchStepFileDescriptor getCopyWithNewExtension(BatchStepFileDescriptor batchStepFile, String extension) {
164 		return new BatchStepFileDescriptor(batchStepFile.getJobName(), batchStepFile.getStepName(), extension);
165 	}
166 	
167 	/**
168 	 * Writes a batch step semaphore file to the directory; Writes the details to the file. 
169 	 * Throws a RuntimeException if the file already exists. 
170 	 * 
171 	 * @param batchStepFile the step for which a semaphore file will be written
172 	 * @param details additional details to add to the semaphore file 
173 	 */
174     private void writeBatchStepFileToSystem(BatchStepFileDescriptor batchStepFile, Object details) {
175     	LOG.debug("Writing "+ batchStepFile.getExtension() +" file for "+ batchStepFile);
176     	
177         String fileName = getDirectoryPath() + batchStepFile.getName();
178         File file = new File(fileName);
179         if (!file.exists()) {
180             try {
181             	LOG.info("Creating new "+ batchStepFile.getExtension() +" file: "+ file.getName());
182                 file.createNewFile();
183                 
184                 //if further details exist write them to the file
185                 if (details != null) {
186                     if (details instanceof Throwable) {
187                         writeErrorMessageToFile(file, (Throwable)details);
188                     }
189                     else if (details instanceof Integer) {
190                         writeStepIndexToFile(file, (Integer)details);
191                     }
192                 }
193                 
194             } catch (IOException e) {
195                 throw new RuntimeException(e);
196             }
197         }
198         else {
199         	throw new RuntimeException("Step "+ batchStepFile.getExtension() +" file: "+ fileName +" already exists"); 
200         }    	        
201     }    
202     
203     /**
204      * Removes the semaphore file for the specified step
205      * 
206      * @param batchStepFile the step for which the semaphore file should be removed
207      */
208     public void removeBatchStepFileFromSystem(BatchStepFileDescriptor batchStepFile) {
209     	LOG.info("Removing "+ batchStepFile.getExtension() +" file for "+ batchStepFile);
210     	
211         String fileName = getDirectoryPath() + batchStepFile.getName();
212         File file = new File(fileName);
213         if (file != null && file.exists()) {
214         	boolean successfulDelete = file.delete();
215         	if (!successfulDelete) {
216         		LOG.error("Failed to delete "+ fileName +" from the system.");
217         	}
218         }
219         else {
220         	//don't need to throw an exception if the file doesn't exist- but note it in the logs
221         	LOG.warn("Step "+ batchStepFile.getExtension() +" file: "+ fileName +" doesn't exist");
222         }    	        
223     }
224     
225     /**
226      * Returns the result file (either a success file or an error file) if one exists for the specified step. Otherwise null is returned.
227      * 
228      * @param batchStepFile the step for which a result file is requested
229      * @return the descriptor of the result file if one exists, null otherwise
230      */
231     public BatchStepFileDescriptor getResultFile(BatchStepFileDescriptor batchStepFile) {
232         LOG.debug("Looking for a result file for "+ batchStepFile);
233         
234         String successFileName = getDirectoryPath() + batchStepFile.getNameNoExtension() +"."+ BatchStepFileDescriptor.getFileExtensionSuccess();
235         File successFile = new File(successFileName);
236         if (successFile != null && successFile.exists()) {
237     		LOG.info("Found .success result file for "+ batchStepFile);
238     		
239         	return new BatchStepFileDescriptor(successFile);
240         }
241         
242         String errorFileName = getDirectoryPath() + batchStepFile.getNameNoExtension() +"."+ BatchStepFileDescriptor.getFileExtensionError();
243         File errorFile = new File(errorFileName);
244         if (errorFile != null && errorFile.exists()) {
245     		LOG.info("Found .error result file for "+ batchStepFile);
246     		
247             return new BatchStepFileDescriptor(errorFile);        	
248         }
249         
250         return null;
251     }    
252     
253     /**
254      * Checks whether the step's semaphore file in the descriptor is null and if not whether the step's semaphore file in the directory is empty
255      * 
256      * @param batchStepFile the descriptor for which a semaphore file existence check is requested
257      * @return true if the step's semaphore file is empty, false otherwise. Throws a RuntimeException if the file does not exist.
258      */
259     public boolean isFileEmpty(BatchStepFileDescriptor batchStepFile) {
260     	File resultFile = batchStepFile.getStepFile();
261     	if (resultFile == null) {
262     		throw new RuntimeException(batchStepFile + BatchStepFileDescriptor.STEP_FILE_EXTENSION_SEPARATOR + batchStepFile.getExtension() +" does not exist");
263     	}
264     	
265     	return isFileEmpty(resultFile);
266     }
267     
268     /**
269      * Returns the index of the step contained in the file
270      * 
271      * @param batchStepFile the file from which to retrieve the step index
272      * @return the step index, or -1 if there was no content in the file
273      */
274     public int getStepIndexFromFile(BatchStepFileDescriptor batchStepFile) {
275         File runFile = batchStepFile.getStepFile();
276         if (runFile != null) {
277             List<String> contents = getFileContents(runFile);
278             if (contents.size() > 0) {
279                 return Integer.parseInt(contents.get(0));
280             }
281         }        
282         
283         return -1;
284     }
285     
286     /**
287      * Reads the contents of the semaphore file (normally the error file), and writes the contents to the requested Logger.
288      * 
289      * @param batchStepFile the descriptor whose semaphore file's contents should be written to the Logger
290      * @param log the log to write the file contents to
291      */
292     public void logFileContents(BatchStepFileDescriptor batchStepFile, Logger log) {
293     	File resultFile = batchStepFile.getStepFile();
294     	if (resultFile != null) {
295     	    List<String> contents = getFileContents(resultFile);
296             String toLog = "";
297             
298     	    for (String line : contents) {
299                 toLog += line + "\n";                    	        
300     	    }
301     	    
302             log.error("Exception found in "+ resultFile.getName() +"\n"+ toLog);
303     	}
304     }
305     
306     /**
307      * Returns the Exception contained in an error result file
308      * 
309      * @param errorResultFile
310      * @return the exception
311      */
312     public String getExceptionFromFile(BatchStepFileDescriptor errorResultFile) {
313         if (errorResultFile.isStepFileAnErrorResultFile()) {
314             File resultFile = errorResultFile.getStepFile();
315             if (resultFile != null) {
316                 List<String> contents = getFileContents(resultFile);
317                 String toLog = "";
318                 
319                 for (String line : contents) {
320                     toLog += line + "\n";                               
321                 }
322                 
323                 return toLog;
324             }
325         }
326         
327         return "";
328     }
329     
330     /**
331      * Creates a shutdown hook and adds it to the local Runtime so that if the container's VM is shut down,
332      * the semaphore files will be removed
333      */
334     public void addShutdownHook() {
335         Runtime.getRuntime().addShutdownHook(new Thread() {
336             
337             public void run() {
338                 removeBatchContainerSemaphore();
339                 if (isBatchContainerRunning()) {
340                     LOG.info("the batch container was not shut down successfully; .runlock still exists.");
341                 }
342             }
343             
344         });
345     }
346     
347     /** 
348      * Returns a List<String> containing each line in the file
349      * 
350      * @param file the file whose contents we want to retrieve
351      * @return a String array containing each line in the file
352      */
353     private List<String> getFileContents(File file) {
354         ArrayList<String> results = new ArrayList<String>();
355         try {
356             FileReader fileReader = new FileReader(file.getAbsolutePath());
357                 
358             String line = "";
359             BufferedReader inputBufferedReader = new BufferedReader(fileReader);
360         
361             String currentLine = inputBufferedReader.readLine();
362             while (currentLine != null) {
363                 results.add(currentLine);
364                 currentLine = inputBufferedReader.readLine();
365             }
366             inputBufferedReader.close();    
367             
368             return results;
369             
370         } catch (IOException e) {
371             throw new RuntimeException("getFileContents() : " + e.getMessage(), e);
372         }
373     }
374 	
375     /**
376      * Returns the absolute path to the directory
377      * 
378      * @return the absolute path to the batch container directory
379      */
380 	private String getDirectoryPath() {
381 		return directory.getAbsolutePath() + File.separator;
382 	}
383     
384 	/**
385 	 * Checks whether the specified File is empty.
386 	 * 
387 	 * @param file the file to check
388 	 * @return true if the file is empty, false otherwise
389 	 */
390     private boolean isFileEmpty(File file) {    	
391     	return file.length() == 0;
392     }
393     
394     /**
395      * Writes the index of the step to the File
396      * 
397      * @param runFile the file to write to
398      * @param stepIndex the step index to write
399      */
400     private void writeStepIndexToFile(File runFile, Integer stepIndex) {
401         PrintStream printStream = initializePrintStream(runFile);
402         
403         //write index
404         printStream.print(stepIndex.intValue());
405         printStream.flush();
406         
407         destroyPrintStream(printStream);
408     }
409     
410     /**
411      * Writes the stack trace of Throwable to the File
412      * 
413      * @param errorFile the file to write to
414      * @param error the error to write
415      */
416     private void writeErrorMessageToFile(File errorFile, Throwable error) {
417     	PrintStream printStream = initializePrintStream(errorFile);
418 
419     	//write error    	
420     	error.printStackTrace(printStream);
421     	printStream.flush();
422 
423     	destroyPrintStream(printStream);
424     }    
425     
426     /**
427      * Initializes a PrintStream to use for writing to.
428      * 
429      * @param errorFile the file to write to
430      * @return a new PrintStream to use to write to the file
431      */
432     private PrintStream initializePrintStream(File errorFile) {
433     	//initialize PrintStream
434         PrintStream printStream;
435     	try {
436     		printStream = new PrintStream(errorFile);
437     	}
438     	catch (FileNotFoundException e) {
439     		LOG.error(e);
440     		throw new RuntimeException(e);
441     	}
442     	
443     	return printStream;
444     }
445     
446     /**
447      * Closes the PrintStream
448      * 
449      * @param printStream the print stream to close
450      */
451     private void destroyPrintStream(PrintStream printStream) {
452     	//close PrintStream
453         if(printStream != null) {        
454             printStream.close();
455             printStream = null;
456         }    	    	    	
457     }    	
458 }