View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 1.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/ecl1.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.web.spring.controller;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.Map;
21  
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.apache.commons.lang.ArrayUtils;
26  import org.kuali.rice.core.framework.parameter.ParameterConstants;
27  import org.kuali.rice.core.framework.services.CoreFrameworkServiceLocator;
28  import org.kuali.rice.core.util.RiceKeyConstants;
29  import org.kuali.rice.kew.exception.WorkflowException;
30  import org.kuali.rice.kew.util.KEWConstants;
31  import org.kuali.rice.kns.bo.AdHocRouteRecipient;
32  import org.kuali.rice.kns.document.Document;
33  import org.kuali.rice.kns.exception.DocumentAuthorizationException;
34  import org.kuali.rice.kns.exception.UnknownDocumentIdException;
35  import org.kuali.rice.kns.exception.ValidationException;
36  import org.kuali.rice.kns.question.ConfirmationQuestion;
37  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
38  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
39  import org.kuali.rice.kns.service.BusinessObjectService;
40  import org.kuali.rice.kns.service.DataDictionaryService;
41  import org.kuali.rice.kns.service.DocumentHelperService;
42  import org.kuali.rice.kns.service.DocumentService;
43  import org.kuali.rice.kns.service.KNSServiceLocator;
44  import org.kuali.rice.kns.service.KNSServiceLocatorWeb;
45  import org.kuali.rice.kns.util.GlobalVariables;
46  import org.kuali.rice.kns.util.KNSConstants;
47  import org.kuali.rice.kns.util.KNSPropertyConstants;
48  import org.kuali.rice.kns.util.SessionTicket;
49  import org.kuali.rice.kns.util.WebUtils;
50  import org.kuali.rice.kns.web.spring.form.DocumentFormBase;
51  import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
52  import org.springframework.validation.BindingResult;
53  import org.springframework.web.bind.annotation.ModelAttribute;
54  import org.springframework.web.bind.annotation.RequestMapping;
55  import org.springframework.web.servlet.ModelAndView;
56  
57  /**
58   * Base controller class for all KRAD <code>DocumentView</code> screens working
59   * with <code>Document</code> models
60   * 
61   * <p>
62   * Provides default controller implementations for the standard document actions
63   * including: doc handler (retrieve from doc search and action list), save,
64   * route (and other KEW actions)
65   * </p>
66   * 
67   * @author Kuali Rice Team (rice.collab@kuali.org)
68   */
69  public abstract class DocumentControllerBase extends UifControllerBase {
70  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DocumentControllerBase.class);
71  
72  	// COMMAND constants which cause docHandler to load an existing document
73  	// instead of creating a new one
74  	protected static final String[] DOCUMENT_LOAD_COMMANDS = { KEWConstants.ACTIONLIST_COMMAND,
75  			KEWConstants.DOCSEARCH_COMMAND, KEWConstants.SUPERUSER_COMMAND, KEWConstants.HELPDESK_ACTIONLIST_COMMAND };
76  
77  	private BusinessObjectService businessObjectService;
78  	private BusinessObjectAuthorizationService businessObjectAuthorizationService;
79  	private BusinessObjectMetaDataService businessObjectMetaDataService;
80  	private DataDictionaryService dataDictionaryService;
81  	private DocumentService documentService;
82  	private DocumentHelperService documentHelperService;
83  
84  	@Override
85  	public abstract DocumentFormBase createInitialForm(HttpServletRequest request);
86  
87  	/**
88  	 * Used to funnel all document handling through, we could do useful things
89  	 * like log and record various openings and status Additionally it may be
90  	 * nice to have a single dispatcher that can know how to dispatch to a
91  	 * redirect url for document specific handling but we may not need that as
92  	 * all we should need is the document to be able to load itself based on
93  	 * document id and then which action forward or redirect is pertinent for
94  	 * the document type.
95  	 */
96  	@RequestMapping(params = "methodToCall=docHandler")
97  	public ModelAndView docHandler(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result, HttpServletRequest request,
98  			HttpServletResponse response) throws Exception {
99  		String command = form.getCommand();
100 
101 		// in all of the following cases we want to load the document
102 		if (ArrayUtils.contains(DOCUMENT_LOAD_COMMANDS, command) && form.getDocId() != null) {
103 			loadDocument(form);
104 		}
105 		else if (KEWConstants.INITIATE_COMMAND.equals(command)) {
106 			createDocument(form);
107 		}
108 		else {
109 			LOG.error("docHandler called with invalid parameters");
110 			throw new IllegalStateException("docHandler called with invalid parameters");
111 		}
112 
113 		// TODO: authorization on document actions
114 		// if (KEWConstants.SUPERUSER_COMMAND.equalsIgnoreCase(command)) {
115 		// form.setSuppressAllButtons(true);
116 		// }
117 
118 		return getUIFModelAndView(form);
119 	}
120 
121 	/**
122 	 * Loads the document by its provided document header id. This has been
123 	 * abstracted out so that it can be overridden in children if the need
124 	 * arises.
125 	 * 
126 	 * @param form
127 	 *            - form instance that contains the doc id parameter and where
128 	 *            the retrieved document instance should be set
129 	 */
130 	protected void loadDocument(DocumentFormBase form) throws WorkflowException {
131 		String docId = form.getDocId();
132 
133 		Document doc = null;
134 		doc = getDocumentService().getByDocumentHeaderId(docId);
135 		if (doc == null) {
136 			throw new UnknownDocumentIdException(
137 					"Document no longer exists.  It may have been cancelled before being saved.");
138 		}
139 
140 		KualiWorkflowDocument workflowDocument = doc.getDocumentHeader().getWorkflowDocument();
141 		if (!getDocumentHelperService().getDocumentAuthorizer(doc).canOpen(doc,
142 				GlobalVariables.getUserSession().getPerson())) {
143 			throw buildAuthorizationException("open", doc);
144 		}
145 
146 		// re-retrieve the document using the current user's session - remove
147 		// the system user from the WorkflowDcument object
148 		if (workflowDocument != doc.getDocumentHeader().getWorkflowDocument()) {
149 			LOG.warn("Workflow document changed via canOpen check");
150 			doc.getDocumentHeader().setWorkflowDocument(workflowDocument);
151 		}
152 
153 		form.setDocument(doc);
154 		KualiWorkflowDocument workflowDoc = doc.getDocumentHeader().getWorkflowDocument();
155 		form.setDocTypeName(workflowDoc.getDocumentType());
156 
157 		KNSServiceLocatorWeb.getSessionDocumentService().addDocumentToUserSession(GlobalVariables.getUserSession(), workflowDoc);
158 	}
159 
160 	/**
161 	 * Creates a new document of the type specified by the docTypeName property
162 	 * of the given form. This has been abstracted out so that it can be
163 	 * overridden in children if the need arises.
164 	 * 
165 	 * @param form
166 	 *            - form instance that contains the doc type parameter and where
167 	 *            the new document instance should be set
168 	 */
169 	protected void createDocument(DocumentFormBase form) throws WorkflowException {
170 		Document doc = getDocumentService().getNewDocument(form.getDocTypeName());
171 
172 		form.setDocument(doc);
173 		form.setDocTypeName(doc.getDocumentHeader().getWorkflowDocument().getDocumentType());
174 	}
175 
176 	/**
177 	 * Saves the <code>Document</code> instance
178 	 */
179 	@RequestMapping(params = "methodToCall=save")
180 	public ModelAndView save(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result, HttpServletRequest request,
181 			HttpServletResponse response) throws Exception {
182 
183 		doProcessingAfterPost(form, request);
184 
185 		// get any possible changes to to adHocWorkgroups
186 		// TODO turn this back on
187 		// refreshAdHocRoutingWorkgroupLookups(request, form);
188 
189 		Document document = form.getDocument();
190 
191 		String viewName = checkAndWarnAboutSensitiveData(form, request, response,
192 				KNSPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "save", "");
193 		// TODO if the happens we may need to save form to session or account
194 		// for it
195 		if (viewName != null) {
196 			return new ModelAndView(viewName);
197 		}
198 
199 		try {
200     		// save in workflow
201     		getDocumentService().saveDocument(document);
202     
203     		GlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_SAVED);
204     		form.setAnnotation("");
205 		}
206 		catch(ValidationException vex) {
207 		    // swallow exception so screen will draw with errors
208 		}
209 
210 		return getUIFModelAndView(form);
211 	}
212 
213 	/**
214 	 * Routes the <code>Document</code> instance using the document service
215 	 */
216 	@RequestMapping(params = "methodToCall=route")
217 	public ModelAndView route(@ModelAttribute("KualiForm") DocumentFormBase form, BindingResult result, HttpServletRequest request,
218 			HttpServletResponse response) throws Exception {
219 		doProcessingAfterPost(form, request);
220 
221 		// TODO: prerules
222 		// kualiDocumentFormBase.setDerivedValuesOnForm(request);
223 		// ActionForward preRulesForward = promptBeforeValidation(mapping, form,
224 		// request, response);
225 		// if (preRulesForward != null) {
226 		// return preRulesForward;
227 		// }
228 
229 		Document document = form.getDocument();
230 
231 		String viewName = checkAndWarnAboutSensitiveData(form, request, response,
232 				KNSPropertyConstants.DOCUMENT_EXPLANATION, document.getDocumentHeader().getExplanation(), "route", "");
233 		if (viewName != null) {
234 			return new ModelAndView(viewName);
235 		}
236 
237 		// TODO: adhoc recipients
238 		// getDocumentService().routeDocument(document, form.getAnnotation(),
239 		// combineAdHocRecipients(kualiDocumentFormBase));
240 		getDocumentService().routeDocument(document, form.getAnnotation(), new ArrayList<AdHocRouteRecipient>());
241 
242 		GlobalVariables.getMessageList().add(RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL);
243 		form.setAnnotation("");
244 
245 		// GlobalVariables.getUserSession().addObject(DocumentAuthorizerBase.USER_SESSION_METHOD_TO_CALL_COMPLETE_OBJECT_KEY,Boolean.TRUE);
246 		return getUIFModelAndView(form);
247 	}
248 
249 	/**
250 	 * Does all special processing on a document that should happen on each HTTP
251 	 * post (ie, save, route, approve, etc).
252 	 * 
253 	 * @param form
254 	 * @param request
255 	 */
256 	protected void doProcessingAfterPost(DocumentFormBase form, HttpServletRequest request) {
257 		getBusinessObjectService().linkUserFields(form.getDocument());
258 	}
259 
260 	// TODO this needs more analysis before porting can finish
261 	/*
262 	 * protected void refreshAdHocRoutingWorkgroupLookups(HttpServletRequest
263 	 * request, DocumentFormBase form) throws WorkflowException { for
264 	 * (Enumeration<String> i = request.getParameterNames();
265 	 * i.hasMoreElements();) { String parameterName = i.nextElement();
266 	 * 
267 	 * // TODO does this really belong in the loop if
268 	 * (parameterName.equals("newAdHocRouteWorkgroup.recipientName") &&
269 	 * !"".equals(request.getParameter(parameterName))) { //check for namespace
270 	 * String namespace = KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE; if
271 	 * (request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode") !=
272 	 * null &&
273 	 * !"".equals(request.getParameter("newAdHocRouteWorkgroup.recipientName"
274 	 * ).trim())) {
275 	 * 
276 	 * namespace =
277 	 * request.getParameter("newAdHocRouteWorkgroup.recipientNamespaceCode"
278 	 * ).trim(); } Group group =
279 	 * getIdentityManagementService().getGroupByName(namespace,
280 	 * request.getParameter(parameterName)); if (group != null) {
281 	 * form.getNewAdHocRouteWorkgroup().setId(group.getGroupId());
282 	 * form.getNewAdHocRouteWorkgroup().setRecipientName(group.getGroupName());
283 	 * form
284 	 * .getNewAdHocRouteWorkgroup().setRecipientNamespaceCode(group.getNamespaceCode
285 	 * ()); } else { throw new
286 	 * RuntimeException("Invalid workgroup id passed as parameter."); } }
287 	 * 
288 	 * // TODO need to look at screen, will most of this just be bound to the
289 	 * form by spring? if (parameterName.startsWith("adHocRouteWorkgroup[") &&
290 	 * !"".equals(request.getParameter(parameterName))) { if
291 	 * (parameterName.endsWith(".recipientName")) { int lineNumber =
292 	 * Integer.parseInt(StringUtils.substringBetween(parameterName, "[", "]"));
293 	 * //check for namespace String namespaceParam = "adHocRouteWorkgroup[" +
294 	 * lineNumber + "].recipientNamespaceCode"; String namespace =
295 	 * KimConstants.KIM_GROUP_DEFAULT_NAMESPACE_CODE; if
296 	 * (request.getParameter(namespaceParam) != null &&
297 	 * !"".equals(request.getParameter(namespaceParam).trim())) { namespace =
298 	 * request.getParameter(namespaceParam).trim(); } Group group =
299 	 * getIdentityManagementService().getGroupByName(namespace,
300 	 * request.getParameter(parameterName)); if (group != null) {
301 	 * form.getAdHocRouteWorkgroup(lineNumber).setId(group.getGroupId());
302 	 * form.getAdHocRouteWorkgroup
303 	 * (lineNumber).setRecipientName(group.getGroupName());
304 	 * form.getAdHocRouteWorkgroup
305 	 * (lineNumber).setRecipientNamespaceCode(group.getNamespaceCode()); } else
306 	 * { throw new
307 	 * RuntimeException("Invalid workgroup id passed as parameter."); } } } } }
308 	 */
309 
310 	/**
311 	 * Checks if the given value matches patterns that indicate sensitive data
312 	 * and if configured to give a warning for sensitive data will prompt the
313 	 * user to continue.
314 	 * 
315 	 * @param form
316 	 * @param request
317 	 * @param response
318 	 * @param fieldName
319 	 *            - name of field with value being checked
320 	 * @param fieldValue
321 	 *            - value to check for sensitive data
322 	 * @param caller
323 	 *            - method that should be called back from question
324 	 * @param context
325 	 *            - additional context that needs to be passed back with the
326 	 *            question response
327 	 * @return - view for spring to forward to, or null if processing should
328 	 *         continue
329 	 * @throws Exception
330 	 */
331 	protected String checkAndWarnAboutSensitiveData(DocumentFormBase form, HttpServletRequest request,
332 			HttpServletResponse response, String fieldName, String fieldValue, String caller, String context)
333 			throws Exception {
334 
335 		String viewName = null;
336 		Document document = form.getDocument();
337 
338 		boolean containsSensitiveData = WebUtils.containsSensitiveDataPatternMatch(fieldValue);
339 
340 		// check if warning is configured in which case we will prompt, or if
341 		// not business rules will thrown an error
342 		boolean warnForSensitiveData = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(
343 				KNSConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
344 				KNSConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS_WARNING_IND);
345 
346 		// determine if the question has been asked yet
347 		Map<String, String> ticketContext = new HashMap<String, String>();
348 		ticketContext.put(KNSPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
349 		ticketContext.put(KNSConstants.CALLING_METHOD, caller);
350 		ticketContext.put(KNSPropertyConstants.NAME, fieldName);
351 
352 		boolean questionAsked = GlobalVariables.getUserSession().hasMatchingSessionTicket(
353 				KNSConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET, ticketContext);
354 
355 		// start in logic for confirming the sensitive data
356 		if (containsSensitiveData && warnForSensitiveData && !questionAsked) {
357 			Object question = request.getParameter(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
358 			if (question == null || !KNSConstants.DOCUMENT_SENSITIVE_DATA_QUESTION.equals(question)) {
359 
360 				// TODO not ready for question framework yet
361 				/*
362 				 * // question hasn't been asked, prompt to continue return
363 				 * this.performQuestionWithoutInput(mapping, form, request,
364 				 * response, KNSConstants.DOCUMENT_SENSITIVE_DATA_QUESTION,
365 				 * getKualiConfigurationService()
366 				 * .getPropertyString(RiceKeyConstants
367 				 * .QUESTION_SENSITIVE_DATA_DOCUMENT),
368 				 * KNSConstants.CONFIRMATION_QUESTION, caller, context);
369 				 */
370 				viewName = "ask_user_questions";
371 			}
372 			else {
373 				Object buttonClicked = request.getParameter(KNSConstants.QUESTION_CLICKED_BUTTON);
374 
375 				// if no button clicked just reload the doc
376 				if (ConfirmationQuestion.NO.equals(buttonClicked)) {
377 					// TODO figure out what to return
378 					viewName = "user_says_no";
379 				}
380 
381 				// answered yes, create session ticket so we not to ask question
382 				// again if there are further question requests
383 				SessionTicket ticket = new SessionTicket(KNSConstants.SENSITIVE_DATA_QUESTION_SESSION_TICKET);
384 				ticket.setTicketContext(ticketContext);
385 				GlobalVariables.getUserSession().putSessionTicket(ticket);
386 			}
387 		}
388 
389 		// returning null will indicate processing should continue (no redirect)
390 		return viewName;
391 	}
392 
393 	/**
394 	 * Convenience method for building authorization exceptions
395 	 * 
396 	 * @param action
397 	 *            - the action that was requested
398 	 * @param document
399 	 *            - document instance the action was requested for
400 	 */
401 	protected DocumentAuthorizationException buildAuthorizationException(String action, Document document) {
402 		return new DocumentAuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(),
403 				action, document.getDocumentNumber());
404 	}
405 
406 	public BusinessObjectService getBusinessObjectService() {
407 		if (this.businessObjectService == null) {
408 			this.businessObjectService = KNSServiceLocator.getBusinessObjectService();
409 		}
410 		return this.businessObjectService;
411 	}
412 
413 	public void setBusinessObjectService(BusinessObjectService businessObjectService) {
414 		this.businessObjectService = businessObjectService;
415 	}
416 
417 	public BusinessObjectAuthorizationService getBusinessObjectAuthorizationService() {
418 		if (this.businessObjectAuthorizationService == null) {
419 			this.businessObjectAuthorizationService = KNSServiceLocatorWeb.getBusinessObjectAuthorizationService();
420 		}
421 		return this.businessObjectAuthorizationService;
422 	}
423 
424 	public void setBusinessObjectAuthorizationService(
425 			BusinessObjectAuthorizationService businessObjectAuthorizationService) {
426 		this.businessObjectAuthorizationService = businessObjectAuthorizationService;
427 	}
428 
429 	public BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
430 		if (this.businessObjectMetaDataService == null) {
431 			this.businessObjectMetaDataService = KNSServiceLocatorWeb.getBusinessObjectMetaDataService();
432 		}
433 		return this.businessObjectMetaDataService;
434 	}
435 
436 	public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) {
437 		this.businessObjectMetaDataService = businessObjectMetaDataService;
438 	}
439 
440 	public DataDictionaryService getDataDictionaryService() {
441 		if (this.dataDictionaryService == null) {
442 			this.dataDictionaryService = KNSServiceLocatorWeb.getDataDictionaryService();
443 		}
444 		return this.dataDictionaryService;
445 	}
446 
447 	public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
448 		this.dataDictionaryService = dataDictionaryService;
449 	}
450 
451 	public DocumentService getDocumentService() {
452 		if (this.documentService == null) {
453 			this.documentService = KNSServiceLocatorWeb.getDocumentService();
454 		}
455 		return this.documentService;
456 	}
457 
458 	public void setDocumentService(DocumentService documentService) {
459 		this.documentService = documentService;
460 	}
461 
462 	public DocumentHelperService getDocumentHelperService() {
463 		if (this.documentHelperService == null) {
464 			this.documentHelperService = KNSServiceLocatorWeb.getDocumentHelperService();
465 		}
466 		return this.documentHelperService;
467 	}
468 
469 	public void setDocumentHelperService(DocumentHelperService documentHelperService) {
470 		this.documentHelperService = documentHelperService;
471 	}
472 }