View Javadoc
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.web.forms;
20  
21  import bsh.EvalError;
22  import bsh.Interpreter;
23  import groovy.lang.Binding;
24  import groovy.lang.GroovyShell;
25  import groovy.lang.Script;
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.struts.action.ActionErrors;
28  import org.apache.struts.action.ActionMapping;
29  import org.apache.struts.action.ActionMessage;
30  import org.apache.struts.action.ActionMessages;
31  import org.apache.struts.upload.FormFile;
32  import org.itracker.IssueException;
33  import org.itracker.WorkflowException;
34  import org.itracker.core.resources.ITrackerResources;
35  import org.itracker.model.*;
36  import org.itracker.model.util.*;
37  import org.itracker.services.*;
38  import org.itracker.web.util.*;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  import javax.servlet.ServletException;
43  import javax.servlet.http.HttpServletRequest;
44  import javax.servlet.http.HttpSession;
45  import java.io.IOException;
46  import java.util.*;
47  
48  /**
49   * This form is by the struts actions to pass issue data.
50   */
51  public class IssueForm extends ITrackerForm {
52  
53      /**
54       *
55       */
56      private static final long serialVersionUID = 1L;
57  
58      private static final Logger log = LoggerFactory.getLogger(IssueForm.class);
59  
60      private Integer id = null;
61      private String caller = null;
62      private Integer projectId = null;
63      private Integer creatorId = null;
64      private Integer ownerId = null;
65      private String description = null;
66      private Integer severity = null;
67      private Integer status = null;
68      private Integer prevStatus = null;
69      private String resolution = null;
70      private Integer targetVersion = null;
71      private Integer[] components = new Integer[0];
72      private Integer[] versions = new Integer[0];
73      private String attachmentDescription = null;
74      transient private FormFile attachment = null;
75      private String history = null;
76      // lets try to put Integer,String here:
77      private HashMap<String, String> customFields = new HashMap<>();
78      private IssueRelation.Type relationType = null;
79      private Integer relatedIssueId = null;
80  
81      /**
82       * The most general way to run scripts. All matching of event and fields
83       * are embedded within. As a result, optionValues parameter will
84       * contain updated values and form will contain new default values
85       * if appropriate.
86       *
87       * @param projectScriptModels is a list of scripts.
88       * @param event               is an event type.
89       * @param currentValues       values mapped to field-ids
90       * @param optionValues        is a map of current values to fields by field-Id.
91       * @param currentErrors       is a container for errors.
92       */
93      public void processFieldScripts(List<ProjectScript> projectScriptModels, int event, Map<Integer, String> currentValues, Map<Integer, List<NameValuePair>> optionValues, ActionMessages currentErrors) throws WorkflowException {
94          if ((!isWorkflowScriptsAllowed()) || projectScriptModels == null || projectScriptModels.size() == 0)
95              return;
96          log.debug("Processing " + projectScriptModels.size() + " field scripts for project " + projectScriptModels.get(0).getProject().getId());
97  
98          List<ProjectScript> scriptsToRun = new ArrayList<>(projectScriptModels.size());
99          for (ProjectScript model : projectScriptModels) {
100             if (model.getScript().getEvent() == event) {
101                 scriptsToRun.add(model);
102             }
103         }
104         // order the scripts by priority
105         Collections.sort(scriptsToRun, ProjectScript.PRIORITY_COMPARATOR);
106 
107         if (log.isDebugEnabled()) {
108             log.debug(scriptsToRun.size() + " eligible scripts found for event " + event);
109         }
110 
111         String currentValue;
112         for (ProjectScript currentScript : scriptsToRun) {
113             try {
114                 currentValue = currentValues.get((currentScript.getFieldType() == Configuration.Type.customfield?
115                         currentScript.getFieldId():currentScript.getFieldType().getLegacyCode()));
116                 log.debug("Running script " + currentScript.getScript().getId()
117                         + " with priority " + currentScript.getPriority());
118 
119                 log.debug("Before script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId())
120                         + " (" + currentScript.getFieldId() + ") is "
121                         + currentValue + "'");
122 
123                 List<NameValuePair> options;
124                 if (currentScript.getFieldType() == Configuration.Type.customfield) {
125                     options = optionValues.get(currentScript.getFieldId());
126                     if (null == options) {
127                         options = Collections.emptyList();
128                         optionValues.put(currentScript.getFieldId(), options);
129                     }
130                 } else {
131                     if (!optionValues.containsKey(currentScript.getFieldType().getLegacyCode())){
132                         options = Collections.emptyList();
133                         optionValues.put(currentScript.getFieldType().getLegacyCode(), options);
134                     } else {
135                         options = optionValues.get(currentScript.getFieldType().getLegacyCode());
136                     }
137                 }
138 
139                 currentValue = processFieldScript(currentScript, event,
140                         currentValue,
141                         options, currentErrors);
142                 currentValues.put( (currentScript.getFieldType() == Configuration.Type.customfield?
143                         currentScript.getFieldId():currentScript.getFieldType().getLegacyCode()),
144                         currentValue );
145 
146 
147                 log.debug("After script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId())
148                         + " (" + currentScript.getFieldId() + ") is "
149                         + currentValue + "'");
150 
151             } catch (WorkflowException we) {
152                 log.error("Error processing script ", we);
153                 currentErrors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system.message", we.getMessage(), "Workflow"));
154             }
155         }
156 
157 
158         // apply new values
159         for (ProjectScript script: projectScriptModels) {
160             if (script.getScript().getEvent() == event) {
161                 final String val;
162                 switch (script.getFieldType()) {
163                     case status:
164                         val = currentValues.get(Configuration.Type.status.getLegacyCode());
165                         try {
166                             setStatus(Integer.valueOf(val));
167                         } catch (RuntimeException re) {/* OK */}
168                         break;
169                     case severity:
170                         val = currentValues.get(Configuration.Type.severity.getLegacyCode());
171                         try {
172                             setSeverity(Integer.valueOf(val));
173                         } catch (RuntimeException re) {/* OK */}
174                         break;
175                     case resolution:
176                         val = currentValues.get(Configuration.Type.resolution.getLegacyCode());
177                         setResolution(val);
178                         break;
179                     case customfield:
180                         getCustomFields().put(String.valueOf(script.getFieldId()), currentValues.get(script.getFieldId()));
181                         break;
182                     default:
183                         log.warn("unsupported field type in script: " + script.getFieldType() + " in project " + script.getProject().getName());
184                         break;
185                 }
186             }
187         }
188     }
189 
190 
191     /**
192      * Run provided BEANSHELL script against form instance, taking into account
193      * incoming event type, field raised an event and current values.
194      * As a result, a set of new current values is returned and if
195      * appropriate, default values are changed in form.
196      * TODO: should issue, project, user, services be available too?
197      *
198      * @param projectScript is a script to run.
199      * @param event         is an event type.
200      * @param currentValue  the current field value
201      * @param optionValues  is a set of valid option-values.
202      * @param currentErrors is a container for occured errors.
203      * @return new changed currentValue.
204      */
205     public String processFieldScript(ProjectScript projectScript, int event, String currentValue, List<NameValuePair> optionValues, ActionMessages currentErrors) throws WorkflowException {
206         if (projectScript == null) {
207             throw new WorkflowException("ProjectScript was null.", WorkflowException.INVALID_ARGS);
208         }
209         if (currentErrors == null) {
210             throw new WorkflowException("Errors was null.", WorkflowException.INVALID_ARGS);
211         }
212 
213         if (!isWorkflowScriptsAllowed()) {
214             return currentValue;
215         }
216 
217         String result = currentValue;
218 
219         try {
220             if (projectScript.getScript().getLanguage() != WorkflowScript.ScriptLanguage.Groovy) {
221                 result = processBeanShellScript(projectScript, currentValue, optionValues, currentErrors, event);
222             } else {
223                 result = processGroovyScript(projectScript, currentValue, optionValues, currentErrors, event);
224             }
225             if (log.isDebugEnabled()) {
226                 log.debug("processFieldScript: Script returned current value of '" + optionValues + "' (" + (optionValues != null ? optionValues.getClass().getName() : "NULL") + ")");
227             }
228         } catch (EvalError evalError) {
229             log.error("processFieldScript: eval failed: " + projectScript, evalError);
230             currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
231                     new ActionMessage("itracker.web.error.invalidscriptdata", evalError.getMessage()));
232         } catch (RuntimeException e) {
233             log.warn("processFieldScript: Error processing field script: " + projectScript, e);
234             currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
235                     new ActionMessage("itracker.web.error.system.message",
236                             new Object[]{
237                                     e.getMessage(),
238                                     ITrackerResources.getString("itracker.web.attr.script") // Script
239                             }));
240         }
241         if (log.isDebugEnabled()) {
242             log.debug("processFieldScript: returning " + result + ", errors: " + currentErrors);
243         }
244         return result;
245     }
246 
247     private String processGroovyScript(final ProjectScript projectScript,
248                                        final String currentValue,
249                                        final List<NameValuePair> optionValues,
250                                        final ActionMessages currentErrors,
251                                        final int event) {
252 
253         final Map<String,Object> ctx = new HashMap<>(8);
254         ctx.put("currentValue", StringUtils.defaultString(currentValue));
255         ctx.put("event", event);
256         ctx.put("fieldId", (projectScript.getFieldType() == Configuration.Type.customfield ?
257                 projectScript.getFieldId() : projectScript.getFieldType().getLegacyCode()));
258 
259         ctx.put("optionValues", Collections.unmodifiableList(optionValues));
260         ctx.put("currentErrors", currentErrors);
261         ctx.put("currentForm", this);
262 
263         final Binding binding = new Binding(ctx);
264 
265         GroovyShell shell = new GroovyShell();
266         Script script = shell.parse(projectScript.getScript().getScript(),
267                         projectScript.getScript().getName());
268         script.setBinding(binding);
269         Object ret = script.run();
270         if (!currentErrors.isEmpty()) {
271             return currentValue;
272         }
273         return returnScriptResult(ret, ctx.get("currentValue"), currentValue);
274     }
275 
276     private String processBeanShellScript(final ProjectScript projectScript,
277                                           final String currentValue,
278                                           final List<NameValuePair> optionValues,
279                                           final ActionMessages currentErrors,
280                                           final int event) throws EvalError {
281         Interpreter bshInterpreter = new Interpreter();
282         bshInterpreter.set("event", event);
283         bshInterpreter.set("fieldId", (projectScript.getFieldType()== Configuration.Type.customfield?
284             projectScript.getFieldId():projectScript.getFieldType().getLegacyCode()));
285         bshInterpreter.set("currentValue", StringUtils.defaultString(currentValue));
286         bshInterpreter.set("optionValues", optionValues);
287         bshInterpreter.set("currentErrors", currentErrors);
288         bshInterpreter.set("currentForm", this);
289 
290         Object obj = bshInterpreter.eval(projectScript.getScript().getScript());
291         if (!currentErrors.isEmpty()) {
292             return currentValue;
293         }
294         return returnScriptResult(obj, bshInterpreter.get("currentValue"), currentValue);
295     }
296 
297     private static String returnScriptResult(Object returned, Object assigned, String currentValue) {
298         if (! (returned instanceof CharSequence)) {
299             log.debug("script did not return a value");
300             returned = assigned;
301         }
302         if (returned instanceof CharSequence) {
303             return String.valueOf(returned);
304         }
305         log.debug("failed to get value from script, returning previous value");
306         return currentValue;
307     }
308 
309     public final Issue processFullEdit(Issue issue, Project project, User user,
310                                               Map<Integer, Set<PermissionType>> userPermissions, Locale locale,
311                                               IssueService issueService, ActionMessages errors) throws Exception {
312         int previousStatus = issue.getStatus();
313         boolean needReloadIssue;
314         ActionMessages msg = new ActionMessages();
315         issue = addAttachment(issue, project, user, getITrackerServices(), msg);
316 
317         if (!msg.isEmpty()) {
318             // Validation of attachment failed
319             errors.add(msg);
320             return issue;
321         }
322 
323         needReloadIssue = issueService.setIssueVersions(issue.getId(),
324                 new HashSet<>(Arrays.asList(getVersions())),
325                 user.getId());
326 
327         needReloadIssue = needReloadIssue | issueService.setIssueComponents(issue.getId(),
328                 new HashSet<>(Arrays.asList(getComponents())),
329                 user.getId());
330 
331         // reload issue for further updates
332         if (needReloadIssue) {
333             if (log.isDebugEnabled()) {
334                 log.debug("processFullEdit: updating issue from session: " + issue);
335             }
336             issue = issueService.getIssue(issue.getId());
337         }
338 
339         Integer targetVersion = getTargetVersion();
340         if (targetVersion != null && targetVersion != -1) {
341             ProjectService projectService = ServletContextUtils.getItrackerServices()
342                     .getProjectService();
343             Version version = projectService.getProjectVersion(targetVersion);
344             if (version == null) {
345                 throw new RuntimeException("No version with Id "
346                         + targetVersion);
347             }
348             issue.setTargetVersion(version);
349         }
350 
351         issue.setResolution(getResolution());
352         issue.setSeverity(getSeverity());
353 
354         applyLimitedFields(issue, project, user, userPermissions, locale, issueService);
355 
356         Integer formStatus = getStatus();
357         issue.setStatus(formStatus);
358         if (formStatus != null) {
359             if (log.isDebugEnabled()) {
360                 log.debug("processFullEdit: processing status: " + formStatus);
361             }
362             if (previousStatus != -1) {
363                 // Reopened the issue. Reset the resolution field.
364                 if ((previousStatus >= IssueUtilities.STATUS_ASSIGNED && previousStatus < IssueUtilities.STATUS_RESOLVED)
365                         && (previousStatus >= IssueUtilities.STATUS_RESOLVED && previousStatus < IssueUtilities.STATUS_END)) {
366                     issue.setResolution("");
367                 }
368 
369                 if (previousStatus >= IssueUtilities.STATUS_CLOSED
370                         && !UserUtilities.hasPermission(userPermissions, project
371                         .getId(), UserUtilities.PERMISSION_CLOSE)) {
372                     if (previousStatus < IssueUtilities.STATUS_CLOSED) {
373                         issue.setStatus(previousStatus);
374                     } else {
375                         issue.setStatus(IssueUtilities.STATUS_RESOLVED);
376                     }
377                 }
378 
379                 if (issue.getStatus() < IssueUtilities.STATUS_NEW
380                         || issue.getStatus() >= IssueUtilities.STATUS_END) {
381                     issue.setStatus(previousStatus);
382                 }
383             } else if (issue.getStatus() >= IssueUtilities.STATUS_CLOSED
384                     && !UserUtilities.hasPermission(userPermissions, project
385                     .getId(), PermissionType.ISSUE_CLOSE)) {
386                 issue.setStatus(IssueUtilities.STATUS_RESOLVED);
387             }
388         }
389 
390         if (issue.getStatus() < IssueUtilities.STATUS_NEW) {
391             if (log.isDebugEnabled()) {
392                 log.debug("processFullEdit: status < STATUS_NEW: " + issue.getStatus());
393             }
394             issue.setStatus(IssueUtilities.STATUS_NEW);
395             if (log.isDebugEnabled()) {
396                 log.debug("processFullEdit: updated to: " + issue.getStatus());
397             }
398         } else if (issue.getStatus() >= IssueUtilities.STATUS_END) {
399             if (log.isDebugEnabled()) {
400                 log.debug("processFullEdit: status >= STATUS_END: " + issue.getStatus());
401             }
402             if (!UserUtilities.hasPermission(userPermissions, project.getId(),
403                     PermissionType.ISSUE_CLOSE)) {
404                 issue.setStatus(IssueUtilities.STATUS_RESOLVED);
405             } else {
406                 issue.setStatus(IssueUtilities.STATUS_CLOSED);
407             }
408             if (log.isDebugEnabled()) {
409                 log.debug("processFullEdit: status updated to: " + issue.getStatus());
410             }
411         }
412         if (log.isDebugEnabled()) {
413             log.debug("processFullEdit: updating issue " + issue);
414         }
415         return issueService.updateIssue(issue, user.getId());
416     }
417 
418     public final void applyLimitedFields(Issue issue, Project project,
419                                                 User user, Map<Integer, Set<PermissionType>> userPermissionsMap,
420                                                 Locale locale,  IssueService issueService) throws Exception {
421 
422         issue.setDescription(getDescription());
423 
424         setIssueFields(issue, user, locale,  issueService);
425         setOwner(issue, user, userPermissionsMap);
426         addHistoryEntry(issue, user);
427     }
428 
429     private void setIssueFields(Issue issue, User user, Locale locale,
430                                        IssueService issueService) throws Exception {
431         if (log.isDebugEnabled()) {
432             log.debug("setIssueFields: called");
433         }
434         List<CustomField> projectCustomFields = issue.getProject()
435                 .getCustomFields();
436         if (log.isDebugEnabled()) {
437             log.debug("setIssueFields: got project custom fields: " + projectCustomFields);
438         }
439 
440         if (projectCustomFields == null || projectCustomFields.size() == 0) {
441             log.debug("setIssueFields: no custom fields, returning...");
442             return;
443         }
444 
445 
446         // here you see some of the ugly side of Struts 1.3 - the forms... they
447         // can only contain Strings and some simple objects types...
448         HashMap<String, String> formCustomFields = getCustomFields();
449 
450         if (log.isDebugEnabled()) {
451             log.debug("setIssueFields: got form custom fields: " + formCustomFields);
452         }
453 
454         if (formCustomFields == null || formCustomFields.size() == 0) {
455             log.debug("setIssueFields: no form custom fields, returning..");
456             return;
457         }
458 
459         ResourceBundle bundle = ITrackerResources.getBundle(locale);
460         Iterator<CustomField> customFieldsIt = projectCustomFields.iterator();
461         // declare iteration fields
462         CustomField field;
463         String fieldValue;
464         IssueField issueField;
465         try {
466             if (log.isDebugEnabled()) {
467                 log.debug("setIssueFields: processing project fields");
468             }
469             // set values to issue-fields and add if needed
470             while (customFieldsIt.hasNext()) {
471 
472                 field = customFieldsIt.next();
473                 fieldValue = (String) formCustomFields.get(String.valueOf(field
474                         .getId()));
475 
476                 // remove the existing field for re-setting
477                 issueField = getIssueField(issue, field);
478 
479 
480                 if (fieldValue != null && fieldValue.trim().length() > 0) {
481                     if (null == issueField) {
482                         issueField = new IssueField(issue, field);
483                         issue.getFields().add(issueField);
484                     }
485 
486                     issueField.setValue(fieldValue, bundle);
487                 } else {
488                     if (null != issueField) {
489                         issue.getFields().remove(issueField);
490                     }
491                 }
492             }
493 
494             // set new issue fields for later saving
495 //			issue.setFields(issueFieldsList);
496 
497 //			issueService.setIssueFields(issue.getId(), issueFieldsList);
498         } catch (Exception e) {
499             log.error("setIssueFields: failed to process custom fields", e);
500             throw e;
501         }
502     }
503 
504     private static IssueField getIssueField(Issue issue, CustomField field) {
505         Iterator<IssueField> it = issue.getFields().iterator();
506         IssueField issueField;
507         while (it.hasNext()) {
508             issueField = it.next();
509             if (issueField.getCustomField().equals(field)) {
510                 return issueField;
511             }
512         }
513         return null;
514 
515     }
516 
517     private void setOwner(Issue issue, User user,
518                                  Map<Integer, Set<PermissionType>> userPermissionsMap) throws Exception {
519         if (log.isDebugEnabled()) {
520             log.debug("setOwner: called to " + getOwnerId());
521         }
522         Integer currentOwner = (issue.getOwner() == null) ? null : issue
523                 .getOwner().getId();
524 
525         Integer ownerId = getOwnerId();
526 
527         if (ownerId == null || ownerId.equals(currentOwner)) {
528             if (log.isDebugEnabled()) {
529                 log.debug("setOwner: returning, existing owner is the same: " + issue.getOwner());
530             }
531             return;
532         }
533 
534         if (UserUtilities.hasPermission(userPermissionsMap,
535                 UserUtilities.PERMISSION_ASSIGN_OTHERS)
536                 || (UserUtilities.hasPermission(userPermissionsMap,
537                 UserUtilities.PERMISSION_ASSIGN_SELF) && user.getId()
538                 .equals(ownerId))
539                 || (UserUtilities.hasPermission(userPermissionsMap,
540                 UserUtilities.PERMISSION_UNASSIGN_SELF)
541                 && user.getId().equals(currentOwner) && ownerId == -1)) {
542             User newOwner = ServletContextUtils.getItrackerServices().getUserService().getUser(ownerId);
543             if (log.isDebugEnabled()) {
544                 log.debug("setOwner: setting new owner " + newOwner + " to " + issue);
545             }
546             issue.setOwner(newOwner);
547 //			issueService.assignIssue(issue.getId(), ownerId, user.getId());
548         }
549 
550     }
551 
552     private void addHistoryEntry(Issue issue, User user) throws Exception {
553         try {
554             String history = getHistory();
555 
556             if (history == null || history.equals("")) {
557                 if (log.isDebugEnabled()) {
558                     log.debug("addHistoryEntry: skip history to " + issue);
559                 }
560                 return;
561             }
562 
563 
564             if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_SURPRESS_HISTORY_HTML, issue.getProject().getOptions())) {
565                 history = HTMLUtilities.removeMarkup(history);
566             } else if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_LITERAL_HISTORY_HTML, issue.getProject().getOptions())) {
567                 history = HTMLUtilities.escapeTags(history);
568             } else {
569                 history = HTMLUtilities.newlinesToBreaks(history);
570             }
571 
572 
573             if (log.isDebugEnabled()) {
574                 log.debug("addHistoryEntry: adding history to " + issue);
575             }
576             IssueHistory issueHistory = new IssueHistory(issue, user, history,
577                     IssueUtilities.HISTORY_STATUS_AVAILABLE);
578 
579             issueHistory.setDescription(getHistory());
580             issueHistory.setCreateDate(new Date());
581 
582             issueHistory.setLastModifiedDate(new Date());
583             issue.getHistory().add(issueHistory);
584 
585 
586 //  TODO why do we need to updateIssue here, and can not later?
587 //			issueService.updateIssue(issue, user.getId());
588         } catch (Exception e) {
589             log.error("addHistoryEntry: failed to add", e);
590             throw e;
591         }
592 //		issueService.addIssueHistory(issueHistory);
593         if (log.isDebugEnabled()) {
594             log.debug("addHistoryEntry: added history for issue " + issue);
595         }
596     }
597 
598     public final Issue processLimitedEdit(Issue issue, Project project,
599                                                  User user, Map<Integer, Set<PermissionType>> userPermissionsMap,
600                                                  Locale locale, IssueService issueService, ActionMessages messages)
601             throws Exception {
602         ActionMessages msg = new ActionMessages();
603         issue = addAttachment(issue, project, user, ServletContextUtils.getItrackerServices(), msg);
604 
605         if (!msg.isEmpty()) {
606             messages.add(msg);
607             return issue;
608         }
609 
610         Integer formStatus = getStatus();
611 
612         if (formStatus != null) {
613 
614             if (issue.getStatus() >= IssueUtilities.STATUS_RESOLVED
615                     && formStatus >= IssueUtilities.STATUS_CLOSED
616                     && UserUtilities.hasPermission(userPermissionsMap,
617                     UserUtilities.PERMISSION_CLOSE)) {
618 
619                 issue.setStatus(formStatus);
620             }
621 
622         }
623 
624         applyLimitedFields(issue, project, user, userPermissionsMap, locale, issueService);
625         return issueService.updateIssue(issue, user.getId());
626 
627     }
628 
629     /**
630      * method needed to prepare request for edit_issue.jsp
631      */
632     public static void setupJspEnv(ActionMapping mapping,
633                                          IssueForm issueForm, HttpServletRequest request, Issue issue,
634                                          IssueService issueService, UserService userService,
635                                          Map<Integer, Set<PermissionType>> userPermissions,
636                                          Map<Integer, List<NameValuePair>> listOptions, ActionMessages errors)
637             throws ServletException, IOException, WorkflowException {
638 
639         if (log.isDebugEnabled()) {
640             log.debug("setupJspEnv: for issue " + issue);
641         }
642 
643         NotificationService notificationService = ServletContextUtils
644                 .getItrackerServices().getNotificationService();
645         String pageTitleKey = "itracker.web.editissue.title";
646         String pageTitleArg = request.getParameter("id");
647         Locale locale = LoginUtilities.getCurrentLocale(request);
648         User um = LoginUtilities.getCurrentUser(request);
649         List<NameValuePair> statuses = WorkflowUtilities.getListOptions(
650                 listOptions, IssueUtilities.FIELD_STATUS);
651         String statusName = IssueUtilities.getStatusName(issue.getStatus(), locale);
652         boolean hasFullEdit = UserUtilities.hasPermission(userPermissions,
653                 issue.getProject().getId(), UserUtilities.PERMISSION_EDIT_FULL);
654         List<NameValuePair> resolutions = WorkflowUtilities.getListOptions(
655                 listOptions, IssueUtilities.FIELD_RESOLUTION);
656         String severityName = IssueUtilities.getSeverityName(issue
657                 .getSeverity(), locale);
658         List<NameValuePair> components = WorkflowUtilities.getListOptions(
659                 listOptions, IssueUtilities.FIELD_COMPONENTS);
660         List<NameValuePair> versions = WorkflowUtilities.getListOptions(
661                 listOptions, IssueUtilities.FIELD_VERSIONS);
662         List<NameValuePair> targetVersion = WorkflowUtilities.getListOptions(
663                 listOptions, IssueUtilities.FIELD_TARGET_VERSION);
664         List<Component> issueComponents = issue.getComponents();
665         Collections.sort(issueComponents);
666         List<Version> issueVersions = issue.getVersions();
667         Collections.sort(issueVersions, new Version.VersionComparator());
668         /* Get project fields and put name and value in map */
669         setupProjectFieldsMapJspEnv(issue.getProject().getCustomFields(), issue.getFields(), request);
670 
671         setupRelationsRequestEnv(issue.getRelations(), request);
672 
673 
674         request.setAttribute("pageTitleKey", pageTitleKey);
675         request.setAttribute("pageTitleArg", pageTitleArg);
676         request.getSession().setAttribute(Constants.LIST_OPTIONS_KEY,
677                 listOptions);
678         request.setAttribute("targetVersions", targetVersion);
679         request.setAttribute("components", components);
680         request.setAttribute("versions", versions);
681         request.setAttribute("hasIssueNotification", notificationService
682                 .hasIssueNotification(issue, um.getId()));
683         request.setAttribute("hasHardIssueNotification", IssueUtilities.hasHardNotification(issue, issue.getProject(), um.getId()));
684         request.setAttribute("hasEditIssuePermission", UserUtilities
685                 .hasPermission(userPermissions, issue.getProject().getId(),
686                         UserUtilities.PERMISSION_EDIT));
687         request.setAttribute("canCreateIssue",
688                 issue.getProject().getStatus() == Status.ACTIVE
689                         && UserUtilities.hasPermission(userPermissions, issue
690                         .getProject().getId(),
691                         UserUtilities.PERMISSION_CREATE));
692         request.setAttribute("issueComponents", issueComponents);
693         request.setAttribute("issueVersions",
694                 issueVersions == null ? new ArrayList<Version>()
695                         : issueVersions);
696         request.setAttribute("statuses", statuses);
697         request.setAttribute("statusName", statusName);
698         request.setAttribute("hasFullEdit", hasFullEdit);
699         request.setAttribute("resolutions", resolutions);
700         request.setAttribute("severityName", severityName);
701         request.setAttribute("hasPredefinedResolutionsOption", ProjectUtilities
702                 .hasOption(ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS,
703                         issue.getProject().getOptions()));
704         request.setAttribute("issueOwnerName",
705                 (issue.getOwner() == null ? ITrackerResources.getString(
706                         "itracker.web.generic.unassigned", locale)
707                         : issue.getOwner().getFirstName() + " "
708                         + issue.getOwner().getLastName()));
709         request.setAttribute("isStatusResolved",
710                 issue.getStatus() >= IssueUtilities.STATUS_RESOLVED);
711 
712 
713         request.setAttribute("fieldSeverity", WorkflowUtilities.getListOptions(
714                 listOptions, IssueUtilities.FIELD_SEVERITY));
715         request.setAttribute("possibleOwners", WorkflowUtilities
716                 .getListOptions(listOptions, IssueUtilities.FIELD_OWNER));
717 
718         request.setAttribute("hasNoViewAttachmentOption", ProjectUtilities
719                 .hasOption(ProjectUtilities.OPTION_NO_ATTACHMENTS, issue
720                         .getProject().getOptions()));
721 
722         if (log.isDebugEnabled()) {
723             log.debug("setupJspEnv: options " + issue.getProject().getOptions() + ", hasNoViewAttachmentOption: " + request.getAttribute("hasNoViewAttachmentOption"));
724         }
725 
726         setupNotificationsInRequest(request, issue, notificationService);
727 
728         // setup issue to request, as it's needed by the jsp.
729         request.setAttribute(Constants.ISSUE_KEY, issue);
730         request.setAttribute("issueForm", issueForm);
731         request.setAttribute(Constants.PROJECT_KEY, issue.getProject());
732         List<IssueHistory> issueHistory = issueService.getIssueHistory(issue
733                 .getId());
734         Collections.sort(issueHistory, IssueHistory.CREATE_DATE_COMPARATOR);
735         request.setAttribute("issueHistory", issueHistory);
736 
737 
738     }
739 
740     /**
741      * Get project fields and put name and value in map
742      * TODO: simplify this code, it's not readable, unsave yet.
743      */
744     public static final void setupProjectFieldsMapJspEnv(List<CustomField> projectFields, Collection<IssueField> issueFields, HttpServletRequest request) {
745         Map<CustomField, String> projectFieldsMap = new HashMap<CustomField, String>();
746 
747         if (projectFields != null && projectFields.size() > 0) {
748             Collections.sort(projectFields, CustomField.ID_COMPARATOR);
749 
750             HashMap<String, String> fieldValues = new HashMap<String, String>();
751             Iterator<IssueField> issueFieldsIt = issueFields.iterator();
752             while (issueFieldsIt.hasNext()) {
753                 IssueField issueField = issueFieldsIt.next();
754 
755                 if (issueField.getCustomField() != null
756                         && issueField.getCustomField().getId() > 0) {
757                     if (issueField.getCustomField().getFieldType() == CustomField.Type.DATE) {
758                         Locale locale = LoginUtilities.getCurrentLocale(request);
759                         String value = issueField.getValue(locale);
760                         fieldValues.put(issueField.getCustomField().getId()
761                                 .toString(), value);
762                     } else {
763                         fieldValues.put(issueField.getCustomField().getId()
764                                 .toString(), issueField
765                                 .getStringValue());
766                     }
767                 }
768             }
769             Iterator<CustomField> fieldsIt = projectFields.iterator();
770             CustomField field;
771             while (fieldsIt.hasNext()) {
772 
773                 field = fieldsIt.next();
774 
775                 String fieldValue = fieldValues.get(String.valueOf(field
776                         .getId()));
777                 if (null == fieldValue) {
778                     fieldValue = "";
779                 };
780                 projectFieldsMap.put(field, fieldValue);
781 
782             }
783 
784             request.setAttribute("projectFieldsMap", projectFieldsMap);
785         }
786     }
787 
788     protected static void setupRelationsRequestEnv(List<IssueRelation> relations, HttpServletRequest request) {
789         Collections.sort(relations, IssueRelation.LAST_MODIFIED_DATE_COMPARATOR);
790         request.setAttribute("issueRelations", relations);
791 
792     }
793 
794     public static void setupNotificationsInRequest(
795             HttpServletRequest request, Issue issue,
796             NotificationService notificationService) {
797         List<Notification> notifications = notificationService
798                 .getIssueNotifications(issue);
799 
800         Collections.sort(notifications, Notification.TYPE_COMPARATOR);
801 
802         request.setAttribute("notifications", notifications);
803         Map<User, Set<Notification.Role>> notificationMap = NotificationUtilities
804                 .mappedRoles(notifications);
805         request.setAttribute("notificationMap", notificationMap);
806         request.setAttribute("notifiedUsers", notificationMap.keySet());
807     }
808 
809     /**
810      * Adds an attachment to issue.
811      *
812      * @return updated issue
813      */
814     public Issue addAttachment(Issue issue, Project project, User user,
815                                        ITrackerServices services, ActionMessages messages) {
816 
817 
818         FormFile file = getAttachment();
819 
820         if (file == null || file.getFileName().trim().length() < 1) {
821             log.info("addAttachment: skipping file " + file);
822             return issue;
823         }
824 
825         if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_NO_ATTACHMENTS,
826                 project.getOptions())) {
827             messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.validate.attachment.disabled", project.getName()));
828             return issue;
829         }
830 
831         String origFileName = file.getFileName();
832         String contentType = file.getContentType();
833         int fileSize = file.getFileSize();
834 
835         String attachmentDescription = getAttachmentDescription();
836 
837         if (null == contentType || 0 >= contentType.length()) {
838             log.info("addAttachment: got no mime-type, using default plain-text");
839             contentType = "text/plain";
840         }
841 
842         if (log.isDebugEnabled()) {
843             log.debug("addAttachment: adding file, name: " + origFileName
844                     + " of type " + file.getContentType() + ", description: "
845                     + getAttachmentDescription() + ". filesize: " + fileSize);
846         }
847         ActionMessages validation = AttachmentUtilities.validate(file, services);
848         if (validation.isEmpty()) {
849 
850 //		if (AttachmentUtilities.checkFile(file, getITrackerServices())) {
851             int lastSlash = Math.max(origFileName.lastIndexOf('/'),
852                     origFileName.lastIndexOf('\\'));
853             if (lastSlash > -1) {
854                 origFileName = origFileName.substring(lastSlash + 1);
855             }
856 
857             IssueAttachment attachmentModel = new IssueAttachment(issue,
858                     origFileName, contentType, attachmentDescription, fileSize,
859                     user);
860 
861             attachmentModel.setIssue(issue);
862 //			issue.getAttachments().add(attachmentModel);
863             byte[] fileData;
864             try {
865                 fileData = file.getFileData();
866             } catch (IOException e) {
867                 log.error("addAttachment: failed to get file data", e);
868                 messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system"));
869                 return issue;
870             }
871             if (services.getIssueService()
872                     .addIssueAttachment(attachmentModel, fileData)) {
873                 return services.getIssueService().getIssue(issue.getId());
874             }
875 
876 
877         } else {
878             if (log.isDebugEnabled()) {
879                 log.debug("addAttachment: failed to validate: " + origFileName + ", " + validation);
880             }
881             messages.add(validation);
882         }
883         return issue;
884     }
885 
886     public final void setupIssueForm(Issue issue,
887                                             final Map<Integer, List<NameValuePair>> listOptions,
888                                             HttpServletRequest request, ActionMessages errors)
889             throws WorkflowException {
890         HttpSession session = request.getSession(true);
891 
892         IssueService issueService = ServletContextUtils.getItrackerServices()
893                 .getIssueService();
894         Locale locale = (Locale) session.getAttribute(Constants.LOCALE_KEY);
895         setId(issue.getId());
896         setProjectId(issue.getProject().getId());
897         setPrevStatus(issue.getStatus());
898         setCaller(request.getParameter("caller"));
899 
900         setDescription(HTMLUtilities.handleQuotes(issue
901                 .getDescription()));
902         setStatus(issue.getStatus());
903 
904         if (!ProjectUtilities.hasOption(
905                 ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS, issue
906                 .getProject().getOptions())) {
907             // TODO What happens here, validation?
908             try {
909                 issue.setResolution(IssueUtilities.checkResolutionName(issue
910                         .getResolution(), locale));
911             } catch (MissingResourceException | NumberFormatException mre) {
912                 log.error(mre.getMessage());
913             }
914         }
915 
916         setResolution(HTMLUtilities.handleQuotes(issue
917                 .getResolution()));
918         setSeverity(issue.getSeverity());
919 
920         setTargetVersion(issue.getTargetVersion() == null ? -1
921                 : issue.getTargetVersion().getId());
922 
923         setOwnerId((issue.getOwner() == null ? -1 : issue.getOwner()
924                 .getId()));
925 
926         List<IssueField> fields = issue.getFields();
927         HashMap<String, String> customFields = new HashMap<String, String>();
928         for (IssueField field : fields) {
929             customFields.put(field.getCustomField().getId().toString(),
930                     field.getValue(locale));
931         }
932 
933         setCustomFields(customFields);
934 
935         HashSet<Integer> selectedComponents = issueService
936                 .getIssueComponentIds(issue.getId());
937         if (selectedComponents != null) {
938             Integer[] componentIds;
939             ArrayList<Integer> components = new ArrayList<>(
940                     selectedComponents);
941             componentIds = components.toArray(new Integer[components.size()]);
942             setComponents(componentIds);
943         }
944 
945         HashSet<Integer> selectedVersions = issueService
946                 .getIssueVersionIds(issue.getId());
947         if (selectedVersions != null) {
948             Integer[] versionIds;
949             ArrayList<Integer> versions = new ArrayList<>(
950                     selectedVersions);
951             versionIds = versions.toArray(new Integer[versions.size()]);
952             setVersions(versionIds);
953         }
954 
955         invokeProjectScripts(issue.getProject(), WorkflowUtilities.EVENT_FIELD_ONPOPULATE, listOptions, errors);
956 
957     }
958 
959     public void invokeProjectScripts(Project project, int event, final Map<Integer, List<NameValuePair>> options, ActionMessages errors)
960             throws WorkflowException {
961         final Map<Integer, String> values = new HashMap<>(options.size());
962         for (CustomField field: project.getCustomFields()) {
963             values.put(field.getId()
964                     , getCustomFields().get(String.valueOf(field.getId())));
965         }
966         values.put(Configuration.Type.status.getLegacyCode(),
967                 String.valueOf(getStatus()));
968         values.put(Configuration.Type.severity.getLegacyCode(),
969                 String.valueOf(getSeverity()));
970         values.put(Configuration.Type.resolution.getLegacyCode(),
971                 getResolution());
972 
973         processFieldScripts(project.getScripts(),
974                 event, values, options, errors);
975 
976     }
977 
978     public  Map<Integer, List<NameValuePair>> invokeProjectScripts(Project project, int event, ActionMessages errors)
979             throws WorkflowException {
980 
981         final Map<Integer, List<NameValuePair>> options = EditIssueActionUtil.mappedFieldOptions(project.getCustomFields()) ;
982         invokeProjectScripts(project, event, options, errors);
983         return options;
984     }
985 
986     public FormFile getAttachment() {
987         return attachment;
988     }
989 
990     public void setAttachment(FormFile attachment) {
991         this.attachment = attachment;
992     }
993 
994     public String getAttachmentDescription() {
995         return attachmentDescription;
996     }
997 
998     public void setAttachmentDescription(String attachmentDescription) {
999         this.attachmentDescription = attachmentDescription;
1000     }
1001 
1002     public String getCaller() {
1003         return caller;
1004     }
1005 
1006     public void setCaller(String caller) {
1007         this.caller = caller;
1008     }
1009 
1010     public Integer[] getComponents() {
1011         if (null == components)
1012             return null;
1013         return components.clone();
1014     }
1015 
1016     public void setComponents(Integer[] components) {
1017         if (null == components)
1018             this.components = null;
1019         else
1020             this.components = components.clone();
1021     }
1022 
1023     public Integer getCreatorId() {
1024         return creatorId;
1025     }
1026 
1027     public void setCreatorId(Integer creatorId) {
1028         this.creatorId = creatorId;
1029     }
1030 
1031     // let's try to put Integer,String here:
1032     public HashMap<String, String> getCustomFields() {
1033         return customFields;
1034     }
1035 
1036     // let's try to put Integer,String here:
1037     public void setCustomFields(HashMap<String, String> customFields) {
1038         this.customFields = customFields;
1039     }
1040 
1041     public String getDescription() {
1042         return description;
1043     }
1044 
1045     public void setDescription(String description) {
1046         this.description = description;
1047     }
1048 
1049     public String getHistory() {
1050         return history;
1051     }
1052 
1053     public void setHistory(String history) {
1054         this.history = history;
1055     }
1056 
1057     public Integer getId() {
1058         return id;
1059     }
1060 
1061     public void setId(Integer id) {
1062         this.id = id;
1063     }
1064 
1065     public Integer getOwnerId() {
1066         return ownerId;
1067     }
1068 
1069     public void setOwnerId(Integer ownerId) {
1070         this.ownerId = ownerId;
1071     }
1072 
1073     public Integer getPrevStatus() {
1074         return prevStatus;
1075     }
1076 
1077     public void setPrevStatus(Integer prevStatus) {
1078         this.prevStatus = prevStatus;
1079     }
1080 
1081     public Integer getProjectId() {
1082         return projectId;
1083     }
1084 
1085     public void setProjectId(Integer projectId) {
1086         this.projectId = projectId;
1087     }
1088 
1089     public Integer getRelatedIssueId() {
1090         return relatedIssueId;
1091     }
1092 
1093     public void setRelatedIssueId(Integer relatedIssueId) {
1094         this.relatedIssueId = relatedIssueId;
1095     }
1096 
1097     public IssueRelation.Type getRelationType() {
1098         return relationType;
1099     }
1100 
1101     public void setRelationType(IssueRelation.Type relationType) {
1102         this.relationType = relationType;
1103     }
1104 
1105     public String getResolution() {
1106         return resolution;
1107     }
1108 
1109     public void setResolution(String resolution) {
1110         this.resolution = resolution;
1111     }
1112 
1113     public Integer getSeverity() {
1114         return severity;
1115     }
1116 
1117     public void setSeverity(Integer severity) {
1118         this.severity = severity;
1119     }
1120 
1121     public Integer getStatus() {
1122         return status;
1123     }
1124 
1125     public void setStatus(Integer status) {
1126         this.status = status;
1127     }
1128 
1129     public Integer getTargetVersion() {
1130         return targetVersion;
1131     }
1132 
1133     public void setTargetVersion(Integer targetVersion) {
1134         this.targetVersion = targetVersion;
1135     }
1136 
1137     public Integer[] getVersions() {
1138         if (null == versions)
1139             return null;
1140         return versions.clone();
1141     }
1142 
1143     public void setVersions(Integer[] versions) {
1144         if (null == versions)
1145             this.versions = null;
1146         else
1147             this.versions = versions.clone();
1148     }
1149 
1150     /**
1151      * This methods adds in validation for custom fields. It makes sure the
1152      * datatype matches and also that all required fields have been populated.
1153      *
1154      * @param mapping the ActionMapping object
1155      * @param request the current HttpServletRequest object
1156      * @return an ActionErrors object containing any validation errors
1157      */
1158     public ActionErrors validate(ActionMapping mapping,
1159                                  HttpServletRequest request) {
1160         if (log.isDebugEnabled()) {
1161             log.debug("validate called: mapping: " + mapping + ", request: "
1162                     + request);
1163         }
1164         ActionErrors errors = super.validate(mapping, request);
1165 
1166         if (log.isDebugEnabled()) {
1167             log.debug("validate called: mapping: " + mapping + ", request: "
1168                     + request + ", errors: " + errors);
1169         }
1170 
1171         try {
1172             if (null != getId()) {
1173                 Issue issue;
1174                 try {
1175                     issue = getITrackerServices().getIssueService().getIssue(
1176                             getId());
1177                 } catch (Exception e) {
1178                     return errors;
1179                 }
1180 
1181                 Locale locale = (Locale) request.getSession().getAttribute(
1182                         Constants.LOCALE_KEY);
1183                 User currUser = (User) request.getSession().getAttribute(
1184                         Constants.USER_KEY);
1185                 List<NameValuePair> ownersList = EditIssueActionUtil
1186                         .getAssignableIssueOwnersList(issue,
1187                                 issue.getProject(), currUser, locale,
1188                                 getITrackerServices().getUserService(),
1189                                 RequestHelper.getUserPermissions(request
1190                                         .getSession()));
1191 
1192                 setupJspEnv(mapping, this, request, issue,
1193                         getITrackerServices().getIssueService(),
1194                         getITrackerServices().getUserService(), RequestHelper
1195                         .getUserPermissions(request.getSession()),
1196                         EditIssueActionUtil.getListOptions(request, issue,
1197                                 ownersList, RequestHelper
1198                                 .getUserPermissions(request
1199                                         .getSession()), issue
1200                                 .getProject(), currUser), errors);
1201 
1202                 if (errors.isEmpty() && issue.getProject() == null) {
1203                     if (log.isDebugEnabled()) {
1204                         log.debug("validate: issue project is null: " + issue);
1205                     }
1206                     errors.add(ActionMessages.GLOBAL_MESSAGE,
1207                             new ActionMessage(
1208                                     "itracker.web.error.invalidproject"));
1209                 } else if (errors.isEmpty()
1210                         && issue.getProject().getStatus() != Status.ACTIVE) {
1211                     if (log.isDebugEnabled()) {
1212                         log.debug("validate: issue project is not active: " + issue);
1213                     }
1214                     errors.add(ActionMessages.GLOBAL_MESSAGE,
1215                             new ActionMessage(
1216                                     "itracker.web.error.projectlocked"));
1217                 } else if (errors.isEmpty() && !"editIssueForm".equals(mapping.getName())) {
1218                     if (log.isDebugEnabled()) {
1219                         log.debug("validate: validation had errors for " + issue + ": " + errors);
1220                     }
1221 
1222                     if (UserUtilities.hasPermission(RequestHelper.getUserPermissions(request.getSession()),
1223                             issue.getProject().getId(),
1224                             UserUtilities.PERMISSION_EDIT_FULL)) {
1225                         validateProjectFields(issue.getProject(), request, errors);
1226                     }
1227 
1228 
1229                     validateProjectScripts(issue.getProject(), errors);
1230                     validateAttachment(this.getAttachment(), getITrackerServices(), errors);
1231                 }
1232             } else {
1233                 EditIssueActionUtil.setupCreateIssue(request);
1234                 HttpSession session = request.getSession();
1235                 Project project = (Project) session
1236                         .getAttribute(Constants.PROJECT_KEY);
1237                 if (log.isDebugEnabled()) {
1238                     log.debug("validate: validating create new issue for project: " + page);
1239                 }
1240                 validateProjectFields(project, request, errors);
1241                 validateProjectScripts(project, errors);
1242                 validateAttachment(this.getAttachment(), getITrackerServices(), errors);
1243             }
1244         } catch (Exception e) {
1245             e.printStackTrace();
1246             log.error("validate: unexpected exception", e);
1247             errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
1248                     "itracker.web.error.system"));
1249         }
1250         if (log.isDebugEnabled()) {
1251             log.debug("validate: returning errors: " + errors);
1252         }
1253         return errors;
1254     }
1255 
1256     private static void validateAttachment(FormFile attachment, ITrackerServices services, ActionMessages errors) {
1257         if (null != attachment) {
1258             ActionMessages msg = AttachmentUtilities.validate(attachment, services);
1259             if (!msg.isEmpty()) {
1260                 if (log.isDebugEnabled()) {
1261                     log.debug("validateAttachment: failed to validate, " + msg);
1262                 }
1263                 errors.add(msg);
1264             }
1265         }
1266     }
1267 
1268     private static void validateProjectFields(Project project,
1269                                               HttpServletRequest request, ActionErrors errors) {
1270 
1271         List<CustomField> projectFields = project.getCustomFields();
1272         if (null != projectFields && projectFields.size() > 0) {
1273 
1274             Locale locale = LoginUtilities.getCurrentLocale(request);
1275 
1276             ResourceBundle bundle = ITrackerResources.getBundle(locale);
1277             for (CustomField customField : projectFields) {
1278                 String fieldValue = request.getParameter("customFields("
1279                         + customField.getId() + ")");
1280                 if (fieldValue != null && !fieldValue.equals("")) {
1281 
1282                     // Don't create an IssueField only so that we can call
1283                     // setValue to validate the value!
1284                     try {
1285                         customField.checkAssignable(fieldValue, locale, bundle);
1286                     } catch (IssueException ie) {
1287                         String label = CustomFieldUtilities.getCustomFieldName(
1288                                 customField.getId(), locale);
1289                         errors.add(ActionMessages.GLOBAL_MESSAGE,
1290                                 new ActionMessage(ie.getType(), label));
1291                     }
1292                 } else if (customField.isRequired()) {
1293                     String label = CustomFieldUtilities.getCustomFieldName(
1294                             customField.getId(), locale);
1295                     errors.add(ActionMessages.GLOBAL_MESSAGE,
1296                             new ActionMessage(IssueException.TYPE_CF_REQ_FIELD,
1297                                     label));
1298                 }
1299             }
1300         }
1301     }
1302 
1303     private void validateProjectScripts(Project project, ActionErrors errors)
1304             throws WorkflowException {
1305 
1306         invokeProjectScripts(project, WorkflowUtilities.EVENT_FIELD_ONVALIDATE, errors);
1307 
1308     }
1309 
1310     public static boolean isWorkflowScriptsAllowed() {
1311         Boolean val = ServletContextUtils.getItrackerServices().getConfigurationService().getBooleanProperty("allow_workflowscripts", true);
1312         if (log.isDebugEnabled()) {
1313             log.debug("isWorkflowScriptsAllowed: {}allowed by configuration 'allow_workflowscripts'", !val?"NOT ":"");
1314         }
1315         return val;
1316     }
1317 
1318 }