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}