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