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}