001 /**
002 * Copyright 2005-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kns.util;
017
018 import java.io.ByteArrayOutputStream;
019 import java.io.IOException;
020 import java.io.InputStream;
021 import java.io.OutputStream;
022 import java.util.Arrays;
023 import java.util.Collection;
024 import java.util.Enumeration;
025 import java.util.HashMap;
026 import java.util.Hashtable;
027 import java.util.Iterator;
028 import java.util.LinkedHashMap;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.Set;
032 import java.util.regex.Matcher;
033 import java.util.regex.Pattern;
034
035 import javax.servlet.ServletException;
036 import javax.servlet.http.HttpServletRequest;
037 import javax.servlet.http.HttpServletResponse;
038 import javax.servlet.http.HttpSession;
039 import javax.servlet.jsp.PageContext;
040
041 import org.apache.commons.lang.StringEscapeUtils;
042 import org.apache.commons.lang.StringUtils;
043 import org.apache.log4j.Level;
044 import org.apache.log4j.Logger;
045 import org.apache.struts.Globals;
046 import org.apache.struts.action.ActionForm;
047 import org.apache.struts.action.ActionMapping;
048 import org.apache.struts.action.ActionServletWrapper;
049 import org.apache.struts.upload.CommonsMultipartRequestHandler;
050 import org.apache.struts.upload.FormFile;
051 import org.apache.struts.upload.MultipartRequestHandler;
052 import org.apache.struts.upload.MultipartRequestWrapper;
053 import org.kuali.rice.core.api.CoreApiServiceLocator;
054 import org.kuali.rice.core.api.config.property.ConfigurationService;
055 import org.kuali.rice.core.api.util.RiceKeyConstants;
056 import org.kuali.rice.kew.api.action.ActionRequest;
057 import org.kuali.rice.kew.api.action.RecipientType;
058 import org.kuali.rice.kim.api.role.Role;
059 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
060 import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
061 import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
062 import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
063 import org.kuali.rice.kns.service.KNSServiceLocator;
064 import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler;
065 import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
066 import org.kuali.rice.kns.web.struts.form.KualiForm;
067 import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
068 import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase;
069 import org.kuali.rice.kns.web.ui.Field;
070 import org.kuali.rice.kns.web.ui.Row;
071 import org.kuali.rice.kns.web.ui.Section;
072 import org.kuali.rice.krad.datadictionary.AttributeDefinition;
073 import org.kuali.rice.krad.datadictionary.AttributeSecurity;
074 import org.kuali.rice.krad.datadictionary.DataDictionary;
075 import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
076 import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
077 import org.kuali.rice.krad.document.Document;
078 import org.kuali.rice.krad.exception.ValidationException;
079 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
080 import org.kuali.rice.krad.util.GlobalVariables;
081 import org.kuali.rice.krad.util.KRADConstants;
082 import org.kuali.rice.krad.util.MessageMap;
083 import org.kuali.rice.krad.util.ObjectUtils;
084
085 /**
086 * General helper methods for handling requests.
087 */
088 public class WebUtils {
089 private static final Logger LOG = Logger.getLogger(WebUtils.class);
090
091 private static final String IMAGE_COORDINATE_CLICKED_X_EXTENSION = ".x";
092 private static final String IMAGE_COORDINATE_CLICKED_Y_EXTENSION = ".y";
093
094 private static final String APPLICATION_IMAGE_URL_PROPERTY_PREFIX = "application.custom.image.url";
095 private static final String DEFAULT_IMAGE_URL_PROPERTY_NAME = "kr.externalizable.images.url";
096
097 /**
098 * Prefixes indicating an absolute url
099 */
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. <X> and </X>) <li>
762 * [font COLOR], where COLOR represents any valid html color (i.e. color
763 * name or hexcode preceeded by #) will be filtered into <font
764 * color="COLOR"/> <li>[/font] will be filtered into </font> <li>
765 * [table CLASS], where CLASS gives the style class to use, will be filter
766 * into <table class="CLASS"/> <li>[/table] will be filtered into
767 * </table> <li>[td CLASS], where CLASS gives the style class to use,
768 * will be filter into <td class="CLASS"/>
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(" ", " ").replaceAll("( | )", " ");
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 }