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 }