View Javadoc

1   /*
2    * Copyright 2006-2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.rules;
17  
18  import java.util.Arrays;
19  import java.util.Iterator;
20  import java.util.NoSuchElementException;
21  
22  import javax.servlet.http.HttpServletRequest;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.struts.action.ActionForm;
26  import org.kuali.rice.core.util.RiceConstants;
27  import org.kuali.rice.kns.document.Document;
28  import org.kuali.rice.kns.question.ConfirmationQuestion;
29  import org.kuali.rice.kns.rule.PromptBeforeValidation;
30  import org.kuali.rice.kns.rule.event.PromptBeforeValidationEvent;
31  import org.kuali.rice.kns.util.KNSConstants;
32  import org.kuali.rice.kns.web.struts.form.KualiForm;
33  
34  /**
35   * 
36   * This class simplifies requesting clarifying user input prior to applying business rules. It mostly shields the classes that
37   * extend it from being aware of the web layer, even though the input is collected via a series of one or more request/response
38   * cycles.
39   * 
40   * Beware: method calls with side-effects will have unusual results. While it looks like the doRules method is executed
41   * sequentially, in fact, it is more of a geometric series: if n questions are asked, then the code up to and including the first
42   * question is executed n times, the second n-1 times, ..., the last question only one time.
43   * 
44   * 
45   */
46  public abstract class PromptBeforeValidationBase implements PromptBeforeValidation {
47  
48      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PromptBeforeValidationBase.class);
49  
50      protected String question;
51      protected String buttonClicked;
52      protected PromptBeforeValidationEvent event;
53      protected KualiForm form;
54  
55      private class IsAskingException extends RuntimeException {
56      }
57  
58      /**
59       * 
60       * This class acts similarly to HTTP session, but working inside a REQUEST parameter
61       * 
62       * 
63       */
64      /**
65       * This is a description of what this class does - wliang don't forget to fill this in. 
66       * 
67       * @author Kuali Rice Team (rice.collab@kuali.org)
68       *
69       */
70      public class ContextSession {
71          private final static String DELIMITER = ".";
72          PromptBeforeValidationEvent event;
73  
74          public ContextSession(String context, PromptBeforeValidationEvent event) {
75              this.event = event;
76  
77              this.event.setQuestionContext(context);
78              if (this.event.getQuestionContext() == null) {
79                  this.event.setQuestionContext("");
80              }
81  
82          }
83  
84          /**
85           * Whether a question with a given ID has already been asked
86           * 
87           * @param id the ID of the question, an arbitrary value, but must be consistent
88           * @return
89           */
90          public boolean hasAsked(String id) {
91              return StringUtils.contains(event.getQuestionContext(), id);
92          }
93  
94          /**
95           * Invoked to indicate that the user should be prompted a question
96           * 
97           * @param id the ID of the question, an arbitrary value, but must be consistent
98           * @param text the question text, to be displayed to the user
99           */
100         public void askQuestion(String id, String text) {
101             event.setQuestionId(id);
102             event.setQuestionType(KNSConstants.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(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
164         buttonClicked = request.getParameter(KNSConstants.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(KNSConstants.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 }