View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.impl.ui;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.util.io.SerializationUtils;
20  import org.kuali.rice.krad.uif.UifParameters;
21  import org.kuali.rice.krad.web.controller.InquiryController;
22  import org.kuali.rice.krad.web.form.InquiryForm;
23  import org.kuali.rice.krad.web.form.UifFormBase;
24  import org.kuali.rice.krms.impl.repository.ActionBo;
25  import org.kuali.rice.krms.impl.repository.AgendaBo;
26  import org.kuali.rice.krms.impl.repository.AgendaItemBo;
27  import org.kuali.rice.krms.impl.repository.ContextBoService;
28  import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
29  import org.kuali.rice.krms.impl.repository.RepositoryBoIncrementer;
30  import org.kuali.rice.krms.impl.repository.RuleBo;
31  import org.springframework.stereotype.Controller;
32  import org.springframework.validation.BindingResult;
33  import org.springframework.web.bind.annotation.ModelAttribute;
34  import org.springframework.web.bind.annotation.RequestMapping;
35  import org.springframework.web.servlet.ModelAndView;
36  
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  
40  @Controller
41  @RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_INQUIRY_PATH)
42  public class AgendaInquiryController  extends InquiryController {
43  
44      private static final RepositoryBoIncrementer ruleIdIncrementer = new RepositoryBoIncrementer(RuleBo.RULE_SEQ_NAME);
45  
46  
47      /**
48       * This method updates the existing rule in the agenda.
49       */
50      @RequestMapping(params = "methodToCall=" + "viewRule")
51      public ModelAndView viewRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
52              HttpServletRequest request, HttpServletResponse response) throws Exception {
53  
54          AgendaEditor agendaEditor = getAgendaEditor(form);
55          agendaEditor.setAddRuleInProgress(false);
56  
57          // this is the root of the tree:
58          AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
59  
60          String selectedItemId = agendaEditor.getSelectedAgendaItemId();
61          AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
62  
63          setAgendaItemLine(form, node);
64  
65          form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-ViewRule-Page");
66          return super.navigate(form, result, request, response);
67      }
68  
69      /**
70       * @param form
71       * @return the {@link AgendaEditor} from the form
72       */
73      private AgendaEditor getAgendaEditor(UifFormBase form) {
74          InquiryForm inquiryForm = (InquiryForm) form;
75          return ((AgendaEditor)inquiryForm.getDataObject());
76      }
77  
78      /**
79       * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
80       *
81       * @param agenda
82       * @return
83       */
84      private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
85          AgendaItemBo firstItem = null;
86          if (agenda != null && agenda.getItems() != null) for (AgendaItemBo agendaItem : agenda.getItems()) {
87              if (agenda.getFirstItemId().equals(agendaItem.getId())) {
88                  firstItem = agendaItem;
89                  break;
90              }
91          }
92          return firstItem;
93      }
94  
95      /**
96       * Search the tree for the agenda item with the given id.
97       */
98      private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
99          if (node == null) throw new IllegalArgumentException("node must be non-null");
100 
101         AgendaItemBo result = null;
102 
103         if (StringUtils.equals(node.getId(), agendaItemId)) {
104             result = node;
105         } else {
106             for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
107                 AgendaItemBo child = childAccessor.getChild(node);
108                 if (child != null) {
109                     result = getAgendaItemById(child, agendaItemId);
110                     if (result != null) break;
111                 }
112             }
113         }
114         return result;
115     }
116 
117     /**
118      * This method sets the agendaItemLine for adding/editing AgendaItems.
119      * The agendaItemLine is a copy of the agendaItem so that changes are not applied when
120      * they are abandoned.  If the agendaItem is null a new empty agendaItemLine is created.
121      *
122      * @param form
123      * @param agendaItem
124      */
125     private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) {
126         AgendaEditor agendaEditor = getAgendaEditor(form);
127         if (agendaItem == null) {
128             RuleBo rule = new RuleBo();
129             rule.setId(ruleIdIncrementer.getNewId());
130             if (StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
131                 rule.setNamespace("");
132             } else {
133                 rule.setNamespace(getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace());
134             }
135             agendaItem = new AgendaItemBo();
136             agendaItem.setRule(rule);
137             agendaEditor.setAgendaItemLine(agendaItem);
138         } else {
139             // TODO: Add a copy not the reference
140             agendaEditor.setAgendaItemLine((AgendaItemBo) SerializationUtils.deepCopy(agendaItem));
141         }
142 
143 
144         if (agendaItem.getRule().getActions().isEmpty()) {
145             ActionBo actionBo = new ActionBo();
146             actionBo.setTypeId("");
147             actionBo.setNamespace(agendaItem.getRule().getNamespace());
148             actionBo.setRule(agendaItem.getRule());
149             actionBo.setSequenceNumber(1);
150             agendaEditor.setAgendaItemLineRuleAction(actionBo);
151         } else {
152             agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0));
153         }
154 
155         agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes());
156     }
157 
158     /**
159      * <p>This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations
160      * require less boiler plate.</p>
161      *
162      * <p>The word 'child' in AgendaItemChildAccessor means child in the strict data structures sense, in that the
163      * instance passed in holds a reference to some other node (or null).  However, when discussing the agenda tree
164      * and algorithms for manipulating it, the meaning of 'child' is somewhat different, and there are notions of
165      * 'sibling' and 'cousin' that are tossed about too. It's probably worth explaining that somewhat here:</p>
166      *
167      * <p>General principals of relationships when talking about the agenda tree:
168      * <ul>
169      * <li>Generation boundaries (parent to child) are across 'When TRUE' and 'When FALSE' references.</li>
170      * <li>"Age" among siblings & cousins goes from top (oldest) to bottom (youngest).</li>
171      * <li>siblings are related by 'Always' references.</li>
172      * </ul>
173      * </p>
174      * <p>This diagram of an agenda tree and the following examples seek to illustrate these principals:</p>
175      * <img src="doc-files/AgendaEditorController-1.png" alt="Example Agenda Items"/>
176      * <p>Examples:
177      * <ul>
178      * <li>A is the parent of B, C, & D</li>
179      * <li>E is the younger sibling of A</li>
180      * <li>B is the older cousin of C</li>
181      * <li>C is the older sibling of D</li>
182      * <li>F is the younger cousin of D</li>
183      * </ul>
184      * </p>
185      */
186     protected static class AgendaItemChildAccessor {
187 
188         private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
189 
190         private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE);
191         private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE);
192         private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS);
193 
194         /**
195          * Accessors for all linked items
196          */
197         private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
198 
199         /**
200          * Accessors for children (so ALWAYS is omitted);
201          */
202         private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
203 
204         private final Child whichChild;
205 
206         private AgendaItemChildAccessor(Child whichChild) {
207             if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
208             this.whichChild = whichChild;
209         }
210 
211         /**
212          * @return the referenced child
213          */
214         public AgendaItemBo getChild(AgendaItemBo parent) {
215             switch (whichChild) {
216             case WHEN_TRUE: return parent.getWhenTrue();
217             case WHEN_FALSE: return parent.getWhenFalse();
218             case ALWAYS: return parent.getAlways();
219             default: throw new IllegalStateException();
220             }
221         }
222 
223         /**
224          * Sets the child reference and the child id
225          */
226         public void setChild(AgendaItemBo parent, AgendaItemBo child) {
227             switch (whichChild) {
228             case WHEN_TRUE:
229                 parent.setWhenTrue(child);
230                 parent.setWhenTrueId(child == null ? null : child.getId());
231                 break;
232             case WHEN_FALSE:
233                 parent.setWhenFalse(child);
234                 parent.setWhenFalseId(child == null ? null : child.getId());
235                 break;
236             case ALWAYS:
237                 parent.setAlways(child);
238                 parent.setAlwaysId(child == null ? null : child.getId());
239                 break;
240             default: throw new IllegalStateException();
241             }
242         }
243     }
244 
245     /**
246      * return the contextBoService
247      */
248     private ContextBoService getContextBoService() {
249         return KrmsRepositoryServiceLocator.getContextBoService();
250     }
251 
252 }