View Javadoc

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