001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krms.impl.ui;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.krad.service.KRADServiceLocator;
020 import org.kuali.rice.krad.service.SequenceAccessorService;
021 import org.kuali.rice.krad.uif.UifParameters;
022 import org.kuali.rice.krad.util.ObjectUtils;
023 import org.kuali.rice.krad.web.controller.InquiryController;
024 import org.kuali.rice.krad.web.form.InquiryForm;
025 import org.kuali.rice.krad.web.form.UifFormBase;
026 import org.kuali.rice.krms.impl.repository.ActionBo;
027 import org.kuali.rice.krms.impl.repository.AgendaBo;
028 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
029 import org.kuali.rice.krms.impl.repository.ContextBoService;
030 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
031 import org.kuali.rice.krms.impl.repository.RuleBo;
032 import org.springframework.stereotype.Controller;
033 import org.springframework.validation.BindingResult;
034 import org.springframework.web.bind.annotation.ModelAttribute;
035 import org.springframework.web.bind.annotation.RequestMapping;
036 import org.springframework.web.servlet.ModelAndView;
037
038 import javax.servlet.http.HttpServletRequest;
039 import javax.servlet.http.HttpServletResponse;
040
041 @Controller
042 @RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_INQUIRY_PATH)
043 public class AgendaInquiryController extends InquiryController {
044
045 /**
046 * This method updates the existing rule in the agenda.
047 */
048 @RequestMapping(params = "methodToCall=" + "viewRule")
049 public ModelAndView viewRule(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result,
050 HttpServletRequest request, HttpServletResponse response) throws Exception {
051
052 AgendaEditor agendaEditor = getAgendaEditor(form);
053 agendaEditor.setAddRuleInProgress(false);
054 // this is the root of the tree:
055 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda());
056 //TODO: remove the hardcoded item number ... its used only for testing until the selectedAgendaItemId stuff works
057 String selectedItemId = agendaEditor.getSelectedAgendaItemId();
058 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId);
059
060 setAgendaItemLine(form, node);
061
062 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-ViewRule-Page");
063 return super.navigate(form, result, request, response);
064 }
065
066 /**
067 * @param form
068 * @return the {@link AgendaEditor} from the form
069 */
070 private AgendaEditor getAgendaEditor(UifFormBase form) {
071 InquiryForm inquiryForm = (InquiryForm) form;
072 return ((AgendaEditor)inquiryForm.getDataObject());
073 }
074
075 /**
076 * This method finds and returns the first agenda item in the agenda, or null if there are no items presently
077 *
078 * @param agenda
079 * @return
080 */
081 private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) {
082 AgendaItemBo firstItem = null;
083 if (agenda != null && agenda.getItems() != null) for (AgendaItemBo agendaItem : agenda.getItems()) {
084 if (agenda.getFirstItemId().equals(agendaItem.getId())) {
085 firstItem = agendaItem;
086 break;
087 }
088 }
089 return firstItem;
090 }
091
092 /**
093 * Search the tree for the agenda item with the given id.
094 */
095 private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) {
096 if (node == null) throw new IllegalArgumentException("node must be non-null");
097
098 AgendaItemBo result = null;
099
100 if (agendaItemId.equals(node.getId())) {
101 result = node;
102 } else {
103 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) {
104 AgendaItemBo child = childAccessor.getChild(node);
105 if (child != null) {
106 result = getAgendaItemById(child, agendaItemId);
107 if (result != null) break;
108 }
109 }
110 }
111 return result;
112 }
113
114 /**
115 * This method sets the agendaItemLine for adding/editing AgendaItems.
116 * The agendaItemLine is a copy of the agendaItem so that changes are not applied when
117 * they are abandoned. If the agendaItem is null a new empty agendaItemLine is created.
118 *
119 * @param form
120 * @param agendaItem
121 */
122 private void setAgendaItemLine(UifFormBase form, AgendaItemBo agendaItem) {
123 AgendaEditor agendaEditor = getAgendaEditor(form);
124 if (agendaItem == null) {
125 RuleBo rule = new RuleBo();
126 rule.setId(getSequenceAccessorService().getNextAvailableSequenceNumber("KRMS_RULE_S")
127 .toString());
128 if (StringUtils.isBlank(agendaEditor.getAgenda().getContextId())) {
129 rule.setNamespace("");
130 } else {
131 rule.setNamespace(getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()).getNamespace());
132 }
133 agendaItem = new AgendaItemBo();
134 agendaItem.setRule(rule);
135 agendaEditor.setAgendaItemLine(agendaItem);
136 } else {
137 // TODO: Add a copy not the reference
138 agendaEditor.setAgendaItemLine((AgendaItemBo) ObjectUtils.deepCopy(agendaItem));
139 }
140
141
142 if (agendaItem.getRule().getActions().isEmpty()) {
143 ActionBo actionBo = new ActionBo();
144 actionBo.setTypeId("");
145 actionBo.setNamespace(agendaItem.getRule().getNamespace());
146 actionBo.setRuleId(agendaItem.getRule().getId());
147 actionBo.setSequenceNumber(1);
148 agendaEditor.setAgendaItemLineRuleAction(actionBo);
149 } else {
150 agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0));
151 }
152
153 agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes());
154 }
155
156 /**
157 * <p>This class abstracts getting and setting a child of an AgendaItemBo, making some recursive operations
158 * require less boiler plate.</p>
159 *
160 * <p>The word 'child' in AgendaItemChildAccessor means child in the strict data structures sense, in that the
161 * instance passed in holds a reference to some other node (or null). However, when discussing the agenda tree
162 * and algorithms for manipulating it, the meaning of 'child' is somewhat different, and there are notions of
163 * 'sibling' and 'cousin' that are tossed about too. It's probably worth explaining that somewhat here:</p>
164 *
165 * <p>General principals of relationships when talking about the agenda tree:
166 * <ul>
167 * <li>Generation boundaries (parent to child) are across 'When TRUE' and 'When FALSE' references.</li>
168 * <li>"Age" among siblings & cousins goes from top (oldest) to bottom (youngest).</li>
169 * <li>siblings are related by 'Always' references.</li>
170 * </ul>
171 * </p>
172 * <p>This diagram of an agenda tree and the following examples seek to illustrate these principals:</p>
173 * <img src="doc-files/AgendaEditorController-1.png" alt="Example Agenda Items"/>
174 * <p>Examples:
175 * <ul>
176 * <li>A is the parent of B, C, & D</li>
177 * <li>E is the younger sibling of A</li>
178 * <li>B is the older cousin of C</li>
179 * <li>C is the older sibling of D</li>
180 * <li>F is the younger cousin of D</li>
181 * </ul>
182 * </p>
183 */
184 protected static class AgendaItemChildAccessor {
185
186 private enum Child { WHEN_TRUE, WHEN_FALSE, ALWAYS };
187
188 private static final AgendaItemChildAccessor whenTrue = new AgendaItemChildAccessor(Child.WHEN_TRUE);
189 private static final AgendaItemChildAccessor whenFalse = new AgendaItemChildAccessor(Child.WHEN_FALSE);
190 private static final AgendaItemChildAccessor always = new AgendaItemChildAccessor(Child.ALWAYS);
191
192 /**
193 * Accessors for all linked items
194 */
195 private static final AgendaItemChildAccessor [] linkedNodes = { whenTrue, whenFalse, always };
196
197 /**
198 * Accessors for children (so ALWAYS is omitted);
199 */
200 private static final AgendaItemChildAccessor [] children = { whenTrue, whenFalse };
201
202 private final Child whichChild;
203
204 private AgendaItemChildAccessor(Child whichChild) {
205 if (whichChild == null) throw new IllegalArgumentException("whichChild must be non-null");
206 this.whichChild = whichChild;
207 }
208
209 /**
210 * @return the referenced child
211 */
212 public AgendaItemBo getChild(AgendaItemBo parent) {
213 switch (whichChild) {
214 case WHEN_TRUE: return parent.getWhenTrue();
215 case WHEN_FALSE: return parent.getWhenFalse();
216 case ALWAYS: return parent.getAlways();
217 default: throw new IllegalStateException();
218 }
219 }
220
221 /**
222 * Sets the child reference and the child id
223 */
224 public void setChild(AgendaItemBo parent, AgendaItemBo child) {
225 switch (whichChild) {
226 case WHEN_TRUE:
227 parent.setWhenTrue(child);
228 parent.setWhenTrueId(child == null ? null : child.getId());
229 break;
230 case WHEN_FALSE:
231 parent.setWhenFalse(child);
232 parent.setWhenFalseId(child == null ? null : child.getId());
233 break;
234 case ALWAYS:
235 parent.setAlways(child);
236 parent.setAlwaysId(child == null ? null : child.getId());
237 break;
238 default: throw new IllegalStateException();
239 }
240 }
241 }
242
243 /**
244 * return the sequenceAssessorService
245 */
246 private SequenceAccessorService getSequenceAccessorService() {
247 return KRADServiceLocator.getSequenceAccessorService();
248 }
249
250 /**
251 * return the contextBoService
252 */
253 private ContextBoService getContextBoService() {
254 return KrmsRepositoryServiceLocator.getContextBoService();
255 }
256
257 }