001: /*
002: * Copyright 2006-2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.core.rules;
017:
018: import java.util.Arrays;
019: import java.util.Iterator;
020: import java.util.NoSuchElementException;
021:
022: import javax.servlet.http.HttpServletRequest;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.apache.struts.action.ActionForm;
026: import org.kuali.RiceConstants;
027: import org.kuali.core.document.Document;
028: import org.kuali.core.question.ConfirmationQuestion;
029: import org.kuali.core.rule.PreRulesCheck;
030: import org.kuali.core.rule.event.PreRulesCheckEvent;
031: import org.kuali.core.web.struts.form.KualiForm;
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: *
044: */
045: public abstract class PreRulesContinuationBase implements PreRulesCheck {
046:
047: protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
048: .getLogger(PreRulesContinuationBase.class);
049:
050: protected String question;
051: protected String buttonClicked;
052: protected PreRulesCheckEvent 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: public class ContextSession {
065: private final static String DELIMITER = ".";
066: PreRulesCheckEvent event;
067:
068: public ContextSession(String context, PreRulesCheckEvent event) {
069: this .event = event;
070:
071: this .event.setQuestionContext(context);
072: if (this .event.getQuestionContext() == null) {
073: this .event.setQuestionContext("");
074: }
075:
076: }
077:
078: public boolean hasAsked(String id) {
079: return StringUtils.contains(event.getQuestionContext(), id);
080: }
081:
082: public void askQuestion(String id, String text) {
083: event.setQuestionId(id);
084: event.setQuestionType(RiceConstants.CONFIRMATION_QUESTION);
085: event.setQuestionText(text);
086: event.setPerformQuestion(true);
087:
088: }
089:
090: public void setAttribute(String name, String value) {
091: if (LOG.isDebugEnabled()) {
092: LOG.debug("setAttribute(" + name + "," + value + ")");
093: }
094: event.setQuestionContext(event.getQuestionContext()
095: + DELIMITER + name + DELIMITER + value);
096:
097: }
098:
099: public String getAttribute(String name) {
100: if (LOG.isDebugEnabled()) {
101: LOG.debug("getAttribute(" + name + ")");
102: }
103: String result = null;
104:
105: Iterator values = Arrays.asList(
106: event.getQuestionContext().split("\\" + DELIMITER))
107: .iterator();
108:
109: while (values.hasNext()) {
110: if (values.next().equals(name)) {
111: try {
112: result = (String) values.next();
113: } catch (NoSuchElementException e) {
114: result = null;
115: }
116: }
117: }
118: if (LOG.isDebugEnabled()) {
119: LOG.debug("returning " + result);
120: }
121: return result;
122: }
123:
124: }
125:
126: public abstract boolean doRules(Document document);
127:
128: private boolean isAborting;
129:
130: ContextSession session;
131:
132: public PreRulesContinuationBase() {
133: }
134:
135: public boolean processPreRuleChecks(ActionForm form,
136: HttpServletRequest request, PreRulesCheckEvent event) {
137:
138: question = request
139: .getParameter(RiceConstants.QUESTION_INST_ATTRIBUTE_NAME);
140: buttonClicked = request
141: .getParameter(RiceConstants.QUESTION_CLICKED_BUTTON);
142: this .event = event;
143: this .form = (KualiForm) form;
144:
145: if (LOG.isDebugEnabled()) {
146: LOG.debug("Question is: " + question);
147: LOG.debug("ButtonClicked: " + buttonClicked);
148: LOG.debug("QuestionContext() is: "
149: + event.getQuestionContext());
150: }
151:
152: session = new ContextSession(request
153: .getParameter(RiceConstants.QUESTION_CONTEXT), event);
154:
155: boolean result = false;
156:
157: try {
158: result = doRules(event.getDocument());
159: } catch (IsAskingException e) {
160: return false;
161: }
162:
163: if (isAborting) {
164: return false;
165: }
166:
167: return result;
168: }
169:
170: /**
171: *
172: * This bounces the user back to the document as if they had never tried to routed it. (Business rules are not invoked.)
173: *
174: */
175: public void abortRulesCheck() {
176: event.setActionForwardName(RiceConstants.MAPPING_BASIC);
177: isAborting = true;
178: }
179:
180: /**
181: *
182: * This method poses a Y/N question to the user.
183: *
184: * Code that invokes this method will behave a bit strangely, so you should try to keep it as simple as possible.
185: *
186: * @param id
187: * @param text
188: * @return
189: */
190: public boolean askOrAnalyzeYesNoQuestion(String id, String text) {
191:
192: if (LOG.isDebugEnabled()) {
193: LOG.debug("Entering askOrAnalyzeYesNoQuestion(" + id + ","
194: + text + ")");
195: }
196:
197: String cached = (String) session.getAttribute(id);
198: if (cached != null) {
199: LOG.debug("returning cached value: " + id + "=" + cached);
200: return new Boolean(cached).booleanValue();
201: }
202:
203: if (id.equals(question)) {
204: session.setAttribute(id, Boolean
205: .toString(!ConfirmationQuestion.NO
206: .equals(buttonClicked)));
207: return !ConfirmationQuestion.NO.equals(buttonClicked);
208: } else if (!session.hasAsked(id)) {
209: if (LOG.isDebugEnabled()) {
210: LOG.debug("Forcing question to be asked: " + id);
211: }
212: session.askQuestion(id, text);
213: }
214:
215: LOG.debug("Throwing Exception to force return to Action");
216: throw new IsAskingException();
217: }
218:
219: }
|