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