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    }