View Javadoc
1   package org.kuali.ole.sys.context;
2   
3   
4   import java.io.File;
5   import java.util.ArrayList;
6   import java.util.Date;
7   import java.util.List;
8   
9   import org.apache.commons.lang.StringUtils;
10  import org.apache.log4j.Appender;
11  import org.apache.log4j.FileAppender;
12  import org.apache.log4j.Logger;
13  import org.apache.log4j.NDC;
14  import org.kuali.ole.sys.OLEConstants;
15  import org.kuali.ole.sys.batch.Job;
16  import org.kuali.ole.sys.batch.Step;
17  import org.kuali.rice.core.api.config.property.ConfigurationService;
18  import org.kuali.rice.core.api.datetime.DateTimeService;
19  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
20  import org.kuali.rice.krad.service.KualiModuleService;
21  import org.kuali.rice.krad.service.ModuleService;
22  
23  
24  /**
25   * BatchStepExecutor executes a Step in its own Thread and writes either a .success or .error file after execution.
26   * This class notifies the ContainerStepListener when a Step has started and when it has completed.
27   *
28   * BatchStepExecutor adds a ConsoleAppender to its Logger if one hasn't been configured.
29   *
30   */
31  public class BatchStepExecutor implements Runnable {
32      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(BatchStepExecutor.class);
33  
34  	private ParameterService parameterService;
35  	private DateTimeService dateTimeService;
36  	private BatchContainerDirectory batchContainerDirectory;
37  	private BatchStepFileDescriptor batchStepFile;
38  	private Step step;
39  	private int stepIndex;
40  
41  	private Appender ndcAppender;
42  	private boolean ndcSet;
43  	private String logFileName;
44  
45  	private List<ContainerStepListener> containerStepListeners;
46  
47  	/**
48  	 * @param parameterService the ParameterService used by Job
49  	 * @param dateTimeService the DateTimeService used by Job
50  	 * @param batchContainerDirectory the batch container directory
51  	 * @param batchStepFile the descriptor containing information about the step to execute
52  	 * @param step the Step to execute
53  	 * @param stepIndex the index of the step in the job
54  	 */
55  	public BatchStepExecutor(ParameterService parameterService, DateTimeService dateTimeService, BatchContainerDirectory batchContainerDirectory, BatchStepFileDescriptor batchStepFile, Step step, int stepIndex) {
56  	    BatchLogger.addConsoleAppender(LOG);
57  
58  		this.parameterService = parameterService;
59  		this.dateTimeService = dateTimeService;
60  
61  		this.batchContainerDirectory = batchContainerDirectory;
62  		this.batchStepFile = batchStepFile;
63  		this.step = step;
64  		this.stepIndex = stepIndex;
65  
66  		this.containerStepListeners = new ArrayList<ContainerStepListener>();
67  
68  		LOG.info("Initialized thread executor for "+ batchStepFile);
69  	}
70  
71  	/**
72  	 * Execute the Step via Job.runStep(). Setup NDC logging so the Step has its own log file. Remove the NDC logging once the step is finished executing.
73  	 * Notify the ContainerStepListeners when the step starts and finishes.
74  	 */
75  	@Override
76      public void run() {
77  	    Date stepRunDate = dateTimeService.getCurrentDate();
78  	    batchStepFile.setStartedDate(stepRunDate);
79  	    batchStepFile.setStepIndex(new Integer(stepIndex));
80  
81  		setupNDCLogging();
82  		notifyStepStarted();
83  
84  		try {
85  			LOG.info("Running "+ batchStepFile);
86  
87  			boolean result = Job.runStep(parameterService, batchStepFile.getJobName(), stepIndex, step, stepRunDate);
88  
89  			if (result) {
90  				LOG.info("Step returned true");
91  				batchContainerDirectory.writeBatchStepSuccessfulResultFile(batchStepFile);
92  			}
93  			else {
94  				LOG.info("Step returned false");
95  				batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile);
96  			}
97  
98  		} catch (Throwable throwable) {
99  			LOG.info("Step threw an error: ", throwable);
100 			batchContainerDirectory.writeBatchStepErrorResultFile(batchStepFile, throwable);
101 
102 		} finally {
103 
104 		    notifyStepFinished();
105 			resetNDCLogging();
106 		}
107 	}
108 
109 	/**
110 	 * Adds a ContainerStepListener for step start and completion notifications
111 	 *
112 	 * @param listener the ContainerStepListener
113 	 */
114 	public void addContainerStepListener(ContainerStepListener listener) {
115 	    this.containerStepListeners.add(listener);
116 	}
117 
118 	/**
119 	 * Add a new appender and context to the NDC for this execution of the step
120 	 */
121 	private void setupNDCLogging() {
122         String nestedDiagnosticContext = getNestedDiagnosticContext();
123         logFileName = getLogFileName(nestedDiagnosticContext);
124 
125         ndcAppender = null;
126         ndcSet = false;
127         try {
128             ndcAppender = new FileAppender(BatchLogger.getLogFileAppenderLayout(), logFileName);
129             ndcAppender.addFilter(new NDCFilter(nestedDiagnosticContext));
130             Logger.getRootLogger().addAppender(ndcAppender);
131             NDC.push(nestedDiagnosticContext);
132             ndcSet = true;
133         } catch (Exception ex) {
134             LOG.warn("Could not initialize custom logging for step: " + step.getName(), ex);
135         }
136 	}
137 
138 	/**
139 	 * Remove the appender and context from the NDC
140 	 */
141 	private void resetNDCLogging() {
142         if ( ndcSet ) {
143             ndcAppender.close();
144             Logger.getRootLogger().removeAppender(ndcAppender);
145             NDC.pop();
146         }
147 	}
148 
149 	/**
150 	 * Constructs the name of the log file to write to for this execution of the step
151 	 *
152 	 * @param nestedDiagnosticContext the context returned by getNestedDiagnosticContext() for this step
153 	 * @return the name of the log file
154 	 */
155     private String getLogFileName(String nestedDiagnosticContext) {
156         return SpringContext.getBean( ConfigurationService.class ).getPropertyValueAsString(OLEConstants.REPORTS_DIRECTORY_KEY)
157                 + File.separator
158                 + nestedDiagnosticContext + ".log";
159     }
160 
161     /**
162      * @return the nested diagnostic context string for this step's log file
163      */
164 	@SuppressWarnings("unchecked")
165     private String getNestedDiagnosticContext() {
166         Step unProxiedStep = (Step) ProxyUtils.getTargetIfProxied(step);
167         Class stepClass = unProxiedStep.getClass();
168         ModuleService module = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService( stepClass );
169 
170         String nestedDiagnosticContext =
171                 StringUtils.substringAfter( module.getModuleConfiguration().getNamespaceCode(), "-").toLowerCase()
172                 + File.separator + step.getName()
173                 + "-" + dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate());
174 
175         return nestedDiagnosticContext;
176     }
177 
178 	/**
179 	 * Notify the ContainerStepListeners that the Step has started
180 	 */
181 	private void notifyStepStarted() {
182 	    String shortLogFileName = getShortLogFileName();
183 
184 	    for(ContainerStepListener listener : this.containerStepListeners) {
185 	        listener.stepStarted(batchStepFile, shortLogFileName);
186 	    }
187 	}
188 
189 	/**
190 	 * Notify the ContainerStepListeners that the Step has completed
191 	 */
192 	private void notifyStepFinished() {
193 	    BatchStepFileDescriptor resultFile = batchContainerDirectory.getResultFile(batchStepFile);
194 	    resultFile.setCompletedDate(dateTimeService.getCurrentDate());
195 	    resultFile.setStepIndex(new Integer(stepIndex));
196 
197         String shortLogFileName = getShortLogFileName();
198 
199 	    for(ContainerStepListener listener : this.containerStepListeners) {
200 	        listener.stepFinished(resultFile, shortLogFileName);
201 	    }
202 	}
203 
204 	/**
205 	 * Returns just the name of the log file without the absolute path
206 	 *
207 	 * @return just the name of the log file (not the entire path)
208 	 */
209 	private String getShortLogFileName() {
210         String shortLogFileName = logFileName;
211 
212         File logFile = new File(logFileName);
213         if (logFile.exists()) {
214             shortLogFileName = logFile.getName();
215         }
216         return shortLogFileName;
217 	}
218 }