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