001 /**
002 * Copyright 2005-2014 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.kns.rules;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.apache.struts.action.ActionForm;
020 import org.kuali.rice.core.api.util.RiceConstants;
021 import org.kuali.rice.kns.rule.PromptBeforeValidation;
022 import org.kuali.rice.kns.rule.event.PromptBeforeValidationEvent;
023 import org.kuali.rice.kns.web.struts.form.KualiForm;
024 import org.kuali.rice.krad.document.Document;
025 import org.kuali.rice.kns.question.ConfirmationQuestion;
026 import org.kuali.rice.krad.util.KRADConstants;
027
028 import javax.servlet.http.HttpServletRequest;
029 import java.util.Arrays;
030 import java.util.Iterator;
031 import 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
046 public 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 }