001/**
002 * Copyright 2005-2016 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.kns.rules;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.struts.action.ActionForm;
020import org.kuali.rice.core.api.util.RiceConstants;
021import org.kuali.rice.kns.rule.PromptBeforeValidation;
022import org.kuali.rice.kns.rule.event.PromptBeforeValidationEvent;
023import org.kuali.rice.kns.web.struts.form.KualiForm;
024import org.kuali.rice.krad.document.Document;
025import org.kuali.rice.kns.question.ConfirmationQuestion;
026import org.kuali.rice.krad.util.KRADConstants;
027
028import javax.servlet.http.HttpServletRequest;
029import java.util.Arrays;
030import java.util.Iterator;
031import java.util.NoSuchElementException;
032
033/**
034 * 
035 * This class simplifies requesting clarifying user input prior to applying business rules. It mostly shields the classes that
036 * extend it from being aware of the web layer, even though the input is collected via a series of one or more request/response
037 * cycles.
038 * 
039 * Beware: method calls with side-effects will have unusual results. While it looks like the doRules method is executed
040 * sequentially, in fact, it is more of a geometric series: if n questions are asked, then the code up to and including the first
041 * question is executed n times, the second n-1 times, ..., the last question only one time.
042 * 
043 * @deprecated Only used in KNS classes, use KRAD.
044 */
045@Deprecated
046public abstract class PromptBeforeValidationBase implements PromptBeforeValidation {
047
048    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PromptBeforeValidationBase.class);
049
050    protected String question;
051    protected String buttonClicked;
052    protected PromptBeforeValidationEvent event;
053    protected KualiForm form;
054
055    private class IsAskingException extends RuntimeException {
056    }
057
058    /**
059     * 
060     * This class acts similarly to HTTP session, but working inside a REQUEST parameter
061     * 
062     * 
063     */
064    /**
065     * This is a description of what this class does - wliang don't forget to fill this in. 
066     * 
067     * @author Kuali Rice Team (rice.collab@kuali.org)
068     *
069     */
070    public class ContextSession {
071        private final static String DELIMITER = ".";
072        PromptBeforeValidationEvent event;
073
074        public ContextSession(String context, PromptBeforeValidationEvent event) {
075            this.event = event;
076
077            this.event.setQuestionContext(context);
078            if (this.event.getQuestionContext() == null) {
079                this.event.setQuestionContext("");
080            }
081
082        }
083
084        /**
085         * Whether a question with a given ID has already been asked
086         * 
087         * @param id the ID of the question, an arbitrary value, but must be consistent
088         * @return
089         */
090        public boolean hasAsked(String id) {
091            return StringUtils.contains(event.getQuestionContext(), id);
092        }
093
094        /**
095         * Invoked to indicate that the user should be prompted a question
096         * 
097         * @param id the ID of the question, an arbitrary value, but must be consistent
098         * @param text the question text, to be displayed to the user
099         */
100        public void askQuestion(String id, String text) {
101            event.setQuestionId(id);
102            event.setQuestionType(KRADConstants.CONFIRMATION_QUESTION);
103            event.setQuestionText(text);
104            event.setPerformQuestion(true);
105        }
106
107        public void setAttribute(String name, String value) {
108            if (LOG.isDebugEnabled()) {
109                LOG.debug("setAttribute(" + name + "," + value + ")");
110            }
111            event.setQuestionContext(event.getQuestionContext() + DELIMITER + name + DELIMITER + value);
112
113        }
114
115        public String getAttribute(String name) {
116            if (LOG.isDebugEnabled()) {
117                LOG.debug("getAttribute(" + name + ")");
118            }
119            String result = null;
120
121            Iterator values = Arrays.asList(event.getQuestionContext().split("\\" + DELIMITER)).iterator();
122
123            while (values.hasNext()) {
124                if (values.next().equals(name)) {
125                    try {
126                        result = (String) values.next();
127                    }
128                    catch (NoSuchElementException e) {
129                        result = null;
130                    }
131                }
132            }
133            if (LOG.isDebugEnabled()) {
134                LOG.debug("returning " + result);
135            }
136            return result;
137        }
138
139    }
140
141    /**
142     * Implementations will override this method to do perform the actual prompting and/or logic
143     * 
144     * They are able to utilize the following methods:
145     * <li> {@link PromptBeforeValidationBase#abortRulesCheck()}
146     * <li> {@link PromptBeforeValidationBase#askOrAnalyzeYesNoQuestion(String, String)}
147     * <li> {@link #hasAsked(String)}
148     * 
149     * @param document
150     * @return
151     */
152    public abstract boolean doPrompts(Document document);
153
154    private boolean isAborting;
155
156    ContextSession session;
157
158    public PromptBeforeValidationBase() {
159    }
160
161
162    public boolean processPrompts(ActionForm form, HttpServletRequest request, PromptBeforeValidationEvent event) {
163        question = request.getParameter(KRADConstants.QUESTION_INST_ATTRIBUTE_NAME);
164        buttonClicked = request.getParameter(KRADConstants.QUESTION_CLICKED_BUTTON);
165        this.event = event;
166        this.form = (KualiForm) form;
167
168        if (LOG.isDebugEnabled()) {
169            LOG.debug("Question is: " + question);
170            LOG.debug("ButtonClicked: " + buttonClicked);
171            LOG.debug("QuestionContext() is: " + event.getQuestionContext());
172        }
173
174        session = new ContextSession(request.getParameter(KRADConstants.QUESTION_CONTEXT), event);
175
176        boolean result = false;
177
178        try {
179            result = doPrompts(event.getDocument());
180        }
181        catch (IsAskingException e) {
182            return false;
183        }
184
185        if (isAborting) {
186            return false;
187        }
188
189        return result;
190    }
191
192    /**
193     * This bounces the user back to the document as if they had never tried to routed it. (Business rules are not invoked
194     * and the action is not executed.)
195     * 
196     */
197    public void abortRulesCheck() {
198        event.setActionForwardName(RiceConstants.MAPPING_BASIC);
199        isAborting = true;
200    }
201
202    /**
203     * This method poses a Y/N question to the user.  If the user has already answered the question, then it returns whether
204     * the answer to the question was yes or no
205     * 
206     * Code that invokes this method will behave a bit strangely, so you should try to keep it as simple as possible.
207     * 
208     * @param id an ID for the question
209     * @param text the text of the question, to be displayed on the screen
210     * @return true if the user answered Yes, false if the user answers no
211     * @throws IsAskingException if the user needs to be prompted the question
212     */
213    public boolean askOrAnalyzeYesNoQuestion(String id, String text) throws IsAskingException {
214
215        if (LOG.isDebugEnabled()) {
216            LOG.debug("Entering askOrAnalyzeYesNoQuestion(" + id + "," + text + ")");
217        }
218
219        String cached = (String) session.getAttribute(id);
220        if (cached != null) {
221            LOG.debug("returning cached value: " + id + "=" + cached);
222            return new Boolean(cached).booleanValue();
223        }
224
225        if (id.equals(question)) {
226            session.setAttribute(id, Boolean.toString(!ConfirmationQuestion.NO.equals(buttonClicked)));
227            return !ConfirmationQuestion.NO.equals(buttonClicked);
228        }
229        else if (!session.hasAsked(id)) {
230            if (LOG.isDebugEnabled()) {
231                LOG.debug("Forcing question to be asked: " + id);
232            }
233            session.askQuestion(id, text);
234        }
235
236        LOG.debug("Throwing Exception to force return to Action");
237        throw new IsAskingException();
238    }
239
240}