Clover Coverage Report - itracker
Coverage timestamp: Tue May 1 2012 16:42:12 CEST
117   345   55   11.7
62   217   0.47   10
10     5.5  
1    
 
 
  WorkflowUtilities       Line # 45 117 55 0% 0.0
 
No Tests
 
1    /*
2    * This software was designed and created by Jason Carroll.
3    * Copyright (c) 2002, 2003, 2004 Jason Carroll.
4    * The author can be reached at jcarroll@cowsultants.com
5    * ITracker website: http://www.cowsultants.com
6    * ITracker forums: http://www.cowsultants.com/phpBB/index.php
7    *
8    * This program is free software; you can redistribute it and/or modify
9    * it only under the terms of the GNU General Public License as published by
10    * the Free Software Foundation; either version 2 of the License, or
11    * (at your option) any later version.
12    *
13    * This program is distributed in the hope that it will be useful,
14    * but WITHOUT ANY WARRANTY; without even the implied warranty of
15    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16    * GNU General Public License for more details.
17    */
18   
19    package org.itracker.services.util;
20   
21    import java.lang.reflect.Field;
22    import java.util.ArrayList;
23    import java.util.Collection;
24    import java.util.HashMap;
25    import java.util.LinkedList;
26    import java.util.List;
27    import java.util.Locale;
28    import java.util.Map;
29   
30    import org.apache.log4j.Logger;
31    import org.apache.struts.action.ActionErrors;
32    import org.apache.struts.action.ActionMessage;
33    import org.apache.struts.action.ActionMessages;
34    import org.apache.struts.validator.ValidatorForm;
35    import org.itracker.core.resources.ITrackerResources;
36    import org.itracker.model.NameValuePair;
37    import org.itracker.model.ProjectScript;
38    import org.itracker.services.exceptions.WorkflowException;
39   
40    import bsh.Interpreter;
41   
42    /**
43    * Contains utilities used when displaying and processing workflow and field events
44    */
 
45    public class WorkflowUtilities {
46   
47    /** Fires for each field when building the form. Mainly used to build dynamic list options. */
48    public static final int EVENT_FIELD_ONPOPULATE = 1;
49    /** NOT CURRENTLY IMPLEMENTED. Use the onPopulate event instead. In the future, this event may be implemented to allow for list sorting after list value population. */
50    public static final int EVENT_FIELD_ONSORT = 2;
51    /** Fires to set the current value of a form field. This will overwrite any data in the form field pulled from the database. */
52    public static final int EVENT_FIELD_ONSETDEFAULT = 3;
53    /** Fires on validation of the form field. */
54    public static final int EVENT_FIELD_ONVALIDATE = 4;
55    /** Fires after validation, but before the data is committed to the database. */
56    public static final int EVENT_FIELD_ONPRESUBMIT = 5;
57    /** Fires after all data is submitted to the db for all fields. Performed right before the response is sent. */
58    public static final int EVENT_FIELD_ONPOSTSUBMIT = 6;
59   
60    private static final Logger logger = Logger.getLogger(WorkflowUtilities.class);
61   
 
62  0 toggle public WorkflowUtilities() {
63    }
64   
65    /**
66    * Returns a title of workflow event, according to selected locale.
67    * @param value is an identifier of incoming event.
68    * @param locale is a selected locale.
69    * @return a name of event or something like "MISSING KEY: <resourceBundleKey>".
70    */
 
71  0 toggle public static String getEventName(int value, Locale locale) {
72  0 final String eventName = getEventName(Integer.toString(value), locale);
73    // assert null != eventName : "event name should never be null.";
74  0 return eventName;
75    }
76   
 
77  0 toggle public static String getEventName(String value, Locale locale) {
78  0 return ITrackerResources.getString(ITrackerResources.KEY_BASE_WORKFLOW_EVENT + value, locale);
79    }
80   
81    /**
82    * Returns an array of pairs (eventName, eventId), where eventName
83    * is an event title, according to selected locale.
84    * @param locale is a selected locale.
85    * @return an array of pairs (eventName, eventId), which is never null.
86    */
 
87  0 toggle public static NameValuePair[] getEvents(Locale locale) {
88  0 NameValuePair[] eventNames = new NameValuePair[6];
89  0 eventNames[0] = new NameValuePair(getEventName(EVENT_FIELD_ONPOPULATE, locale), Integer.toString(EVENT_FIELD_ONPOPULATE));
90  0 eventNames[1] = new NameValuePair(getEventName(EVENT_FIELD_ONSORT, locale), Integer.toString(EVENT_FIELD_ONSORT));
91  0 eventNames[2] = new NameValuePair(getEventName(EVENT_FIELD_ONSETDEFAULT, locale), Integer.toString(EVENT_FIELD_ONSETDEFAULT));
92  0 eventNames[3] = new NameValuePair(getEventName(EVENT_FIELD_ONVALIDATE, locale), Integer.toString(EVENT_FIELD_ONVALIDATE));
93  0 eventNames[4] = new NameValuePair(getEventName(EVENT_FIELD_ONPRESUBMIT, locale), Integer.toString(EVENT_FIELD_ONPRESUBMIT));
94  0 eventNames[5] = new NameValuePair(getEventName(EVENT_FIELD_ONPOSTSUBMIT, locale), Integer.toString(EVENT_FIELD_ONPOSTSUBMIT));
95  0 return eventNames;
96    }
97   
98    /**
99    * Select a list of NameValuePair objects from provided map object according
100    * to fieldId selector. Typesafe version of #getListOptions(Map, Integer)
101    * @param listOptions is a map, with stored NameValuePair objects lists
102    * associated with specific integer id.
103    * @param fieldId is a selector from map.
104    * @return a list of objects, which may be empty, but never null.
105    */
 
106  0 toggle public static List<NameValuePair> getListOptions(Map<Integer, List<NameValuePair>> listOptions, int fieldId) {
107  0 return getListOptions(listOptions, Integer.valueOf(fieldId));
108    }
109   
110    /**
111    * Select a list of NameValuePair objects from provided map object according
112    * to fieldId selector.
113    * @param listOptions is a map, with stored NameValuePair objects lists
114    * associated with specific integer id.
115    * @param fieldId is a selector from map.
116    * @return a list of objects, which may be empty, but never null.
117    */
 
118  0 toggle @SuppressWarnings("unchecked")
119    public static List<NameValuePair> getListOptions(Map listOptions, Integer fieldId) {
120  0 List<NameValuePair> options = new ArrayList<NameValuePair>();
121   
122  0 if(listOptions != null && listOptions.size() != 0 && fieldId != null) {
123  0 Object mapOptions = listOptions.get(fieldId);
124  0 if(mapOptions != null) {
125  0 options = (List<NameValuePair>) mapOptions;
126    }
127    }
128   
129  0 return options;
130    }
131   
132    /**
133    * The most general way to run scripts. All matching of event and fields
134    * are embedded within. As a result, currentValues parameter will
135    * contain updated values and form will contain new default values
136    * if appropriate.
137    * @param projectScriptModels is a list of scripts.
138    * @param event is an event type.
139    * @param currentValues is a map of current values to fields.
140    * @param currentErrors is a container for errors.
141    * @param form contains default values of fields.
142    * @throws org.itracker.services.exceptions.WorkflowException
143    */
 
144  0 toggle public static void processFieldScripts(List<ProjectScript> projectScriptModels, int event, Map<Integer, List<NameValuePair>> currentValues, ActionMessages currentErrors, ValidatorForm form) throws WorkflowException {
145  0 if(projectScriptModels == null || projectScriptModels.size() == 0) {
146  0 return;
147    }
148  0 logger.debug("Processing " + projectScriptModels.size() + " field scripts for project " + projectScriptModels.get(0).getProject().getId());
149   
150  0 List<ProjectScript> scriptsToRun = new ArrayList<ProjectScript>();
151  0 for(int i = 0; i < projectScriptModels.size(); i++) {
152  0 if(projectScriptModels.get(i).getScript().getEvent() == event) {
153  0 int insertIndex = 0;
154  0 for(insertIndex = 0; insertIndex < scriptsToRun.size(); insertIndex++) {
155  0 if(projectScriptModels.get(i).getPriority() < ((ProjectScript) scriptsToRun.get(insertIndex)).getPriority()) {
156  0 break;
157    }
158    }
159  0 scriptsToRun.add(insertIndex,projectScriptModels.get(i));
160    }
161    }
162  0 logger.debug(scriptsToRun.size() + " eligible scripts found for event " + event);
163   
164  0 if (currentValues == null) {
165  0 currentValues = new HashMap<Integer, List<NameValuePair>>();
166    }
167   
168  0 for (int i = 0; i < scriptsToRun.size(); i++) {
169  0 ProjectScript currentScript = (ProjectScript) scriptsToRun.get(i);
170  0 try {
171  0 logger.debug("Running script " + currentScript.getScript().getId() + " with priority " + currentScript.getPriority());
172  0 List<NameValuePair> currentValue = currentValues.get(currentScript.getFieldId());
173   
174  0 logger.debug("Before script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId()) + " (" + currentScript.getFieldId() + ") is " + (currentValue == null ? "NULL" : "'" + currentValue.toString() + "' (" + currentValue.getClass().getName() + "'"));
175  0 currentValue = processFieldScript(currentScript, event, currentScript.getFieldId(), currentValue, currentErrors, form);
176  0 logger.debug("After script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId()) + " (" + currentScript.getFieldId() + ") is " + (currentValue == null ? "NULL" : "'" + currentValue.toString() + "' (" + currentValue.getClass().getName() + "'"));
177   
178  0 currentValues.put(currentScript.getFieldId(), currentValue);
179    } catch(WorkflowException we) {
180  0 logger.error("Error processing script " + currentScript.getScript().getId() + ": " + we.getMessage());
181    }
182    }
183    }
184   
185    /**
186    * Run appropriate script, selecting it from provided list by matching
187    * event and field.
188    * @param projectScripts is a list of provided scripts.
189    * @param event is an event type.
190    * @param fieldId is a field, associated with event.
191    * @param currentValue is a set of current values.
192    * @param currentErrors is a container for errors.
193    * @param form is a form, holder of default values.
194    * @return new set of values.
195    * @throws org.itracker.services.exceptions.WorkflowException
196    */
 
197  0 toggle public static List<NameValuePair> processFieldScripts(List<ProjectScript> projectScripts, int event, Integer fieldId, List<NameValuePair> currentValue, ActionErrors currentErrors, ValidatorForm form) throws WorkflowException {
198  0 if(projectScripts == null || projectScripts.size() == 0 || fieldId == null) {
199  0 return null;
200    }
201  0 logger.debug("Processing " + projectScripts.size() + " field scripts for project " + projectScripts.get(0).getProject().getId());
202   
203  0 List<ProjectScript> scriptsToRun = new LinkedList<ProjectScript>();
204  0 for(int i = 0; i < projectScripts.size(); i++) {
205  0 if(projectScripts.get(i).getScript().getEvent() == event && fieldId.equals(projectScripts.get(i).getFieldId())) {
206  0 int insertIndex = 0;
207  0 for(insertIndex = 0; insertIndex < scriptsToRun.size(); insertIndex++) {
208  0 if(projectScripts.get(i).getPriority() < ((ProjectScript) scriptsToRun.get(insertIndex)).getPriority()) {
209  0 break;
210    }
211    }
212  0 scriptsToRun.add(insertIndex,projectScripts.get(i));
213    }
214    }
215  0 logger.debug(scriptsToRun.size() + " eligible scripts found for event " + event + " on field " + fieldId);
216   
217  0 for(int i = 0; i < scriptsToRun.size(); i++) {
218  0 ProjectScript currentScript = (ProjectScript) scriptsToRun.get(i);
219  0 try {
220  0 logger.debug("Running script " + currentScript.getScript().getId() + " with priority " + currentScript.getPriority());
221  0 currentValue = processFieldScript(currentScript, event, fieldId, currentValue, currentErrors, form);
222    } catch(WorkflowException we) {
223  0 logger.error("Error processing script " + currentScript.getScript().getId() + ": " + we.getMessage());
224    }
225    }
226   
227  0 return currentValue;
228    }
229   
230    /**
231    * Run provided BEANSHELL script against form instance, taking into account
232    * incoming event type, field raised an event and current values.
233    * As a result, a set of new current values is returned and if
234    * appropriate, default values are changed in form.
235    * TODO: should issue, project, user, services be available too?
236    *
237    * @param projectScript is a script to run.
238    * @param event is an event type.
239    * @param fieldId is a field id associated with event.
240    * @param currentValues is a set of current values.
241    * @param currentErrors is a container for occured errors.
242    * @param form is a form instance, holding values.
243    * @return new current values.
244    * @throws org.itracker.services.exceptions.WorkflowException
245    */
 
246  0 toggle public static List<NameValuePair> processFieldScript(ProjectScript projectScript, int event, Integer fieldId, List<NameValuePair> currentValues, ActionMessages currentErrors, ValidatorForm form) throws WorkflowException {
247  0 if (projectScript == null) {
248  0 throw new WorkflowException("ProjectScript was null.", WorkflowException.INVALID_ARGS);
249    }
250  0 if (currentErrors == null) {
251  0 throw new WorkflowException("Errors was null.", WorkflowException.INVALID_ARGS);
252    }
253   
254  0 try {
255  0 Interpreter bshInterpreter = new Interpreter();
256  0 bshInterpreter.set("event", event);
257  0 bshInterpreter.set("fieldId", fieldId);
258    // TODO: remove currentValue from bshInterpreter, it's a collection of current values.
259  0 bshInterpreter.set("currentValue", currentValues);
260  0 bshInterpreter.set("currentValues", currentValues);
261  0 bshInterpreter.set("currentErrors", currentErrors);
262  0 bshInterpreter.set("currentForm", form);
263  0 bshInterpreter.eval(projectScript.getScript().getScript());
264    // TODO: is this necessary? It should stay the same list-object..
265    // currentValues = (List<NameValuePair>)bshInterpreter.get("currentValue");
266  0 if (logger.isDebugEnabled()) {
267  0 logger.debug("processFieldScript: Script returned current value of '" + currentValues + "' (" + (currentValues != null ? currentValues.getClass().getName() : "NULL") + ")");
268    }
269  0 if (event == EVENT_FIELD_ONSETDEFAULT && form != null && currentValues != null) {
270  0 if (logger.isDebugEnabled()) {
271  0 logger.debug("processFieldScript: Setting current form field value for field " + IssueUtilities.getFieldName(projectScript.getFieldId()) + " to '" + currentValues + "'");
272    }
273  0 setFormProperty(form, projectScript.getFieldId(), currentValues);
274    }
275    } catch(Exception e) {
276  0 logger.error("processFieldScript: Error processing field script.", e);
277    // TODO: error-handling..?
278  0 currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
279    new ActionMessage("itracker.web.error.system.message",
280    new Object[]{ITrackerResources.getString("itracker.web.attr.script"), // Script
281    e.getMessage()
282    }));
283    }
284  0 if (logger.isDebugEnabled()) {
285  0 logger.debug("processFieldScript: returning " + currentValues + ", errors: " + currentErrors);
286    }
287  0 return currentValues;
288    }
289   
 
290  0 toggle @SuppressWarnings("unchecked")
291    private static void setFormProperty(ValidatorForm form, Integer fieldId, Object currentValue) {
292  0 String fieldName = IssueUtilities.getFieldName(fieldId);
293  0 int fieldType = IssueUtilities.getFieldType(fieldId);
294  0 if(fieldType == IssueUtilities.FIELD_TYPE_SINGLE) {
295  0 try {
296  0 Field formField = form.getClass().getField( fieldName );
297  0 if ( formField != null ) {
298  0 formField.set( form , currentValue );
299    } else {
300  0 throw new IllegalArgumentException( "no field with name "
301    + fieldName + " found in form " + form );
302    }
303    } catch ( NoSuchFieldException e ) {
304  0 e.printStackTrace();
305    } catch ( IllegalAccessException e ) {
306  0 e.printStackTrace();
307    }
308  0 } else if(fieldType == IssueUtilities.FIELD_TYPE_INDEXED) {
309  0 try {
310  0 Object indexedField = null;
311  0 Field formField = form.getClass().getField(fieldName);
312  0 indexedField = formField.get( form );
313  0 if ( indexedField instanceof List ) {
314  0 ((List)indexedField).set( 0 , currentValue );
315  0 } else if ( indexedField instanceof Collection ) {
316  0 ((Collection)indexedField).add( currentValue );
317    } else {
318  0 throw new IllegalArgumentException( "field with name "
319    + fieldName + " found in form " + form + " is of unknown type" );
320    }
321    } catch ( NoSuchFieldException e ) {
322  0 e.printStackTrace();
323    } catch ( IllegalAccessException e ) {
324  0 e.printStackTrace();
325    }
326  0 } else if(fieldType == IssueUtilities.FIELD_TYPE_MAP) {
327  0 try {
328  0 Object indexedField = null;
329  0 Field formField = form.getClass().getField( fieldName );
330  0 indexedField = formField.get( form );
331  0 if ( indexedField instanceof Map ) {
332  0 ((Map)indexedField).put( fieldId.toString(), currentValue );
333    } else {
334  0 throw new IllegalArgumentException( "field with name "
335    + fieldName + " found in form " + form + " is of unknown type" );
336    }
337    } catch ( NoSuchFieldException e ) {
338  0 e.printStackTrace();
339    } catch ( IllegalAccessException e ) {
340  0 e.printStackTrace();
341    }
342    }
343    }
344   
345    }