IssueForm.java
/*
* This software was designed and created by Jason Carroll.
* Copyright (c) 2002, 2003, 2004 Jason Carroll.
* The author can be reached at jcarroll@cowsultants.com
* ITracker website: http://www.cowsultants.com
* ITracker forums: http://www.cowsultants.com/phpBB/index.php
*
* This program is free software; you can redistribute it and/or modify
* it only under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package org.itracker.web.forms;
import bsh.EvalError;
import bsh.Interpreter;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.upload.FormFile;
import org.itracker.IssueException;
import org.itracker.WorkflowException;
import org.itracker.core.resources.ITrackerResources;
import org.itracker.model.*;
import org.itracker.model.util.*;
import org.itracker.services.*;
import org.itracker.web.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;
/**
* This form is by the struts actions to pass issue data.
*/
public class IssueForm extends ITrackerForm {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(IssueForm.class);
private Integer id = null;
private String caller = null;
private Integer projectId = null;
private Integer creatorId = null;
private Integer ownerId = null;
private String description = null;
private Integer severity = null;
private Integer status = null;
private Integer prevStatus = null;
private String resolution = null;
private Integer targetVersion = null;
private Integer[] components = new Integer[0];
private Integer[] versions = new Integer[0];
private String attachmentDescription = null;
transient private FormFile attachment = null;
private String history = null;
// lets try to put Integer,String here:
private HashMap<String, String> customFields = new HashMap<>();
private IssueRelation.Type relationType = null;
private Integer relatedIssueId = null;
/**
* The most general way to run scripts. All matching of event and fields
* are embedded within. As a result, optionValues parameter will
* contain updated values and form will contain new default values
* if appropriate.
*
* @param projectScriptModels is a list of scripts.
* @param event is an event type.
* @param currentValues values mapped to field-ids
* @param optionValues is a map of current values to fields by field-Id.
* @param currentErrors is a container for errors.
*/
public void processFieldScripts(List<ProjectScript> projectScriptModels, int event, Map<Integer, String> currentValues, Map<Integer, List<NameValuePair>> optionValues, ActionMessages currentErrors) throws WorkflowException {
if ((!isWorkflowScriptsAllowed()) || projectScriptModels == null || projectScriptModels.size() == 0)
return;
log.debug("Processing " + projectScriptModels.size() + " field scripts for project " + projectScriptModels.get(0).getProject().getId());
List<ProjectScript> scriptsToRun = new ArrayList<>(projectScriptModels.size());
for (ProjectScript model : projectScriptModels) {
if (model.getScript().getEvent() == event) {
scriptsToRun.add(model);
}
}
// order the scripts by priority
Collections.sort(scriptsToRun, ProjectScript.PRIORITY_COMPARATOR);
if (log.isDebugEnabled()) {
log.debug(scriptsToRun.size() + " eligible scripts found for event " + event);
}
String currentValue;
for (ProjectScript currentScript : scriptsToRun) {
try {
currentValue = currentValues.get((currentScript.getFieldType() == Configuration.Type.customfield?
currentScript.getFieldId():currentScript.getFieldType().getLegacyCode()));
log.debug("Running script " + currentScript.getScript().getId()
+ " with priority " + currentScript.getPriority());
log.debug("Before script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId())
+ " (" + currentScript.getFieldId() + ") is "
+ currentValue + "'");
List<NameValuePair> options;
if (currentScript.getFieldType() == Configuration.Type.customfield) {
options = optionValues.get(currentScript.getFieldId());
if (null == options) {
options = Collections.emptyList();
optionValues.put(currentScript.getFieldId(), options);
}
} else {
if (!optionValues.containsKey(currentScript.getFieldType().getLegacyCode())){
options = Collections.emptyList();
optionValues.put(currentScript.getFieldType().getLegacyCode(), options);
} else {
options = optionValues.get(currentScript.getFieldType().getLegacyCode());
}
}
currentValue = processFieldScript(currentScript, event,
currentValue,
options, currentErrors);
currentValues.put( (currentScript.getFieldType() == Configuration.Type.customfield?
currentScript.getFieldId():currentScript.getFieldType().getLegacyCode()),
currentValue );
log.debug("After script current value for field " + IssueUtilities.getFieldName(currentScript.getFieldId())
+ " (" + currentScript.getFieldId() + ") is "
+ currentValue + "'");
} catch (WorkflowException we) {
log.error("Error processing script ", we);
currentErrors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system.message", we.getMessage(), "Workflow"));
}
}
// apply new values
for (ProjectScript script: projectScriptModels) {
if (script.getScript().getEvent() == event) {
final String val;
switch (script.getFieldType()) {
case status:
val = currentValues.get(Configuration.Type.status.getLegacyCode());
try {
setStatus(Integer.valueOf(val));
} catch (RuntimeException re) {/* OK */}
break;
case severity:
val = currentValues.get(Configuration.Type.severity.getLegacyCode());
try {
setSeverity(Integer.valueOf(val));
} catch (RuntimeException re) {/* OK */}
break;
case resolution:
val = currentValues.get(Configuration.Type.resolution.getLegacyCode());
setResolution(val);
break;
case customfield:
getCustomFields().put(String.valueOf(script.getFieldId()), currentValues.get(script.getFieldId()));
break;
default:
log.warn("unsupported field type in script: " + script.getFieldType() + " in project " + script.getProject().getName());
break;
}
}
}
}
/**
* Run provided BEANSHELL script against form instance, taking into account
* incoming event type, field raised an event and current values.
* As a result, a set of new current values is returned and if
* appropriate, default values are changed in form.
* TODO: should issue, project, user, services be available too?
*
* @param projectScript is a script to run.
* @param event is an event type.
* @param currentValue the current field value
* @param optionValues is a set of valid option-values.
* @param currentErrors is a container for occured errors.
* @return new changed currentValue.
*/
public String processFieldScript(ProjectScript projectScript, int event, String currentValue, List<NameValuePair> optionValues, ActionMessages currentErrors) throws WorkflowException {
if (projectScript == null) {
throw new WorkflowException("ProjectScript was null.", WorkflowException.INVALID_ARGS);
}
if (currentErrors == null) {
throw new WorkflowException("Errors was null.", WorkflowException.INVALID_ARGS);
}
if (!isWorkflowScriptsAllowed()) {
return currentValue;
}
String result = currentValue;
try {
if (projectScript.getScript().getLanguage() != WorkflowScript.ScriptLanguage.Groovy) {
result = processBeanShellScript(projectScript, currentValue, optionValues, currentErrors, event);
} else {
result = processGroovyScript(projectScript, currentValue, optionValues, currentErrors, event);
}
if (log.isDebugEnabled()) {
log.debug("processFieldScript: Script returned current value of '" + optionValues + "' (" + (optionValues != null ? optionValues.getClass().getName() : "NULL") + ")");
}
} catch (EvalError evalError) {
log.error("processFieldScript: eval failed: " + projectScript, evalError);
currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("itracker.web.error.invalidscriptdata", evalError.getMessage()));
} catch (RuntimeException e) {
log.warn("processFieldScript: Error processing field script: " + projectScript, e);
currentErrors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage("itracker.web.error.system.message",
new Object[]{
e.getMessage(),
ITrackerResources.getString("itracker.web.attr.script") // Script
}));
}
if (log.isDebugEnabled()) {
log.debug("processFieldScript: returning " + result + ", errors: " + currentErrors);
}
return result;
}
private String processGroovyScript(final ProjectScript projectScript,
final String currentValue,
final List<NameValuePair> optionValues,
final ActionMessages currentErrors,
final int event) {
final Map<String,Object> ctx = new HashMap<>(8);
ctx.put("currentValue", StringUtils.defaultString(currentValue));
ctx.put("event", event);
ctx.put("fieldId", (projectScript.getFieldType() == Configuration.Type.customfield ?
projectScript.getFieldId() : projectScript.getFieldType().getLegacyCode()));
ctx.put("optionValues", Collections.unmodifiableList(optionValues));
ctx.put("currentErrors", currentErrors);
ctx.put("currentForm", this);
final Binding binding = new Binding(ctx);
GroovyShell shell = new GroovyShell();
Script script = shell.parse(projectScript.getScript().getScript(),
projectScript.getScript().getName());
script.setBinding(binding);
Object ret = script.run();
if (!currentErrors.isEmpty()) {
return currentValue;
}
return returnScriptResult(ret, ctx.get("currentValue"), currentValue);
}
private String processBeanShellScript(final ProjectScript projectScript,
final String currentValue,
final List<NameValuePair> optionValues,
final ActionMessages currentErrors,
final int event) throws EvalError {
Interpreter bshInterpreter = new Interpreter();
bshInterpreter.set("event", event);
bshInterpreter.set("fieldId", (projectScript.getFieldType()== Configuration.Type.customfield?
projectScript.getFieldId():projectScript.getFieldType().getLegacyCode()));
bshInterpreter.set("currentValue", StringUtils.defaultString(currentValue));
bshInterpreter.set("optionValues", optionValues);
bshInterpreter.set("currentErrors", currentErrors);
bshInterpreter.set("currentForm", this);
Object obj = bshInterpreter.eval(projectScript.getScript().getScript());
if (!currentErrors.isEmpty()) {
return currentValue;
}
return returnScriptResult(obj, bshInterpreter.get("currentValue"), currentValue);
}
private static String returnScriptResult(Object returned, Object assigned, String currentValue) {
if (! (returned instanceof CharSequence)) {
log.debug("script did not return a value");
returned = assigned;
}
if (returned instanceof CharSequence) {
return String.valueOf(returned);
}
log.debug("failed to get value from script, returning previous value");
return currentValue;
}
public final Issue processFullEdit(Issue issue, Project project, User user,
Map<Integer, Set<PermissionType>> userPermissions, Locale locale,
IssueService issueService, ActionMessages errors) throws Exception {
int previousStatus = issue.getStatus();
boolean needReloadIssue;
ActionMessages msg = new ActionMessages();
issue = addAttachment(issue, project, user, getITrackerServices(), msg);
if (!msg.isEmpty()) {
// Validation of attachment failed
errors.add(msg);
return issue;
}
needReloadIssue = issueService.setIssueVersions(issue.getId(),
new HashSet<>(Arrays.asList(getVersions())),
user.getId());
needReloadIssue = needReloadIssue | issueService.setIssueComponents(issue.getId(),
new HashSet<>(Arrays.asList(getComponents())),
user.getId());
// reload issue for further updates
if (needReloadIssue) {
if (log.isDebugEnabled()) {
log.debug("processFullEdit: updating issue from session: " + issue);
}
issue = issueService.getIssue(issue.getId());
}
Integer targetVersion = getTargetVersion();
if (targetVersion != null && targetVersion != -1) {
ProjectService projectService = ServletContextUtils.getItrackerServices()
.getProjectService();
Version version = projectService.getProjectVersion(targetVersion);
if (version == null) {
throw new RuntimeException("No version with Id "
+ targetVersion);
}
issue.setTargetVersion(version);
}
issue.setResolution(getResolution());
issue.setSeverity(getSeverity());
applyLimitedFields(issue, project, user, userPermissions, locale, issueService);
Integer formStatus = getStatus();
issue.setStatus(formStatus);
if (formStatus != null) {
if (log.isDebugEnabled()) {
log.debug("processFullEdit: processing status: " + formStatus);
}
if (previousStatus != -1) {
// Reopened the issue. Reset the resolution field.
if ((previousStatus >= IssueUtilities.STATUS_ASSIGNED && previousStatus < IssueUtilities.STATUS_RESOLVED)
&& (previousStatus >= IssueUtilities.STATUS_RESOLVED && previousStatus < IssueUtilities.STATUS_END)) {
issue.setResolution("");
}
if (previousStatus >= IssueUtilities.STATUS_CLOSED
&& !UserUtilities.hasPermission(userPermissions, project
.getId(), UserUtilities.PERMISSION_CLOSE)) {
if (previousStatus < IssueUtilities.STATUS_CLOSED) {
issue.setStatus(previousStatus);
} else {
issue.setStatus(IssueUtilities.STATUS_RESOLVED);
}
}
if (issue.getStatus() < IssueUtilities.STATUS_NEW
|| issue.getStatus() >= IssueUtilities.STATUS_END) {
issue.setStatus(previousStatus);
}
} else if (issue.getStatus() >= IssueUtilities.STATUS_CLOSED
&& !UserUtilities.hasPermission(userPermissions, project
.getId(), PermissionType.ISSUE_CLOSE)) {
issue.setStatus(IssueUtilities.STATUS_RESOLVED);
}
}
if (issue.getStatus() < IssueUtilities.STATUS_NEW) {
if (log.isDebugEnabled()) {
log.debug("processFullEdit: status < STATUS_NEW: " + issue.getStatus());
}
issue.setStatus(IssueUtilities.STATUS_NEW);
if (log.isDebugEnabled()) {
log.debug("processFullEdit: updated to: " + issue.getStatus());
}
} else if (issue.getStatus() >= IssueUtilities.STATUS_END) {
if (log.isDebugEnabled()) {
log.debug("processFullEdit: status >= STATUS_END: " + issue.getStatus());
}
if (!UserUtilities.hasPermission(userPermissions, project.getId(),
PermissionType.ISSUE_CLOSE)) {
issue.setStatus(IssueUtilities.STATUS_RESOLVED);
} else {
issue.setStatus(IssueUtilities.STATUS_CLOSED);
}
if (log.isDebugEnabled()) {
log.debug("processFullEdit: status updated to: " + issue.getStatus());
}
}
if (log.isDebugEnabled()) {
log.debug("processFullEdit: updating issue " + issue);
}
return issueService.updateIssue(issue, user.getId());
}
public final void applyLimitedFields(Issue issue, Project project,
User user, Map<Integer, Set<PermissionType>> userPermissionsMap,
Locale locale, IssueService issueService) throws Exception {
issue.setDescription(getDescription());
setIssueFields(issue, user, locale, issueService);
setOwner(issue, user, userPermissionsMap);
addHistoryEntry(issue, user);
}
private void setIssueFields(Issue issue, User user, Locale locale,
IssueService issueService) throws Exception {
if (log.isDebugEnabled()) {
log.debug("setIssueFields: called");
}
List<CustomField> projectCustomFields = issue.getProject()
.getCustomFields();
if (log.isDebugEnabled()) {
log.debug("setIssueFields: got project custom fields: " + projectCustomFields);
}
if (projectCustomFields == null || projectCustomFields.size() == 0) {
log.debug("setIssueFields: no custom fields, returning...");
return;
}
// here you see some of the ugly side of Struts 1.3 - the forms... they
// can only contain Strings and some simple objects types...
HashMap<String, String> formCustomFields = getCustomFields();
if (log.isDebugEnabled()) {
log.debug("setIssueFields: got form custom fields: " + formCustomFields);
}
if (formCustomFields == null || formCustomFields.size() == 0) {
log.debug("setIssueFields: no form custom fields, returning..");
return;
}
ResourceBundle bundle = ITrackerResources.getBundle(locale);
Iterator<CustomField> customFieldsIt = projectCustomFields.iterator();
// declare iteration fields
CustomField field;
String fieldValue;
IssueField issueField;
try {
if (log.isDebugEnabled()) {
log.debug("setIssueFields: processing project fields");
}
// set values to issue-fields and add if needed
while (customFieldsIt.hasNext()) {
field = customFieldsIt.next();
fieldValue = (String) formCustomFields.get(String.valueOf(field
.getId()));
// remove the existing field for re-setting
issueField = getIssueField(issue, field);
if (fieldValue != null && fieldValue.trim().length() > 0) {
if (null == issueField) {
issueField = new IssueField(issue, field);
issue.getFields().add(issueField);
}
issueField.setValue(fieldValue, bundle);
} else {
if (null != issueField) {
issue.getFields().remove(issueField);
}
}
}
// set new issue fields for later saving
// issue.setFields(issueFieldsList);
// issueService.setIssueFields(issue.getId(), issueFieldsList);
} catch (Exception e) {
log.error("setIssueFields: failed to process custom fields", e);
throw e;
}
}
private static IssueField getIssueField(Issue issue, CustomField field) {
Iterator<IssueField> it = issue.getFields().iterator();
IssueField issueField;
while (it.hasNext()) {
issueField = it.next();
if (issueField.getCustomField().equals(field)) {
return issueField;
}
}
return null;
}
private void setOwner(Issue issue, User user,
Map<Integer, Set<PermissionType>> userPermissionsMap) throws Exception {
if (log.isDebugEnabled()) {
log.debug("setOwner: called to " + getOwnerId());
}
Integer currentOwner = (issue.getOwner() == null) ? null : issue
.getOwner().getId();
Integer ownerId = getOwnerId();
if (ownerId == null || ownerId.equals(currentOwner)) {
if (log.isDebugEnabled()) {
log.debug("setOwner: returning, existing owner is the same: " + issue.getOwner());
}
return;
}
if (UserUtilities.hasPermission(userPermissionsMap,
UserUtilities.PERMISSION_ASSIGN_OTHERS)
|| (UserUtilities.hasPermission(userPermissionsMap,
UserUtilities.PERMISSION_ASSIGN_SELF) && user.getId()
.equals(ownerId))
|| (UserUtilities.hasPermission(userPermissionsMap,
UserUtilities.PERMISSION_UNASSIGN_SELF)
&& user.getId().equals(currentOwner) && ownerId == -1)) {
User newOwner = ServletContextUtils.getItrackerServices().getUserService().getUser(ownerId);
if (log.isDebugEnabled()) {
log.debug("setOwner: setting new owner " + newOwner + " to " + issue);
}
issue.setOwner(newOwner);
// issueService.assignIssue(issue.getId(), ownerId, user.getId());
}
}
private void addHistoryEntry(Issue issue, User user) throws Exception {
try {
String history = getHistory();
if (history == null || history.equals("")) {
if (log.isDebugEnabled()) {
log.debug("addHistoryEntry: skip history to " + issue);
}
return;
}
if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_SURPRESS_HISTORY_HTML, issue.getProject().getOptions())) {
history = HTMLUtilities.removeMarkup(history);
} else if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_LITERAL_HISTORY_HTML, issue.getProject().getOptions())) {
history = HTMLUtilities.escapeTags(history);
} else {
history = HTMLUtilities.newlinesToBreaks(history);
}
if (log.isDebugEnabled()) {
log.debug("addHistoryEntry: adding history to " + issue);
}
IssueHistory issueHistory = new IssueHistory(issue, user, history,
IssueUtilities.HISTORY_STATUS_AVAILABLE);
issueHistory.setDescription(getHistory());
issueHistory.setCreateDate(new Date());
issueHistory.setLastModifiedDate(new Date());
issue.getHistory().add(issueHistory);
// TODO why do we need to updateIssue here, and can not later?
// issueService.updateIssue(issue, user.getId());
} catch (Exception e) {
log.error("addHistoryEntry: failed to add", e);
throw e;
}
// issueService.addIssueHistory(issueHistory);
if (log.isDebugEnabled()) {
log.debug("addHistoryEntry: added history for issue " + issue);
}
}
public final Issue processLimitedEdit(Issue issue, Project project,
User user, Map<Integer, Set<PermissionType>> userPermissionsMap,
Locale locale, IssueService issueService, ActionMessages messages)
throws Exception {
ActionMessages msg = new ActionMessages();
issue = addAttachment(issue, project, user, ServletContextUtils.getItrackerServices(), msg);
if (!msg.isEmpty()) {
messages.add(msg);
return issue;
}
Integer formStatus = getStatus();
if (formStatus != null) {
if (issue.getStatus() >= IssueUtilities.STATUS_RESOLVED
&& formStatus >= IssueUtilities.STATUS_CLOSED
&& UserUtilities.hasPermission(userPermissionsMap,
UserUtilities.PERMISSION_CLOSE)) {
issue.setStatus(formStatus);
}
}
applyLimitedFields(issue, project, user, userPermissionsMap, locale, issueService);
return issueService.updateIssue(issue, user.getId());
}
/**
* method needed to prepare request for edit_issue.jsp
*/
public static void setupJspEnv(ActionMapping mapping,
IssueForm issueForm, HttpServletRequest request, Issue issue,
IssueService issueService, UserService userService,
Map<Integer, Set<PermissionType>> userPermissions,
Map<Integer, List<NameValuePair>> listOptions, ActionMessages errors)
throws ServletException, IOException, WorkflowException {
if (log.isDebugEnabled()) {
log.debug("setupJspEnv: for issue " + issue);
}
NotificationService notificationService = ServletContextUtils
.getItrackerServices().getNotificationService();
String pageTitleKey = "itracker.web.editissue.title";
String pageTitleArg = request.getParameter("id");
Locale locale = LoginUtilities.getCurrentLocale(request);
User um = LoginUtilities.getCurrentUser(request);
List<NameValuePair> statuses = WorkflowUtilities.getListOptions(
listOptions, IssueUtilities.FIELD_STATUS);
String statusName = IssueUtilities.getStatusName(issue.getStatus(), locale);
boolean hasFullEdit = UserUtilities.hasPermission(userPermissions,
issue.getProject().getId(), UserUtilities.PERMISSION_EDIT_FULL);
List<NameValuePair> resolutions = WorkflowUtilities.getListOptions(
listOptions, IssueUtilities.FIELD_RESOLUTION);
String severityName = IssueUtilities.getSeverityName(issue
.getSeverity(), locale);
List<NameValuePair> components = WorkflowUtilities.getListOptions(
listOptions, IssueUtilities.FIELD_COMPONENTS);
List<NameValuePair> versions = WorkflowUtilities.getListOptions(
listOptions, IssueUtilities.FIELD_VERSIONS);
List<NameValuePair> targetVersion = WorkflowUtilities.getListOptions(
listOptions, IssueUtilities.FIELD_TARGET_VERSION);
List<Component> issueComponents = issue.getComponents();
Collections.sort(issueComponents);
List<Version> issueVersions = issue.getVersions();
Collections.sort(issueVersions, new Version.VersionComparator());
/* Get project fields and put name and value in map */
setupProjectFieldsMapJspEnv(issue.getProject().getCustomFields(), issue.getFields(), request);
setupRelationsRequestEnv(issue.getRelations(), request);
request.setAttribute("pageTitleKey", pageTitleKey);
request.setAttribute("pageTitleArg", pageTitleArg);
request.getSession().setAttribute(Constants.LIST_OPTIONS_KEY,
listOptions);
request.setAttribute("targetVersions", targetVersion);
request.setAttribute("components", components);
request.setAttribute("versions", versions);
request.setAttribute("hasIssueNotification", notificationService
.hasIssueNotification(issue, um.getId()));
request.setAttribute("hasHardIssueNotification", IssueUtilities.hasHardNotification(issue, issue.getProject(), um.getId()));
request.setAttribute("hasEditIssuePermission", UserUtilities
.hasPermission(userPermissions, issue.getProject().getId(),
UserUtilities.PERMISSION_EDIT));
request.setAttribute("canCreateIssue",
issue.getProject().getStatus() == Status.ACTIVE
&& UserUtilities.hasPermission(userPermissions, issue
.getProject().getId(),
UserUtilities.PERMISSION_CREATE));
request.setAttribute("issueComponents", issueComponents);
request.setAttribute("issueVersions",
issueVersions == null ? new ArrayList<Version>()
: issueVersions);
request.setAttribute("statuses", statuses);
request.setAttribute("statusName", statusName);
request.setAttribute("hasFullEdit", hasFullEdit);
request.setAttribute("resolutions", resolutions);
request.setAttribute("severityName", severityName);
request.setAttribute("hasPredefinedResolutionsOption", ProjectUtilities
.hasOption(ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS,
issue.getProject().getOptions()));
request.setAttribute("issueOwnerName",
(issue.getOwner() == null ? ITrackerResources.getString(
"itracker.web.generic.unassigned", locale)
: issue.getOwner().getFirstName() + " "
+ issue.getOwner().getLastName()));
request.setAttribute("isStatusResolved",
issue.getStatus() >= IssueUtilities.STATUS_RESOLVED);
request.setAttribute("fieldSeverity", WorkflowUtilities.getListOptions(
listOptions, IssueUtilities.FIELD_SEVERITY));
request.setAttribute("possibleOwners", WorkflowUtilities
.getListOptions(listOptions, IssueUtilities.FIELD_OWNER));
request.setAttribute("hasNoViewAttachmentOption", ProjectUtilities
.hasOption(ProjectUtilities.OPTION_NO_ATTACHMENTS, issue
.getProject().getOptions()));
if (log.isDebugEnabled()) {
log.debug("setupJspEnv: options " + issue.getProject().getOptions() + ", hasNoViewAttachmentOption: " + request.getAttribute("hasNoViewAttachmentOption"));
}
setupNotificationsInRequest(request, issue, notificationService);
// setup issue to request, as it's needed by the jsp.
request.setAttribute(Constants.ISSUE_KEY, issue);
request.setAttribute("issueForm", issueForm);
request.setAttribute(Constants.PROJECT_KEY, issue.getProject());
List<IssueHistory> issueHistory = issueService.getIssueHistory(issue
.getId());
Collections.sort(issueHistory, IssueHistory.CREATE_DATE_COMPARATOR);
request.setAttribute("issueHistory", issueHistory);
}
/**
* Get project fields and put name and value in map
* TODO: simplify this code, it's not readable, unsave yet.
*/
public static final void setupProjectFieldsMapJspEnv(List<CustomField> projectFields, Collection<IssueField> issueFields, HttpServletRequest request) {
Map<CustomField, String> projectFieldsMap = new HashMap<CustomField, String>();
if (projectFields != null && projectFields.size() > 0) {
Collections.sort(projectFields, CustomField.ID_COMPARATOR);
HashMap<String, String> fieldValues = new HashMap<String, String>();
Iterator<IssueField> issueFieldsIt = issueFields.iterator();
while (issueFieldsIt.hasNext()) {
IssueField issueField = issueFieldsIt.next();
if (issueField.getCustomField() != null
&& issueField.getCustomField().getId() > 0) {
if (issueField.getCustomField().getFieldType() == CustomField.Type.DATE) {
Locale locale = LoginUtilities.getCurrentLocale(request);
String value = issueField.getValue(locale);
fieldValues.put(issueField.getCustomField().getId()
.toString(), value);
} else {
fieldValues.put(issueField.getCustomField().getId()
.toString(), issueField
.getStringValue());
}
}
}
Iterator<CustomField> fieldsIt = projectFields.iterator();
CustomField field;
while (fieldsIt.hasNext()) {
field = fieldsIt.next();
String fieldValue = fieldValues.get(String.valueOf(field
.getId()));
if (null == fieldValue) {
fieldValue = "";
};
projectFieldsMap.put(field, fieldValue);
}
request.setAttribute("projectFieldsMap", projectFieldsMap);
}
}
protected static void setupRelationsRequestEnv(List<IssueRelation> relations, HttpServletRequest request) {
Collections.sort(relations, IssueRelation.LAST_MODIFIED_DATE_COMPARATOR);
request.setAttribute("issueRelations", relations);
}
public static void setupNotificationsInRequest(
HttpServletRequest request, Issue issue,
NotificationService notificationService) {
List<Notification> notifications = notificationService
.getIssueNotifications(issue);
Collections.sort(notifications, Notification.TYPE_COMPARATOR);
request.setAttribute("notifications", notifications);
Map<User, Set<Notification.Role>> notificationMap = NotificationUtilities
.mappedRoles(notifications);
request.setAttribute("notificationMap", notificationMap);
request.setAttribute("notifiedUsers", notificationMap.keySet());
}
/**
* Adds an attachment to issue.
*
* @return updated issue
*/
public Issue addAttachment(Issue issue, Project project, User user,
ITrackerServices services, ActionMessages messages) {
FormFile file = getAttachment();
if (file == null || file.getFileName().trim().length() < 1) {
log.info("addAttachment: skipping file " + file);
return issue;
}
if (ProjectUtilities.hasOption(ProjectUtilities.OPTION_NO_ATTACHMENTS,
project.getOptions())) {
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.validate.attachment.disabled", project.getName()));
return issue;
}
String origFileName = file.getFileName();
String contentType = file.getContentType();
int fileSize = file.getFileSize();
String attachmentDescription = getAttachmentDescription();
if (null == contentType || 0 >= contentType.length()) {
log.info("addAttachment: got no mime-type, using default plain-text");
contentType = "text/plain";
}
if (log.isDebugEnabled()) {
log.debug("addAttachment: adding file, name: " + origFileName
+ " of type " + file.getContentType() + ", description: "
+ getAttachmentDescription() + ". filesize: " + fileSize);
}
ActionMessages validation = AttachmentUtilities.validate(file, services);
if (validation.isEmpty()) {
// if (AttachmentUtilities.checkFile(file, getITrackerServices())) {
int lastSlash = Math.max(origFileName.lastIndexOf('/'),
origFileName.lastIndexOf('\\'));
if (lastSlash > -1) {
origFileName = origFileName.substring(lastSlash + 1);
}
IssueAttachment attachmentModel = new IssueAttachment(issue,
origFileName, contentType, attachmentDescription, fileSize,
user);
attachmentModel.setIssue(issue);
// issue.getAttachments().add(attachmentModel);
byte[] fileData;
try {
fileData = file.getFileData();
} catch (IOException e) {
log.error("addAttachment: failed to get file data", e);
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system"));
return issue;
}
if (services.getIssueService()
.addIssueAttachment(attachmentModel, fileData)) {
return services.getIssueService().getIssue(issue.getId());
}
} else {
if (log.isDebugEnabled()) {
log.debug("addAttachment: failed to validate: " + origFileName + ", " + validation);
}
messages.add(validation);
}
return issue;
}
public final void setupIssueForm(Issue issue,
final Map<Integer, List<NameValuePair>> listOptions,
HttpServletRequest request, ActionMessages errors)
throws WorkflowException {
HttpSession session = request.getSession(true);
IssueService issueService = ServletContextUtils.getItrackerServices()
.getIssueService();
Locale locale = (Locale) session.getAttribute(Constants.LOCALE_KEY);
setId(issue.getId());
setProjectId(issue.getProject().getId());
setPrevStatus(issue.getStatus());
setCaller(request.getParameter("caller"));
setDescription(HTMLUtilities.handleQuotes(issue
.getDescription()));
setStatus(issue.getStatus());
if (!ProjectUtilities.hasOption(
ProjectUtilities.OPTION_PREDEFINED_RESOLUTIONS, issue
.getProject().getOptions())) {
// TODO What happens here, validation?
try {
issue.setResolution(IssueUtilities.checkResolutionName(issue
.getResolution(), locale));
} catch (MissingResourceException | NumberFormatException mre) {
log.error(mre.getMessage());
}
}
setResolution(HTMLUtilities.handleQuotes(issue
.getResolution()));
setSeverity(issue.getSeverity());
setTargetVersion(issue.getTargetVersion() == null ? -1
: issue.getTargetVersion().getId());
setOwnerId((issue.getOwner() == null ? -1 : issue.getOwner()
.getId()));
List<IssueField> fields = issue.getFields();
HashMap<String, String> customFields = new HashMap<String, String>();
for (IssueField field : fields) {
customFields.put(field.getCustomField().getId().toString(),
field.getValue(locale));
}
setCustomFields(customFields);
HashSet<Integer> selectedComponents = issueService
.getIssueComponentIds(issue.getId());
if (selectedComponents != null) {
Integer[] componentIds;
ArrayList<Integer> components = new ArrayList<>(
selectedComponents);
componentIds = components.toArray(new Integer[components.size()]);
setComponents(componentIds);
}
HashSet<Integer> selectedVersions = issueService
.getIssueVersionIds(issue.getId());
if (selectedVersions != null) {
Integer[] versionIds;
ArrayList<Integer> versions = new ArrayList<>(
selectedVersions);
versionIds = versions.toArray(new Integer[versions.size()]);
setVersions(versionIds);
}
invokeProjectScripts(issue.getProject(), WorkflowUtilities.EVENT_FIELD_ONPOPULATE, listOptions, errors);
}
public void invokeProjectScripts(Project project, int event, final Map<Integer, List<NameValuePair>> options, ActionMessages errors)
throws WorkflowException {
final Map<Integer, String> values = new HashMap<>(options.size());
for (CustomField field: project.getCustomFields()) {
values.put(field.getId()
, getCustomFields().get(String.valueOf(field.getId())));
}
values.put(Configuration.Type.status.getLegacyCode(),
String.valueOf(getStatus()));
values.put(Configuration.Type.severity.getLegacyCode(),
String.valueOf(getSeverity()));
values.put(Configuration.Type.resolution.getLegacyCode(),
getResolution());
processFieldScripts(project.getScripts(),
event, values, options, errors);
}
public Map<Integer, List<NameValuePair>> invokeProjectScripts(Project project, int event, ActionMessages errors)
throws WorkflowException {
final Map<Integer, List<NameValuePair>> options = EditIssueActionUtil.mappedFieldOptions(project.getCustomFields()) ;
invokeProjectScripts(project, event, options, errors);
return options;
}
public FormFile getAttachment() {
return attachment;
}
public void setAttachment(FormFile attachment) {
this.attachment = attachment;
}
public String getAttachmentDescription() {
return attachmentDescription;
}
public void setAttachmentDescription(String attachmentDescription) {
this.attachmentDescription = attachmentDescription;
}
public String getCaller() {
return caller;
}
public void setCaller(String caller) {
this.caller = caller;
}
public Integer[] getComponents() {
if (null == components)
return null;
return components.clone();
}
public void setComponents(Integer[] components) {
if (null == components)
this.components = null;
else
this.components = components.clone();
}
public Integer getCreatorId() {
return creatorId;
}
public void setCreatorId(Integer creatorId) {
this.creatorId = creatorId;
}
// let's try to put Integer,String here:
public HashMap<String, String> getCustomFields() {
return customFields;
}
// let's try to put Integer,String here:
public void setCustomFields(HashMap<String, String> customFields) {
this.customFields = customFields;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getHistory() {
return history;
}
public void setHistory(String history) {
this.history = history;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getOwnerId() {
return ownerId;
}
public void setOwnerId(Integer ownerId) {
this.ownerId = ownerId;
}
public Integer getPrevStatus() {
return prevStatus;
}
public void setPrevStatus(Integer prevStatus) {
this.prevStatus = prevStatus;
}
public Integer getProjectId() {
return projectId;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public Integer getRelatedIssueId() {
return relatedIssueId;
}
public void setRelatedIssueId(Integer relatedIssueId) {
this.relatedIssueId = relatedIssueId;
}
public IssueRelation.Type getRelationType() {
return relationType;
}
public void setRelationType(IssueRelation.Type relationType) {
this.relationType = relationType;
}
public String getResolution() {
return resolution;
}
public void setResolution(String resolution) {
this.resolution = resolution;
}
public Integer getSeverity() {
return severity;
}
public void setSeverity(Integer severity) {
this.severity = severity;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getTargetVersion() {
return targetVersion;
}
public void setTargetVersion(Integer targetVersion) {
this.targetVersion = targetVersion;
}
public Integer[] getVersions() {
if (null == versions)
return null;
return versions.clone();
}
public void setVersions(Integer[] versions) {
if (null == versions)
this.versions = null;
else
this.versions = versions.clone();
}
/**
* This methods adds in validation for custom fields. It makes sure the
* datatype matches and also that all required fields have been populated.
*
* @param mapping the ActionMapping object
* @param request the current HttpServletRequest object
* @return an ActionErrors object containing any validation errors
*/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
if (log.isDebugEnabled()) {
log.debug("validate called: mapping: " + mapping + ", request: "
+ request);
}
ActionErrors errors = super.validate(mapping, request);
if (log.isDebugEnabled()) {
log.debug("validate called: mapping: " + mapping + ", request: "
+ request + ", errors: " + errors);
}
try {
if (null != getId()) {
Issue issue;
try {
issue = getITrackerServices().getIssueService().getIssue(
getId());
} catch (Exception e) {
return errors;
}
Locale locale = (Locale) request.getSession().getAttribute(
Constants.LOCALE_KEY);
User currUser = (User) request.getSession().getAttribute(
Constants.USER_KEY);
List<NameValuePair> ownersList = EditIssueActionUtil
.getAssignableIssueOwnersList(issue,
issue.getProject(), currUser, locale,
getITrackerServices().getUserService(),
RequestHelper.getUserPermissions(request
.getSession()));
setupJspEnv(mapping, this, request, issue,
getITrackerServices().getIssueService(),
getITrackerServices().getUserService(), RequestHelper
.getUserPermissions(request.getSession()),
EditIssueActionUtil.getListOptions(request, issue,
ownersList, RequestHelper
.getUserPermissions(request
.getSession()), issue
.getProject(), currUser), errors);
if (errors.isEmpty() && issue.getProject() == null) {
if (log.isDebugEnabled()) {
log.debug("validate: issue project is null: " + issue);
}
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(
"itracker.web.error.invalidproject"));
} else if (errors.isEmpty()
&& issue.getProject().getStatus() != Status.ACTIVE) {
if (log.isDebugEnabled()) {
log.debug("validate: issue project is not active: " + issue);
}
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(
"itracker.web.error.projectlocked"));
} else if (errors.isEmpty() && !"editIssueForm".equals(mapping.getName())) {
if (log.isDebugEnabled()) {
log.debug("validate: validation had errors for " + issue + ": " + errors);
}
if (UserUtilities.hasPermission(RequestHelper.getUserPermissions(request.getSession()),
issue.getProject().getId(),
UserUtilities.PERMISSION_EDIT_FULL)) {
validateProjectFields(issue.getProject(), request, errors);
}
validateProjectScripts(issue.getProject(), errors);
validateAttachment(this.getAttachment(), getITrackerServices(), errors);
}
} else {
EditIssueActionUtil.setupCreateIssue(request);
HttpSession session = request.getSession();
Project project = (Project) session
.getAttribute(Constants.PROJECT_KEY);
if (log.isDebugEnabled()) {
log.debug("validate: validating create new issue for project: " + page);
}
validateProjectFields(project, request, errors);
validateProjectScripts(project, errors);
validateAttachment(this.getAttachment(), getITrackerServices(), errors);
}
} catch (Exception e) {
e.printStackTrace();
log.error("validate: unexpected exception", e);
errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
"itracker.web.error.system"));
}
if (log.isDebugEnabled()) {
log.debug("validate: returning errors: " + errors);
}
return errors;
}
private static void validateAttachment(FormFile attachment, ITrackerServices services, ActionMessages errors) {
if (null != attachment) {
ActionMessages msg = AttachmentUtilities.validate(attachment, services);
if (!msg.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("validateAttachment: failed to validate, " + msg);
}
errors.add(msg);
}
}
}
private static void validateProjectFields(Project project,
HttpServletRequest request, ActionErrors errors) {
List<CustomField> projectFields = project.getCustomFields();
if (null != projectFields && projectFields.size() > 0) {
Locale locale = LoginUtilities.getCurrentLocale(request);
ResourceBundle bundle = ITrackerResources.getBundle(locale);
for (CustomField customField : projectFields) {
String fieldValue = request.getParameter("customFields("
+ customField.getId() + ")");
if (fieldValue != null && !fieldValue.equals("")) {
// Don't create an IssueField only so that we can call
// setValue to validate the value!
try {
customField.checkAssignable(fieldValue, locale, bundle);
} catch (IssueException ie) {
String label = CustomFieldUtilities.getCustomFieldName(
customField.getId(), locale);
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(ie.getType(), label));
}
} else if (customField.isRequired()) {
String label = CustomFieldUtilities.getCustomFieldName(
customField.getId(), locale);
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(IssueException.TYPE_CF_REQ_FIELD,
label));
}
}
}
}
private void validateProjectScripts(Project project, ActionErrors errors)
throws WorkflowException {
invokeProjectScripts(project, WorkflowUtilities.EVENT_FIELD_ONVALIDATE, errors);
}
public static boolean isWorkflowScriptsAllowed() {
Boolean val = ServletContextUtils.getItrackerServices().getConfigurationService().getBooleanProperty("allow_workflowscripts", true);
if (log.isDebugEnabled()) {
log.debug("isWorkflowScriptsAllowed: {}allowed by configuration 'allow_workflowscripts'", !val?"NOT ":"");
}
return val;
}
}