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