View Javadoc

1   /*******************************************************************************
2    *  Imixs Workflow 
3    *  Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,  
4    *  http://www.imixs.com
5    *  
6    *  This program is free software; you can redistribute it and/or 
7    *  modify it under the terms of the GNU General Public License 
8    *  as published by the Free Software Foundation; either version 2 
9    *  of the License, or (at your option) any later version.
10   *  
11   *  This program is distributed in the hope that it will be useful, 
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
14   *  General Public License for more details.
15   *  
16   *  You can receive a copy of the GNU General Public
17   *  License at http://www.gnu.org/licenses/gpl.html
18   *  
19   *  Project: 
20   *  	http://www.imixs.org
21   *  	http://java.net/projects/imixs-workflow
22   *  
23   *  Contributors:  
24   *  	Imixs Software Solutions GmbH - initial API and implementation
25   *  	Ralph Soika - Software Developer
26   *******************************************************************************/
27  
28  package org.imixs.workflow;
29  
30  import java.text.DateFormat;
31  import java.util.Date;
32  import java.util.List;
33  import java.util.Vector;
34  import java.util.logging.Logger;
35  
36  import org.imixs.workflow.exceptions.ModelException;
37  import org.imixs.workflow.exceptions.PluginException;
38  import org.imixs.workflow.exceptions.ProcessingErrorException;
39  
40  /**
41   * The Workflowkernel is the core component of this Framework to control the
42   * processing of a workitem. A <code>Workflowmanager</code> loads an instance of
43   * a Workflowkernel and hand over a <code>Model</code> and register
44   * <code>Plugins</code> for processing one or many workitems.
45   * 
46   * @author Ralph Soika
47   * @version 1.1
48   * @see org.imixs.workflow.WorkflowManager
49   */
50  
51  public class WorkflowKernel {
52  
53  	/** Log-Levels **/
54  	public static final int LOG_LEVEL_SEVERE = 0;
55  	public static final int LOG_LEVEL_WARNING = 1;
56  	public static final int LOG_LEVEL_FINE = 2;
57  
58  	/** Plugin objects **/
59  	private Vector vectorPluginsClassNames = null;
60  	private Vector vectorPlugins = null;
61  	private WorkflowContext ctx = null;
62  	private ItemCollection documentContext = null;
63  	private ItemCollection documentActivity = null;
64  	// private ItemCollection documentProcess = null;
65  	private Vector vectorEdgeHistory = new Vector();
66  
67  	private static Logger logger = Logger.getLogger("org.imixs.workflow");
68  
69  	/**
70  	 * Constructor initialize the contextObject and plugin vectors
71  	 */
72  	public WorkflowKernel(WorkflowContext actx) {
73  		ctx = actx;
74  
75  		vectorPluginsClassNames = new Vector();
76  		vectorPlugins = new Vector();
77  	}
78  
79  	public void registerPlugin(String aClassName) {
80  		vectorPluginsClassNames.addElement(aClassName);
81  		if (ctx.getLogLevel() == LOG_LEVEL_FINE)
82  			logger.info("[WorkflowKernel] register Plugin: " + aClassName);
83  	}
84  
85  	/**
86  	 * Processes a workitem. The Workitem have at least provide the properties
87  	 * "$processid" and "$activityid"
88  	 * 
89  	 * @param document
90  	 * @throws ProcessingErrorException
91  	 */
92  	public void process(ItemCollection document)
93  			throws ProcessingErrorException {
94  
95  		vectorEdgeHistory = new Vector();
96  		documentContext = document;
97  
98  		// check document context
99  		if (document == null)
100 			throw new ProcessingErrorException(
101 					"[WorkflowKernel] processing error: document==null");
102 
103 		// check processID
104 		if (document.getItemValueInteger("$processid") <= 0)
105 			throw new ProcessingErrorException(
106 					"[WorkflowKernel] processing error: $processid undefined ("
107 							+ document.getItemValueInteger("$processid") + ")");
108 
109 		// check activityid
110 
111 		if (document.getItemValueInteger("$activityid") <= 0)
112 			throw new ProcessingErrorException(
113 					"[WorkflowKernel] processing error: $activityid undefined ("
114 							+ document.getItemValueInteger("$activityid") + ")");
115 
116 		// Check if $UniqueID is available
117 		if ("".equals(documentContext.getItemValueString("$UniqueID"))) {
118 			// generating a new one
119 			documentContext.replaceItemValue("$UniqueID", generateUniqueID());
120 		}
121 
122 		// log the general processing message
123 		String msg = "[WorkflowKernel] processing="
124 				+ documentContext.getItemValueString("$UniqueID")
125 				+ ", $modelversion="
126 				+ document.getItemValueString("$modelversion")
127 				+ ", $processid=" + document.getItemValueInteger("$processid")
128 				+ ", $activityid="
129 				+ document.getItemValueInteger("$activityid");
130 
131 		if (ctx.getLogLevel() == LOG_LEVEL_FINE)
132 			msg += ", Log-Level=FINE ";
133 		else if (ctx.getLogLevel() == LOG_LEVEL_WARNING)
134 			msg += ", Log-Level=WARNING ";
135 		else
136 			msg += ", Log-Level=SEVERE ";
137 
138 		logger.info(msg);
139 
140 		// Check if $WorkItemID is available
141 		if ("".equals(documentContext.getItemValueString("$WorkItemID"))) {
142 			documentContext.replaceItemValue("$WorkItemID", generateUniqueID());
143 		}
144 
145 		// now process all activites defined by the model
146 		while (hasMoreActivities())
147 			processActivity();
148 
149 	}
150 
151 	/**
152 	 * This method controls the Process-Chain The method returns true if a) a
153 	 * valid $activityid exists b) the attribute $activityidlist has more valid
154 	 * ActivityIDs
155 	 * 
156 	 **/
157 	private boolean hasMoreActivities() {
158 		int integerID;
159 
160 		// is $activityid provided?
161 		integerID = documentContext.getItemValueInteger("$activityid");
162 
163 		if ((integerID > 0))
164 			return true;
165 		else {
166 			// no - test for property $ActivityIDList
167 			List v = documentContext.getItemValue("$activityidlist");
168 
169 			// remove 0 values if contained!
170 			while (v.indexOf(Integer.valueOf(0)) > -1) {
171 				v.remove(v.indexOf(Integer.valueOf(0)));
172 			}
173 
174 			// test if an id is found....
175 			if (v.size() > 0) {
176 				// yes - load next ID from activityID List
177 				int iNextID = 0;
178 				Object oA = v.get(0);
179 				if (oA instanceof Integer)
180 					iNextID = ((Integer) oA).intValue();
181 				if (oA instanceof Double)
182 					iNextID = ((Double) oA).intValue();
183 
184 				if (iNextID > 0) {
185 					// load activity
186 					if (ctx.getLogLevel() == LOG_LEVEL_FINE)
187 						logger.info("[WorkflowKernel] loading next activityID from $activityidlist="
188 								+ iNextID);
189 					v.remove(0);
190 					// update document context
191 					documentContext.replaceItemValue("$activityid",
192 							Integer.valueOf(iNextID));
193 					documentContext.replaceItemValue("$activityidlist", v);
194 					return true;
195 				}
196 			}
197 			// no more Activities defined
198 			return false;
199 		}
200 
201 	}
202 
203 	/**
204 	 * This method creates a unique key which can be used as a primary key. The
205 	 * method is used by the 'checkWorkItemID()' method and the checkUniqueID()
206 	 * method.
207 	 * <p>
208 	 * The uniqueid consits of two parts containing a random unique char
209 	 * sequence
210 	 * 
211 	 * @return
212 	 */
213 	public static String generateUniqueID() {
214 		String sIDPart1 = Long.toHexString(System.currentTimeMillis());
215 		double d = Math.random() * 900000000;
216 		int i = new Double(d).intValue();
217 		String sIDPart2 = Integer.toHexString(i);
218 		String id = sIDPart1 + "-" + sIDPart2;
219 
220 		return id;
221 	}
222 
223 	/**
224 	 * This method process a activity instance by loading and running all
225 	 * plugins.
226 	 * 
227 	 * If an FollowUp Activity is defined (keyFollowUp="1" &
228 	 * numNextActivityID>0) it will be attached at the $ActiviyIDList.
229 	 * 
230 	 * @throws ProcessingErrorException
231 	 */
232 	private void processActivity() throws ProcessingErrorException {
233 
234 		loadActivity();
235 
236 		// run plugins - PluginExceptions will bubble up....
237 		loadPlugins();
238 		int iStatus = runPlugins();
239 		closePlugins(iStatus);
240 
241 		if (iStatus == Plugin.PLUGIN_ERROR) {
242 			throw new ProcessingErrorException(
243 					"[WorkflowKernel] Error in Plugin detected.");
244 		}
245 
246 		writeLog();
247 
248 		// put current edge in history
249 		vectorEdgeHistory.addElement(documentActivity
250 				.getItemValueInteger("numprocessid")
251 				+ "."
252 				+ documentActivity.getItemValueInteger("numactivityid"));
253 
254 		/*** get Next Task **/
255 		int iNewProcessID = documentActivity
256 				.getItemValueInteger("numnextprocessid");
257 		if (ctx.getLogLevel() == LOG_LEVEL_FINE)
258 			logger.info("[WorkflowKernel] next $processid=" + iNewProcessID
259 					+ "");
260 
261 		// NextProcessID will only be set if NextTask>0
262 		if (iNewProcessID > 0) {
263 			documentContext.replaceItemValue("$processid",
264 					Integer.valueOf(iNewProcessID));
265 		}
266 
267 		// clear ActivityID and create new workflowActivity Instance
268 		documentContext.replaceItemValue("$activityid", Integer.valueOf(0));
269 
270 		// FollowUp Activity ?
271 		String sFollowUp = documentActivity.getItemValueString("keyFollowUp");
272 		int iNextActivityID = documentActivity
273 				.getItemValueInteger("numNextActivityID");
274 		if ("1".equals(sFollowUp) && iNextActivityID > 0) {
275 			this.appendActivityID(iNextActivityID);
276 		}
277 
278 	}
279 
280 	/**
281 	 * This method adds a new ActivityID into the current activityList
282 	 * ($ActivityIDList) The activity list may not contain 0 values.
283 	 * 
284 	 */
285 	@SuppressWarnings("unchecked")
286 	private void appendActivityID(int aID) {
287 
288 		// check if activityidlist is available
289 		List v = documentContext.getItemValue("$ActivityIDList");
290 		if (v == null)
291 			v = new Vector();
292 		// clear list?
293 		if ((v.size() == 1) && ("".equals(v.get(0).toString())))
294 			v = new Vector();
295 
296 		v.add(Integer.valueOf(aID));
297 
298 		// remove 0 values if contained!
299 		while (v.indexOf(Integer.valueOf(0)) > -1) {
300 			v.remove(v.indexOf(Integer.valueOf(0)));
301 		}
302 
303 		documentContext.replaceItemValue("$ActivityIDList", v);
304 		if (ctx.getLogLevel() == LOG_LEVEL_FINE)
305 			logger.info("[WorkflowKernel]  append new Activity ID=" + aID);
306 
307 	}
308 
309 	/**
310 	 * This method is responsible for the internal workflow log. The attribute
311 	 * txtworkflowactivitylog logs the work-flow from one process to another.
312 	 * 
313 	 */
314 	private void writeLog() {
315 
316 		String sLog = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
317 				DateFormat.MEDIUM).format(new Date());
318 
319 		// 22.9.2004 13:50:41|1000.90:=1000
320 		sLog = sLog + "|"
321 				+ documentActivity.getItemValueInteger("numprocessid") + "."
322 				+ documentActivity.getItemValueInteger("numactivityid") + ":="
323 				+ documentActivity.getItemValueInteger("numnextprocessid");
324 
325 		List vLog =  documentContext
326 				.getItemValue("txtworkflowactivitylog");
327 		if (vLog == null)
328 			vLog = new Vector();
329 
330 		vLog.add(sLog);
331 		documentContext.replaceItemValue("txtworkflowactivitylog", vLog);
332 		documentContext
333 				.replaceItemValue("numlastactivityid", Integer
334 						.valueOf(documentActivity
335 								.getItemValueInteger("numactivityid")));
336 
337 	}
338 
339 	/**
340 	 * This Method loads the current Activity Entity from the provides Model
341 	 * 
342 	 * The method also verifies the activity to be valid
343 	 * 
344 	 * @throws ProcessingErrorException
345 	 */
346 	private void loadActivity() throws ProcessingErrorException {
347 
348 		int aProcessID = documentContext.getItemValueInteger("$processid");
349 		int aActivityID = documentContext.getItemValueInteger("$activityid");
350 
351 		// determine model version
352 		String sModelVersion = documentContext
353 				.getItemValueString("$modelversion");
354 
355 		// depending of the provided Workflow Context call different methods
356 		if (ctx instanceof ExtendedWorkflowContext)
357 			documentActivity = ((ExtendedWorkflowContext) ctx)
358 					.getExtendedModel().getActivityEntityByVersion(aProcessID,
359 							aActivityID, sModelVersion);
360 		else
361 			documentActivity = ctx.getModel().getActivityEntity(aProcessID,
362 					aActivityID);
363 
364 		if (documentActivity == null)
365 			throw new ProcessingErrorException("[WorkflowKernel] model entry "
366 					+ aProcessID + "." + aActivityID + " not found");
367 
368 		if (ctx.getLogLevel() == LOG_LEVEL_FINE)
369 			logger.info("[WorkflowKernel] loadActivity: " + aProcessID + "."
370 					+ aActivityID + " successfully");
371 
372 		// Check for loop in edge history
373 		if (vectorEdgeHistory != null) {
374 			if (vectorEdgeHistory.indexOf((aProcessID + "." + aActivityID)) != -1)
375 				throw new ModelException("[WorkflowKernel] loop detected "
376 						+ aProcessID + "." + aActivityID + ","
377 						+ vectorEdgeHistory.toString());
378 		}
379 
380 	}
381 
382 	/**
383 	 * loads all plugins
384 	 * 
385 	 * @throws ProcessingErrorException
386 	 */
387 	private void loadPlugins() throws PluginException {
388 
389 		// Warning if no plugins registerd!
390 		if (vectorPluginsClassNames.size() == 0
391 				&& ctx.getLogLevel() >= LOG_LEVEL_FINE)
392 			logger.info("[WorkflowKernel] Warning loadPlugins: no plugins defined!");
393 
394 		for (int i = 0; i < vectorPluginsClassNames.size(); i++) {
395 			/**** Pluglin laden ***/
396 			String sPluginClass = vectorPluginsClassNames.elementAt(i)
397 					.toString();
398 
399 			if ((sPluginClass != null) && (!"".equals(sPluginClass))) {
400 				if (ctx.getLogLevel() == LOG_LEVEL_FINE) {
401 					logger.info("[WorkflowKernel] loading Plugin "
402 							+ sPluginClass + "...");
403 				}
404 				Class clazz = null;
405 				try {
406 					clazz = Class.forName(sPluginClass);
407 					Plugin plugin = (Plugin) clazz.newInstance();
408 					plugin.init(ctx);
409 					vectorPlugins.add(plugin);
410 
411 				} catch (ClassNotFoundException e) {
412 					throw new PluginException(
413 							"[WorkflowKernel] Could not create Plugin: "
414 									+ sPluginClass + " Reason: " + e.toString(),
415 							e);
416 				} catch (InstantiationException e) {
417 					throw new PluginException(
418 							"[WorkflowKernel] Could not create Plugin: "
419 									+ sPluginClass + " Reason: " + e.toString(),
420 							e);
421 				} catch (IllegalAccessException e) {
422 					throw new PluginException(
423 							"[WorkflowKernel] Could not create Plugin: "
424 									+ sPluginClass + " Reason: " + e.toString(),
425 							e);
426 				}
427 
428 			}
429 
430 		}
431 
432 	}
433 
434 	/**
435 	 * This method runs all registered plugins until the run method of a plugin
436 	 * breaks with an error In this case the method stops The attribute
437 	 * txtWorkflowPluginLog will store the list of process plugins with a
438 	 * timestamp
439 	 * 
440 	 * @throws ProcessingErrorException
441 	 */
442 	private int runPlugins() throws PluginException {
443 		int iStatus;
444 		String sPluginName = null;
445 		List<String> localPluginLog = new Vector<String>();
446 		List<String> vectorPluginLog;
447 
448 		try {
449 
450 			vectorPluginLog = documentContext
451 					.getItemValue("txtWorkflowPluginLog");
452 			for (int i = 0; i < vectorPlugins.size(); i++) {
453 				Plugin plugin = (Plugin) vectorPlugins.elementAt(i);
454 				sPluginName = plugin.getClass().getName();
455 				if (ctx.getLogLevel() == LOG_LEVEL_FINE)
456 					logger.info("[WorkflowKernel] running Plugin: "
457 							+ sPluginName + "...");
458 
459 				try {
460 					iStatus = plugin.run(documentContext, documentActivity);
461 				} catch (PluginException e) {
462 					throw new PluginException(
463 							"[WorkflowKernel] Error running Plugin: "
464 									+ sPluginName + " Reason: " + e.toString(),
465 							e);
466 				}
467 				// write PluginLog
468 				String sLog = DateFormat.getDateTimeInstance(DateFormat.LONG,
469 						DateFormat.MEDIUM).format(new Date());
470 
471 				sLog = sLog + " " + plugin.getClass().getName() + "=" + iStatus;
472 
473 				localPluginLog.add(sLog);
474 				vectorPluginLog.add(sLog);
475 
476 				if (iStatus == Plugin.PLUGIN_ERROR) {
477 					// write PluginLog into workitem
478 					documentContext.replaceItemValue("txtWorkflowPluginLog",
479 							vectorPluginLog);
480 
481 					// log error....
482 					logger.severe("[WorkflowKernel] Error processing Plugin: "
483 							+ sPluginName);
484 					logger.severe("[WorkflowKernel] Plugin-Log: ");
485 					for (String sLogEntry : localPluginLog)
486 						logger.severe("[WorkflowKernel]   " + sLogEntry);
487 
488 					return Plugin.PLUGIN_ERROR;
489 				}
490 			}
491 			// write PluginLog into workitem
492 			documentContext.replaceItemValue("txtWorkflowPluginLog",
493 					vectorPluginLog);
494 			return Plugin.PLUGIN_OK;
495 
496 		} catch (PluginException e) {
497 			// log plugin stack!....
498 			logger.severe("[WorkflowKernel] Plugin-Stack: ");
499 			for (String sLogEntry : localPluginLog)
500 				logger.severe("[WorkflowKernel]   " + sLogEntry);
501 			// re throw the PluginException !
502 			throw e;
503 		}
504 
505 	}
506 
507 	private void closePlugins(int astatus) throws PluginException {
508 
509 		for (int i = 0; i < vectorPlugins.size(); i++) {
510 			Plugin plugin = (Plugin) vectorPlugins.elementAt(i);
511 			if (ctx.getLogLevel() == LOG_LEVEL_FINE)
512 				logger.info("[WorkflowKernel] closing Plugin: "
513 						+ plugin.getClass().getName() + "...");
514 			try {
515 				plugin.close(astatus);
516 			} catch (PluginException e) {
517 				throw new PluginException(
518 						"[WorkflowKernel] Could not close Plugin: "
519 								+ plugin.getClass().getName() + " Reason: "
520 								+ e.toString(), e);
521 			}
522 		}
523 		// reset Plugins
524 		vectorPlugins = new Vector();
525 
526 	}
527 
528 }