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 }