View Javadoc
1   /*
2    * Copyright 2010 Capgemini and Contributors
3    *
4    * Licensed under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  
17  package net.sf.appstatus.core.batch;
18  
19  import java.util.Date;
20  import java.util.List;
21  import java.util.Vector;
22  
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  /**
27   * Log job progress agent.
28   *
29   * @author Guillaume Mary
30   * @author Nicolas Richeton
31   *
32   */
33  public abstract class AbstractBatchProgressMonitor implements IBatchProgressMonitorExt {
34  
35  	/**
36  	 * Do not show estimated end date if more than 1 year.
37  	 */
38  	private static Date maxDate = new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365);
39  
40  	/**
41  	 * Reference to the batch description
42  	 */
43  	private final IBatch batch;
44  
45  	protected List<AbstractBatchProgressMonitor> currentChildren;
46  
47  	/**
48  	 * Current item being processed
49  	 */
50  	protected Object currentItem;
51  	protected boolean done = false;
52  
53  	protected Long endTime = null;
54  
55  	protected final String executionId;
56  	protected long itemCount = 0;
57  	private String lastMessage;
58  
59  	private long lastUpdate = -1;
60  	private long lastWriteTimestamp;
61  
62  	private Logger logger = LoggerFactory.getLogger("batch");
63  
64  	private String name;
65  
66  	protected AbstractBatchProgressMonitor parent;
67  
68  	private int parentWork;
69  	protected final List<String> rejectedItems = new Vector<String>();
70  	protected long startTime;
71  
72  	/**
73  	 * Batch execution status. true if batch terminated normaly.
74  	 */
75  	private boolean success;
76  
77  	protected final List<String> successItems = new Vector<String>();
78  
79  	private String taskDescription;
80  
81  	private String taskGroup;
82  
83  	/**
84  	 * Name of the task, set with beginTask
85  	 * <p>
86  	 * Init to null : cannot be set multiple times. beginTask will fail if
87  	 * taskName is not null.
88  	 */
89  	protected String taskName = null;
90  
91  	protected int totalWork = UNKNOW;
92  
93  	protected int worked = 0;
94  
95  	/**
96  	 * Delay between progress logging. Default to 1s
97  	 */
98  	private long writingDelay = 1000;
99  
100 	/**
101 	 * Constructor used for main monitor.
102 	 *
103 	 * @param executionId
104 	 *            job execution id
105 	 */
106 	public AbstractBatchProgressMonitor(String executionId, IBatch batch) {
107 		this.executionId = executionId;
108 		this.batch = batch;
109 		this.currentChildren = new Vector<AbstractBatchProgressMonitor>();
110 		this.batch.setProgressMonitor(this);
111 		startTime = System.currentTimeMillis();
112 		getLogger().info("[{}] {}: Init progress monitoring (id: {})",
113 				new Object[] { this.batch.getGroup(), batch.getName(), batch.getUuid() });
114 		touch();
115 	}
116 
117 	/**
118 	 * Protected constructor used to create a sub task.
119 	 *
120 	 * @param executionId
121 	 *            execution id
122 	 * @param parent
123 	 *            parent monitor
124 	 * @param parentWork
125 	 *            parent amount of work
126 	 */
127 	protected AbstractBatchProgressMonitor(String executionId, IBatchProgressMonitor parent, int parentWork,
128 			IBatch batch) {
129 		this.executionId = executionId;
130 		this.parent = (AbstractBatchProgressMonitor) parent;
131 		this.parentWork = parentWork;
132 		this.currentChildren = new Vector<AbstractBatchProgressMonitor>();
133 		this.batch = batch;
134 		startTime = System.currentTimeMillis();
135 		touch();
136 
137 	}
138 
139 	/**
140 	 * {@inheritDoc}
141 	 */
142 	public void beginTask(String name, String description, int totalWork) {
143 		// This method can only be called one.
144 		if (this.taskName != null) {
145 			throw new IllegalStateException(
146 					"beginTask can only be called once (" + name + ", " + description + ", " + totalWork + ")");
147 		}
148 		this.totalWork = totalWork;
149 		this.taskName = name;
150 		this.taskDescription = description;
151 		getLogger().info("[{}] {}: Begin {} ({}), steps : {}", new Object[] { getBatch().getGroup(),
152 				getBatch().getName(), name, description, String.valueOf(totalWork) });
153 		touch();
154 
155 	}
156 
157 	/**
158 	 * {@inheritDoc}
159 	 */
160 	public IBatchProgressMonitor createSubTask(int work) {
161 		AbstractBatchProgressMonitor newSubTask = (AbstractBatchProgressMonitor) newInstance(work);
162 		newSubTask.setLogger(logger);
163 		currentChildren.add(newSubTask);
164 		touch();
165 		return newSubTask;
166 	}
167 
168 	/**
169 	 * {@inheritDoc}
170 	 */
171 	public void done() {
172 		if (parent != null) {
173 			endTask(true);
174 			getLogger().info("[{}] {}: End {}, {} ms", new Object[] { getBatch().getGroup(), getBatch().getName(), name,
175 					System.currentTimeMillis() - startTime });
176 		} else {
177 			endBatch(true);
178 			getLogger().info("[{}] {}: End batch, {} ms", new Object[] { getBatch().getGroup(), getBatch().getName(),
179 					System.currentTimeMillis() - startTime });
180 
181 		}
182 
183 		touch();
184 
185 	}
186 
187 	protected void endBatch(boolean success) {
188 		getMainMonitor().success = success;
189 		getMainMonitor().done = true;
190 		getMainMonitor().endTime = System.currentTimeMillis();
191 		getMainMonitor().currentItem = null;
192 
193 		if (getMainMonitor().totalWork != UNKNOW && getMainMonitor().worked < getMainMonitor().totalWork) {
194 			getMainMonitor().worked = getMainMonitor().totalWork;
195 		}
196 		getMainMonitor().currentChildren.clear();
197 
198 		onBatchEnd();
199 	}
200 
201 	protected void endTask(boolean success) {
202 
203 		done = true;
204 		currentItem = null;
205 		this.endTime = System.currentTimeMillis();
206 		this.success = success;
207 
208 		if (parent != null) {
209 			parent.worked(parentWork);
210 			parent.currentChildren.remove(this);
211 		}
212 
213 	}
214 
215 	public void fail(String reason) {
216 		fail(reason, null);
217 
218 	}
219 
220 	public void fail(String reason, Throwable t) {
221 		message("Failed: " + reason + " " + (t != null ? t.getMessage() : ""));
222 
223 		// Mark job as finished and failure
224 		endTask(false);
225 		endBatch(false);
226 
227 		getLogger().error("[{}] {}: FAILED ({}) , duration: {} ms", this.batch.getGroup(), batch.getName(), reason,
228 				String.valueOf(endTime - startTime), t);
229 		touch();
230 	}
231 
232 	public IBatch getBatch() {
233 		return batch;
234 	}
235 
236 	public Object getCurrentItem() {
237 		return currentItem;
238 	}
239 
240 	/**
241 	 * Returns end date is the task is finished, or an estimate if task is still
242 	 * running.
243 	 *
244 	 * @return
245 	 */
246 	protected Date getEndDate() {
247 
248 		// If batch is running we try to estimate end date
249 		if (!done) {
250 
251 			// Cannot predict end date if progress is unknown
252 			if (totalWork == UNKNOW) {
253 				return null;
254 			}
255 
256 			long currentTime = System.currentTimeMillis();
257 			long elapsed = currentTime - startTime;
258 
259 			if (getProgress() != 0.0d) {
260 				long estEndTime = currentTime + (long) (totalWork * elapsed / getProgress());
261 
262 				Date endDate = new Date(estEndTime);
263 
264 				if (endDate.before(maxDate)) {
265 					return endDate;
266 				}
267 			}
268 		}
269 
270 		// If batch is done, get the end date
271 		if (this.endTime != null) {
272 			return new Date(this.endTime);
273 		}
274 
275 		// No end date ?
276 		return null;
277 	}
278 
279 	public long getItemCount() {
280 		return itemCount;
281 	}
282 
283 	public String getLastMessage() {
284 		return lastMessage;
285 	}
286 
287 	public Date getLastUpdate() {
288 		return new Date(getMainMonitor().lastUpdate);
289 	}
290 
291 	protected Logger getLogger() {
292 		return getMainMonitor().logger;
293 	}
294 
295 	/**
296 	 * Get the top parent.
297 	 *
298 	 * @return
299 	 */
300 	protected AbstractBatchProgressMonitor getMainMonitor() {
301 		AbstractBatchProgressMonitor main = this;
302 
303 		while (main.parent != null) {
304 			main = main.parent;
305 		}
306 
307 		return main;
308 	}
309 
310 	protected int getParentWork() {
311 		return parentWork;
312 	}
313 
314 	/*
315 	 * (non-Javadoc)
316 	 *
317 	 * @see net.sf.appstatus.core.batch.IBatchProgressMonitorExt#getProgress()
318 	 */
319 	public float getProgress() {
320 
321 		if (totalWork == UNKNOW) {
322 			return UNKNOW;
323 		}
324 
325 		float result = worked;
326 
327 		for (AbstractBatchProgressMonitor child : currentChildren) {
328 
329 			if (child.getTotalWork() != UNKNOW && !child.isDone()) {
330 				float childProgress = child.getParentWork() * child.getProgress() / child.getTotalWork();
331 				result = result + childProgress;
332 			}
333 
334 		}
335 
336 		return result;
337 	}
338 
339 	/**
340 	 * Get the list of rejected items.
341 	 *
342 	 * @return empty list or list of rejected items.
343 	 */
344 	public List<String> getRejectedItems() {
345 		return rejectedItems;
346 	}
347 
348 	protected Date getStartDate() {
349 		return new Date(startTime);
350 	}
351 
352 	public String getTaskDescription() {
353 		return taskDescription;
354 	}
355 
356 	public String getTaskGroup() {
357 		return taskGroup;
358 	};
359 
360 	public String getTaskName() {
361 		return taskName;
362 	}
363 
364 	/**
365 	 * {@inheritDoc}
366 	 */
367 	public int getTotalWork() {
368 		return this.totalWork;
369 	}
370 
371 	public long getWritingDelay() {
372 		return writingDelay;
373 	}
374 
375 	public boolean isCancelRequested() {
376 		return false;
377 	}
378 
379 	public boolean isDone() {
380 		return done;
381 	}
382 
383 	/**
384 	 * Check if the message is loggable.
385 	 *
386 	 * @param lastWriteTimestamp
387 	 *            last write timestamp
388 	 * @return true if the diffrence between the last write time stamp and the
389 	 *         current is greater than the delay.
390 	 */
391 	private boolean isLoggable(long lastWriteTimestamp) {
392 		if (System.currentTimeMillis() - lastWriteTimestamp > writingDelay) {
393 			return true;
394 		}
395 		return false;
396 	}
397 
398 	public boolean isSuccess() {
399 		return success;
400 	}
401 
402 	/**
403 	 * {@inheritDoc}
404 	 */
405 	public void message(String message) {
406 		String actionName = name;
407 		if (name == null) {
408 			actionName = batch.getName();
409 		}
410 
411 		getLogger().info("[{}] {}: {} ## {}", getBatch().getGroup(), getBatch().getName(), actionName, message);
412 		lastMessage = message;
413 		getMainMonitor().lastMessage = message;
414 		touch();
415 	}
416 
417 	protected abstract IBatchProgressMonitor newInstance(int work);
418 
419 	protected void onBatchEnd() {
420 
421 	}
422 
423 	/**
424 	 * {@inheritDoc}
425 	 */
426 	public void reject(String itemId, String reason) {
427 		reject(itemId, reason, null);
428 	}
429 
430 	/**
431 	 * {@inheritDoc}
432 	 */
433 	public void reject(String itemId, String reason, Throwable e) {
434 		getMainMonitor().rejectedItems.add(itemId);
435 		getLogger().warn("[{}] {}: {} rejected {} ({})", getBatch().getGroup(), getBatch().getName(), name, itemId,
436 				reason, e);
437 
438 		touch();
439 	}
440 
441 	/**
442 	 * {@inheritDoc}
443 	 */
444 	public void reject(String[] itemIds, String reason) {
445 		reject(itemIds, reason, null);
446 	}
447 
448 	/**
449 	 * {@inheritDoc}
450 	 */
451 	public void reject(String[] itemIds, String reason, Throwable e) {
452 		if (itemIds != null) {
453 			for (String id : itemIds) {
454 				reject(id, reason, e);
455 			}
456 		}
457 	}
458 
459 	/**
460 	 * {@inheritDoc}
461 	 */
462 	public void setCurrentItem(Object item) {
463 		if (currentItem != item) {
464 			getMainMonitor().itemCount++;
465 		}
466 
467 		currentItem = item;
468 
469 		if (isLoggable(lastWriteTimestamp)) {
470 			lastWriteTimestamp = System.currentTimeMillis();
471 			String actionName = name;
472 			if (actionName == null) {
473 				actionName = batch.getName();
474 			}
475 			getLogger().info("[{}] {}: {} working on {} (#{})",
476 					new Object[] { getBatch().getGroup(), getBatch().getName(), actionName, item, itemCount });
477 		}
478 		touch();
479 
480 	}
481 
482 	public void setLogger(Logger loggerParam) {
483 		getMainMonitor().logger = loggerParam;
484 	}
485 
486 	/**
487 	 * {@inheritDoc}
488 	 */
489 	public void setTotalWork(int totalWork) {
490 		if (totalWork < 0) {
491 			throw new IllegalArgumentException("totalWork cannot be negative : " + totalWork);
492 		}
493 
494 		this.totalWork = totalWork;
495 	}
496 
497 	/**
498 	 * Set the writing delay.
499 	 *
500 	 * @param writingDelay
501 	 *            writing delay
502 	 */
503 	public void setWritingDelay(long writingDelay) {
504 		this.writingDelay = writingDelay;
505 	}
506 
507 	/**
508 	 * Updated 'lastUpdate' value.
509 	 */
510 	protected void touch() {
511 		getMainMonitor().lastUpdate = System.currentTimeMillis();
512 	}
513 
514 	/**
515 	 * {@inheritDoc}
516 	 */
517 	public void worked(int work) {
518 
519 		if (work < 0) {
520 			throw new IllegalArgumentException("work cannot be negative : " + work);
521 		}
522 		worked = worked + work;
523 
524 		// Ensure work can not exceed totalWork.
525 		if (totalWork != UNKNOW) {
526 			if (worked > totalWork) {
527 				worked = totalWork;
528 			}
529 		}
530 
531 		// Log
532 		if (isLoggable(lastWriteTimestamp)) {
533 			float cp = getMainMonitor().getProgress() * 100f / getMainMonitor().getTotalWork();
534 
535 			lastWriteTimestamp = System.currentTimeMillis();
536 			getLogger().info("[{}] {}: progress {}%", new Object[] { getBatch().getGroup(), getBatch().getName(), cp });
537 
538 			if (cp > 100) {
539 				throw new RuntimeException();
540 			}
541 		}
542 
543 		// Refresh lastUpdate
544 		touch();
545 	}
546 
547 }