View Javadoc
1   /**
2    * Copyright 2005-2012 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.util;
17  
18  import java.io.ByteArrayOutputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.Enumeration;
25  import java.util.HashMap;
26  import java.util.Hashtable;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import javax.servlet.ServletException;
35  import javax.servlet.http.HttpServletRequest;
36  import javax.servlet.http.HttpServletResponse;
37  import javax.servlet.http.HttpSession;
38  import javax.servlet.jsp.PageContext;
39  
40  import org.apache.commons.lang.StringEscapeUtils;
41  import org.apache.commons.lang.StringUtils;
42  import org.apache.log4j.Level;
43  import org.apache.log4j.Logger;
44  import org.apache.struts.Globals;
45  import org.apache.struts.action.ActionForm;
46  import org.apache.struts.action.ActionMapping;
47  import org.apache.struts.action.ActionServletWrapper;
48  import org.apache.struts.upload.CommonsMultipartRequestHandler;
49  import org.apache.struts.upload.FormFile;
50  import org.apache.struts.upload.MultipartRequestHandler;
51  import org.apache.struts.upload.MultipartRequestWrapper;
52  import org.kuali.rice.core.api.config.property.ConfigurationService;
53  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
54  import org.kuali.rice.core.api.util.RiceKeyConstants;
55  import org.kuali.rice.kew.api.action.ActionRequest;
56  import org.kuali.rice.kew.api.action.RecipientType;
57  import org.kuali.rice.kim.api.role.Role;
58  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
59  import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
60  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
61  import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
62  import org.kuali.rice.kns.service.KNSServiceLocator;
63  import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler;
64  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
65  import org.kuali.rice.kns.web.struts.form.KualiForm;
66  import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
67  import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase;
68  import org.kuali.rice.kns.web.ui.Field;
69  import org.kuali.rice.kns.web.ui.Row;
70  import org.kuali.rice.kns.web.ui.Section;
71  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
72  import org.kuali.rice.krad.datadictionary.AttributeSecurity;
73  import org.kuali.rice.krad.datadictionary.DataDictionary;
74  import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
75  import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
76  import org.kuali.rice.krad.document.Document;
77  import org.kuali.rice.krad.exception.ValidationException;
78  import org.kuali.rice.krad.service.KRADServiceLocator;
79  import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
80  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
81  import org.kuali.rice.krad.util.GlobalVariables;
82  import org.kuali.rice.krad.util.KRADConstants;
83  import org.kuali.rice.krad.util.MessageMap;
84  import org.kuali.rice.krad.util.ObjectUtils;
85  import org.springframework.transaction.PlatformTransactionManager;
86  import org.springframework.transaction.TransactionStatus;
87  import org.springframework.transaction.support.TransactionCallback;
88  import org.springframework.transaction.support.TransactionTemplate;
89  
90  /**
91   * General helper methods for handling requests.
92   */
93  public class WebUtils {
94  	private static final Logger LOG = Logger.getLogger(WebUtils.class);
95  
96  	private static final String IMAGE_COORDINATE_CLICKED_X_EXTENSION = ".x";
97  	private static final String IMAGE_COORDINATE_CLICKED_Y_EXTENSION = ".y";
98  
99  	private static final String APPLICATION_IMAGE_URL_PROPERTY_PREFIX = "application.custom.image.url";
100 	private static final String DEFAULT_IMAGE_URL_PROPERTY_NAME = "kr.externalizable.images.url";
101 
102     /**
103      * Prefixes indicating an absolute url
104      */
105     private static final String[] SCHEMES = { "http://", "https://" };
106 
107 	/**
108 	 * A request attribute name that indicates that a
109 	 * {@link org.kuali.rice.kns.exception.FileUploadLimitExceededException} has already been thrown for the
110 	 * request.
111 	 */
112 	public static final String FILE_UPLOAD_LIMIT_EXCEEDED_EXCEPTION_ALREADY_THROWN = "fileUploadLimitExceededExceptionAlreadyThrown";
113 
114 	private static ConfigurationService configurationService;
115     private static PlatformTransactionManager transactionManager;
116 
117 	/**
118 	 * Checks for methodToCall parameter, and picks off the value using set dot
119 	 * notation. Handles the problem of image submits.
120 	 *
121 	 * @param request
122 	 * @return methodToCall String
123 	 */
124 	public static String parseMethodToCall(ActionForm form, HttpServletRequest request) {
125 		String methodToCall = null;
126 
127 		// check if is specified cleanly
128 		if (StringUtils.isNotBlank(request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER))) {
129 			if (form instanceof KualiForm
130 					&& !((KualiForm) form).shouldMethodToCallParameterBeUsed(KRADConstants.DISPATCH_REQUEST_PARAMETER,
131 							request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER), request)) {
132 				throw new RuntimeException("Cannot verify that the methodToCall should be "
133 						+ request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER));
134 			}
135 			methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
136 			// include .x at the end of the parameter to make it consistent w/
137 			// other parameters
138 			request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, KRADConstants.DISPATCH_REQUEST_PARAMETER + "."
139 					+ methodToCall + IMAGE_COORDINATE_CLICKED_X_EXTENSION);
140 		}
141 
142 		/**
143 		 * The reason why we are checking for a ".x" at the end of the parameter
144 		 * name: It is for the image names that in addition to sending the form
145 		 * data, the web browser sends the x,y coordinate of where the user
146 		 * clicked on the image. If the image input is not given a name then the
147 		 * browser sends the x and y coordinates as the "x" and "y" input
148 		 * fields. If the input image does have a name, the x and y coordinates
149 		 * are sent using the format name.x and name.y.
150 		 */
151 		if (methodToCall == null) {
152 			// iterate through parameters looking for methodToCall
153 			for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
154 				String parameterName = (String) i.nextElement();
155 
156 				// check if the parameter name is a specifying the methodToCall
157 				if (isMethodToCall(parameterName)) {
158 					methodToCall = getMethodToCallSettingAttribute(form, request, parameterName);
159 					break;
160 				}
161 				else {
162 					// KULRICE-1218: Check if the parameter's values match (not
163 					// just the name)
164 					for (String value : request.getParameterValues(parameterName)) {
165 						// adding period to startsWith check - don't want to get
166 						// confused with methodToCallFoobar
167 						if (isMethodToCall(value)) {
168 							methodToCall = getMethodToCallSettingAttribute(form, request, value);
169 							// why is there not a break outer loop here?
170 						}
171 					}
172 				}
173 			}
174 		}
175 
176 		return methodToCall;
177 	}
178 
179     /**
180 	 * Checks if a string signifies a methodToCall string
181 	 *
182 	 * @param string
183 	 *            the string to check
184 	 * @return true if is a methodToCall
185 	 */
186 	private static boolean isMethodToCall(String string) {
187 		// adding period to startsWith check - don't want to get confused with
188 		// methodToCallFoobar
189 		return string.startsWith(KRADConstants.DISPATCH_REQUEST_PARAMETER + ".");
190 	}
191 
192 	/**
193 	 * Parses out the methodToCall command and also sets the request attribute
194 	 * for the methodToCall.
195 	 *
196 	 * @param form
197 	 *            the ActionForm
198 	 * @param request
199 	 *            the request to set the attribute on
200 	 * @param string
201 	 *            the methodToCall string
202 	 * @return the methodToCall command
203 	 */
204 	private static String getMethodToCallSettingAttribute(ActionForm form, HttpServletRequest request, String string) {
205 
206 		if (form instanceof KualiForm
207 				&& !((KualiForm) form).shouldMethodToCallParameterBeUsed(string, request.getParameter(string), request)) {
208 			throw new RuntimeException("Cannot verify that the methodToCall should be " + string);
209 		}
210 		// always adding a coordinate even if not an image
211 		final String attributeValue = endsWithCoordinates(string) ? string : string
212 				+ IMAGE_COORDINATE_CLICKED_X_EXTENSION;
213 		final String methodToCall = StringUtils.substringBetween(attributeValue,
214 				KRADConstants.DISPATCH_REQUEST_PARAMETER + ".", ".");
215 		request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, attributeValue);
216 		return methodToCall;
217 	}
218 
219 	/**
220 	 * Iterates through and logs (at the given level) all attributes and
221 	 * parameters of the given request onto the given Logger
222 	 *
223 	 * @param request
224 	 * @param logger
225 	 */
226 	public static void logRequestContents(Logger logger, Level level, HttpServletRequest request) {
227 		if (logger.isEnabledFor(level)) {
228 			logger.log(level, "--------------------");
229 			logger.log(level, "HttpRequest attributes:");
230 			for (Enumeration e = request.getAttributeNames(); e.hasMoreElements();) {
231 				String attrName = (String) e.nextElement();
232 				Object attrValue = request.getAttribute(attrName);
233 
234 				if (attrValue.getClass().isArray()) {
235 					logCollection(logger, level, attrName, Arrays.asList((Object[]) attrValue));
236 				}
237 				else if (attrValue instanceof Collection) {
238 					logCollection(logger, level, attrName, (Collection) attrValue);
239 				}
240 				else if (attrValue instanceof Map) {
241 					logMap(logger, level, attrName, (Map) attrValue);
242 				}
243 				else {
244 					logObject(logger, level, attrName, attrValue);
245 				}
246 			}
247 
248 			logger.log(level, "--------------------");
249 			logger.log(level, "HttpRequest parameters:");
250 			for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
251 				String paramName = (String) i.nextElement();
252 				String[] paramValues = request.getParameterValues(paramName);
253 
254 				logArray(logger, level, paramName, paramValues);
255 			}
256 
257 			logger.log(level, "--------------------");
258 		}
259 	}
260 
261 	private static void logArray(Logger logger, Level level, String arrayName, Object[] array) {
262 		StringBuffer value = new StringBuffer("[");
263 		for (int i = 0; i < array.length; ++i) {
264 			if (i > 0) {
265 				value.append(",");
266 			}
267 			value.append(array[i]);
268 		}
269 		value.append("]");
270 
271 		logThing(logger, level, arrayName, value);
272 	}
273 
274 	private static void logCollection(Logger logger, Level level, String collectionName, Collection c) {
275 		StringBuffer value = new StringBuffer("{");
276 		for (Iterator i = c.iterator(); i.hasNext();) {
277 			value.append(i.next());
278 			if (i.hasNext()) {
279 				value.append(",");
280 			}
281 		}
282 		value.append("}");
283 
284 		logThing(logger, level, collectionName, value);
285 	}
286 
287 	private static void logMap(Logger logger, Level level, String mapName, Map m) {
288 		StringBuffer value = new StringBuffer("{");
289 		for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
290 			Map.Entry e = (Map.Entry) i.next();
291 			value.append("('" + e.getKey() + "','" + e.getValue() + "')");
292 		}
293 		value.append("}");
294 
295 		logThing(logger, level, mapName, value);
296 	}
297 
298 	private static void logObject(Logger logger, Level level, String objectName, Object o) {
299 		logThing(logger, level, objectName, "'" + o + "'");
300 	}
301 
302 	private static void logThing(Logger logger, Level level, String thingName, Object thing) {
303 		logger.log(level, "    '" + thingName + "' => " + thing);
304 	}
305 
306 	/**
307 	 * A file that is not of type text/plain or text/html can be output through
308 	 * the response using this method.
309 	 *
310 	 * @param response
311 	 * @param contentType
312 	 * @param byteArrayOutputStream
313 	 * @param fileName
314 	 */
315 	public static void saveMimeOutputStreamAsFile(HttpServletResponse response, String contentType,
316 			ByteArrayOutputStream byteArrayOutputStream, String fileName) throws IOException {
317 
318 		// set response
319 		response.setContentType(contentType);
320 		response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
321 		response.setHeader("Expires", "0");
322 		response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
323 		response.setHeader("Pragma", "public");
324 		response.setContentLength(byteArrayOutputStream.size());
325 
326 		// write to output
327 		OutputStream outputStream = response.getOutputStream();
328 		byteArrayOutputStream.writeTo(response.getOutputStream());
329 		outputStream.flush();
330 		outputStream.close();
331 	}
332 
333 	/**
334 	 * A file that is not of type text/plain or text/html can be output through
335 	 * the response using this method.
336 	 *
337 	 * @param response
338 	 * @param contentType
339 	 * @param inStream
340 	 * @param fileName
341 	 */
342 	public static void saveMimeInputStreamAsFile(HttpServletResponse response, String contentType,
343 			InputStream inStream, String fileName, int fileSize) throws IOException {
344 
345 		// set response
346 		response.setContentType(contentType);
347 		response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
348 		response.setHeader("Expires", "0");
349 		response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
350 		response.setHeader("Pragma", "public");
351 		response.setContentLength(fileSize);
352 
353 		// write to output
354 		OutputStream out = response.getOutputStream();
355 		while (inStream.available() > 0) {
356 			out.write(inStream.read());
357 		}
358 		out.flush();
359 	}
360 
361 	/**
362 	 * JSTL function to return the tab state of the tab from the form.
363 	 *
364 	 * @param form
365 	 * @param tabKey
366 	 * @return
367 	 */
368 	public static String getTabState(KualiForm form, String tabKey) {
369 		return form.getTabState(tabKey);
370 	}
371 
372 	public static void incrementTabIndex(KualiForm form, String tabKey) {
373 		form.incrementTabIndex();
374 	}
375 
376     /**
377      * Attempts to reopen sub tabs which would have been closed for inactive records
378      *
379      * @param sections the list of Sections whose rows and fields to set the open tab state on
380      * @param tabStates the map of tabKey->tabState.  This map will be modified to set entries to "OPEN"
381      * @param collectionName the name of the collection reopening
382      */
383     public static void reopenInactiveRecords(List<Section> sections, Map<String, String> tabStates, String collectionName) {
384         for (Section section : sections) {
385             for (Row row: section.getRows()) {
386                 for (Field field : row.getFields()) {
387                     if (field != null) {
388                         if (Field.CONTAINER.equals(field.getFieldType()) && StringUtils.startsWith(field.getContainerName(), collectionName)) {
389                             final String tabKey = WebUtils.generateTabKey(FieldUtils.generateCollectionSubTabName(field));
390                             tabStates.put(tabKey, KualiForm.TabState.OPEN.name());
391                         }
392                     }
393                 }
394             }
395         }
396     }
397 
398 	/**
399 	 * Generates a String from the title that can be used as a Map key.
400 	 *
401 	 * @param tabTitle
402 	 * @return
403 	 */
404 	public static String generateTabKey(String tabTitle) {
405 		String key = "";
406 		if (!StringUtils.isBlank(tabTitle)) {
407 			key = tabTitle.replaceAll("\\W", "");
408 			// if (key.length() > 25) {
409 			// key = key.substring(0, 24);
410 			// }
411 		}
412 
413 		return key;
414 	}
415 
416 	public static void getMultipartParameters(HttpServletRequest request, ActionServletWrapper servletWrapper,
417 			ActionForm form, ActionMapping mapping) {
418 		Map params = new HashMap();
419 
420 		// Get the ActionServletWrapper from the form bean
421 		// ActionServletWrapper servletWrapper = getServletWrapper();
422 
423 		try {
424 			CommonsMultipartRequestHandler multipartHandler = new CommonsMultipartRequestHandler();
425 			if (multipartHandler != null) {
426 				// Set servlet and mapping info
427 				if (servletWrapper != null) {
428 					// from pojoformbase
429 					// servlet only affects tempdir on local disk
430 					servletWrapper.setServletFor(multipartHandler);
431 				}
432 				multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY));
433 				// Initialize multipart request class handler
434 				multipartHandler.handleRequest(request);
435 
436 				Collection<FormFile> files = multipartHandler.getFileElements().values();
437 				Enumeration keys = multipartHandler.getFileElements().keys();
438 
439 				while (keys.hasMoreElements()) {
440 					Object key = keys.nextElement();
441 					FormFile file = (FormFile) multipartHandler.getFileElements().get(key);
442 					long maxSize = WebUtils.getMaxUploadSize(form);
443 					if (LOG.isDebugEnabled()) {
444 						LOG.debug(file.getFileSize());
445 					}
446 					if (maxSize > 0 && Long.parseLong(file.getFileSize() + "") > maxSize) {
447 
448 						GlobalVariables.getMessageMap().putError(key.toString(),
449 								RiceKeyConstants.ERROR_UPLOADFILE_SIZE,
450 								new String[] { file.getFileName(), Long.toString(maxSize) });
451 
452 					}
453 				}
454 
455 				// get file elements for kualirequestprocessor
456 				if (servletWrapper == null) {
457 					request.setAttribute(KRADConstants.UPLOADED_FILE_REQUEST_ATTRIBUTE_KEY,
458 							getFileParametersForMultipartRequest(request, multipartHandler));
459 				}
460 			}
461 		}
462 		catch (ServletException e) {
463 			throw new ValidationException("unable to handle multipart request " + e.getMessage(), e);
464 		}
465 	}
466 
467 	public static long getMaxUploadSize(ActionForm form) {
468 		long max = 0L;
469 		KualiMultipartRequestHandler multipartHandler = new KualiMultipartRequestHandler();
470 		if (form instanceof PojoFormBase) {
471 			max = multipartHandler.calculateMaxUploadSizeToMaxOfList(((PojoFormBase) form).getMaxUploadSizes());
472 		}
473 		if (LOG.isDebugEnabled()) {
474 			LOG.debug("Max File Upload Size: " + max);
475 		}
476 		return max;
477 	}
478 
479 	private static Map getFileParametersForMultipartRequest(HttpServletRequest request,
480 			MultipartRequestHandler multipartHandler) {
481 		Map parameters = new HashMap();
482 		Hashtable elements = multipartHandler.getFileElements();
483 		Enumeration e = elements.keys();
484 		while (e.hasMoreElements()) {
485 			String key = (String) e.nextElement();
486 			parameters.put(key, elements.get(key));
487 		}
488 
489 		if (request instanceof MultipartRequestWrapper) {
490 			request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest();
491 			e = request.getParameterNames();
492 			while (e.hasMoreElements()) {
493 				String key = (String) e.nextElement();
494 				parameters.put(key, request.getParameterValues(key));
495 			}
496 		}
497 		else {
498 			LOG.debug("Gathering multipart parameters for unwrapped request");
499 		}
500 		return parameters;
501 	}
502 
503 	// end multipart
504 
505 	public static void registerEditableProperty(PojoFormBase form, String editablePropertyName) {
506 		form.registerEditableProperty(editablePropertyName);
507 	}
508 
509 	public static boolean isDocumentSession(Document document, PojoFormBase docForm) {
510 		boolean sessionDoc = document instanceof org.kuali.rice.krad.document.SessionDocument;
511 		boolean dataDictionarySessionDoc = false;
512 		if (!sessionDoc) {
513 			DataDictionary dataDictionary = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary();
514 			if (docForm instanceof KualiMaintenanceForm) {
515 				KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) docForm;
516 				if (dataDictionary != null) {
517 					if (maintenanceForm.getDocTypeName() != null) {
518                         MaintenanceDocumentEntry maintenanceDocumentEntry = (MaintenanceDocumentEntry) dataDictionary.getDocumentEntry(maintenanceForm.getDocTypeName());
519 						dataDictionarySessionDoc = maintenanceDocumentEntry.isSessionDocument();
520 					}
521 				}
522 			}
523 			else {
524 				if (document != null && dataDictionary != null) {
525 					KNSDocumentEntry documentEntry = (KNSDocumentEntry) dataDictionary.getDocumentEntry(document.getClass().getName());
526 					dataDictionarySessionDoc = documentEntry.isSessionDocument();
527 				}
528 			}
529 		}
530 		return sessionDoc || dataDictionarySessionDoc;
531 	}
532 
533 	public static boolean isFormSessionDocument(PojoFormBase form) {
534 		Document document = null;
535 		if (KualiDocumentFormBase.class.isAssignableFrom(form.getClass())) {
536 			KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
537 			document = docForm.getDocument();
538 		}
539 		return isDocumentSession(document, form);
540 	}
541 
542 	public static String KEY_KUALI_FORM_IN_SESSION = "KualiForm";
543 
544 	public static ActionForm getKualiForm(PageContext pageContext) {
545 		return getKualiForm((HttpServletRequest) pageContext.getRequest());
546 	}
547 
548 	public static ActionForm getKualiForm(HttpServletRequest request) {
549 		if (request.getAttribute(KEY_KUALI_FORM_IN_SESSION) != null) {
550 			return (ActionForm) request.getAttribute(KEY_KUALI_FORM_IN_SESSION);
551 		}
552 		else {
553 			final HttpSession session = request.getSession(false);
554 			return session != null ? (ActionForm) session.getAttribute(KEY_KUALI_FORM_IN_SESSION) : null;
555 		}
556 	}
557 
558 	public static boolean isPropertyEditable(Set<String> editableProperties, String propertyName) {
559 		if (LOG.isDebugEnabled()) {
560 			LOG.debug("isPropertyEditable(" + propertyName + ")");
561 		}
562 
563 		boolean returnVal = editableProperties == null
564 				|| editableProperties.contains(propertyName)
565 				|| (getIndexOfCoordinateExtension(propertyName) == -1 ? false : editableProperties
566 						.contains(propertyName.substring(0, getIndexOfCoordinateExtension(propertyName))));
567 		if (!returnVal) {
568 			if (LOG.isDebugEnabled()) {
569 				LOG.debug("isPropertyEditable(" + propertyName + ") == false / editableProperties: "
570 						+ editableProperties);
571 			}
572 		}
573 		return returnVal;
574 	}
575 
576 	public static boolean endsWithCoordinates(String parameter) {
577 		return parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION)
578 				|| parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
579 	}
580 
581 	public static int getIndexOfCoordinateExtension(String parameter) {
582 		int indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION);
583         if (indexOfCoordinateExtension == -1) {
584             indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
585         }
586 		return indexOfCoordinateExtension;
587 	}
588 
589     public static boolean isInquiryHiddenField(String className, String fieldName, Object formObject, String propertyName) {
590     	boolean isHidden = false;
591     	String hiddenInquiryFields = getKualiConfigurationService().getPropertyValueAsString(className + ".hidden");
592     	if (StringUtils.isEmpty(hiddenInquiryFields)) {
593     		return isHidden;
594     	}
595     	List hiddenFields = Arrays.asList(hiddenInquiryFields.replaceAll(" ", "").split(","));
596     	if (hiddenFields.contains(fieldName.trim())) {
597     		isHidden = true;
598     	}
599     	return isHidden;
600     }
601 
602     public static boolean isHiddenKimObjectType(String type, String configParameter) {
603     	boolean hideType = false;
604     	String hiddenTypes = getKualiConfigurationService().getPropertyValueAsString(configParameter);
605     	if (StringUtils.isEmpty(hiddenTypes)) {
606     		return hideType;
607     	}
608     	List hiddenTypeValues = Arrays.asList(hiddenTypes.replaceAll(" ", "").split(","));
609     	if (hiddenTypeValues.contains(type.trim())) {
610     		hideType = true;
611     	}
612     	return hideType;
613     }
614 
615 	public static String getFullyMaskedValue(String className, String fieldName, Object formObject, String propertyName) {
616 		String displayMaskValue = null;
617 		Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
618 
619 		DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
620 				.getDataDictionary().getDictionaryObjectEntry(className);
621 		AttributeDefinition a = entry.getAttributeDefinition(fieldName);
622 
623 		AttributeSecurity attributeSecurity = a.getAttributeSecurity();
624 		if (attributeSecurity != null && attributeSecurity.isMask()) {
625 			MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
626 			displayMaskValue = maskFormatter.maskValue(propertyValue);
627 
628 		}
629 		return displayMaskValue;
630 	}
631 
632 	public static String getPartiallyMaskedValue(String className, String fieldName, Object formObject,
633 			String propertyName) {
634 		String displayMaskValue = null;
635 		Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
636 
637 		DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
638 				.getDataDictionary().getDictionaryObjectEntry(className);
639 		AttributeDefinition a = entry.getAttributeDefinition(fieldName);
640 
641 		AttributeSecurity attributeSecurity = a.getAttributeSecurity();
642 		if (attributeSecurity != null && attributeSecurity.isPartialMask()) {
643 			MaskFormatter partialMaskFormatter = attributeSecurity.getPartialMaskFormatter();
644 			displayMaskValue = partialMaskFormatter.maskValue(propertyValue);
645 
646 		}
647 		return displayMaskValue;
648 	}
649 
650 	public static boolean canFullyUnmaskField(final String businessObjectClassName, final String fieldName, final KualiForm form) {
651 		try {
652 		    final Class businessObjClass = Class.forName(businessObjectClassName);
653 	        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
654 	        return template.execute(new TransactionCallback<Boolean>() {
655 	            @Override
656 	            public Boolean doInTransaction(TransactionStatus status) {
657 	                if (form instanceof KualiDocumentFormBase) {
658 	                    return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
659 	                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
660 	                            ((KualiDocumentFormBase) form).getDocument());
661 	                }
662 	                else {
663 	                    return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
664 	                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
665 	                }
666 	            }
667 	        });
668 		}
669 		catch (Exception e) {
670 			throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
671 		}
672 	}
673 
674 	public static boolean canPartiallyUnmaskField(String businessObjectClassName, final String fieldName, final KualiForm form) {
675 		try {
676 			final Class businessObjClass = Class.forName(businessObjectClassName);
677 	        TransactionTemplate template = new TransactionTemplate(getTransactionManager());
678 	        return template.execute(new TransactionCallback<Boolean>() {
679 	            @Override
680 	            public Boolean doInTransaction(TransactionStatus status) {
681 	                if (form instanceof KualiDocumentFormBase) {
682 	                    return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
683 	                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
684 	                            ((KualiDocumentFormBase) form).getDocument());
685 	                }
686 	                else {
687 	                    return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
688 	                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
689 	                }
690 	            }
691 	        });
692 		}
693 		catch (Exception e) {
694 			throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
695 		}
696 	}
697 
698 	public static boolean canAddNoteAttachment(final Document document) {
699         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
700         return template.execute(new TransactionCallback<Boolean>() {
701             @Override
702             public Boolean doInTransaction(TransactionStatus status) {
703                 boolean canViewNoteAttachment = false;
704                 DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
705                         document);
706                 canViewNoteAttachment = documentAuthorizer.canAddNoteAttachment(document, null, GlobalVariables
707                         .getUserSession().getPerson());
708                 return canViewNoteAttachment;
709             }
710         });
711 	}
712 
713 	public static boolean canViewNoteAttachment(final Document document, final String attachmentTypeCode) {
714         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
715         return template.execute(new TransactionCallback<Boolean>() {
716             @Override
717             public Boolean doInTransaction(TransactionStatus status) {
718                 boolean canViewNoteAttachment = false;
719                 DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
720                         document);
721                 canViewNoteAttachment = documentAuthorizer.canViewNoteAttachment(document, attachmentTypeCode, GlobalVariables
722                         .getUserSession().getPerson());
723                 return canViewNoteAttachment;
724             }
725         });
726 	}
727 
728 	public static boolean canDeleteNoteAttachment(final Document document, final String attachmentTypeCode, final String authorUniversalIdentifier) {
729         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
730         return template.execute(new TransactionCallback<Boolean>() {
731             @Override
732             public Boolean doInTransaction(TransactionStatus status) {
733                 boolean canDeleteNoteAttachment = false;
734                 DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
735                         document);
736                 canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "false",
737                         GlobalVariables.getUserSession().getPerson());
738                 if (canDeleteNoteAttachment) {
739                     return canDeleteNoteAttachment;
740                 }
741                 else {
742                     canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "true",
743                             GlobalVariables.getUserSession().getPerson());
744                     if (canDeleteNoteAttachment
745                             && !authorUniversalIdentifier.equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) {
746                         canDeleteNoteAttachment = false;
747                     }
748                 }
749                 return canDeleteNoteAttachment;
750             }
751         });
752 	}
753 
754 	public static void reuseErrorMapFromPreviousRequest(KualiDocumentFormBase kualiDocumentFormBase) {
755 		if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == null) {
756 			LOG.error("Error map from previous request is null!");
757 			return;
758 		}
759 		MessageMap errorMapFromGlobalVariables = GlobalVariables.getMessageMap();
760 		if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == errorMapFromGlobalVariables) {
761 			// if we've switched them already, then return early and do nothing
762 			return;
763 		}
764 		if (!errorMapFromGlobalVariables.hasNoErrors()) {
765 			throw new RuntimeException("Cannot replace error map because it is not empty");
766 		}
767 		GlobalVariables.setMessageMap(kualiDocumentFormBase.getMessageMapFromPreviousRequest());
768 		GlobalVariables.getMessageMap().clearErrorPath();
769 	}
770 
771 	/**
772 	 * Excapes out HTML to prevent XSS attacks, and replaces the following
773 	 * strings to allow for a limited set of HTML tags
774 	 *
775 	 * <li>[X] and [/X], where X represents any 1 or 2 letter string may be used
776 	 * to specify the equivalent tag in HTML (i.e. &lt;X&gt; and &lt;/X&gt;) <li>
777 	 * [font COLOR], where COLOR represents any valid html color (i.e. color
778 	 * name or hexcode preceeded by #) will be filtered into &lt;font
779 	 * color="COLOR"/&gt; <li>[/font] will be filtered into &lt;/font&gt; <li>
780 	 * [table CLASS], where CLASS gives the style class to use, will be filter
781 	 * into &lt;table class="CLASS"/&gt; <li>[/table] will be filtered into
782 	 * &lt;/table&gt; <li>[td CLASS], where CLASS gives the style class to use,
783 	 * will be filter into &lt;td class="CLASS"/&gt;
784 	 *
785 	 * @param inputString
786 	 * @return
787 	 */
788 	public static String filterHtmlAndReplaceRiceMarkup(String inputString) {
789 		String outputString = StringEscapeUtils.escapeHtml(inputString);
790 		// string has been escaped of all <, >, and & (and other characters)
791 
792 		Map<String, String> findAndReplacePatterns = new HashMap<String, String>();
793 
794 		// now replace our rice custom markup into html
795 
796 		// DON'T ALLOW THE SCRIPT TAG OR ARBITRARY IMAGES/URLS/ETC. THROUGH
797 
798 		// filter any one character tags
799 		findAndReplacePatterns.put("\\[([A-Za-z])\\]", "<$1>");
800 		findAndReplacePatterns.put("\\[/([A-Za-z])\\]", "</$1>");
801 		// filter any two character tags
802 		findAndReplacePatterns.put("\\[([A-Za-z]{2})\\]", "<$1>");
803 		findAndReplacePatterns.put("\\[/([A-Za-z]{2})\\]", "</$1>");
804 		// filter the font tag
805 		findAndReplacePatterns.put("\\[font (#[0-9A-Fa-f]{1,6}|[A-Za-z]+)\\]", "<font color=\"$1\">");
806 		findAndReplacePatterns.put("\\[/font\\]", "</font>");
807 		// filter the table tag
808 		findAndReplacePatterns.put("\\[table\\]", "<table>");
809 		findAndReplacePatterns.put("\\[table ([A-Za-z]+)\\]", "<table class=\"$1\">");
810 		findAndReplacePatterns.put("\\[/table\\]", "</table>");
811 		// fiter td with class
812 		findAndReplacePatterns.put("\\[td ([A-Za-z]+)\\]", "<td class=\"$1\">");
813 
814 		for (String findPattern : findAndReplacePatterns.keySet()) {
815 			Pattern p = Pattern.compile(findPattern);
816 			Matcher m = p.matcher(outputString);
817 			if (m.find()) {
818 				String replacePattern = findAndReplacePatterns.get(findPattern);
819 				outputString = m.replaceAll(replacePattern);
820 			}
821 		}
822 
823 		return outputString;
824 	}
825 
826     /**
827 	 * Determines and returns the URL for question button images; looks first
828 	 * for a property "application.custom.image.url", and if that is missing,
829 	 * uses the image url returned by getDefaultButtonImageUrl()
830 	 *
831 	 * @param imageName
832 	 *            the name of the image to find a button for
833 	 * @return the URL where question button images are located
834 	 */
835 	public static String getButtonImageUrl(String imageName) {
836 		String buttonImageUrl = getKualiConfigurationService().getPropertyValueAsString(
837                 WebUtils.APPLICATION_IMAGE_URL_PROPERTY_PREFIX + "." + imageName);
838 		if (StringUtils.isBlank(buttonImageUrl)) {
839 			buttonImageUrl = getDefaultButtonImageUrl(imageName);
840 		}
841 		return buttonImageUrl;
842 	}
843 
844     public static String getAttachmentImageForUrl(String contentType) {
845         String image = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_PREFIX + contentType);
846         if (StringUtils.isEmpty(image)) {
847             return getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_DEFAULT);
848         }
849         return image;
850     }
851 
852 	/**
853 	 * Generates a default button image URL, in the form of:
854 	 * ${kr.externalizable.images.url}buttonsmall_${imageName}.gif
855 	 *
856 	 * @param imageName
857 	 *            the image name to generate a default button name for
858 	 * @return the default button image url
859 	 */
860 	public static String getDefaultButtonImageUrl(String imageName) {
861 		return getKualiConfigurationService().getPropertyValueAsString(WebUtils.DEFAULT_IMAGE_URL_PROPERTY_NAME)
862 				+ "buttonsmall_" + imageName + ".gif";
863 	}
864 
865     /**
866 	 * @return an implementation of the KualiConfigurationService
867 	 */
868 	public static ConfigurationService getKualiConfigurationService() {
869 		if (configurationService == null) {
870 			configurationService = GlobalResourceLoader.getService("kualiConfigurationService");
871 		}
872 		return configurationService;
873 	}
874     public static PlatformTransactionManager getTransactionManager() {
875         if ( transactionManager == null ) {
876             transactionManager = GlobalResourceLoader.getService("transactionManager");
877         }
878         return transactionManager;
879     }
880 
881     /**
882      * Takes a string an converts the whitespace which would be ignored in an
883      * HTML document into HTML elements so the whitespace is preserved
884      *
885      * @param startingString The string to preserve whitespace in
886      * @return A string whose whitespace has been converted to HTML elements to preserve the whitespace in an HTML document
887      */
888     public static String preserveWhitespace(String startingString) {
889     	String convertedString = startingString.replaceAll("\n", "<br />");
890     	convertedString = convertedString.replaceAll("  ", "&nbsp;&nbsp;").replaceAll("(&nbsp; | &nbsp;)", "&nbsp;&nbsp;");
891     	return convertedString;
892     }
893 
894     public static String getKimGroupDisplayName(final String groupId) {
895     	if(StringUtils.isBlank(groupId)) {
896     		throw new IllegalArgumentException("Group ID must have a value");
897     	}
898         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
899         return template.execute(new TransactionCallback<String>() {
900             @Override
901             public String doInTransaction(TransactionStatus status) {
902                 return KimApiServiceLocator.getGroupService().getGroup(groupId).getName();
903             }
904         });
905     }
906 
907     public static String getPrincipalDisplayName(final String principalId) {
908     	if(StringUtils.isBlank(principalId)) {
909     		throw new IllegalArgumentException("Principal ID must have a value");
910     	}
911         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
912         return template.execute(new TransactionCallback<String>() {
913             @Override
914             public String doInTransaction(TransactionStatus status) {
915                 return KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId).getDefaultName().getCompositeName();
916             }
917         });
918     }
919 
920     /**
921      * Takes an {@link org.kuali.rice.kew.api.action.ActionRequest} with a recipient type of
922      * {@link org.kuali.rice.kew.api.action.RecipientType#ROLE} and returns the display name for the role.
923      *
924      * @param actionRequest the action request
925      * @return the display name for the role
926      * @throws IllegalArgumentException if the action request is null, or the recipient type is not ROLE
927      */
928     public static String getRoleDisplayName(final ActionRequest actionRequest) {
929         if(actionRequest == null) {
930             throw new IllegalArgumentException("actionRequest must be non-null");
931         }
932         if (RecipientType.ROLE != actionRequest.getRecipientType()) {
933             throw new IllegalArgumentException("actionRequest recipient must be a Role");
934         }
935         TransactionTemplate template = new TransactionTemplate(getTransactionManager());
936         return template.execute(new TransactionCallback<String>() {
937             @Override
938             public String doInTransaction(TransactionStatus status) {
939                 String result = "";
940                 Role role = KimApiServiceLocator.getRoleService().getRole(actionRequest.getRoleName());
941 
942                 if (role != null) {
943                     result = role.getName();
944                 } else if (!StringUtils.isBlank(actionRequest.getQualifiedRoleNameLabel())) {
945                     result = actionRequest.getQualifiedRoleNameLabel();
946                 } else {
947                     result = actionRequest.getRoleName();
948                 }
949 
950                 return result;
951             }
952         });
953     }
954 
955     /**
956      * Returns an absolute URL which is a combination of a base part plus path,
957      * or in the case that the path is already an absolute URL, the path alone
958      * @param base the url base path
959      * @param path the path to append to base
960      * @return an absolute URL representing the combination of base+path, or path alone if it is already absolute
961      */
962     public static String toAbsoluteURL(String base, String path) {
963         boolean abs = false;
964         if (StringUtils.isBlank(path)) {
965             path = "";
966         } else {
967             for (String scheme: SCHEMES) {
968                 if (path.startsWith(scheme)) {
969                     abs = true;
970                     break;
971                 }
972             }
973         }
974         if (abs) {
975             return path;
976         }
977         return base + path;
978     }
979 
980 }