IssueForm.java

  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. package org.itracker.web.forms;

  19. import bsh.EvalError;
  20. import bsh.Interpreter;
  21. import groovy.lang.Binding;
  22. import groovy.lang.GroovyShell;
  23. import groovy.lang.Script;
  24. import org.apache.commons.lang.StringUtils;
  25. import org.apache.struts.action.ActionErrors;
  26. import org.apache.struts.action.ActionMapping;
  27. import org.apache.struts.action.ActionMessage;
  28. import org.apache.struts.action.ActionMessages;
  29. import org.apache.struts.upload.FormFile;
  30. import org.itracker.IssueException;
  31. import org.itracker.WorkflowException;
  32. import org.itracker.core.resources.ITrackerResources;
  33. import org.itracker.model.*;
  34. import org.itracker.model.util.*;
  35. import org.itracker.services.*;
  36. import org.itracker.web.util.*;
  37. import org.slf4j.Logger;
  38. import org.slf4j.LoggerFactory;

  39. import javax.servlet.ServletException;
  40. import javax.servlet.http.HttpServletRequest;
  41. import javax.servlet.http.HttpSession;
  42. import java.io.IOException;
  43. import java.util.*;

  44. /**
  45.  * This form is by the struts actions to pass issue data.
  46.  */
  47. public class IssueForm extends ITrackerForm {

  48.     /**
  49.      *
  50.      */
  51.     private static final long serialVersionUID = 1L;

  52.     private static final Logger log = LoggerFactory.getLogger(IssueForm.class);

  53.     private Integer id = null;
  54.     private String caller = null;
  55.     private Integer projectId = null;
  56.     private Integer creatorId = null;
  57.     private Integer ownerId = null;
  58.     private String description = null;
  59.     private Integer severity = null;
  60.     private Integer status = null;
  61.     private Integer prevStatus = null;
  62.     private String resolution = null;
  63.     private Integer targetVersion = null;
  64.     private Integer[] components = new Integer[0];
  65.     private Integer[] versions = new Integer[0];
  66.     private String attachmentDescription = null;
  67.     transient private FormFile attachment = null;
  68.     private String history = null;
  69.     // lets try to put Integer,String here:
  70.     private HashMap<String, String> customFields = new HashMap<>();
  71.     private IssueRelation.Type relationType = null;
  72.     private Integer relatedIssueId = null;

  73.     /**
  74.      * The most general way to run scripts. All matching of event and fields
  75.      * are embedded within. As a result, optionValues parameter will
  76.      * contain updated values and form will contain new default values
  77.      * if appropriate.
  78.      *
  79.      * @param projectScriptModels is a list of scripts.
  80.      * @param event               is an event type.
  81.      * @param currentValues       values mapped to field-ids
  82.      * @param optionValues        is a map of current values to fields by field-Id.
  83.      * @param currentErrors       is a container for errors.
  84.      */
  85.     public void processFieldScripts(List<ProjectScript> projectScriptModels, int event, Map<Integer, String> currentValues, Map<Integer, List<NameValuePair>> optionValues, ActionMessages currentErrors) throws WorkflowException {
  86.         if ((!isWorkflowScriptsAllowed()) || projectScriptModels == null || projectScriptModels.size() == 0)
  87.             return;
  88.         log.debug("Processing " + projectScriptModels.size() + " field scripts for project " + projectScriptModels.get(0).getProject().getId());

  89.         List<ProjectScript> scriptsToRun = new ArrayList<>(projectScriptModels.size());
  90.         for (ProjectScript model : projectScriptModels) {
  91.             if (model.getScript().getEvent() == event) {
  92.                 scriptsToRun.add(model);
  93.             }
  94.         }
  95.         // order the scripts by priority
  96.         Collections.sort(scriptsToRun, ProjectScript.PRIORITY_COMPARATOR);

  97.         if (log.isDebugEnabled()) {
  98.             log.debug(scriptsToRun.size() + " eligible scripts found for event " + event);
  99.         }

  100.         String currentValue;
  101.         for (ProjectScript currentScript : scriptsToRun) {
  102.             try {
  103.                 currentValue = currentValues.get((currentScript.getFieldType() == Configuration.Type.customfield?
  104.                         currentScript.getFieldId():currentScript.getFieldType().getLegacyCode()));
  105.                 log.debug("Running script " + currentScript.getScript().getId()
  106.                         + " with priority " + currentScript.getPriority());

  107.                 log.debug("Before script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId())
  108.                         + " (" + currentScript.getFieldId() + ") is "
  109.                         + currentValue + "'");

  110.                 List<NameValuePair> options;
  111.                 if (currentScript.getFieldType() == Configuration.Type.customfield) {
  112.                     options = optionValues.get(currentScript.getFieldId());
  113.                     if (null == options) {
  114.                         options = Collections.emptyList();
  115.                         optionValues.put(currentScript.getFieldId(), options);
  116.                     }
  117.                 } else {
  118.                     if (!optionValues.containsKey(currentScript.getFieldType().getLegacyCode())){
  119.                         options = Collections.emptyList();
  120.                         optionValues.put(currentScript.getFieldType().getLegacyCode(), options);
  121.                     } else {
  122.                         options = optionValues.get(currentScript.getFieldType().getLegacyCode());
  123.                     }
  124.                 }

  125.                 currentValue = processFieldScript(currentScript, event,
  126.                         currentValue,
  127.                         options, currentErrors);
  128.                 currentValues.put( (currentScript.getFieldType() == Configuration.Type.customfield?
  129.                         currentScript.getFieldId():currentScript.getFieldType().getLegacyCode()),
  130.                         currentValue );


  131.                 log.debug("After script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId())
  132.                         + " (" + currentScript.getFieldId() + ") is "
  133.                         + currentValue + "'");

  134.             } catch (WorkflowException we) {
  135.                 log.error("Error processing script ", we);
  136.                 currentErrors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system.message", we.getMessage(), "Workflow"));
  137.             }
  138.         }


  139.         // apply new values
  140.         for (ProjectScript script: projectScriptModels) {
  141.             if (script.getScript().getEvent() == event) {
  142.                 final String val;
  143.                 switch (script.getFieldType()) {
  144.                     case status:
  145.                         val = currentValues.get(Configuration.Type.status.getLegacyCode());
  146.                         try {
  147.                             setStatus(Integer.valueOf(val));
  148.                         } catch (RuntimeException re) {/* OK */}
  149.                         break;
  150.                     case severity:
  151.                         val = currentValues.get(Configuration.Type.severity.getLegacyCode());
  152.                         try {
  153.                             setSeverity(Integer.valueOf(val));
  154.                         } catch (RuntimeException re) {/* OK */}
  155.                         break;
  156.                     case resolution:
  157.                         val = currentValues.get(Configuration.Type.resolution.getLegacyCode());
  158.                         setResolution(val);
  159.                         break;
  160.                     case customfield:
  161.                         getCustomFields().put(String.valueOf(script.getFieldId()), currentValues.get(script.getFieldId()));
  162.                         break;
  163.                     default:
  164.                         log.warn("unsupported field type in script: " + script.getFieldType() + " in project " + script.getProject().getName());
  165.                         break;
  166.                 }
  167.             }
  168.         }
  169.     }


  170.     /**
  171.      * Run provided BEANSHELL script against form instance, taking into account
  172.      * incoming event type, field raised an event and current values.
  173.      * As a result, a set of new current values is returned and if
  174.      * appropriate, default values are changed in form.
  175.      * TODO: should issue, project, user, services be available too?
  176.      *
  177.      * @param projectScript is a script to run.
  178.      * @param event         is an event type.
  179.      * @param currentValue  the current field value
  180.      * @param optionValues  is a set of valid option-values.
  181.      * @param currentErrors is a container for occured errors.
  182.      * @return new changed currentValue.
  183.      */
  184.     public String processFieldScript(ProjectScript projectScript, int event, String currentValue, List<NameValuePair> optionValues, ActionMessages currentErrors) throws WorkflowException {
  185.         if (projectScript == null) {
  186.             throw new WorkflowException("ProjectScript was null.", WorkflowException.INVALID_ARGS);
  187.         }
  188.         if (currentErrors == null) {
  189.             throw new WorkflowException("Errors was null.", WorkflowException.INVALID_ARGS);
  190.         }

  191.         if (!isWorkflowScriptsAllowed()) {
  192.             return currentValue;
  193.         }

  194.         String result = currentValue;

  195.         try {
  196.             if (projectScript.getScript().getLanguage() != WorkflowScript.ScriptLanguage.Groovy) {
  197.                 result = processBeanShellScript(projectScript, currentValue, optionValues, currentErrors, event);
  198.             } else {
  199.                 result = processGroovyScript(projectScript, currentValue, optionValues, currentErrors, event);
  200.             }
  201.             if (log.isDebugEnabled()) {
  202.                 log.debug("processFieldScript: Script returned current value of '" + optionValues + "' (" + (optionValues != null ? optionValues.getClass().getName() : "NULL") + ")");
  203.             }
  204.         } catch (EvalError evalError) {
  205.             log.error("processFieldScript: eval failed: " + projectScript, evalError);
  206.             currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
  207.                     new ActionMessage("itracker.web.error.invalidscriptdata", evalError.getMessage()));
  208.         } catch (RuntimeException e) {
  209.             log.warn("processFieldScript: Error processing field script: " + projectScript, e);
  210.             currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
  211.                     new ActionMessage("itracker.web.error.system.message",
  212.                             new Object[]{
  213.                                     e.getMessage(),
  214.                                     ITrackerResources.getString("itracker.web.attr.script") // Script
  215.                             }));
  216.         }
  217.         if (log.isDebugEnabled()) {
  218.             log.debug("processFieldScript: returning " + result + ", errors: " + currentErrors);
  219.         }
  220.         return result;
  221.     }

  222.     private String processGroovyScript(final ProjectScript projectScript,
  223.                                        final String currentValue,
  224.                                        final List<NameValuePair> optionValues,
  225.                                        final ActionMessages currentErrors,
  226.                                        final int event) {

  227.         final Map<String,Object> ctx = new HashMap<>(8);
  228.         ctx.put("currentValue", StringUtils.defaultString(currentValue));
  229.         ctx.put("event", event);
  230.         ctx.put("fieldId", (projectScript.getFieldType() == Configuration.Type.customfield ?
  231.                 projectScript.getFieldId() : projectScript.getFieldType().getLegacyCode()));

  232.         ctx.put("optionValues", Collections.unmodifiableList(optionValues));
  233.         ctx.put("currentErrors", currentErrors);
  234.         ctx.put("currentForm", this);

  235.         final Binding binding = new Binding(ctx);

  236.         GroovyShell shell = new GroovyShell();
  237.         Script script = shell.parse(projectScript.getScript().getScript(),
  238.                         projectScript.getScript().getName());
  239.         script.setBinding(binding);
  240.         Object ret = script.run();
  241.         if (!currentErrors.isEmpty()) {
  242.             return currentValue;
  243.         }
  244.         return returnScriptResult(ret, ctx.get("currentValue"), currentValue);
  245.     }

  246.     private String processBeanShellScript(final ProjectScript projectScript,
  247.                                           final String currentValue,
  248.                                           final List<NameValuePair> optionValues,
  249.                                           final ActionMessages currentErrors,
  250.                                           final int event) throws EvalError {
  251.         Interpreter bshInterpreter = new Interpreter();
  252.         bshInterpreter.set("event", event);
  253.         bshInterpreter.set("fieldId", (projectScript.getFieldType()== Configuration.Type.customfield?
  254.             projectScript.getFieldId():projectScript.getFieldType().getLegacyCode()));
  255.         bshInterpreter.set("currentValue", StringUtils.defaultString(currentValue));
  256.         bshInterpreter.set("optionValues", optionValues);
  257.         bshInterpreter.set("currentErrors", currentErrors);
  258.         bshInterpreter.set("currentForm", this);

  259.         Object obj = bshInterpreter.eval(projectScript.getScript().getScript());
  260.         if (!currentErrors.isEmpty()) {
  261.             return currentValue;
  262.         }
  263.         return returnScriptResult(obj, bshInterpreter.get("currentValue"), currentValue);
  264.     }

  265.     private static String returnScriptResult(Object returned, Object assigned, String currentValue) {
  266.         if (! (returned instanceof CharSequence)) {
  267.             log.debug("script did not return a value");
  268.             returned = assigned;
  269.         }
  270.         if (returned instanceof CharSequence) {
  271.             return String.valueOf(returned);
  272.         }
  273.         log.debug("failed to get value from script, returning previous value");
  274.         return currentValue;
  275.     }

  276.     public final Issue processFullEdit(Issue issue, Project project, User user,
  277.                                               Map<Integer, Set<PermissionType>> userPermissions, Locale locale,
  278.                                               IssueService issueService, ActionMessages errors) throws Exception {
  279.         int previousStatus = issue.getStatus();
  280.         boolean needReloadIssue;
  281.         ActionMessages msg = new ActionMessages();
  282.         issue = addAttachment(issue, project, user, getITrackerServices(), msg);

  283.         if (!msg.isEmpty()) {
  284.             // Validation of attachment failed
  285.             errors.add(msg);
  286.             return issue;
  287.         }

  288.         needReloadIssue = issueService.setIssueVersions(issue.getId(),
  289.                 new HashSet<>(Arrays.asList(getVersions())),
  290.                 user.getId());

  291.         needReloadIssue = needReloadIssue | issueService.setIssueComponents(issue.getId(),
  292.                 new HashSet<>(Arrays.asList(getComponents())),
  293.                 user.getId());

  294.         // reload issue for further updates
  295.         if (needReloadIssue) {
  296.             if (log.isDebugEnabled()) {
  297.                 log.debug("processFullEdit: updating issue from session: " + issue);
  298.             }
  299.             issue = issueService.getIssue(issue.getId());
  300.         }

  301.         Integer targetVersion = getTargetVersion();
  302.         if (targetVersion != null && targetVersion != -1) {
  303.             ProjectService projectService = ServletContextUtils.getItrackerServices()
  304.                     .getProjectService();
  305.             Version version = projectService.getProjectVersion(targetVersion);
  306.             if (version == null) {
  307.                 throw new RuntimeException("No version with Id "
  308.                         + targetVersion);
  309.             }
  310.             issue.setTargetVersion(version);
  311.         }

  312.         issue.setResolution(getResolution());
  313.         issue.setSeverity(getSeverity());

  314.         applyLimitedFields(issue, project, user, userPermissions, locale, issueService);

  315.         Integer formStatus = getStatus();
  316.         issue.setStatus(formStatus);
  317.         if (formStatus != null) {
  318.             if (log.isDebugEnabled()) {
  319.                 log.debug("processFullEdit: processing status: " + formStatus);
  320.             }
  321.             if (previousStatus != -1) {
  322.                 // Reopened the issue. Reset the resolution field.
  323.                 if ((previousStatus >= IssueUtilities.STATUS_ASSIGNED && previousStatus < IssueUtilities.STATUS_RESOLVED)
  324.                         && (previousStatus >= IssueUtilities.STATUS_RESOLVED && previousStatus < IssueUtilities.STATUS_END)) {
  325.                     issue.setResolution("");
  326.                 }

  327.                 if (previousStatus >= IssueUtilities.STATUS_CLOSED
  328.                         && !UserUtilities.hasPermission(userPermissions, project
  329.                         .getId(), UserUtilities.PERMISSION_CLOSE)) {
  330.                     if (previousStatus < IssueUtilities.STATUS_CLOSED) {
  331.                         issue.setStatus(previousStatus);
  332.                     } else {
  333.                         issue.setStatus(IssueUtilities.STATUS_RESOLVED);
  334.                     }
  335.                 }

  336.                 if (issue.getStatus() < IssueUtilities.STATUS_NEW
  337.                         || issue.getStatus() >= IssueUtilities.STATUS_END) {
  338.                     issue.setStatus(previousStatus);
  339.                 }
  340.             } else if (issue.getStatus() >= IssueUtilities.STATUS_CLOSED
  341.                     && !UserUtilities.hasPermission(userPermissions, project
  342.                     .getId(), PermissionType.ISSUE_CLOSE)) {
  343.                 issue.setStatus(IssueUtilities.STATUS_RESOLVED);
  344.             }
  345.         }

  346.         if (issue.getStatus() < IssueUtilities.STATUS_NEW) {
  347.             if (log.isDebugEnabled()) {
  348.                 log.debug("processFullEdit: status < STATUS_NEW: " + issue.getStatus());
  349.             }
  350.             issue.setStatus(IssueUtilities.STATUS_NEW);
  351.             if (log.isDebugEnabled()) {
  352.                 log.debug("processFullEdit: updated to: " + issue.getStatus());
  353.             }
  354.         } else if (issue.getStatus() >= IssueUtilities.STATUS_END) {
  355.             if (log.isDebugEnabled()) {
  356.                 log.debug("processFullEdit: status >= STATUS_END: " + issue.getStatus());
  357.             }
  358.             if (!UserUtilities.hasPermission(userPermissions, project.getId(),
  359.                     PermissionType.ISSUE_CLOSE)) {
  360.                 issue.setStatus(IssueUtilities.STATUS_RESOLVED);
  361.             } else {
  362.                 issue.setStatus(IssueUtilities.STATUS_CLOSED);
  363.             }
  364.             if (log.isDebugEnabled()) {
  365.                 log.debug("processFullEdit: status updated to: " + issue.getStatus());
  366.             }
  367.         }
  368.         if (log.isDebugEnabled()) {
  369.             log.debug("processFullEdit: updating issue " + issue);
  370.         }
  371.         return issueService.updateIssue(issue, user.getId());
  372.     }

  373.     public final void applyLimitedFields(Issue issue, Project project,
  374.                                                 User user, Map<Integer, Set<PermissionType>> userPermissionsMap,
  375.                                                 Locale locale,  IssueService issueService) throws Exception {

  376.         issue.setDescription(getDescription());

  377.         setIssueFields(issue, user, locale,  issueService);
  378.         setOwner(issue, user, userPermissionsMap);
  379.         addHistoryEntry(issue, user);
  380.     }

  381.     private void setIssueFields(Issue issue, User user, Locale locale,
  382.                                        IssueService issueService) throws Exception {
  383.         if (log.isDebugEnabled()) {
  384.             log.debug("setIssueFields: called");
  385.         }
  386.         List<CustomField> projectCustomFields = issue.getProject()
  387.                 .getCustomFields();
  388.         if (log.isDebugEnabled()) {
  389.             log.debug("setIssueFields: got project custom fields: " + projectCustomFields);
  390.         }

  391.         if (projectCustomFields == null || projectCustomFields.size() == 0) {
  392.             log.debug("setIssueFields: no custom fields, returning...");
  393.             return;
  394.         }


  395.         // here you see some of the ugly side of Struts 1.3 - the forms... they
  396.         // can only contain Strings and some simple objects types...
  397.         HashMap<String, String> formCustomFields = getCustomFields();

  398.         if (log.isDebugEnabled()) {
  399.             log.debug("setIssueFields: got form custom fields: " + formCustomFields);
  400.         }

  401.         if (formCustomFields == null || formCustomFields.size() == 0) {
  402.             log.debug("setIssueFields: no form custom fields, returning..");
  403.             return;
  404.         }

  405.         ResourceBundle bundle = ITrackerResources.getBundle(locale);
  406.         Iterator<CustomField> customFieldsIt = projectCustomFields.iterator();
  407.         // declare iteration fields
  408.         CustomField field;
  409.         String fieldValue;
  410.         IssueField issueField;
  411.         try {
  412.             if (log.isDebugEnabled()) {
  413.                 log.debug("setIssueFields: processing project fields");
  414.             }
  415.             // set values to issue-fields and add if needed
  416.             while (customFieldsIt.hasNext()) {

  417.                 field = customFieldsIt.next();
  418.                 fieldValue = (String) formCustomFields.get(String.valueOf(field
  419.                         .getId()));

  420.                 // remove the existing field for re-setting
  421.                 issueField = getIssueField(issue, field);


  422.                 if (fieldValue != null && fieldValue.trim().length() > 0) {
  423.                     if (null == issueField) {
  424.                         issueField = new IssueField(issue, field);
  425.                         issue.getFields().add(issueField);
  426.                     }

  427.                     issueField.setValue(fieldValue, bundle);
  428.                 } else {
  429.                     if (null != issueField) {
  430.                         issue.getFields().remove(issueField);
  431.                     }
  432.                 }
  433.             }

  434.             // set new issue fields for later saving
  435. //          issue.setFields(issueFieldsList);

  436. //          issueService.setIssueFields(issue.getId(), issueFieldsList);
  437.         } catch (Exception e) {
  438.             log.error("setIssueFields: failed to process custom fields", e);
  439.             throw e;
  440.         }
  441.     }

  442.     private static IssueField getIssueField(Issue issue, CustomField field) {
  443.         Iterator<IssueField> it = issue.getFields().iterator();
  444.         IssueField issueField;
  445.         while (it.hasNext()) {
  446.             issueField = it.next();
  447.             if (issueField.getCustomField().equals(field)) {
  448.                 return issueField;
  449.             }
  450.         }
  451.         return null;

  452.     }

  453.     private void setOwner(Issue issue, User user,
  454.                                  Map<Integer, Set<PermissionType>> userPermissionsMap) throws Exception {
  455.         if (log.isDebugEnabled()) {
  456.             log.debug("setOwner: called to " + getOwnerId());
  457.         }
  458.         Integer currentOwner = (issue.getOwner() == null) ? null : issue
  459.                 .getOwner().getId();

  460.         Integer ownerId = getOwnerId();

  461.         if (ownerId == null || ownerId.equals(currentOwner)) {
  462.             if (log.isDebugEnabled()) {
  463.                 log.debug("setOwner: returning, existing owner is the same: " + issue.getOwner());
  464.             }
  465.             return;
  466.         }

  467.         if (UserUtilities.hasPermission(userPermissionsMap,
  468.                 UserUtilities.PERMISSION_ASSIGN_OTHERS)
  469.                 || (UserUtilities.hasPermission(userPermissionsMap,
  470.                 UserUtilities.PERMISSION_ASSIGN_SELF) && user.getId()
  471.                 .equals(ownerId))
  472.                 || (UserUtilities.hasPermission(userPermissionsMap,
  473.                 UserUtilities.PERMISSION_UNASSIGN_SELF)
  474.                 && user.getId().equals(currentOwner) && ownerId == -1)) {
  475.             User newOwner = ServletContextUtils.getItrackerServices().getUserService().getUser(ownerId);
  476.             if (log.isDebugEnabled()) {
  477.                 log.debug("setOwner: setting new owner " + newOwner + " to " + issue);
  478.             }
  479.             issue.setOwner(newOwner);
  480. //          issueService.assignIssue(issue.getId(), ownerId, user.getId());
  481.         }

  482.     }

  483.     private void addHistoryEntry(Issue issue, User user) throws Exception {
  484.         try {
  485.             String history = getHistory();

  486.             if (history == null || history.equals("")) {
  487.                 if (log.isDebugEnabled()) {
  488.                     log.debug("addHistoryEntry: skip history to " + issue);
  489.                 }
  490.                 return;
  491.             }


  492.             if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_SURPRESS_HISTORY_HTML, issue.getProject().getOptions())) {
  493.                 history = HTMLUtilities.removeMarkup(history);
  494.             } else if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_LITERAL_HISTORY_HTML, issue.getProject().getOptions())) {
  495.                 history = HTMLUtilities.escapeTags(history);
  496.             } else {
  497.                 history = HTMLUtilities.newlinesToBreaks(history);
  498.             }


  499.             if (log.isDebugEnabled()) {
  500.                 log.debug("addHistoryEntry: adding history to " + issue);
  501.             }
  502.             IssueHistory issueHistory = new IssueHistory(issue, user, history,
  503.                     IssueUtilities.HISTORY_STATUS_AVAILABLE);

  504.             issueHistory.setDescription(getHistory());
  505.             issueHistory.setCreateDate(new Date());

  506.             issueHistory.setLastModifiedDate(new Date());
  507.             issue.getHistory().add(issueHistory);


  508. //  TODO why do we need to updateIssue here, and can not later?
  509. //          issueService.updateIssue(issue, user.getId());
  510.         } catch (Exception e) {
  511.             log.error("addHistoryEntry: failed to add", e);
  512.             throw e;
  513.         }
  514. //      issueService.addIssueHistory(issueHistory);
  515.         if (log.isDebugEnabled()) {
  516.             log.debug("addHistoryEntry: added history for issue " + issue);
  517.         }
  518.     }

  519.     public final Issue processLimitedEdit(Issue issue, Project project,
  520.                                                  User user, Map<Integer, Set<PermissionType>> userPermissionsMap,
  521.                                                  Locale locale, IssueService issueService, ActionMessages messages)
  522.             throws Exception {
  523.         ActionMessages msg = new ActionMessages();
  524.         issue = addAttachment(issue, project, user, ServletContextUtils.getItrackerServices(), msg);

  525.         if (!msg.isEmpty()) {
  526.             messages.add(msg);
  527.             return issue;
  528.         }

  529.         Integer formStatus = getStatus();

  530.         if (formStatus != null) {

  531.             if (issue.getStatus() >= IssueUtilities.STATUS_RESOLVED
  532.                     && formStatus >= IssueUtilities.STATUS_CLOSED
  533.                     && UserUtilities.hasPermission(userPermissionsMap,
  534.                     UserUtilities.PERMISSION_CLOSE)) {

  535.                 issue.setStatus(formStatus);
  536.             }

  537.         }

  538.         applyLimitedFields(issue, project, user, userPermissionsMap, locale, issueService);
  539.         return issueService.updateIssue(issue, user.getId());

  540.     }

  541.     /**
  542.      * method needed to prepare request for edit_issue.jsp
  543.      */
  544.     public static void setupJspEnv(ActionMapping mapping,
  545.                                          IssueForm issueForm, HttpServletRequest request, Issue issue,
  546.                                          IssueService issueService, UserService userService,
  547.                                          Map<Integer, Set<PermissionType>> userPermissions,
  548.                                          Map<Integer, List<NameValuePair>> listOptions, ActionMessages errors)
  549.             throws ServletException, IOException, WorkflowException {

  550.         if (log.isDebugEnabled()) {
  551.             log.debug("setupJspEnv: for issue " + issue);
  552.         }

  553.         NotificationService notificationService = ServletContextUtils
  554.                 .getItrackerServices().getNotificationService();
  555.         String pageTitleKey = "itracker.web.editissue.title";
  556.         String pageTitleArg = request.getParameter("id");
  557.         Locale locale = LoginUtilities.getCurrentLocale(request);
  558.         User um = LoginUtilities.getCurrentUser(request);
  559.         List<NameValuePair> statuses = WorkflowUtilities.getListOptions(
  560.                 listOptions, IssueUtilities.FIELD_STATUS);
  561.         String statusName = IssueUtilities.getStatusName(issue.getStatus(), locale);
  562.         boolean hasFullEdit = UserUtilities.hasPermission(userPermissions,
  563.                 issue.getProject().getId(), UserUtilities.PERMISSION_EDIT_FULL);
  564.         List<NameValuePair> resolutions = WorkflowUtilities.getListOptions(
  565.                 listOptions, IssueUtilities.FIELD_RESOLUTION);
  566.         String severityName = IssueUtilities.getSeverityName(issue
  567.                 .getSeverity(), locale);
  568.         List<NameValuePair> components = WorkflowUtilities.getListOptions(
  569.                 listOptions, IssueUtilities.FIELD_COMPONENTS);
  570.         List<NameValuePair> versions = WorkflowUtilities.getListOptions(
  571.                 listOptions, IssueUtilities.FIELD_VERSIONS);
  572.         List<NameValuePair> targetVersion = WorkflowUtilities.getListOptions(
  573.                 listOptions, IssueUtilities.FIELD_TARGET_VERSION);
  574.         List<Component> issueComponents = issue.getComponents();
  575.         Collections.sort(issueComponents);
  576.         List<Version> issueVersions = issue.getVersions();
  577.         Collections.sort(issueVersions, new Version.VersionComparator());
  578.         /* Get project fields and put name and value in map */
  579.         setupProjectFieldsMapJspEnv(issue.getProject().getCustomFields(), issue.getFields(), request);

  580.         setupRelationsRequestEnv(issue.getRelations(), request);


  581.         request.setAttribute("pageTitleKey", pageTitleKey);
  582.         request.setAttribute("pageTitleArg", pageTitleArg);
  583.         request.getSession().setAttribute(Constants.LIST_OPTIONS_KEY,
  584.                 listOptions);
  585.         request.setAttribute("targetVersions", targetVersion);
  586.         request.setAttribute("components", components);
  587.         request.setAttribute("versions", versions);
  588.         request.setAttribute("hasIssueNotification", notificationService
  589.                 .hasIssueNotification(issue, um.getId()));
  590.         request.setAttribute("hasHardIssueNotification", IssueUtilities.hasHardNotification(issue, issue.getProject(), um.getId()));
  591.         request.setAttribute("hasEditIssuePermission", UserUtilities
  592.                 .hasPermission(userPermissions, issue.getProject().getId(),
  593.                         UserUtilities.PERMISSION_EDIT));
  594.         request.setAttribute("canCreateIssue",
  595.                 issue.getProject().getStatus() == Status.ACTIVE
  596.                         && UserUtilities.hasPermission(userPermissions, issue
  597.                         .getProject().getId(),
  598.                         UserUtilities.PERMISSION_CREATE));
  599.         request.setAttribute("issueComponents", issueComponents);
  600.         request.setAttribute("issueVersions",
  601.                 issueVersions == null ? new ArrayList<Version>()
  602.                         : issueVersions);
  603.         request.setAttribute("statuses", statuses);
  604.         request.setAttribute("statusName", statusName);
  605.         request.setAttribute("hasFullEdit", hasFullEdit);
  606.         request.setAttribute("resolutions", resolutions);
  607.         request.setAttribute("severityName", severityName);
  608.         request.setAttribute("hasPredefinedResolutionsOption", ProjectUtilities
  609.                 .hasOption(ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS,
  610.                         issue.getProject().getOptions()));
  611.         request.setAttribute("issueOwnerName",
  612.                 (issue.getOwner() == null ? ITrackerResources.getString(
  613.                         "itracker.web.generic.unassigned", locale)
  614.                         : issue.getOwner().getFirstName() + " "
  615.                         + issue.getOwner().getLastName()));
  616.         request.setAttribute("isStatusResolved",
  617.                 issue.getStatus() >= IssueUtilities.STATUS_RESOLVED);


  618.         request.setAttribute("fieldSeverity", WorkflowUtilities.getListOptions(
  619.                 listOptions, IssueUtilities.FIELD_SEVERITY));
  620.         request.setAttribute("possibleOwners", WorkflowUtilities
  621.                 .getListOptions(listOptions, IssueUtilities.FIELD_OWNER));

  622.         request.setAttribute("hasNoViewAttachmentOption", ProjectUtilities
  623.                 .hasOption(ProjectUtilities.OPTION_NO_ATTACHMENTS, issue
  624.                         .getProject().getOptions()));

  625.         if (log.isDebugEnabled()) {
  626.             log.debug("setupJspEnv: options " + issue.getProject().getOptions() + ", hasNoViewAttachmentOption: " + request.getAttribute("hasNoViewAttachmentOption"));
  627.         }

  628.         setupNotificationsInRequest(request, issue, notificationService);

  629.         // setup issue to request, as it's needed by the jsp.
  630.         request.setAttribute(Constants.ISSUE_KEY, issue);
  631.         request.setAttribute("issueForm", issueForm);
  632.         request.setAttribute(Constants.PROJECT_KEY, issue.getProject());
  633.         List<IssueHistory> issueHistory = issueService.getIssueHistory(issue
  634.                 .getId());
  635.         Collections.sort(issueHistory, IssueHistory.CREATE_DATE_COMPARATOR);
  636.         request.setAttribute("issueHistory", issueHistory);


  637.     }

  638.     /**
  639.      * Get project fields and put name and value in map
  640.      * TODO: simplify this code, it's not readable, unsave yet.
  641.      */
  642.     public static final void setupProjectFieldsMapJspEnv(List<CustomField> projectFields, Collection<IssueField> issueFields, HttpServletRequest request) {
  643.         Map<CustomField, String> projectFieldsMap = new HashMap<CustomField, String>();

  644.         if (projectFields != null && projectFields.size() > 0) {
  645.             Collections.sort(projectFields, CustomField.ID_COMPARATOR);

  646.             HashMap<String, String> fieldValues = new HashMap<String, String>();
  647.             Iterator<IssueField> issueFieldsIt = issueFields.iterator();
  648.             while (issueFieldsIt.hasNext()) {
  649.                 IssueField issueField = issueFieldsIt.next();

  650.                 if (issueField.getCustomField() != null
  651.                         && issueField.getCustomField().getId() > 0) {
  652.                     if (issueField.getCustomField().getFieldType() == CustomField.Type.DATE) {
  653.                         Locale locale = LoginUtilities.getCurrentLocale(request);
  654.                         String value = issueField.getValue(locale);
  655.                         fieldValues.put(issueField.getCustomField().getId()
  656.                                 .toString(), value);
  657.                     } else {
  658.                         fieldValues.put(issueField.getCustomField().getId()
  659.                                 .toString(), issueField
  660.                                 .getStringValue());
  661.                     }
  662.                 }
  663.             }
  664.             Iterator<CustomField> fieldsIt = projectFields.iterator();
  665.             CustomField field;
  666.             while (fieldsIt.hasNext()) {

  667.                 field = fieldsIt.next();

  668.                 String fieldValue = fieldValues.get(String.valueOf(field
  669.                         .getId()));
  670.                 if (null == fieldValue) {
  671.                     fieldValue = "";
  672.                 };
  673.                 projectFieldsMap.put(field, fieldValue);

  674.             }

  675.             request.setAttribute("projectFieldsMap", projectFieldsMap);
  676.         }
  677.     }

  678.     protected static void setupRelationsRequestEnv(List<IssueRelation> relations, HttpServletRequest request) {
  679.         Collections.sort(relations, IssueRelation.LAST_MODIFIED_DATE_COMPARATOR);
  680.         request.setAttribute("issueRelations", relations);

  681.     }

  682.     public static void setupNotificationsInRequest(
  683.             HttpServletRequest request, Issue issue,
  684.             NotificationService notificationService) {
  685.         List<Notification> notifications = notificationService
  686.                 .getIssueNotifications(issue);

  687.         Collections.sort(notifications, Notification.TYPE_COMPARATOR);

  688.         request.setAttribute("notifications", notifications);
  689.         Map<User, Set<Notification.Role>> notificationMap = NotificationUtilities
  690.                 .mappedRoles(notifications);
  691.         request.setAttribute("notificationMap", notificationMap);
  692.         request.setAttribute("notifiedUsers", notificationMap.keySet());
  693.     }

  694.     /**
  695.      * Adds an attachment to issue.
  696.      *
  697.      * @return updated issue
  698.      */
  699.     public Issue addAttachment(Issue issue, Project project, User user,
  700.                                        ITrackerServices services, ActionMessages messages) {


  701.         FormFile file = getAttachment();

  702.         if (file == null || file.getFileName().trim().length() < 1) {
  703.             log.info("addAttachment: skipping file " + file);
  704.             return issue;
  705.         }

  706.         if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_NO_ATTACHMENTS,
  707.                 project.getOptions())) {
  708.             messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.validate.attachment.disabled", project.getName()));
  709.             return issue;
  710.         }

  711.         String origFileName = file.getFileName();
  712.         String contentType = file.getContentType();
  713.         int fileSize = file.getFileSize();

  714.         String attachmentDescription = getAttachmentDescription();

  715.         if (null == contentType || 0 >= contentType.length()) {
  716.             log.info("addAttachment: got no mime-type, using default plain-text");
  717.             contentType = "text/plain";
  718.         }

  719.         if (log.isDebugEnabled()) {
  720.             log.debug("addAttachment: adding file, name: " + origFileName
  721.                     + " of type " + file.getContentType() + ", description: "
  722.                     + getAttachmentDescription() + ". filesize: " + fileSize);
  723.         }
  724.         ActionMessages validation = AttachmentUtilities.validate(file, services);
  725.         if (validation.isEmpty()) {

  726. //      if (AttachmentUtilities.checkFile(file, getITrackerServices())) {
  727.             int lastSlash = Math.max(origFileName.lastIndexOf('/'),
  728.                     origFileName.lastIndexOf('\\'));
  729.             if (lastSlash > -1) {
  730.                 origFileName = origFileName.substring(lastSlash + 1);
  731.             }

  732.             IssueAttachment attachmentModel = new IssueAttachment(issue,
  733.                     origFileName, contentType, attachmentDescription, fileSize,
  734.                     user);

  735.             attachmentModel.setIssue(issue);
  736. //          issue.getAttachments().add(attachmentModel);
  737.             byte[] fileData;
  738.             try {
  739.                 fileData = file.getFileData();
  740.             } catch (IOException e) {
  741.                 log.error("addAttachment: failed to get file data", e);
  742.                 messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system"));
  743.                 return issue;
  744.             }
  745.             if (services.getIssueService()
  746.                     .addIssueAttachment(attachmentModel, fileData)) {
  747.                 return services.getIssueService().getIssue(issue.getId());
  748.             }


  749.         } else {
  750.             if (log.isDebugEnabled()) {
  751.                 log.debug("addAttachment: failed to validate: " + origFileName + ", " + validation);
  752.             }
  753.             messages.add(validation);
  754.         }
  755.         return issue;
  756.     }

  757.     public final void setupIssueForm(Issue issue,
  758.                                             final Map<Integer, List<NameValuePair>> listOptions,
  759.                                             HttpServletRequest request, ActionMessages errors)
  760.             throws WorkflowException {
  761.         HttpSession session = request.getSession(true);

  762.         IssueService issueService = ServletContextUtils.getItrackerServices()
  763.                 .getIssueService();
  764.         Locale locale = (Locale) session.getAttribute(Constants.LOCALE_KEY);
  765.         setId(issue.getId());
  766.         setProjectId(issue.getProject().getId());
  767.         setPrevStatus(issue.getStatus());
  768.         setCaller(request.getParameter("caller"));

  769.         setDescription(HTMLUtilities.handleQuotes(issue
  770.                 .getDescription()));
  771.         setStatus(issue.getStatus());

  772.         if (!ProjectUtilities.hasOption(
  773.                 ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS, issue
  774.                 .getProject().getOptions())) {
  775.             // TODO What happens here, validation?
  776.             try {
  777.                 issue.setResolution(IssueUtilities.checkResolutionName(issue
  778.                         .getResolution(), locale));
  779.             } catch (MissingResourceException | NumberFormatException mre) {
  780.                 log.error(mre.getMessage());
  781.             }
  782.         }

  783.         setResolution(HTMLUtilities.handleQuotes(issue
  784.                 .getResolution()));
  785.         setSeverity(issue.getSeverity());

  786.         setTargetVersion(issue.getTargetVersion() == null ? -1
  787.                 : issue.getTargetVersion().getId());

  788.         setOwnerId((issue.getOwner() == null ? -1 : issue.getOwner()
  789.                 .getId()));

  790.         List<IssueField> fields = issue.getFields();
  791.         HashMap<String, String> customFields = new HashMap<String, String>();
  792.         for (IssueField field : fields) {
  793.             customFields.put(field.getCustomField().getId().toString(),
  794.                     field.getValue(locale));
  795.         }

  796.         setCustomFields(customFields);

  797.         HashSet<Integer> selectedComponents = issueService
  798.                 .getIssueComponentIds(issue.getId());
  799.         if (selectedComponents != null) {
  800.             Integer[] componentIds;
  801.             ArrayList<Integer> components = new ArrayList<>(
  802.                     selectedComponents);
  803.             componentIds = components.toArray(new Integer[components.size()]);
  804.             setComponents(componentIds);
  805.         }

  806.         HashSet<Integer> selectedVersions = issueService
  807.                 .getIssueVersionIds(issue.getId());
  808.         if (selectedVersions != null) {
  809.             Integer[] versionIds;
  810.             ArrayList<Integer> versions = new ArrayList<>(
  811.                     selectedVersions);
  812.             versionIds = versions.toArray(new Integer[versions.size()]);
  813.             setVersions(versionIds);
  814.         }

  815.         invokeProjectScripts(issue.getProject(), WorkflowUtilities.EVENT_FIELD_ONPOPULATE, listOptions, errors);

  816.     }

  817.     public void invokeProjectScripts(Project project, int event, final Map<Integer, List<NameValuePair>> options, ActionMessages errors)
  818.             throws WorkflowException {
  819.         final Map<Integer, String> values = new HashMap<>(options.size());
  820.         for (CustomField field: project.getCustomFields()) {
  821.             values.put(field.getId()
  822.                     , getCustomFields().get(String.valueOf(field.getId())));
  823.         }
  824.         values.put(Configuration.Type.status.getLegacyCode(),
  825.                 String.valueOf(getStatus()));
  826.         values.put(Configuration.Type.severity.getLegacyCode(),
  827.                 String.valueOf(getSeverity()));
  828.         values.put(Configuration.Type.resolution.getLegacyCode(),
  829.                 getResolution());

  830.         processFieldScripts(project.getScripts(),
  831.                 event, values, options, errors);

  832.     }

  833.     public  Map<Integer, List<NameValuePair>> invokeProjectScripts(Project project, int event, ActionMessages errors)
  834.             throws WorkflowException {

  835.         final Map<Integer, List<NameValuePair>> options = EditIssueActionUtil.mappedFieldOptions(project.getCustomFields()) ;
  836.         invokeProjectScripts(project, event, options, errors);
  837.         return options;
  838.     }

  839.     public FormFile getAttachment() {
  840.         return attachment;
  841.     }

  842.     public void setAttachment(FormFile attachment) {
  843.         this.attachment = attachment;
  844.     }

  845.     public String getAttachmentDescription() {
  846.         return attachmentDescription;
  847.     }

  848.     public void setAttachmentDescription(String attachmentDescription) {
  849.         this.attachmentDescription = attachmentDescription;
  850.     }

  851.     public String getCaller() {
  852.         return caller;
  853.     }

  854.     public void setCaller(String caller) {
  855.         this.caller = caller;
  856.     }

  857.     public Integer[] getComponents() {
  858.         if (null == components)
  859.             return null;
  860.         return components.clone();
  861.     }

  862.     public void setComponents(Integer[] components) {
  863.         if (null == components)
  864.             this.components = null;
  865.         else
  866.             this.components = components.clone();
  867.     }

  868.     public Integer getCreatorId() {
  869.         return creatorId;
  870.     }

  871.     public void setCreatorId(Integer creatorId) {
  872.         this.creatorId = creatorId;
  873.     }

  874.     // let's try to put Integer,String here:
  875.     public HashMap<String, String> getCustomFields() {
  876.         return customFields;
  877.     }

  878.     // let's try to put Integer,String here:
  879.     public void setCustomFields(HashMap<String, String> customFields) {
  880.         this.customFields = customFields;
  881.     }

  882.     public String getDescription() {
  883.         return description;
  884.     }

  885.     public void setDescription(String description) {
  886.         this.description = description;
  887.     }

  888.     public String getHistory() {
  889.         return history;
  890.     }

  891.     public void setHistory(String history) {
  892.         this.history = history;
  893.     }

  894.     public Integer getId() {
  895.         return id;
  896.     }

  897.     public void setId(Integer id) {
  898.         this.id = id;
  899.     }

  900.     public Integer getOwnerId() {
  901.         return ownerId;
  902.     }

  903.     public void setOwnerId(Integer ownerId) {
  904.         this.ownerId = ownerId;
  905.     }

  906.     public Integer getPrevStatus() {
  907.         return prevStatus;
  908.     }

  909.     public void setPrevStatus(Integer prevStatus) {
  910.         this.prevStatus = prevStatus;
  911.     }

  912.     public Integer getProjectId() {
  913.         return projectId;
  914.     }

  915.     public void setProjectId(Integer projectId) {
  916.         this.projectId = projectId;
  917.     }

  918.     public Integer getRelatedIssueId() {
  919.         return relatedIssueId;
  920.     }

  921.     public void setRelatedIssueId(Integer relatedIssueId) {
  922.         this.relatedIssueId = relatedIssueId;
  923.     }

  924.     public IssueRelation.Type getRelationType() {
  925.         return relationType;
  926.     }

  927.     public void setRelationType(IssueRelation.Type relationType) {
  928.         this.relationType = relationType;
  929.     }

  930.     public String getResolution() {
  931.         return resolution;
  932.     }

  933.     public void setResolution(String resolution) {
  934.         this.resolution = resolution;
  935.     }

  936.     public Integer getSeverity() {
  937.         return severity;
  938.     }

  939.     public void setSeverity(Integer severity) {
  940.         this.severity = severity;
  941.     }

  942.     public Integer getStatus() {
  943.         return status;
  944.     }

  945.     public void setStatus(Integer status) {
  946.         this.status = status;
  947.     }

  948.     public Integer getTargetVersion() {
  949.         return targetVersion;
  950.     }

  951.     public void setTargetVersion(Integer targetVersion) {
  952.         this.targetVersion = targetVersion;
  953.     }

  954.     public Integer[] getVersions() {
  955.         if (null == versions)
  956.             return null;
  957.         return versions.clone();
  958.     }

  959.     public void setVersions(Integer[] versions) {
  960.         if (null == versions)
  961.             this.versions = null;
  962.         else
  963.             this.versions = versions.clone();
  964.     }

  965.     /**
  966.      * This methods adds in validation for custom fields. It makes sure the
  967.      * datatype matches and also that all required fields have been populated.
  968.      *
  969.      * @param mapping the ActionMapping object
  970.      * @param request the current HttpServletRequest object
  971.      * @return an ActionErrors object containing any validation errors
  972.      */
  973.     public ActionErrors validate(ActionMapping mapping,
  974.                                  HttpServletRequest request) {
  975.         if (log.isDebugEnabled()) {
  976.             log.debug("validate called: mapping: " + mapping + ", request: "
  977.                     + request);
  978.         }
  979.         ActionErrors errors = super.validate(mapping, request);

  980.         if (log.isDebugEnabled()) {
  981.             log.debug("validate called: mapping: " + mapping + ", request: "
  982.                     + request + ", errors: " + errors);
  983.         }

  984.         try {
  985.             if (null != getId()) {
  986.                 Issue issue;
  987.                 try {
  988.                     issue = getITrackerServices().getIssueService().getIssue(
  989.                             getId());
  990.                 } catch (Exception e) {
  991.                     return errors;
  992.                 }

  993.                 Locale locale = (Locale) request.getSession().getAttribute(
  994.                         Constants.LOCALE_KEY);
  995.                 User currUser = (User) request.getSession().getAttribute(
  996.                         Constants.USER_KEY);
  997.                 List<NameValuePair> ownersList = EditIssueActionUtil
  998.                         .getAssignableIssueOwnersList(issue,
  999.                                 issue.getProject(), currUser, locale,
  1000.                                 getITrackerServices().getUserService(),
  1001.                                 RequestHelper.getUserPermissions(request
  1002.                                         .getSession()));

  1003.                 setupJspEnv(mapping, this, request, issue,
  1004.                         getITrackerServices().getIssueService(),
  1005.                         getITrackerServices().getUserService(), RequestHelper
  1006.                         .getUserPermissions(request.getSession()),
  1007.                         EditIssueActionUtil.getListOptions(request, issue,
  1008.                                 ownersList, RequestHelper
  1009.                                 .getUserPermissions(request
  1010.                                         .getSession()), issue
  1011.                                 .getProject(), currUser), errors);

  1012.                 if (errors.isEmpty() && issue.getProject() == null) {
  1013.                     if (log.isDebugEnabled()) {
  1014.                         log.debug("validate: issue project is null: " + issue);
  1015.                     }
  1016.                     errors.add(ActionMessages.GLOBAL_MESSAGE,
  1017.                             new ActionMessage(
  1018.                                     "itracker.web.error.invalidproject"));
  1019.                 } else if (errors.isEmpty()
  1020.                         && issue.getProject().getStatus() != Status.ACTIVE) {
  1021.                     if (log.isDebugEnabled()) {
  1022.                         log.debug("validate: issue project is not active: " + issue);
  1023.                     }
  1024.                     errors.add(ActionMessages.GLOBAL_MESSAGE,
  1025.                             new ActionMessage(
  1026.                                     "itracker.web.error.projectlocked"));
  1027.                 } else if (errors.isEmpty() && !"editIssueForm".equals(mapping.getName())) {
  1028.                     if (log.isDebugEnabled()) {
  1029.                         log.debug("validate: validation had errors for " + issue + ": " + errors);
  1030.                     }

  1031.                     if (UserUtilities.hasPermission(RequestHelper.getUserPermissions(request.getSession()),
  1032.                             issue.getProject().getId(),
  1033.                             UserUtilities.PERMISSION_EDIT_FULL)) {
  1034.                         validateProjectFields(issue.getProject(), request, errors);
  1035.                     }


  1036.                     validateProjectScripts(issue.getProject(), errors);
  1037.                     validateAttachment(this.getAttachment(), getITrackerServices(), errors);
  1038.                 }
  1039.             } else {
  1040.                 EditIssueActionUtil.setupCreateIssue(request);
  1041.                 HttpSession session = request.getSession();
  1042.                 Project project = (Project) session
  1043.                         .getAttribute(Constants.PROJECT_KEY);
  1044.                 if (log.isDebugEnabled()) {
  1045.                     log.debug("validate: validating create new issue for project: " + page);
  1046.                 }
  1047.                 validateProjectFields(project, request, errors);
  1048.                 validateProjectScripts(project, errors);
  1049.                 validateAttachment(this.getAttachment(), getITrackerServices(), errors);
  1050.             }
  1051.         } catch (Exception e) {
  1052.             e.printStackTrace();
  1053.             log.error("validate: unexpected exception", e);
  1054.             errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
  1055.                     "itracker.web.error.system"));
  1056.         }
  1057.         if (log.isDebugEnabled()) {
  1058.             log.debug("validate: returning errors: " + errors);
  1059.         }
  1060.         return errors;
  1061.     }

  1062.     private static void validateAttachment(FormFile attachment, ITrackerServices services, ActionMessages errors) {
  1063.         if (null != attachment) {
  1064.             ActionMessages msg = AttachmentUtilities.validate(attachment, services);
  1065.             if (!msg.isEmpty()) {
  1066.                 if (log.isDebugEnabled()) {
  1067.                     log.debug("validateAttachment: failed to validate, " + msg);
  1068.                 }
  1069.                 errors.add(msg);
  1070.             }
  1071.         }
  1072.     }

  1073.     private static void validateProjectFields(Project project,
  1074.                                               HttpServletRequest request, ActionErrors errors) {

  1075.         List<CustomField> projectFields = project.getCustomFields();
  1076.         if (null != projectFields && projectFields.size() > 0) {

  1077.             Locale locale = LoginUtilities.getCurrentLocale(request);

  1078.             ResourceBundle bundle = ITrackerResources.getBundle(locale);
  1079.             for (CustomField customField : projectFields) {
  1080.                 String fieldValue = request.getParameter("customFields("
  1081.                         + customField.getId() + ")");
  1082.                 if (fieldValue != null && !fieldValue.equals("")) {

  1083.                     // Don't create an IssueField only so that we can call
  1084.                     // setValue to validate the value!
  1085.                     try {
  1086.                         customField.checkAssignable(fieldValue, locale, bundle);
  1087.                     } catch (IssueException ie) {
  1088.                         String label = CustomFieldUtilities.getCustomFieldName(
  1089.                                 customField.getId(), locale);
  1090.                         errors.add(ActionMessages.GLOBAL_MESSAGE,
  1091.                                 new ActionMessage(ie.getType(), label));
  1092.                     }
  1093.                 } else if (customField.isRequired()) {
  1094.                     String label = CustomFieldUtilities.getCustomFieldName(
  1095.                             customField.getId(), locale);
  1096.                     errors.add(ActionMessages.GLOBAL_MESSAGE,
  1097.                             new ActionMessage(IssueException.TYPE_CF_REQ_FIELD,
  1098.                                     label));
  1099.                 }
  1100.             }
  1101.         }
  1102.     }

  1103.     private void validateProjectScripts(Project project, ActionErrors errors)
  1104.             throws WorkflowException {

  1105.         invokeProjectScripts(project, WorkflowUtilities.EVENT_FIELD_ONVALIDATE, errors);

  1106.     }

  1107.     public static boolean isWorkflowScriptsAllowed() {
  1108.         Boolean val = ServletContextUtils.getItrackerServices().getConfigurationService().getBooleanProperty("allow_workflowscripts", true);
  1109.         if (log.isDebugEnabled()) {
  1110.             log.debug("isWorkflowScriptsAllowed: {}allowed by configuration 'allow_workflowscripts'", !val?"NOT ":"");
  1111.         }
  1112.         return val;
  1113.     }

  1114. }