001/** 002 * Copyright 2005-2017 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.data.KradDataServiceLocator; 020import org.kuali.rice.krad.inquiry.InquiryController; 021import org.kuali.rice.krad.uif.UifParameters; 022import org.kuali.rice.krad.web.form.InquiryForm; 023import org.kuali.rice.krad.web.form.UifFormBase; 024import org.kuali.rice.krms.impl.repository.ActionBo; 025import org.kuali.rice.krms.impl.repository.AgendaBo; 026import org.kuali.rice.krms.impl.repository.AgendaItemBo; 027import org.kuali.rice.krms.impl.repository.ContextBoService; 028import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator; 029import org.kuali.rice.krms.impl.repository.RepositoryBoIncrementer; 030import org.kuali.rice.krms.impl.repository.RuleBo; 031import org.springframework.stereotype.Controller; 032import org.springframework.web.bind.annotation.RequestMapping; 033import org.springframework.web.servlet.ModelAndView; 034 035@Controller 036@RequestMapping(value = org.kuali.rice.krms.impl.util.KrmsImplConstants.WebPaths.AGENDA_INQUIRY_PATH) 037public class AgendaInquiryController extends InquiryController { 038 039 private static final RepositoryBoIncrementer ruleIdIncrementer = new RepositoryBoIncrementer(RuleBo.RULE_SEQ_NAME); 040 041 042 /** 043 * This method updates the existing rule in the agenda. 044 */ 045 @RequestMapping(params = "methodToCall=" + "viewRule") 046 public ModelAndView viewRule(UifFormBase form) throws Exception { 047 048 AgendaEditor agendaEditor = getAgendaEditor(form); 049 agendaEditor.setAddRuleInProgress(false); 050 051 // this is the root of the tree: 052 AgendaItemBo firstItem = getFirstAgendaItem(agendaEditor.getAgenda()); 053 054 String selectedItemId = agendaEditor.getSelectedAgendaItemId(); 055 AgendaItemBo node = getAgendaItemById(firstItem, selectedItemId); 056 057 setAgendaItemLine(form, node); 058 059 form.getActionParameters().put(UifParameters.NAVIGATE_TO_PAGE_ID, "AgendaEditorView-ViewRule-Page"); 060 return super.navigate(form); 061 } 062 063 /** 064 * @param form 065 * @return the {@link AgendaEditor} from the form 066 */ 067 private AgendaEditor getAgendaEditor(UifFormBase form) { 068 InquiryForm inquiryForm = (InquiryForm) form; 069 return ((AgendaEditor)inquiryForm.getDataObject()); 070 } 071 072 /** 073 * This method finds and returns the first agenda item in the agenda, or null if there are no items presently 074 * 075 * @param agenda 076 * @return 077 */ 078 private AgendaItemBo getFirstAgendaItem(AgendaBo agenda) { 079 AgendaItemBo firstItem = null; 080 if (agenda != null && agenda.getItems() != null) { 081 for (AgendaItemBo agendaItem : agenda.getItems()) { 082 if (agenda.getFirstItemId().equals(agendaItem.getId())) { 083 firstItem = agendaItem; 084 break; 085 } 086 } 087 } 088 return firstItem; 089 } 090 091 /** 092 * Search the tree for the agenda item with the given id. 093 */ 094 private AgendaItemBo getAgendaItemById(AgendaItemBo node, String agendaItemId) { 095 if (node == null) { 096 throw new IllegalArgumentException("node must be non-null"); 097 } 098 099 AgendaItemBo result = null; 100 101 if (StringUtils.equals(node.getId(), agendaItemId)) { 102 result = node; 103 } else { 104 for (AgendaItemChildAccessor childAccessor : AgendaItemChildAccessor.linkedNodes) { 105 AgendaItemBo child = childAccessor.getChild(node); 106 if (child != null) { 107 result = getAgendaItemById(child, agendaItemId); 108 if (result != null) { 109 break; 110 } 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 agendaEditor.setAgendaItemLine(KradDataServiceLocator.getDataObjectService().copyInstance(agendaItem)); 140 } 141 142 143 if (agendaItem.getRule().getActions().isEmpty()) { 144 ActionBo actionBo = new ActionBo(); 145 actionBo.setTypeId(""); 146 actionBo.setNamespace(agendaItem.getRule().getNamespace()); 147 actionBo.setRule(agendaItem.getRule()); 148 actionBo.setSequenceNumber(1); 149 agendaEditor.setAgendaItemLineRuleAction(actionBo); 150 } else { 151 agendaEditor.setAgendaItemLineRuleAction(agendaItem.getRule().getActions().get(0)); 152 } 153 154 agendaEditor.setCustomRuleActionAttributesMap(agendaEditor.getAgendaItemLineRuleAction().getAttributes()); 155 agendaEditor.setCustomRuleAttributesMap(agendaEditor.getAgendaItemLine().getRule().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) { 208 throw new IllegalArgumentException("whichChild must be non-null"); 209 } 210 this.whichChild = whichChild; 211 } 212 213 /** 214 * @return the referenced child 215 */ 216 public AgendaItemBo getChild(AgendaItemBo parent) { 217 switch (whichChild) { 218 case WHEN_TRUE: return parent.getWhenTrue(); 219 case WHEN_FALSE: return parent.getWhenFalse(); 220 case ALWAYS: return parent.getAlways(); 221 default: throw new IllegalStateException(); 222 } 223 } 224 225 /** 226 * Sets the child reference and the child id 227 */ 228 public void setChild(AgendaItemBo parent, AgendaItemBo child) { 229 switch (whichChild) { 230 case WHEN_TRUE: 231 parent.setWhenTrue(child); 232 parent.setWhenTrueId(child == null ? null : child.getId()); 233 break; 234 case WHEN_FALSE: 235 parent.setWhenFalse(child); 236 parent.setWhenFalseId(child == null ? null : child.getId()); 237 break; 238 case ALWAYS: 239 parent.setAlways(child); 240 parent.setAlwaysId(child == null ? null : child.getId()); 241 break; 242 default: throw new IllegalStateException(); 243 } 244 } 245 } 246 247 /** 248 * return the contextBoService 249 */ 250 private ContextBoService getContextBoService() { 251 return KrmsRepositoryServiceLocator.getContextBoService(); 252 } 253 254}