001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krms.impl.ui;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.kuali.rice.core.api.util.tree.Node;
021 import org.kuali.rice.krad.maintenance.MaintenanceDocument;
022 import org.kuali.rice.krad.service.KRADServiceLocator;
023 import org.kuali.rice.krad.service.SequenceAccessorService;
024 import org.kuali.rice.krad.uif.UifParameters;
025 import org.kuali.rice.krad.util.GlobalVariables;
026 import org.kuali.rice.krad.util.ObjectUtils;
027 import org.kuali.rice.krad.web.controller.MaintenanceDocumentController;
028 import org.kuali.rice.krad.web.form.DocumentFormBase;
029 import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
030 import org.kuali.rice.krad.web.form.UifFormBase;
031 import org.kuali.rice.krms.api.KrmsApiServiceLocator;
032 import org.kuali.rice.krms.api.engine.expression.ComparisonOperatorService;
033 import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
034 import org.kuali.rice.krms.api.repository.term.TermDefinition;
035 import org.kuali.rice.krms.api.repository.term.TermResolverDefinition;
036 import org.kuali.rice.krms.api.repository.term.TermSpecificationDefinition;
037 import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
038 import org.kuali.rice.krms.impl.repository.ActionBo;
039 import org.kuali.rice.krms.api.repository.LogicalOperator;
040 import org.kuali.rice.krms.api.repository.proposition.PropositionType;
041 import org.kuali.rice.krms.impl.repository.AgendaBo;
042 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
043 import org.kuali.rice.krms.impl.repository.ContextBoService;
044 import org.kuali.rice.krms.impl.repository.KrmsAttributeDefinitionService;
045 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
046 import org.kuali.rice.krms.impl.repository.PropositionBo;
047 import org.kuali.rice.krms.impl.repository.RuleBo;
048 import org.kuali.rice.krms.impl.repository.RuleBoService;
049 import org.kuali.rice.krms.impl.repository.TermBo;
050 import org.kuali.rice.krms.impl.rule.AgendaEditorBusRule;
051 import org.kuali.rice.krms.impl.util.KRMSPropertyConstants;
052 import org.kuali.rice.krms.impl.util.KrmsImplConstants;
053 import org.springframework.stereotype.Controller;
054 import org.springframework.validation.BindingResult;
055 import org.springframework.web.bind.annotation.ModelAttribute;
056 import org.springframework.web.bind.annotation.RequestMapping;
057 import org.springframework.web.bind.annotation.RequestMethod;
058 import org.springframework.web.bind.annotation.RequestParam;
059 import org.springframework.web.bind.annotation.ResponseBody;
060 import org.springframework.web.servlet.ModelAndView;
061
062 import javax.servlet.http.HttpServletRequest;
063 import javax.servlet.http.HttpServletResponse;
064 import java.util.ArrayList;
065 import java.util.Collection;
066 import java.util.Collections;
067 import java.util.Enumeration;
068 import java.util.HashMap;
069 import java.util.List;
070 import java.util.Map;
071
072 /**
073 * Controller for the Test UI Page
074 * @author Kuali Rice Team (rice.collab@kuali.org)
075 */
076 @Controller
077 @RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_EDITOR_PATH)
078 public class AgendaEditorController extends MaintenanceDocumentController {
079
080 private SequenceAccessorService sequenceAccessorService;
081
082 /**
083 * Override route to set the setSelectedAgendaItemId to empty and disable all the buttons
084 *
085 * @see org.kuali.rice.krad.web.controller.MaintenanceDocumentController.route
086 * (DocumentFormBase, HttpServletRequest, HttpServletResponse)
087 */
088 @Override
089 @RequestMapping(params = "methodToCall=route")
090 public ModelAndView route(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result,
091 HttpServletRequest request, HttpServletResponse response) {
092
093 ModelAndView modelAndView;
094 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) form;
095 AgendaEditor agendaEditor = ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
096 agendaEditor.setSelectedAgendaItemId("");
097 agendaEditor.setDisableButtons(true);
098 modelAndView = super.route(form, result, request, response);
099
100 return modelAndView;
101 }
102
103 /**
104 * This overridden method does extra work on refresh to update the namespace when the context has been changed.
105 *
106 * @see org.kuali.rice.krad.web.controller.UifControllerBase#refresh(org.kuali.rice.krad.web.form.UifFormBase, org.springframework.validation.BindingResult, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
107 */
108 @RequestMapping(params = "methodToCall=" + "refresh")
109 @Override
110 public ModelAndView refresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
111 HttpServletRequest request, HttpServletResponse response) throws Exception {
112 ModelAndView modelAndView = super.refresh(form, result, request, response);
113
114 // handle return from context lookup
115 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) form;
116 AgendaEditor agendaEditor = ((AgendaEditor) maintenanceForm.getDocument().getNewMaintainableObject().getDataObject());
117 AgendaEditorBusRule rule = new AgendaEditorBusRule();
118 if (rule.validContext(agendaEditor) && rule.validAgendaName(agendaEditor)) {
119 // update the namespace on all agenda related objects if the contest has been changed
120 if (!StringUtils.equals(agendaEditor.getOldContextId(), agendaEditor.getAgenda().getContextId())) {
121 agendaEditor.setOldContextId(agendaEditor.getAgenda().getContextId());
122
123 String namespace = "";
124 if (!StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
125 namespace = getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace();
126 }
127
128 for (AgendaItemBo agendaItem : agendaEditor.getAgenda().getItems()) {
129 agendaItem.getRule().setNamespace(namespace);
130 for (ActionBo action : agendaItem.getRule().getActions()) {
131 action.setNamespace(namespace);
132 }
133 }
134 }
135 }
136 return modelAndView;
137 }
138
139
140 @Override
141 public ModelAndView maintenanceEdit(@ModelAttribute("KualiForm") MaintenanceDocumentForm form, BindingResult result,
142 HttpServletRequest request, HttpServletResponse response) throws Exception {
143
144 // Reset the page Id so that bread crumbs can come back to the default page on EditAgenda
145 form.setPageId(null);
146 return super.maintenanceEdit(form,result,request,response);
147 }
148
149 /**
150 * This method updates the existing rule in the agenda.
151 */
152 @RequestMapping(params = "methodToCall=" + "goToAddRule")
153 public ModelAndView goToAddRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
154 HttpServletRequest request, HttpServletResponse response) throws Exception {
155 setAgendaItemLine(form, null);
156 AgendaEditor agendaEditor = getAgendaEditor(form);
157 agendaEditor.setAddRuleInProgress(true);
158 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
159 return super.navigate(form, result, request, response);
160 }
161
162 /**
163 * This method sets the agendaItemLine for adding/editing AgendaItems.
164 * The agendaItemLine is a copy of the agendaItem so that changes are not applied when
165 * they are abandoned. If the agendaItem is null a new empty agendaItemLine is created.
166 *
167 * @param form
168 * @param agendaItem
169 */
170 private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) {
171 AgendaEditor agendaEditor = getAgendaEditor(form);
172 if (agendaItem == null) {
173 RuleBo rule = new RuleBo();
174 rule.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_RULE_S", RuleBo.class)
175 .toString());
176 if (StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
177 rule.setNamespace("");
178 } else {
179 rule.setNamespace(getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace());
180 }
181 agendaItem = new AgendaItemBo();
182 agendaItem.setRule(rule);
183 agendaEditor.setAgendaItemLine(agendaItem);
184 } else {
185 // TODO: Add a copy not the reference
186 agendaEditor.setAgendaItemLine((AgendaItemBo) ObjectUtils.deepCopy(agendaItem));
187 }
188
189
190 if (agendaItem.getRule().getActions().isEmpty()) {
191 ActionBo actionBo = new ActionBo();
192 actionBo.setTypeId("");
193 actionBo.setNamespace(agendaItem.getRule().getNamespace());
194 actionBo.setRuleId(agendaItem.getRule().getId());
195 actionBo.setSequenceNumber(1);
196 agendaEditor.setAgendaItemLineRuleAction(actionBo);
197 } else {
198 agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0));
199 }
200
201 agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes());
202 agendaEditor.setCustomRuleAttributesMap(agendaEditor.getAgendaItemLine().getRule().getAttributes());
203 }
204
205 /**
206 * This method returns the id of the selected agendaItem.
207 *
208 * @param form
209 * @return selectedAgendaItemId
210 */
211 private String getSelectedAgendaItemId(UifFormBase form) {
212 AgendaEditor agendaEditor = getAgendaEditor(form);
213 return agendaEditor.getSelectedAgendaItemId();
214 }
215
216 /**
217 * This method sets the id of the cut agendaItem.
218 *
219 * @param form
220 * @param cutAgendaItemId
221 */
222 private void setCutAgendaItemId(UifFormBase form, String cutAgendaItemId) {
223 AgendaEditor agendaEditor = getAgendaEditor(form);
224 agendaEditor.setCutAgendaItemId(cutAgendaItemId);
225 }
226
227 /**
228 * This method returns the id of the cut agendaItem.
229 *
230 * @param form
231 * @return cutAgendaItemId
232 */
233 private String getCutAgendaItemId(UifFormBase form) {
234 AgendaEditor agendaEditor = getAgendaEditor(form);
235 return agendaEditor.getCutAgendaItemId();
236 }
237
238 /**
239 * This method updates the existing rule in the agenda.
240 */
241 @RequestMapping(params = "methodToCall=" + "goToEditRule")
242 public ModelAndView goToEditRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
243 HttpServletRequest request, HttpServletResponse response) throws Exception {
244
245 AgendaEditor agendaEditor = getAgendaEditor(form);
246 agendaEditor.setAddRuleInProgress(false);
247 // this is the root of the tree:
248 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
249 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
250 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
251
252 setAgendaItemLine(form, node);
253
254 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
255 return super.navigate(form, result, request, response);
256 }
257
258 /**
259 * This method adds the newly create rule to the agenda.
260 */
261 @RequestMapping(params = "methodToCall=" + "addRule")
262 public ModelAndView addRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
263 HttpServletRequest request, HttpServletResponse response) throws Exception {
264
265 AgendaEditor agendaEditor = getAgendaEditor(form);
266 AgendaBo agenda = agendaEditor.getAgenda();
267 AgendaItemBo newAgendaItem = agendaEditor.getAgendaItemLine();
268
269 if (!validateProposition(newAgendaItem.getRule().getProposition(), newAgendaItem.getRule().getNamespace())) {
270 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
271 // NOTICE short circuit method on invalid proposition
272 return super.navigate(form, result, request, response);
273 }
274
275 newAgendaItem.getRule().setAttributes(agendaEditor.getCustomRuleAttributesMap());
276 updateRuleAction(agendaEditor);
277
278 if (agenda.getItems() == null) {
279 agenda.setItems(new ArrayList<AgendaItemBo>());
280 }
281
282 AgendaEditorBusRule rule = new AgendaEditorBusRule();
283 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) form;
284 MaintenanceDocument document = maintenanceForm.getDocument();
285 if (rule.processAgendaItemBusinessRules(document)) {
286 newAgendaItem.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_ITM_S", AgendaItemBo.class)
287 .toString());
288 newAgendaItem.setAgendaId(getCreateAgendaId(agenda));
289 if (agenda.getFirstItemId() == null) {
290 agenda.setFirstItemId(newAgendaItem.getId());
291 } else {
292 // insert agenda in tree
293 String selectedAgendaItemId = getSelectedAgendaItemId(form);
294 if (StringUtils.isBlank(selectedAgendaItemId)) {
295 // add after the last root node
296 AgendaItemBo node = getFirstAgendaItem(agenda);
297 while (node.getAlways() != null) {
298 node = node.getAlways();
299 }
300 node.setAlwaysId(newAgendaItem.getId());
301 node.setAlways(newAgendaItem);
302 } else {
303 // add after selected node
304 AgendaItemBo firstItem = getFirstAgendaItem(agenda);
305 AgendaItemBo node = getAgendaItemById(firstItem, selectedAgendaItemId);
306 newAgendaItem.setAlwaysId(node.getAlwaysId());
307 newAgendaItem.setAlways(node.getAlways());
308 node.setAlwaysId(newAgendaItem.getId());
309 node.setAlways(newAgendaItem);
310 }
311 }
312 // add it to the collection on the agenda too
313 agenda.getItems().add(newAgendaItem);
314 agendaEditor.setAddRuleInProgress(false);
315 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
316 } else {
317 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-AddRule-Page");
318 }
319 return super.navigate(form, result, request, response);
320 }
321
322 /**
323 * Validate the given proposition and its children. Note that this method is side-effecting,
324 * when errors are detected with the proposition, errors are added to the error map.
325 * @param proposition the proposition to validate
326 * @param namespace the namespace of the parent rule
327 * @return true if the proposition and its children (if any) are considered valid
328 */
329 // TODO also wire up to proposition for faster feedback to the user
330 private boolean validateProposition(PropositionBo proposition, String namespace) {
331 boolean result = true;
332
333 if (proposition != null) { // Null props are allowed.
334
335 if (StringUtils.isBlank(proposition.getDescription())) {
336 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
337 "error.rule.proposition.missingDescription");
338 result &= false;
339 }
340
341 if (StringUtils.isBlank(proposition.getCompoundOpCode())) {
342 // then this is a simple proposition, validate accordingly
343
344 result &= validateSimpleProposition(proposition, namespace);
345
346 } else {
347 // this is a compound proposition (or it should be)
348 List<PropositionBo> compoundComponents = proposition.getCompoundComponents();
349
350 if (!CollectionUtils.isEmpty(proposition.getParameters())) {
351 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
352 "error.rule.proposition.compound.invalidParameter", proposition.getDescription());
353 result &= false;
354 }
355
356 // recurse
357 if (!CollectionUtils.isEmpty(compoundComponents)) for (PropositionBo childProp : compoundComponents) {
358 result &= validateProposition(childProp, namespace);
359 }
360 }
361 }
362
363 return result;
364 }
365
366 /**
367 * Validate the given simple proposition. Note that this method is side-effecting,
368 * when errors are detected with the proposition, errors are added to the error map.
369 * @param proposition the proposition to validate
370 * @param namespace the namespace of the parent rule
371 * @return true if the proposition is considered valid
372 */
373 private boolean validateSimpleProposition(PropositionBo proposition, String namespace) {
374 boolean result = true;
375
376 String propConstant = null;
377 if (proposition.getParameters().get(1) != null) {
378 propConstant = proposition.getParameters().get(1).getValue();
379 }
380 String operator = null;
381 if (proposition.getParameters().get(2) != null) {
382 operator = proposition.getParameters().get(2).getValue();
383 }
384
385 String termId = null;
386 if (proposition.getParameters().get(0) != null) {
387 termId = proposition.getParameters().get(0).getValue();
388 }
389 // Simple proposition requires all of propConstant, termId and operator to be specified
390 if (StringUtils.isBlank(termId)) {
391 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
392 "error.rule.proposition.simple.blankField", proposition.getDescription(), "Term");
393 result &= false;
394 } else {
395 result = validateTerm(proposition, namespace);
396 }
397 if (StringUtils.isBlank(operator)) {
398 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
399 "error.rule.proposition.simple.blankField", proposition.getDescription(), "Operator");
400 result &= false;
401 }
402 if (StringUtils.isBlank(propConstant) && !operator.endsWith("null")) { // ==null and !=null operators have blank values.
403 GlobalVariables.getMessageMap().putErrorForSectionId(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
404 "error.rule.proposition.simple.blankField", proposition.getDescription(), "Value");
405 result &= false;
406 } else if (operator.endsWith("null")) { // ==null and !=null operators have blank values.
407 if (propConstant != null) {
408 proposition.getParameters().get(1).setValue(null);
409 }
410 } else if (!StringUtils.isBlank(termId)) {
411 // validate that the constant value is comparable against the term
412 String termType = lookupTermType(termId);
413 ComparisonOperatorService comparisonOperatorService = KrmsApiServiceLocator.getComparisonOperatorService();
414 if (comparisonOperatorService.canCoerce(termType, propConstant)) {
415 if (comparisonOperatorService.coerce(termType, propConstant) == null) { // HMM, what if we wanted a rule that
416 // checked a null value?
417 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
418 "error.rule.proposition.simple.invalidValue", proposition.getDescription(), propConstant);
419 result &= false;
420 }
421 }
422 }
423
424 if (!CollectionUtils.isEmpty(proposition.getCompoundComponents())) {
425 GlobalVariables.getMessageMap().putError(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
426 "error.rule.proposition.simple.hasChildren", proposition.getDescription());
427 result &= false; // simple prop should not have compound components
428 }
429 return result;
430 }
431
432 /**
433 * Validate the term in the given simple proposition. Note that this method is side-effecting,
434 * when errors are detected with the proposition, errors are added to the error map.
435 * @param proposition the proposition with the term to validate
436 * @param namespace the namespace of the parent rule
437 * @return true if the proposition's term is considered valid
438 */
439 private boolean validateTerm(PropositionBo proposition, String namespace) {
440 boolean result = true;
441
442 String termId = proposition.getParameters().get(0).getValue();
443 if (termId.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
444 // validate parameterized term
445
446 // is the term name non-blank
447 if (StringUtils.isBlank(proposition.getNewTermDescription())) {
448 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
449 "error.rule.proposition.simple.emptyTermName", proposition.getDescription());
450 result &= false;
451 } else { // check if the term name is unique
452
453 Map<String, String> criteria = new HashMap<String, String>();
454
455 criteria.put("description", proposition.getNewTermDescription());
456 criteria.put("specification.namespace", namespace);
457
458 Collection<TermBo> matchingTerms =
459 KRADServiceLocator.getBusinessObjectService().findMatching(TermBo.class, criteria);
460
461 if (!CollectionUtils.isEmpty(matchingTerms)) {
462 // this is a Warning -- maybe it should be an error?
463 GlobalVariables.getMessageMap().putWarningWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
464 "warning.rule.proposition.simple.duplicateTermName", proposition.getDescription());
465 }
466 }
467
468 String termSpecificationId = termId.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
469
470 TermResolverDefinition termResolverDefinition =
471 AgendaEditorMaintainable.getSimplestTermResolver(termSpecificationId, namespace);
472
473 if (termResolverDefinition == null) {
474 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
475 "error.rule.proposition.simple.invalidTerm", proposition.getDescription());
476 result &= false;
477 } else {
478 List<String> parameterNames = new ArrayList<String>(termResolverDefinition.getParameterNames());
479 Collections.sort(parameterNames);
480 for (String parameterName : parameterNames) {
481 if (!proposition.getTermParameters().containsKey(parameterName) ||
482 StringUtils.isBlank(proposition.getTermParameters().get(parameterName))) {
483 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
484 "error.rule.proposition.simple.missingTermParameter", proposition.getDescription());
485 result &= false;
486 break;
487 }
488 }
489 }
490
491 } else {
492 //validate normal term
493 TermDefinition termDefinition = KrmsRepositoryServiceLocator.getTermBoService().getTerm(termId);
494 if (termDefinition == null) {
495 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
496 "error.rule.proposition.simple.invalidTerm", proposition.getDescription());
497 } else if (!namespace.equals(termDefinition.getSpecification().getNamespace())) {
498 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KRMSPropertyConstants.Rule.PROPOSITION_TREE_GROUP_ID,
499 "error.rule.proposition.simple.invalidTerm", proposition.getDescription());
500 }
501 }
502 return result;
503 }
504
505 /**
506 * Lookup the {@link org.kuali.rice.krms.api.repository.term.TermSpecificationDefinitionContract} type.
507 * @param key krms_term_t key
508 * @return String the krms_term_spec_t TYP for the given krms_term_t key given
509 */
510 private String lookupTermType(String key) {
511 TermSpecificationDefinition termSpec = null;
512 if (key.startsWith(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX)) {
513 String termSpecificationId = key.substring(KrmsImplConstants.PARAMETERIZED_TERM_PREFIX.length());
514 termSpec = KrmsRepositoryServiceLocator.getTermBoService().getTermSpecificationById(termSpecificationId);
515 } else {
516 TermDefinition term = KrmsRepositoryServiceLocator.getTermBoService().getTerm(key);
517 if (term != null) {
518 termSpec = term.getSpecification();
519 }
520 }
521 if (termSpec != null) {
522 return termSpec.getType();
523 } else {
524 return null;
525 }
526 }
527
528
529 /**
530 * This method returns the agendaId of the given agenda. If the agendaId is null a new id will be created.
531 */
532 private String getCreateAgendaId(AgendaBo agenda) {
533 if (agenda.getId() == null) {
534 agenda.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_AGENDA_S", AgendaItemBo.class).toString());
535 }
536 return agenda.getId();
537 }
538
539 private void updateRuleAction(AgendaEditor agendaEditor) {
540 agendaEditor.getAgendaItemLine().getRule().setActions(new ArrayList<ActionBo>());
541 if (StringUtils.isNotBlank(agendaEditor.getAgendaItemLineRuleAction().getTypeId())) {
542 agendaEditor.getAgendaItemLineRuleAction().setAttributes(agendaEditor.getCustomRuleActionAttributesMap());
543 agendaEditor.getAgendaItemLine().getRule().getActions().add(agendaEditor.getAgendaItemLineRuleAction());
544 }
545 }
546
547 /**
548 * Build a map from attribute name to attribute definition from all the defined attribute definitions for the
549 * specified rule action type
550 * @param actionTypeId
551 * @return
552 */
553 private Map<String, KrmsAttributeDefinition> buildAttributeDefinitionMap(String actionTypeId) {
554 KrmsAttributeDefinitionService attributeDefinitionService =
555 KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
556
557 // build a map from attribute name to definition
558 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
559
560 List<KrmsAttributeDefinition> attributeDefinitions =
561 attributeDefinitionService.findAttributeDefinitionsByType(actionTypeId);
562
563 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
564 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
565 }
566 return attributeDefinitionMap;
567 }
568
569 /**
570 * This method updates the existing rule in the agenda.
571 */
572 @RequestMapping(params = "methodToCall=" + "editRule")
573 public ModelAndView editRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
574 HttpServletRequest request, HttpServletResponse response) throws Exception {
575 AgendaEditor agendaEditor = getAgendaEditor(form);
576 // this is the root of the tree:
577 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
578 AgendaItemBo node = getAgendaItemById(firstItem, getSelectedAgendaItemId(form));
579 AgendaItemBo agendaItemLine = agendaEditor.getAgendaItemLine();
580
581 if (!validateProposition(agendaItemLine.getRule().getProposition(), agendaItemLine.getRule().getNamespace())) {
582 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
583 // NOTICE short circuit method on invalid proposition
584 return super.navigate(form, result, request, response);
585 }
586
587 agendaItemLine.getRule().setAttributes(agendaEditor.getCustomRuleAttributesMap());
588 updateRuleAction(agendaEditor);
589
590 AgendaEditorBusRule rule = new AgendaEditorBusRule();
591 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) form;
592 MaintenanceDocument document = maintenanceForm.getDocument();
593 if (rule.processAgendaItemBusinessRules(document)) {
594 node.setRule(agendaItemLine.getRule());
595 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-Agenda-Page");
596 } else {
597 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-EditRule-Page");
598 }
599 return super.navigate(form, result, request, response);
600 }
601
602 /**
603 * @return the ALWAYS {@link AgendaItemInstanceChildAccessor} for the last ALWAYS child of the instance accessed by the parameter.
604 * It will by definition refer to null. If the instanceAccessor parameter refers to null, then it will be returned. This is useful
605 * for adding a youngest child to a sibling group.
606 */
607 private AgendaItemInstanceChildAccessor getLastChildsAlwaysAccessor(AgendaItemInstanceChildAccessor instanceAccessor) {
608 AgendaItemBo next = instanceAccessor.getChild();
609 if (next == null) return instanceAccessor;
610 while (next.getAlways() != null) { next = next.getAlways(); };
611 return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
612 }
613
614 /**
615 * @return the accessor to the child with the given agendaItemId under the given parent. This method will search both When TRUE and
616 * When FALSE sibling groups. If the instance with the given id is not found, null is returned.
617 * @see AgendaItemChildAccessor for nomenclature explanation
618 */
619 private AgendaItemInstanceChildAccessor getInstanceAccessorToChild(AgendaItemBo parent, String agendaItemId) {
620
621 // first try When TRUE, then When FALSE via AgendaItemChildAccessor.levelOrderChildren
622 for (AgendaItemChildAccessor levelOrderChildAccessor : AgendaItemChildAccessor.children) {
623
624 AgendaItemBo next = levelOrderChildAccessor.getChild(parent);
625
626 // if the first item matches, return the accessor from the parent
627 if (next != null && agendaItemId.equals(next.getId())) return new AgendaItemInstanceChildAccessor(levelOrderChildAccessor, parent);
628
629 // otherwise walk the children
630 while (next != null && next.getAlwaysId() != null) {
631 if (next.getAlwaysId().equals(agendaItemId)) return new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.always, next);
632 // move down
633 next = next.getAlways();
634 }
635 }
636
637 return null;
638 }
639
640 @RequestMapping(params = "methodToCall=" + "ajaxRefresh")
641 public ModelAndView ajaxRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
642 HttpServletRequest request, HttpServletResponse response)
643 throws Exception {
644 // call the super method to avoid the agenda tree being reloaded from the db
645 return getUIFModelAndView(form);
646 }
647
648 @RequestMapping(params = "methodToCall=" + "moveUp")
649 public ModelAndView moveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
650 HttpServletRequest request, HttpServletResponse response)
651 throws Exception {
652 moveSelectedSubtreeUp(form);
653
654 return super.refresh(form, result, request, response);
655 }
656
657 @RequestMapping(params = "methodToCall=" + "ajaxMoveUp")
658 public ModelAndView ajaxMoveUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
659 HttpServletRequest request, HttpServletResponse response)
660 throws Exception {
661 moveSelectedSubtreeUp(form);
662
663 // call the super method to avoid the agenda tree being reloaded from the db
664 return getUIFModelAndView(form);
665 }
666
667 /**
668 * Exposes Ajax callback to UI to validate entered rule name to copy
669 * @param name the copyRuleName
670 * @param namespace the rule namespace
671 * @return true or false
672 */
673 @RequestMapping(params = "methodToCall=" + "ajaxValidRuleName", method=RequestMethod.GET)
674 public @ResponseBody boolean ajaxValidRuleName(@RequestParam String name, @RequestParam String namespace) {
675 return (getRuleBoService().getRuleByNameAndNamespace(name, namespace) != null);
676 }
677
678 /**
679 *
680 * @param form
681 * @see AgendaItemChildAccessor for nomenclature explanation
682 */
683 private void moveSelectedSubtreeUp(UifFormBase form) {
684
685 /* Rough algorithm for moving a node up. This is a "level order" move. Note that in this tree,
686 * level order means something a bit funky. We are defining a level as it would be displayed in the browser,
687 * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
688 * considered siblings.
689 *
690 * find the following:
691 * node := the selected node
692 * parent := the selected node's parent, its containing node (via when true or when false relationship)
693 * parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
694 *
695 * if (node is first child in sibling group)
696 * if (node is in When FALSE group)
697 * move node to last position in When TRUE group
698 * else
699 * find youngest child of parentsOlderCousin and put node after it
700 * else
701 * move node up within its sibling group
702 */
703
704 AgendaEditor agendaEditor = getAgendaEditor(form);
705 // this is the root of the tree:
706 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
707
708 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
709 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
710 AgendaItemBo parent = getParent(firstItem, selectedItemId);
711 AgendaItemBo parentsOlderCousin = (parent == null) ? null : getNextOldestOfSameGeneration(firstItem, parent);
712
713 StringBuilder ruleEditorMessage = new StringBuilder();
714 AgendaItemChildAccessor childAccessor = getOldestChildAccessor(node, parent);
715 if (childAccessor != null) { // node is first child in sibling group
716 if (childAccessor == AgendaItemChildAccessor.whenFalse) {
717 // move node to last position in When TRUE group
718 AgendaItemInstanceChildAccessor youngestWhenTrueSiblingInsertionPoint =
719 getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenTrue, parent));
720 youngestWhenTrueSiblingInsertionPoint.setChild(node);
721 AgendaItemChildAccessor.whenFalse.setChild(parent, node.getAlways());
722 AgendaItemChildAccessor.always.setChild(node, null);
723
724 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" up ");
725 ruleEditorMessage.append("to last position in When TRUE group of ").append(parent.getRule().getName());
726 } else if (parentsOlderCousin != null) {
727 // find youngest child of parentsOlderCousin and put node after it
728 AgendaItemInstanceChildAccessor youngestWhenFalseSiblingInsertionPoint =
729 getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, parentsOlderCousin));
730 youngestWhenFalseSiblingInsertionPoint.setChild(node);
731 AgendaItemChildAccessor.whenTrue.setChild(parent, node.getAlways());
732 AgendaItemChildAccessor.always.setChild(node, null);
733 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" up ");
734 ruleEditorMessage.append("to When FALSE group of ").append(parentsOlderCousin.getRule().getName());
735 }
736 } else if (!selectedItemId.equals(firstItem.getId())) { // conditional to miss special case of first node
737
738 AgendaItemBo bogusRootNode = null;
739 if (parent == null) {
740 // special case, this is a top level sibling. rig up special parent node
741 bogusRootNode = new AgendaItemBo();
742 AgendaItemChildAccessor.whenTrue.setChild(bogusRootNode, firstItem);
743 parent = bogusRootNode;
744 }
745
746 // move node up within its sibling group
747 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
748 AgendaItemBo olderSibling = accessorToSelectedNode.getInstance();
749 AgendaItemInstanceChildAccessor accessorToOlderSibling = getInstanceAccessorToChild(parent, olderSibling.getId());
750
751 accessorToOlderSibling.setChild(node);
752 accessorToSelectedNode.setChild(node.getAlways());
753 AgendaItemChildAccessor.always.setChild(node, olderSibling);
754
755 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" up ");
756
757 if (bogusRootNode != null) {
758 // clean up special case with bogus root node
759 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenTrueId());
760 ruleEditorMessage.append(" to ").append(getFirstAgendaItem(agendaEditor.getAgenda()).getRule().getName()).append(" When TRUE group");
761 } else {
762 ruleEditorMessage.append(" within its sibling group, above " + olderSibling.getRule().getName());
763 }
764 }
765 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
766 }
767
768 @RequestMapping(params = "methodToCall=" + "moveDown")
769 public ModelAndView moveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
770 HttpServletRequest request, HttpServletResponse response)
771 throws Exception {
772 moveSelectedSubtreeDown(form);
773
774 return super.refresh(form, result, request, response);
775 }
776
777 @RequestMapping(params = "methodToCall=" + "ajaxMoveDown")
778 public ModelAndView ajaxMoveDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
779 HttpServletRequest request, HttpServletResponse response)
780 throws Exception {
781 moveSelectedSubtreeDown(form);
782
783 // call the super method to avoid the agenda tree being reloaded from the db
784 return getUIFModelAndView(form);
785 }
786
787 /**
788 *
789 * @param form
790 * @see AgendaItemChildAccessor for nomenclature explanation
791 */
792 private void moveSelectedSubtreeDown(UifFormBase form) {
793
794 /* Rough algorithm for moving a node down. This is a "level order" move. Note that in this tree,
795 * level order means something a bit funky. We are defining a level as it would be displayed in the browser,
796 * so only the traversal of When FALSE or When TRUE links increments the level, since ALWAYS linked nodes are
797 * considered siblings.
798 *
799 * find the following:
800 * node := the selected node
801 * parent := the selected node's parent, its containing node (via when true or when false relationship)
802 * parentsYoungerCousin := the parent's level-order successor (sibling or cousin)
803 *
804 * if (node is last child in sibling group)
805 * if (node is in When TRUE group)
806 * move node to first position in When FALSE group
807 * else
808 * move to first child of parentsYoungerCousin
809 * else
810 * move node down within its sibling group
811 */
812
813 AgendaEditor agendaEditor = getAgendaEditor(form);
814 // this is the root of the tree:
815 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
816
817 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
818 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
819 AgendaItemBo parent = getParent(firstItem, selectedItemId);
820 AgendaItemBo parentsYoungerCousin = (parent == null) ? null : getNextYoungestOfSameGeneration(firstItem, parent);
821
822 StringBuilder ruleEditorMessage = new StringBuilder();
823 if (node.getAlways() == null && parent != null) { // node is last child in sibling group
824 // set link to selected node to null
825 if (parent.getWhenTrue() != null && isSiblings(parent.getWhenTrue(), node)) { // node is in When TRUE group
826 // move node to first child under When FALSE
827 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
828 accessorToSelectedNode.setChild(null);
829
830 AgendaItemBo parentsFirstChild = parent.getWhenFalse();
831 AgendaItemChildAccessor.whenFalse.setChild(parent, node);
832 AgendaItemChildAccessor.always.setChild(node, parentsFirstChild);
833
834 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" down ");
835 ruleEditorMessage.append("to first child under When FALSE group of ").append(parent.getRule().getName());
836 } else if (parentsYoungerCousin != null) { // node is in the When FALSE group
837 // move to first child of parentsYoungerCousin under When TRUE
838 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
839 accessorToSelectedNode.setChild(null);
840
841 AgendaItemBo parentsYoungerCousinsFirstChild = parentsYoungerCousin.getWhenTrue();
842 AgendaItemChildAccessor.whenTrue.setChild(parentsYoungerCousin, node);
843 AgendaItemChildAccessor.always.setChild(node, parentsYoungerCousinsFirstChild);
844
845 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" down ");
846 ruleEditorMessage.append("to first child under When TRUE group of ").append(parentsYoungerCousin.getRule().getName());
847 }
848 } else if (node.getAlways() != null) { // move node down within its sibling group
849
850 AgendaItemBo bogusRootNode = null;
851 if (parent == null) {
852 // special case, this is a top level sibling. rig up special parent node
853
854 bogusRootNode = new AgendaItemBo();
855 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
856 parent = bogusRootNode;
857 }
858
859 // move node down within its sibling group
860 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
861 AgendaItemBo youngerSibling = node.getAlways();
862 accessorToSelectedNode.setChild(youngerSibling);
863 AgendaItemChildAccessor.always.setChild(node, youngerSibling.getAlways());
864 AgendaItemChildAccessor.always.setChild(youngerSibling, node);
865
866 if (bogusRootNode != null) {
867 // clean up special case with bogus root node
868 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId());
869 }
870 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" down ");
871 ruleEditorMessage.append(" within its sibling group, below ").append(youngerSibling.getRule().getName());
872 } // falls through if already bottom-most
873 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
874 }
875
876 @RequestMapping(params = "methodToCall=" + "moveLeft")
877 public ModelAndView moveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
878 HttpServletRequest request, HttpServletResponse response)
879 throws Exception {
880 moveSelectedSubtreeLeft(form);
881
882 return super.refresh(form, result, request, response);
883 }
884
885 @RequestMapping(params = "methodToCall=" + "ajaxMoveLeft")
886 public ModelAndView ajaxMoveLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
887 HttpServletRequest request, HttpServletResponse response)
888 throws Exception {
889
890 moveSelectedSubtreeLeft(form);
891
892 // call the super method to avoid the agenda tree being reloaded from the db
893 return getUIFModelAndView(form);
894 }
895
896 /**
897 *
898 * @param form
899 * @see AgendaItemChildAccessor for nomenclature explanation
900 */
901 private void moveSelectedSubtreeLeft(UifFormBase form) {
902
903 /*
904 * Move left means make it a younger sibling of it's parent.
905 */
906
907 AgendaEditor agendaEditor = getAgendaEditor(form);
908 // this is the root of the tree:
909 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
910
911 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
912 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
913 AgendaItemBo parent = getParent(firstItem, selectedItemId);
914
915 if (parent != null) {
916 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
917 accessorToSelectedNode.setChild(node.getAlways());
918 AgendaItemChildAccessor.always.setChild(node, parent.getAlways());
919 AgendaItemChildAccessor.always.setChild(parent, node);
920
921 StringBuilder ruleEditorMessage = new StringBuilder();
922 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" left to be a sibling of its parent ").append(parent.getRule().getName());
923 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
924 }
925 }
926
927
928 @RequestMapping(params = "methodToCall=" + "moveRight")
929 public ModelAndView moveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
930 HttpServletRequest request, HttpServletResponse response)
931 throws Exception {
932
933 moveSelectedSubtreeRight(form);
934
935 return super.refresh(form, result, request, response);
936 }
937
938 @RequestMapping(params = "methodToCall=" + "ajaxMoveRight")
939 public ModelAndView ajaxMoveRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
940 HttpServletRequest request, HttpServletResponse response)
941 throws Exception {
942
943 moveSelectedSubtreeRight(form);
944
945 // call the super method to avoid the agenda tree being reloaded from the db
946 return getUIFModelAndView(form);
947 }
948
949 /**
950 *
951 * @param form
952 * @see AgendaItemChildAccessor for nomenclature explanation
953 */
954 private void moveSelectedSubtreeRight(UifFormBase form) {
955
956 /*
957 * Move right prefers moving to bottom of upper sibling's When FALSE branch
958 * ... otherwise ..
959 * moves to top of lower sibling's When TRUE branch
960 */
961
962 AgendaEditor agendaEditor = getAgendaEditor(form);
963 // this is the root of the tree:
964 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
965
966 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
967 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
968 AgendaItemBo parent = getParent(firstItem, selectedItemId);
969
970 AgendaItemBo bogusRootNode = null;
971 if (parent == null) {
972 // special case, this is a top level sibling. rig up special parent node
973 bogusRootNode = new AgendaItemBo();
974 AgendaItemChildAccessor.whenFalse.setChild(bogusRootNode, firstItem);
975 parent = bogusRootNode;
976 }
977
978 AgendaItemInstanceChildAccessor accessorToSelectedNode = getInstanceAccessorToChild(parent, node.getId());
979 AgendaItemBo olderSibling = (accessorToSelectedNode.getInstance() == parent) ? null : accessorToSelectedNode.getInstance();
980
981 StringBuilder ruleEditorMessage = new StringBuilder();
982 if (olderSibling != null) {
983 accessorToSelectedNode.setChild(node.getAlways());
984 AgendaItemInstanceChildAccessor yougestWhenFalseSiblingInsertionPoint =
985 getLastChildsAlwaysAccessor(new AgendaItemInstanceChildAccessor(AgendaItemChildAccessor.whenFalse, olderSibling));
986 yougestWhenFalseSiblingInsertionPoint.setChild(node);
987 AgendaItemChildAccessor.always.setChild(node, null);
988
989 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" right to ");
990 ruleEditorMessage.append(olderSibling.getRule().getName()).append(" When FALSE group.");
991 } else if (node.getAlways() != null) { // has younger sibling
992 accessorToSelectedNode.setChild(node.getAlways());
993 AgendaItemBo childsWhenTrue = node.getAlways().getWhenTrue();
994 AgendaItemChildAccessor.whenTrue.setChild(node.getAlways(), node);
995 AgendaItemChildAccessor.always.setChild(node, childsWhenTrue);
996
997 ruleEditorMessage.append("Moved ").append(node.getRule().getName()).append(" right to ");
998 if (childsWhenTrue != null) { // childsWhenTrue is null if the topmost rule is moved right see bogusRootNode below
999 ruleEditorMessage.append(childsWhenTrue.getRule().getName()).append(" When TRUE group");
1000 }
1001 } // falls through if node is already the rightmost.
1002
1003 if (bogusRootNode != null) {
1004 // clean up special case with bogus root node
1005 agendaEditor.getAgenda().setFirstItemId(bogusRootNode.getWhenFalseId());
1006 ruleEditorMessage.append(getFirstAgendaItem(agendaEditor.getAgenda()).getRule().getName()).append(" When TRUE group");
1007 }
1008 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
1009 }
1010
1011 /**
1012 *
1013 * @param cousin1
1014 * @param cousin2
1015 * @return
1016 * @see AgendaItemChildAccessor for nomenclature explanation
1017 */
1018 private boolean isSiblings(AgendaItemBo cousin1, AgendaItemBo cousin2) {
1019 if (cousin1.equals(cousin2)) return true; // this is a bit abusive
1020
1021 // can you walk to c1 from ALWAYS links of c2?
1022 AgendaItemBo candidate = cousin2;
1023 while (null != (candidate = candidate.getAlways())) {
1024 if (candidate.equals(cousin1)) return true;
1025 }
1026 // can you walk to c2 from ALWAYS links of c1?
1027 candidate = cousin1;
1028 while (null != (candidate = candidate.getAlways())) {
1029 if (candidate.equals(cousin2)) return true;
1030 }
1031 return false;
1032 }
1033
1034 /**
1035 * This method returns the level order accessor (getWhenTrue or getWhenFalse) that relates the parent directly
1036 * to the child. If the two nodes don't have such a relationship, null is returned.
1037 * Note that this only finds accessors for oldest children, not younger siblings.
1038 * @see AgendaItemChildAccessor for nomenclature explanation
1039 */
1040 private AgendaItemChildAccessor getOldestChildAccessor(
1041 AgendaItemBo child, AgendaItemBo parent) {
1042 AgendaItemChildAccessor levelOrderChildAccessor = null;
1043
1044 if (parent != null) {
1045 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.children) {
1046 if (child.equals(childAccessor.getChild(parent))) {
1047 levelOrderChildAccessor = childAccessor;
1048 break;
1049 }
1050 }
1051 }
1052 return levelOrderChildAccessor;
1053 }
1054
1055 /**
1056 * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
1057 *
1058 * @param agenda
1059 * @return
1060 */
1061 private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
1062 AgendaItemBo firstItem = null;
1063 if (agenda != null && agenda.getItems() != null) for (AgendaItemBo agendaItem : agenda.getItems()) {
1064 if (agenda.getFirstItemId().equals(agendaItem.getId())) {
1065 firstItem = agendaItem;
1066 break;
1067 }
1068 }
1069 return firstItem;
1070 }
1071
1072 /**
1073 * @return the closest younger sibling of the agenda item with the given ID, and if there is no such sibling, the closest younger cousin.
1074 * If there is no such cousin either, then null is returned.
1075 * @see AgendaItemChildAccessor for nomenclature explanation
1076 */
1077 private AgendaItemBo getNextYoungestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
1078
1079 int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
1080 List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
1081 buildAgendaItemGenerationList(genList, root, 0, genNumber);
1082
1083 int itemIndex = genList.indexOf(agendaItem);
1084 if (genList.size() > itemIndex + 1) return genList.get(itemIndex + 1);
1085
1086 return null;
1087 }
1088
1089 /**
1090 *
1091 * @param currentLevel
1092 * @param node
1093 * @param agendaItemId
1094 * @return
1095 * @see AgendaItemChildAccessor for nomenclature explanation
1096 */
1097 private int getAgendaItemGenerationNumber(int currentLevel, AgendaItemBo node, String agendaItemId) {
1098 int result = -1;
1099 if (agendaItemId.equals(node.getId())) {
1100 result = currentLevel;
1101 } else {
1102 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1103 AgendaItemBo child = childAccessor.getChild(node);
1104 if (child != null) {
1105 int nextLevel = currentLevel;
1106 // we don't change the level order parent when we traverse ALWAYS links
1107 if (childAccessor != AgendaItemChildAccessor.always) {
1108 nextLevel = currentLevel +1;
1109 }
1110 result = getAgendaItemGenerationNumber(nextLevel, child, agendaItemId);
1111 if (result != -1) break;
1112 }
1113 }
1114 }
1115 return result;
1116 }
1117
1118 /**
1119 *
1120 * @param genList
1121 * @param node
1122 * @param currentLevel
1123 * @param generation
1124 * @see AgendaItemChildAccessor for nomenclature explanation
1125 */
1126 private void buildAgendaItemGenerationList(List<AgendaItemBo> genList, AgendaItemBo node, int currentLevel, int generation) {
1127 if (currentLevel == generation) {
1128 genList.add(node);
1129 }
1130
1131 if (currentLevel > generation) return;
1132
1133 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1134 AgendaItemBo child = childAccessor.getChild(node);
1135 if (child != null) {
1136 int nextLevel = currentLevel;
1137 // we don't change the level order parent when we traverse ALWAYS links
1138 if (childAccessor != AgendaItemChildAccessor.always) {
1139 nextLevel = currentLevel +1;
1140 }
1141 buildAgendaItemGenerationList(genList, child, nextLevel, generation);
1142 }
1143 }
1144 }
1145
1146 /**
1147 * @return the closest older sibling of the agenda item with the given ID, and if there is no such sibling, the closest older cousin.
1148 * If there is no such cousin either, then null is returned.
1149 * @see AgendaItemChildAccessor for nomenclature explanation
1150 */
1151 private AgendaItemBo getNextOldestOfSameGeneration(AgendaItemBo root, AgendaItemBo agendaItem) {
1152
1153 int genNumber = getAgendaItemGenerationNumber(0, root, agendaItem.getId());
1154 List<AgendaItemBo> genList = new ArrayList<AgendaItemBo>();
1155 buildAgendaItemGenerationList(genList, root, 0, genNumber);
1156
1157 int itemIndex = genList.indexOf(agendaItem);
1158 if (itemIndex >= 1) return genList.get(itemIndex - 1);
1159
1160 return null;
1161 }
1162
1163
1164 /**
1165 * returns the parent of the item with the passed in id. Note that {@link AgendaItemBo}s related by ALWAYS relationships are considered siblings.
1166 * @see AgendaItemChildAccessor for nomenclature explanation
1167 */
1168 private AgendaItemBo getParent(AgendaItemBo root, String agendaItemId) {
1169 return getParentHelper(root, null, agendaItemId);
1170 }
1171
1172 /**
1173 *
1174 * @param node
1175 * @param levelOrderParent
1176 * @param agendaItemId
1177 * @return
1178 * @see AgendaItemChildAccessor for nomenclature explanation
1179 */
1180 private AgendaItemBo getParentHelper(AgendaItemBo node, AgendaItemBo levelOrderParent, String agendaItemId) {
1181 AgendaItemBo result = null;
1182 if (agendaItemId.equals(node.getId())) {
1183 result = levelOrderParent;
1184 } else {
1185 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1186 AgendaItemBo child = childAccessor.getChild(node);
1187 if (child != null) {
1188 // we don't change the level order parent when we traverse ALWAYS links
1189 AgendaItemBo lop = (childAccessor == AgendaItemChildAccessor.always) ? levelOrderParent : node;
1190 result = getParentHelper(child, lop, agendaItemId);
1191 if (result != null) break;
1192 }
1193 }
1194 }
1195 return result;
1196 }
1197
1198 /**
1199 * Search the tree for the agenda item with the given id.
1200 */
1201 private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
1202 if (node == null) throw new IllegalArgumentException("node must be non-null");
1203
1204 AgendaItemBo result = null;
1205
1206 if (agendaItemId.equals(node.getId())) {
1207 result = node;
1208 } else {
1209 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1210 AgendaItemBo child = childAccessor.getChild(node);
1211 if (child != null) {
1212 result = getAgendaItemById(child, agendaItemId);
1213 if (result != null) break;
1214 }
1215 }
1216 }
1217 return result;
1218 }
1219
1220 /**
1221 * @param form
1222 * @return the {@link AgendaEditor} from the form
1223 */
1224 private AgendaEditor getAgendaEditor(UifFormBase form) {
1225 MaintenanceDocumentForm maintenanceForm = (MaintenanceDocumentForm) form;
1226 return ((AgendaEditor)maintenanceForm.getDocument().getDocumentDataObject());
1227 }
1228
1229 private void treeToInOrderList(AgendaItemBo agendaItem, List<AgendaItemBo> listToBuild) {
1230 listToBuild.add(agendaItem);
1231 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1232 AgendaItemBo child = childAccessor.getChild(agendaItem);
1233 if (child != null) treeToInOrderList(child, listToBuild);
1234 }
1235 }
1236
1237
1238 @RequestMapping(params = "methodToCall=" + "delete")
1239 public ModelAndView delete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1240 HttpServletRequest request, HttpServletResponse response)
1241 throws Exception {
1242
1243 deleteSelectedSubtree(form);
1244
1245 return super.refresh(form, result, request, response);
1246 }
1247
1248 @RequestMapping(params = "methodToCall=" + "ajaxDelete")
1249 public ModelAndView ajaxDelete(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1250 HttpServletRequest request, HttpServletResponse response)
1251 throws Exception {
1252
1253 deleteSelectedSubtree(form);
1254
1255 // call the super method to avoid the agenda tree being reloaded from the db
1256 return getUIFModelAndView(form);
1257 }
1258
1259
1260 private void deleteSelectedSubtree(UifFormBase form) {
1261 AgendaEditor agendaEditor = getAgendaEditor(form);
1262 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
1263
1264 if (firstItem != null) {
1265 String agendaItemSelected = agendaEditor.getSelectedAgendaItemId();
1266 AgendaItemBo selectedItem = getAgendaItemById(firstItem, agendaItemSelected);
1267
1268 // need to handle the first item here, our recursive method won't handle it.
1269 if (agendaItemSelected.equals(firstItem.getId())) {
1270 agendaEditor.getAgenda().setFirstItemId(firstItem.getAlwaysId());
1271 } else {
1272 deleteAgendaItem(firstItem, agendaItemSelected);
1273 }
1274
1275 StringBuilder ruleEditorMessage = new StringBuilder();
1276 ruleEditorMessage.append("Deleted ").append(selectedItem.getRule().getName());
1277 // remove agenda item and its whenTrue & whenFalse children from the list of agendaItems of the agenda
1278 if (selectedItem.getWhenTrue() != null) {
1279 removeAgendaItem(agendaEditor.getAgenda().getItems(), selectedItem.getWhenTrue());
1280 ruleEditorMessage.append(" and its When TRUE ").append(selectedItem.getWhenTrue().getRule().getName());
1281 }
1282 if (selectedItem.getWhenFalse() != null) {
1283 removeAgendaItem(agendaEditor.getAgenda().getItems(), selectedItem.getWhenFalse());
1284 ruleEditorMessage.append(" and its When FALSE ").append(selectedItem.getWhenFalse().getRule().getName());
1285 }
1286 agendaEditor.getAgenda().getItems().remove(selectedItem);
1287 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
1288 }
1289 }
1290
1291 private void deleteAgendaItem(AgendaItemBo root, String agendaItemIdToDelete) {
1292 if (deleteAgendaItem(root, AgendaItemChildAccessor.whenTrue, agendaItemIdToDelete) ||
1293 deleteAgendaItem(root, AgendaItemChildAccessor.whenFalse, agendaItemIdToDelete) ||
1294 deleteAgendaItem(root, AgendaItemChildAccessor.always, agendaItemIdToDelete)); // TODO: this is confusing, refactor
1295 }
1296
1297 private boolean deleteAgendaItem(AgendaItemBo agendaItem, AgendaItemChildAccessor childAccessor, String agendaItemIdToDelete) {
1298 if (agendaItem == null || childAccessor.getChild(agendaItem) == null) return false;
1299 if (agendaItemIdToDelete.equals(childAccessor.getChild(agendaItem).getId())) {
1300 // delete the child in such a way that any ALWAYS children don't get lost from the tree
1301 AgendaItemBo grandchildToKeep = childAccessor.getChild(agendaItem).getAlways();
1302 childAccessor.setChild(agendaItem, grandchildToKeep);
1303 return true;
1304 } else {
1305 AgendaItemBo child = childAccessor.getChild(agendaItem);
1306 // recurse
1307 for (AgendaItemChildAccessor nextChildAccessor : AgendaItemChildAccessor.linkedNodes) {
1308 if (deleteAgendaItem(child, nextChildAccessor, agendaItemIdToDelete)) return true;
1309 }
1310 }
1311 return false;
1312 }
1313
1314 /**
1315 * Recursively delete the agendaItem and its children from the agendaItemBo list.
1316 * @param items, the list of agendaItemBo that the agenda holds
1317 * @param removeAgendaItem, the agendaItemBo to be removed
1318 */
1319 private void removeAgendaItem(List<AgendaItemBo> items, AgendaItemBo removeAgendaItem) {
1320 if (removeAgendaItem.getWhenTrue() != null) {
1321 removeAgendaItem(items, removeAgendaItem.getWhenTrue());
1322 }
1323 if (removeAgendaItem.getWhenFalse() != null) {
1324 removeAgendaItem(items, removeAgendaItem.getWhenFalse());
1325 }
1326 if (removeAgendaItem.getAlways() != null) {
1327 removeAgendaItem(items, removeAgendaItem.getAlways());
1328 }
1329 items.remove(removeAgendaItem);
1330 }
1331
1332 @RequestMapping(params = "methodToCall=" + "ajaxCut")
1333 public ModelAndView ajaxCut(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1334 HttpServletRequest request, HttpServletResponse response) throws Exception {
1335
1336 AgendaEditor agendaEditor = getAgendaEditor(form);
1337 // this is the root of the tree:
1338 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
1339 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
1340
1341 AgendaItemBo selectedAgendaItem = getAgendaItemById(firstItem, selectedItemId);
1342 setCutAgendaItemId(form, selectedItemId);
1343
1344 StringBuilder ruleEditorMessage = new StringBuilder();
1345 ruleEditorMessage.append("Marked ").append(selectedAgendaItem.getRule().getName()).append(" for cutting.");
1346 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
1347 // call the super method to avoid the agenda tree being reloaded from the db
1348 return getUIFModelAndView(form);
1349 }
1350
1351 @RequestMapping(params = "methodToCall=" + "ajaxPaste")
1352 public ModelAndView ajaxPaste(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1353 HttpServletRequest request, HttpServletResponse response) throws Exception {
1354
1355 AgendaEditor agendaEditor = getAgendaEditor(form);
1356 // this is the root of the tree:
1357 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
1358 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
1359
1360 String agendaItemId = getCutAgendaItemId(form);
1361 if (StringUtils.isNotBlank(selectedItemId) && StringUtils.isNotBlank(agendaItemId)) {
1362 StringBuilder ruleEditorMessage = new StringBuilder();
1363 AgendaItemBo node = getAgendaItemById(firstItem, agendaItemId);
1364 AgendaItemBo orgRefNode = getReferringNode(firstItem, agendaItemId);
1365 AgendaItemBo newRefNode = getAgendaItemById(firstItem, selectedItemId);
1366
1367 if (isSameOrChildNode(node, newRefNode)) {
1368 // note if the cut agenda item is not cleared, then the javascript on the AgendaEditorView will need to be
1369 // updated to deal with a paste that doesn't paste. As the ui disables the paste button after it is clicked
1370 ruleEditorMessage.append("Cannot paste ").append(node.getRule().getName()).append(" to itself.");
1371 } else {
1372 // remove node
1373 if (orgRefNode == null) {
1374 agendaEditor.getAgenda().setFirstItemId(node.getAlwaysId());
1375 } else {
1376 // determine if true, false or always
1377 // do appropriate operation
1378 if (node.getId().equals(orgRefNode.getWhenTrueId())) {
1379 orgRefNode.setWhenTrueId(node.getAlwaysId());
1380 orgRefNode.setWhenTrue(node.getAlways());
1381 } else if(node.getId().equals(orgRefNode.getWhenFalseId())) {
1382 orgRefNode.setWhenFalseId(node.getAlwaysId());
1383 orgRefNode.setWhenFalse(node.getAlways());
1384 } else {
1385 orgRefNode.setAlwaysId(node.getAlwaysId());
1386 orgRefNode.setAlways(node.getAlways());
1387 }
1388 }
1389
1390 // insert node
1391 node.setAlwaysId(newRefNode.getAlwaysId());
1392 node.setAlways(newRefNode.getAlways());
1393 newRefNode.setAlwaysId(node.getId());
1394 newRefNode.setAlways(node);
1395
1396 ruleEditorMessage.append(" Pasted ").append(node.getRule().getName());
1397 ruleEditorMessage.append(" to ").append(newRefNode.getRule().getName());
1398 agendaEditor.setRuleEditorMessage(ruleEditorMessage.toString());
1399
1400 }
1401 setCutAgendaItemId(form, null);
1402 }
1403
1404
1405 // call the super method to avoid the agenda tree being reloaded from the db
1406 return getUIFModelAndView(form);
1407 }
1408
1409 /**
1410 * Updates to the category call back to this method to set the categoryId appropriately
1411 * TODO: shouldn't this happen automatically? We're taking it out of the form by hand here
1412 */
1413 @RequestMapping(params = "methodToCall=" + "ajaxCategoryChangeRefresh")
1414 public ModelAndView ajaxCategoryChangeRefresh(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1415 HttpServletRequest request, HttpServletResponse response)
1416 throws Exception {
1417
1418 String categoryParamName = null;
1419 Enumeration paramNames = request.getParameterNames();
1420 while (paramNames.hasMoreElements()) {
1421 String paramName = paramNames.nextElement().toString();
1422 if (paramName.endsWith("categoryId")) {
1423 categoryParamName = paramName;
1424 break;
1425 }
1426 }
1427
1428 if (categoryParamName != null) {
1429 String categoryId = request.getParameter(categoryParamName);
1430
1431 if (StringUtils.isBlank(categoryId)) { categoryId = null; }
1432
1433 AgendaEditor agendaEditor = getAgendaEditor(form);
1434 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1435 String selectedPropId = agendaEditor.getSelectedPropositionId();
1436
1437 // TODO: This should work even if the prop isn't selected!!! Find the node in edit mode?
1438 if (!StringUtils.isBlank(selectedPropId)) {
1439 Node<RuleTreeNode, String> selectedPropositionNode =
1440 findPropositionTreeNode(rule.getPropositionTree().getRootElement(), selectedPropId);
1441 selectedPropositionNode.getData().getProposition().setCategoryId(categoryId);
1442 }
1443 }
1444
1445 return ajaxRefresh(form, result, request, response);
1446 }
1447
1448 /**
1449 * This method checks if the node is the same as the new parent node or a when-true/when-fase
1450 * child of the new parent node.
1451 *
1452 * @param node - the node to be checked if it's the same or a child
1453 * @param newParent - the parent node to check against
1454 * @return true if same or child, false otherwise
1455 * @see AgendaItemChildAccessor for nomenclature explanation
1456 */
1457 private boolean isSameOrChildNode(AgendaItemBo node, AgendaItemBo newParent) {
1458 return isSameOrChildNodeHelper(node, newParent, AgendaItemChildAccessor.children);
1459 }
1460
1461 private boolean isSameOrChildNodeHelper(AgendaItemBo node, AgendaItemBo newParent, AgendaItemChildAccessor[] childAccessors) {
1462 boolean result = false;
1463 if (newParent == null || node == null) {
1464 return false;
1465 }
1466 if (StringUtils.equals(node.getId(), newParent.getId())) {
1467 result = true;
1468 } else {
1469 for (AgendaItemChildAccessor childAccessor : childAccessors) {
1470 AgendaItemBo child = childAccessor.getChild(node);
1471 if (child != null) {
1472 result = isSameOrChildNodeHelper(child, newParent, AgendaItemChildAccessor.linkedNodes);
1473 if (result == true) break;
1474 }
1475 }
1476 }
1477 return result;
1478 }
1479
1480 /**
1481 * This method returns the node that points to the specified agendaItemId.
1482 * (returns the next older sibling or the parent if no older sibling exists)
1483 *
1484 * @param root - the first agenda item of the agenda
1485 * @param agendaItemId - agenda item id of the agenda item whose referring node is to be returned
1486 * @return AgendaItemBo that points to the specified agenda item
1487 * @see AgendaItemChildAccessor for nomenclature explanation
1488 */
1489 private AgendaItemBo getReferringNode(AgendaItemBo root, String agendaItemId) {
1490 return getReferringNodeHelper(root, null, agendaItemId);
1491 }
1492
1493 private AgendaItemBo getReferringNodeHelper(AgendaItemBo node, AgendaItemBo referringNode, String agendaItemId) {
1494 AgendaItemBo result = null;
1495 if (agendaItemId.equals(node.getId())) {
1496 result = referringNode;
1497 } else {
1498 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
1499 AgendaItemBo child = childAccessor.getChild(node);
1500 if (child != null) {
1501 result = getReferringNodeHelper(child, node, agendaItemId);
1502 if (result != null) break;
1503 }
1504 }
1505 }
1506 return result;
1507 }
1508
1509 /**
1510 * return the sequenceAssessorService
1511 */
1512 private SequenceAccessorService getSequenceAccessorService() {
1513 if ( sequenceAccessorService == null ) {
1514 sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
1515 }
1516 return sequenceAccessorService;
1517 }
1518
1519 /**
1520 * return the contextBoService
1521 */
1522 private ContextBoService getContextBoService() {
1523 return KrmsRepositoryServiceLocator.getContextBoService();
1524 }
1525
1526 /**
1527 * return the contextBoService
1528 */
1529 private RuleBoService getRuleBoService() {
1530 return KrmsRepositoryServiceLocator.getRuleBoService();
1531 }
1532
1533 /**
1534 * binds a child accessor to an AgendaItemBo instance. An {@link AgendaItemInstanceChildAccessor} allows you to
1535 * get and set the referent
1536 */
1537 private static class AgendaItemInstanceChildAccessor {
1538
1539 private final AgendaItemChildAccessor accessor;
1540 private final AgendaItemBo instance;
1541
1542 public AgendaItemInstanceChildAccessor(AgendaItemChildAccessor accessor, AgendaItemBo instance) {
1543 this.accessor = accessor;
1544 this.instance = instance;
1545 }
1546
1547 public void setChild(AgendaItemBo child) {
1548 accessor.setChild(instance, child);
1549 }
1550
1551 public AgendaItemBo getChild() {
1552 return accessor.getChild(instance);
1553 }
1554
1555 public AgendaItemBo getInstance() { return instance; }
1556 }
1557
1558 /**
1559 * <p>This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations
1560 * require less boiler plate.</p>
1561 *
1562 * <p>The word 'child' in AgendaItemChildAccessor means child in the strict data structures sense, in that the
1563 * instance passed in holds a reference to some other node (or null). However, when discussing the agenda tree
1564 * and algorithms for manipulating it, the meaning of 'child' is somewhat different, and there are notions of
1565 * 'sibling' and 'cousin' that are tossed about too. It's probably worth explaining that somewhat here:</p>
1566 *
1567 * <p>General principals of relationships when talking about the agenda tree:
1568 * <ul>
1569 * <li>Generation boundaries (parent to child) are across 'When TRUE' and 'When FALSE' references.</li>
1570 * <li>"Age" among siblings & cousins goes from top (oldest) to bottom (youngest).</li>
1571 * <li>siblings are related by 'Always' references.</li>
1572 * </ul>
1573 * </p>
1574 * <p>This diagram of an agenda tree and the following examples seek to illustrate these principals:</p>
1575 * <img src="doc-files/AgendaEditorController-1.png" alt="Example Agenda Items"/>
1576 * <p>Examples:
1577 * <ul>
1578 * <li>A is the parent of B, C, & D</li>
1579 * <li>E is the younger sibling of A</li>
1580 * <li>B is the older cousin of C</li>
1581 * <li>C is the older sibling of D</li>
1582 * <li>F is the younger cousin of D</li>
1583 * </ul>
1584 * </p>
1585 */
1586 protected static class AgendaItemChildAccessor {
1587
1588 private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
1589
1590 private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE);
1591 private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE);
1592 private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS);
1593
1594 /**
1595 * Accessors for all linked items
1596 */
1597 private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
1598
1599 /**
1600 * Accessors for children (so ALWAYS is omitted);
1601 */
1602 private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
1603
1604 private final Child whichChild;
1605
1606 private AgendaItemChildAccessor(Child whichChild) {
1607 if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
1608 this.whichChild = whichChild;
1609 }
1610
1611 /**
1612 * @return the referenced child
1613 */
1614 public AgendaItemBo getChild(AgendaItemBo parent) {
1615 switch (whichChild) {
1616 case WHEN_TRUE: return parent.getWhenTrue();
1617 case WHEN_FALSE: return parent.getWhenFalse();
1618 case ALWAYS: return parent.getAlways();
1619 default: throw new IllegalStateException();
1620 }
1621 }
1622
1623 /**
1624 * Sets the child reference and the child id
1625 */
1626 public void setChild(AgendaItemBo parent, AgendaItemBo child) {
1627 switch (whichChild) {
1628 case WHEN_TRUE:
1629 parent.setWhenTrue(child);
1630 parent.setWhenTrueId(child == null ? null : child.getId());
1631 break;
1632 case WHEN_FALSE:
1633 parent.setWhenFalse(child);
1634 parent.setWhenFalseId(child == null ? null : child.getId());
1635 break;
1636 case ALWAYS:
1637 parent.setAlways(child);
1638 parent.setAlwaysId(child == null ? null : child.getId());
1639 break;
1640 default: throw new IllegalStateException();
1641 }
1642 }
1643 }
1644 //
1645 // Rule Editor Controller methods
1646 //
1647 @RequestMapping(params = "methodToCall=" + "copyRule")
1648 public ModelAndView copyRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1649 HttpServletRequest request, HttpServletResponse response) throws Exception {
1650
1651 AgendaEditor agendaEditor = getAgendaEditor(form);
1652 String name = agendaEditor.getCopyRuleName();
1653 String namespace = agendaEditor.getNamespace();
1654 // fetch existing rule and copy fields to new rule
1655
1656 final String copyRuleNameErrorPropertyName = "AgendaEditorView-AddRule-Page"; //"copyRuleName",
1657 if (StringUtils.isBlank(name)) {
1658 GlobalVariables.getMessageMap().putError(copyRuleNameErrorPropertyName, "error.rule.missingCopyRuleName");
1659 return super.refresh(form, result, request, response);
1660 }
1661
1662 RuleDefinition oldRuleDefinition = getRuleBoService().getRuleByNameAndNamespace(name, namespace);
1663
1664 if (oldRuleDefinition == null) {
1665 GlobalVariables.getMessageMap().putError(copyRuleNameErrorPropertyName, "error.rule.invalidCopyRuleName", namespace + ":" + name);
1666 return super.refresh(form, result, request, response);
1667 }
1668
1669 RuleBo oldRule = RuleBo.from(oldRuleDefinition);
1670 RuleBo newRule = RuleBo.copyRule(oldRule);
1671 agendaEditor.getAgendaItemLine().setRule( newRule );
1672 // hack to set ui action object to first action in the list
1673 if (!newRule.getActions().isEmpty()) {
1674 agendaEditor.setAgendaItemLineRuleAction( newRule.getActions().get(0));
1675 }
1676 return super.refresh(form, result, request, response);
1677 }
1678
1679
1680 /**
1681 * This method starts an edit proposition.
1682 */
1683 @RequestMapping(params = "methodToCall=" + "goToEditProposition")
1684 public ModelAndView goToEditProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1685 HttpServletRequest request, HttpServletResponse response) throws Exception {
1686
1687 // open the selected node for editing
1688 AgendaEditor agendaEditor = getAgendaEditor(form);
1689 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1690 String selectedPropId = agendaEditor.getSelectedPropositionId();
1691
1692 Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement();
1693 PropositionBo propositionToToggleEdit = null;
1694 boolean newEditMode = true;
1695
1696 // find parent
1697 Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId);
1698 if (parent != null){
1699 List<Node<RuleTreeNode,String>> children = parent.getChildren();
1700 for( int index=0; index< children.size(); index++){
1701 Node<RuleTreeNode,String> child = children.get(index);
1702 if (propIdMatches(child, selectedPropId)){
1703 PropositionBo prop = child.getData().getProposition();
1704 propositionToToggleEdit = prop;
1705 newEditMode = !prop.getEditMode();
1706 break;
1707 } else {
1708 child.getData().getProposition().setEditMode(false);
1709 }
1710 }
1711 }
1712
1713 resetEditModeOnPropositionTree(root);
1714 if (propositionToToggleEdit != null) {
1715 propositionToToggleEdit.setEditMode(newEditMode);
1716 //refresh the tree
1717 rule.refreshPropositionTree(null);
1718 }
1719
1720 return getUIFModelAndView(form);
1721 }
1722
1723 @RequestMapping(params = "methodToCall=" + "addProposition")
1724 public ModelAndView addProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1725 HttpServletRequest request, HttpServletResponse response) throws Exception {
1726
1727 AgendaEditor agendaEditor = getAgendaEditor(form);
1728 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1729 String selectedPropId = agendaEditor.getSelectedPropositionId();
1730
1731
1732 // find parent
1733 Node<RuleTreeNode,String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement();
1734 Node<RuleTreeNode,String> parent = findParentPropositionNode( root, selectedPropId);
1735
1736 resetEditModeOnPropositionTree(root);
1737
1738 // add new child at appropriate spot
1739 if (parent != null){
1740 List<Node<RuleTreeNode,String>> children = parent.getChildren();
1741 for( int index=0; index< children.size(); index++){
1742 Node<RuleTreeNode,String> child = children.get(index);
1743
1744 // if our selected node is a simple proposition, add a new one after
1745 if (propIdMatches(child, selectedPropId)){
1746 // handle special case of adding to a lone simple proposition.
1747 // in this case, we need to change the root level proposition to a compound proposition
1748 // move the existing simple proposition as the first compound component,
1749 // then add a new blank simple prop as the second compound component.
1750 if (parent == root &&
1751 (SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1752 SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()))){
1753
1754 // create a new compound proposition
1755 PropositionBo compound = PropositionBo.createCompoundPropositionBoStub(child.getData().getProposition(), true);
1756 // don't set compound.setEditMode(true) as the Simple Prop in the compound prop is the only prop in edit mode
1757 rule.setProposition(compound);
1758 rule.refreshPropositionTree(null);
1759 }
1760 // handle regular case of adding a simple prop to an existing compound prop
1761 else if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1762 SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType())){
1763
1764 // build new Blank Proposition
1765 PropositionBo blank = PropositionBo.createSimplePropositionBoStub(child.getData().getProposition(),PropositionType.SIMPLE.getCode());
1766 //add it to the parent
1767 PropositionBo parentProp = parent.getData().getProposition();
1768 parentProp.getCompoundComponents().add(((index/2)+1), blank);
1769
1770 rule.refreshPropositionTree(true);
1771 }
1772
1773 break;
1774 }
1775 }
1776 } else {
1777 // special case, if root has no children, add a new simple proposition
1778 // todo: how to add compound proposition. - just add another to the firs simple
1779 if (root.getChildren().isEmpty()){
1780 PropositionBo blank = PropositionBo.createSimplePropositionBoStub(null,PropositionType.SIMPLE.getCode());
1781 blank.setRuleId(rule.getId());
1782 rule.setPropId(blank.getId());
1783 rule.setProposition(blank);
1784 rule.refreshPropositionTree(true);
1785 }
1786 }
1787 return getUIFModelAndView(form);
1788 }
1789
1790 /**
1791 *
1792 * This method adds an opCode Node to separate components in a compound proposition.
1793 *
1794 * @param currentNode
1795 * @param prop
1796 * @return
1797 */
1798 private void addOpCodeNode(Node currentNode, PropositionBo prop, int index){
1799 String opCodeLabel = "";
1800
1801 if (LogicalOperator.AND.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){
1802 opCodeLabel = "AND";
1803 } else if (LogicalOperator.OR.getCode().equalsIgnoreCase(prop.getCompoundOpCode())){
1804 opCodeLabel = "OR";
1805 }
1806 Node<RuleTreeNode, String> aNode = new Node<RuleTreeNode, String>();
1807 aNode.setNodeLabel("");
1808 aNode.setNodeType("ruleTreeNode compoundOpCodeNode");
1809 aNode.setData(new CompoundOpCodeNode(prop));
1810 currentNode.insertChildAt(index, aNode);
1811 }
1812
1813
1814 private boolean propIdMatches(Node<RuleTreeNode, String> node, String propId){
1815 if (propId!=null && node != null && node.getData() != null && propId.equalsIgnoreCase(node.getData().getProposition().getId())) {
1816 return true;
1817 }
1818 return false;
1819 }
1820
1821 /**
1822 * disable edit mode for all Nodes beneath and including the passed in Node
1823 * @param currentNode
1824 */
1825 private void resetEditModeOnPropositionTree(Node<RuleTreeNode, String> currentNode){
1826 if (currentNode.getData() != null){
1827 RuleTreeNode dataNode = currentNode.getData();
1828 dataNode.getProposition().setEditMode(false);
1829 }
1830 List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
1831 for( Node<RuleTreeNode,String> child : children){
1832 resetEditModeOnPropositionTree(child);
1833 }
1834 }
1835
1836 private Node<RuleTreeNode, String> findPropositionTreeNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){
1837 Node<RuleTreeNode,String> bingo = null;
1838 if (currentNode.getData() != null){
1839 RuleTreeNode dataNode = currentNode.getData();
1840 if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId())){
1841 return currentNode;
1842 }
1843 }
1844 List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
1845 for( Node<RuleTreeNode,String> child : children){
1846 bingo = findPropositionTreeNode(child, selectedPropId);
1847 if (bingo != null) break;
1848 }
1849 return bingo;
1850 }
1851
1852 private Node<RuleTreeNode, String> findParentPropositionNode(Node<RuleTreeNode, String> currentNode, String selectedPropId){
1853 Node<RuleTreeNode,String> bingo = null;
1854 if (selectedPropId != null) {
1855 // if it's in children, we have the parent
1856 List<Node<RuleTreeNode,String>> children = currentNode.getChildren();
1857 for( Node<RuleTreeNode,String> child : children){
1858 RuleTreeNode dataNode = child.getData();
1859 if (selectedPropId.equalsIgnoreCase(dataNode.getProposition().getId()))
1860 return currentNode;
1861 }
1862
1863 // if not found check grandchildren
1864 for( Node<RuleTreeNode,String> kid : children){
1865 bingo = findParentPropositionNode(kid, selectedPropId);
1866 if (bingo != null) break;
1867 }
1868 }
1869 return bingo;
1870 }
1871
1872 /**
1873 * This method return the index of the position of the child that matches the id
1874 * @param parent
1875 * @param propId
1876 * @return index if found, -1 if not found
1877 */
1878 private int findChildIndex(Node<RuleTreeNode,String> parent, String propId){
1879 int index;
1880 List<Node<RuleTreeNode,String>> children = parent.getChildren();
1881 for(index=0; index< children.size(); index++){
1882 Node<RuleTreeNode,String> child = children.get(index);
1883 // if our selected node is a simple proposition, add a new one after
1884 if (propIdMatches(child, propId)){
1885 return index;
1886 }
1887 }
1888 return -1;
1889 }
1890
1891 @RequestMapping(params = "methodToCall=" + "movePropositionUp")
1892 public ModelAndView movePropositionUp(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1893 HttpServletRequest request, HttpServletResponse response)
1894 throws Exception {
1895 moveSelectedProposition(form, true);
1896
1897 return getUIFModelAndView(form);
1898 }
1899
1900 @RequestMapping(params = "methodToCall=" + "movePropositionDown")
1901 public ModelAndView movePropositionDown(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1902 HttpServletRequest request, HttpServletResponse response)
1903 throws Exception {
1904 moveSelectedProposition(form, false);
1905
1906 return getUIFModelAndView(form);
1907 }
1908
1909 private void moveSelectedProposition(UifFormBase form, boolean up) {
1910
1911 /* Rough algorithm for moving a node up.
1912 *
1913 * find the following:
1914 * node := the selected node
1915 * parent := the selected node's parent, its containing node (via when true or when false relationship)
1916 * parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
1917 *
1918 */
1919 AgendaEditor agendaEditor = getAgendaEditor(form);
1920 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1921 String selectedPropId = agendaEditor.getSelectedPropositionId();
1922
1923 // find parent
1924 Node<RuleTreeNode,String> parent = findParentPropositionNode(rule.getPropositionTree().getRootElement(), selectedPropId);
1925
1926 // add new child at appropriate spot
1927 if (parent != null){
1928 List<Node<RuleTreeNode,String>> children = parent.getChildren();
1929 for( int index=0; index< children.size(); index++){
1930 Node<RuleTreeNode,String> child = children.get(index);
1931 // if our selected node is a simple proposition, add a new one after
1932 if (propIdMatches(child, selectedPropId)){
1933 if(SimplePropositionNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1934 SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ||
1935 RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(child.getNodeType()) ){
1936
1937 if (((index > 0) && up) || ((index <(children.size() - 1)&& !up))){
1938 //remove it from its current spot
1939 PropositionBo parentProp = parent.getData().getProposition();
1940 PropositionBo workingProp = parentProp.getCompoundComponents().remove(index/2);
1941 if (up){
1942 parentProp.getCompoundComponents().add((index/2)-1, workingProp);
1943 }else{
1944 parentProp.getCompoundComponents().add((index/2)+1, workingProp);
1945 }
1946
1947 // insert it in the new spot
1948 // redisplay the tree (editMode = true)
1949 boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()));
1950 rule.refreshPropositionTree(editMode);
1951 }
1952 }
1953
1954 break;
1955 }
1956 }
1957 }
1958 }
1959 @RequestMapping(params = "methodToCall=" + "movePropositionLeft")
1960 public ModelAndView movePropositionLeft(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
1961 HttpServletRequest request, HttpServletResponse response)
1962 throws Exception {
1963
1964 /* Rough algorithm for moving a node up.
1965 *
1966 * find the following:
1967 * node := the selected node
1968 * parent := the selected node's parent, its containing node (via when true or when false relationship)
1969 * parentsOlderCousin := the parent's level-order predecessor (sibling or cousin)
1970 *
1971 */
1972 AgendaEditor agendaEditor = getAgendaEditor(form);
1973 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
1974 String selectedPropId = agendaEditor.getSelectedPropositionId();
1975
1976 // find agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement()parent
1977 Node<RuleTreeNode,String> root = rule.getPropositionTree().getRootElement();
1978 Node<RuleTreeNode,String> parent = findParentPropositionNode(root, selectedPropId);
1979 if ((parent != null) && (RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(parent.getNodeType()))){
1980 Node<RuleTreeNode,String> granny = findParentPropositionNode(root,parent.getData().getProposition().getId());
1981 if (granny != root){
1982 int oldIndex = findChildIndex(parent, selectedPropId);
1983 int newIndex = findChildIndex(granny, parent.getData().getProposition().getId());
1984 if (oldIndex >= 0 && newIndex >= 0){
1985 PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(oldIndex/2);
1986 granny.getData().getProposition().getCompoundComponents().add((newIndex/2)+1, prop);
1987 rule.refreshPropositionTree(false);
1988 }
1989 } else {
1990 // TODO: do we allow moving up to the root?
1991 // we could add a new top level compound node, with current root as 1st child,
1992 // and move the node to the second child.
1993 }
1994 }
1995 return getUIFModelAndView(form);
1996 }
1997
1998 @RequestMapping(params = "methodToCall=" + "movePropositionRight")
1999 public ModelAndView movePropositionRight(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
2000 HttpServletRequest request, HttpServletResponse response)
2001 throws Exception {
2002 /* Rough algorithm for moving a node Right
2003 * if the selected node is above a compound proposition, move it into the compound proposition as the first child
2004 * if the node is above a simple proposition, do nothing.
2005 * find the following:
2006 * node := the selected node
2007 * parent := the selected node's parent, its containing node
2008 * nextSibling := the node after the selected node
2009 *
2010 */
2011 AgendaEditor agendaEditor = getAgendaEditor(form);
2012 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
2013 String selectedPropId = agendaEditor.getSelectedPropositionId();
2014
2015 // find parent
2016 Node<RuleTreeNode,String> parent = findParentPropositionNode(
2017 rule.getPropositionTree().getRootElement(), selectedPropId);
2018 if (parent != null){
2019 int index = findChildIndex(parent, selectedPropId);
2020 // if we are the last child, do nothing, otherwise
2021 if (index >= 0 && index+1 < parent.getChildren().size()){
2022 Node<RuleTreeNode,String> child = parent.getChildren().get(index);
2023 Node<RuleTreeNode,String> nextSibling = parent.getChildren().get(index+2);
2024 // if selected node above a compound node, move it into it as first child
2025 if(RuleTreeNode.COMPOUND_NODE_TYPE.equalsIgnoreCase(nextSibling.getNodeType()) ){
2026 // remove selected node from it's current spot
2027 PropositionBo prop = parent.getData().getProposition().getCompoundComponents().remove(index/2);
2028 // add it to it's siblings children
2029 nextSibling.getData().getProposition().getCompoundComponents().add(0, prop);
2030 rule.refreshPropositionTree(false);
2031 }
2032 }
2033 }
2034 return getUIFModelAndView(form);
2035 }
2036
2037 /**
2038 * introduces a new compound proposition between the selected proposition and its parent.
2039 * Additionally, it puts a new blank simple proposition underneath the compound proposition
2040 * as a sibling to the selected proposition.
2041 */
2042 @RequestMapping(params = "methodToCall=" + "togglePropositionSimpleCompound")
2043 public ModelAndView togglePropositionSimpleCompound(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
2044 HttpServletRequest request, HttpServletResponse response)
2045 throws Exception {
2046
2047 AgendaEditor agendaEditor = getAgendaEditor(form);
2048 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
2049 String selectedPropId = agendaEditor.getSelectedPropositionId();
2050
2051 resetEditModeOnPropositionTree(rule.getPropositionTree().getRootElement());
2052
2053 if (!StringUtils.isBlank(selectedPropId)) {
2054 // find parent
2055 Node<RuleTreeNode,String> parent = findParentPropositionNode(
2056 rule.getPropositionTree().getRootElement(), selectedPropId);
2057 if (parent != null){
2058
2059 int index = findChildIndex(parent, selectedPropId);
2060
2061 PropositionBo propBo = parent.getChildren().get(index).getData().getProposition();
2062
2063 // create a new compound proposition
2064 PropositionBo compound = PropositionBo.createCompoundPropositionBoStub(propBo, true);
2065 compound.setDescription("New Compound Proposition");
2066 compound.setEditMode(false);
2067
2068 if (parent.getData() == null) { // SPECIAL CASE: this is the only proposition in the tree
2069 rule.setProposition(compound);
2070 } else {
2071 PropositionBo parentBo = parent.getData().getProposition();
2072 List<PropositionBo> siblings = parentBo.getCompoundComponents();
2073
2074 int propIndex = -1;
2075 for (int i=0; i<siblings.size(); i++) {
2076 if (propBo.getId().equals(siblings.get(i).getId())) {
2077 propIndex = i;
2078 break;
2079 }
2080 }
2081
2082 parentBo.getCompoundComponents().set(propIndex, compound);
2083 }
2084 }
2085 }
2086
2087 agendaEditor.getAgendaItemLine().getRule().refreshPropositionTree(true);
2088 return getUIFModelAndView(form);
2089 }
2090
2091
2092 @RequestMapping(params = "methodToCall=" + "cutProposition")
2093 public ModelAndView cutProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
2094 HttpServletRequest request, HttpServletResponse response)
2095 throws Exception {
2096
2097 AgendaEditor agendaEditor = getAgendaEditor(form);
2098 String selectedPropId = agendaEditor.getSelectedPropositionId();
2099 agendaEditor.setCutPropositionId(selectedPropId);
2100
2101 return getUIFModelAndView(form);
2102 }
2103
2104 @RequestMapping(params = "methodToCall=" + "pasteProposition")
2105 public ModelAndView pasteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
2106 HttpServletRequest request, HttpServletResponse response)
2107 throws Exception {
2108
2109 AgendaEditor agendaEditor = getAgendaEditor(form);
2110 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
2111
2112 // get selected id
2113 String cutPropId = agendaEditor.getCutPropositionId();
2114 String selectedPropId = agendaEditor.getSelectedPropositionId();
2115
2116 if (StringUtils.isNotBlank(selectedPropId) && selectedPropId.equals(cutPropId)) {
2117 // do nothing; can't paste to itself
2118 } else {
2119
2120 // proposition tree root
2121 Node<RuleTreeNode, String> root = rule.getPropositionTree().getRootElement();
2122
2123 if (StringUtils.isNotBlank(selectedPropId) && StringUtils.isNotBlank(cutPropId)) {
2124 Node<RuleTreeNode,String> parentNode = findParentPropositionNode(root, selectedPropId);
2125 PropositionBo newParent;
2126 if (parentNode == root){
2127 // special case
2128 // build new top level compound proposition,
2129 // add existing as first child
2130 // then paste cut node as 2nd child
2131 newParent = PropositionBo.createCompoundPropositionBoStub2(
2132 root.getChildren().get(0).getData().getProposition());
2133 newParent.setEditMode(true);
2134 rule.setProposition(newParent);
2135 } else {
2136 newParent = parentNode.getData().getProposition();
2137 }
2138 PropositionBo oldParent = findParentPropositionNode(root, cutPropId).getData().getProposition();
2139
2140 PropositionBo workingProp = null;
2141 // cut from old
2142 if (oldParent != null){
2143 List <PropositionBo> children = oldParent.getCompoundComponents();
2144 for( int index=0; index< children.size(); index++){
2145 if (cutPropId.equalsIgnoreCase(children.get(index).getId())){
2146 workingProp = oldParent.getCompoundComponents().remove(index);
2147 break;
2148 }
2149 }
2150 }
2151
2152 // add to new
2153 if (newParent != null && workingProp != null){
2154 List <PropositionBo> children = newParent.getCompoundComponents();
2155 for( int index=0; index< children.size(); index++){
2156 if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){
2157 children.add(index+1, workingProp);
2158 break;
2159 }
2160 }
2161 }
2162 // TODO: determine edit mode.
2163 // boolean editMode = (SimplePropositionEditNode.NODE_TYPE.equalsIgnoreCase(child.getNodeType()));
2164 rule.refreshPropositionTree(false);
2165 }
2166 }
2167 agendaEditor.setCutPropositionId(null);
2168 // call the super method to avoid the agenda tree being reloaded from the db
2169 return getUIFModelAndView(form);
2170 }
2171
2172 @RequestMapping(params = "methodToCall=" + "deleteProposition")
2173 public ModelAndView deleteProposition(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
2174 HttpServletRequest request, HttpServletResponse response)
2175 throws Exception {
2176 AgendaEditor agendaEditor = getAgendaEditor(form);
2177 String selectedPropId = agendaEditor.getSelectedPropositionId();
2178 Node<RuleTreeNode, String> root = agendaEditor.getAgendaItemLine().getRule().getPropositionTree().getRootElement();
2179
2180 Node<RuleTreeNode, String> parentNode = findParentPropositionNode(root, selectedPropId);
2181
2182 // what if it is the root?
2183 if (parentNode != null && parentNode.getData() != null) { // it is not the root as there is a parent w/ a prop
2184 PropositionBo parent = parentNode.getData().getProposition();
2185 if (parent != null){
2186 List <PropositionBo> children = parent.getCompoundComponents();
2187 for( int index=0; index< children.size(); index++){
2188 if (selectedPropId.equalsIgnoreCase(children.get(index).getId())){
2189 parent.getCompoundComponents().remove(index);
2190 break;
2191 }
2192 }
2193 }
2194 } else { // no parent, it is the root
2195 parentNode.getChildren().clear();
2196 agendaEditor.getAgendaItemLine().getRule().getPropositionTree().setRootElement(null);
2197 agendaEditor.getAgendaItemLine().getRule().setPropId(null);
2198 agendaEditor.getAgendaItemLine().getRule().setProposition(null);
2199 }
2200
2201 agendaEditor.getAgendaItemLine().getRule().refreshPropositionTree(false);
2202 return getUIFModelAndView(form);
2203 }
2204
2205 @RequestMapping(params = "methodToCall=" + "updateCompoundOperator")
2206 public ModelAndView updateCompoundOperator(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
2207 HttpServletRequest request, HttpServletResponse response)
2208 throws Exception {
2209
2210 AgendaEditor agendaEditor = getAgendaEditor(form);
2211 RuleBo rule = agendaEditor.getAgendaItemLine().getRule();
2212 rule.refreshPropositionTree(false);
2213
2214 return getUIFModelAndView(form);
2215 }
2216
2217 }