Java tutorial
/* * 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; } }