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