001/** 002 * Copyright 2005-2015 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 */ 016package org.kuali.rice.krms.impl.ui; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.service.KRADServiceLocator; 020import org.kuali.rice.krad.service.SequenceAccessorService; 021import org.kuali.rice.krad.uif.UifParameters; 022import org.kuali.rice.krad.util.ObjectUtils; 023import org.kuali.rice.krad.web.controller.InquiryController; 024import org.kuali.rice.krad.web.form.InquiryForm; 025import org.kuali.rice.krad.web.form.UifFormBase; 026import org.kuali.rice.krms.impl.repository.ActionBo; 027import org.kuali.rice.krms.impl.repository.AgendaBo; 028import org.kuali.rice.krms.impl.repository.AgendaItemBo; 029import org.kuali.rice.krms.impl.repository.ContextBoService; 030import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator; 031import org.kuali.rice.krms.impl.repository.RuleBo; 032import org.springframework.stereotype.Controller; 033import org.springframework.validation.BindingResult; 034import org.springframework.web.bind.annotation.ModelAttribute; 035import org.springframework.web.bind.annotation.RequestMapping; 036import org.springframework.web.servlet.ModelAndView; 037 038import javax.servlet.http.HttpServletRequest; 039import javax.servlet.http.HttpServletResponse; 040 041@Controller 042@RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_INQUIRY_PATH) 043public 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", RuleBo.class) 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}