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.krad.util; 017 018 import org.apache.commons.codec.EncoderException; 019 import org.apache.commons.codec.net.URLCodec; 020 import org.apache.commons.lang.StringUtils; 021 import org.kuali.rice.core.api.CoreApiServiceLocator; 022 import org.kuali.rice.core.api.encryption.EncryptionService; 023 import org.kuali.rice.core.api.util.Truth; 024 import org.kuali.rice.core.api.util.type.KualiDecimal; 025 import org.kuali.rice.core.web.format.BooleanFormatter; 026 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 027 import org.kuali.rice.coreservice.framework.parameter.ParameterConstants; 028 import org.kuali.rice.coreservice.framework.parameter.ParameterService; 029 import org.kuali.rice.krad.UserSession; 030 import org.kuali.rice.krad.messages.Message; 031 import org.kuali.rice.krad.messages.MessageService; 032 import org.kuali.rice.krad.service.DataObjectMetaDataService; 033 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 034 import org.kuali.rice.krad.service.KualiModuleService; 035 import org.kuali.rice.krad.service.ModuleService; 036 import org.kuali.rice.krad.uif.UifConstants; 037 import org.kuali.rice.krad.uif.UifParameters; 038 import org.kuali.rice.krad.uif.component.Component; 039 import org.kuali.rice.krad.uif.element.Action; 040 import org.kuali.rice.krad.uif.element.Image; 041 import org.kuali.rice.krad.uif.element.Link; 042 import org.kuali.rice.krad.uif.field.ActionField; 043 import org.kuali.rice.krad.uif.field.DataField; 044 import org.kuali.rice.krad.uif.field.Field; 045 import org.kuali.rice.krad.uif.field.FieldGroup; 046 import org.kuali.rice.krad.uif.field.ImageField; 047 import org.kuali.rice.krad.uif.field.LinkField; 048 import org.kuali.rice.krad.uif.field.MessageField; 049 import org.kuali.rice.krad.uif.field.SpaceField; 050 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 051 import org.kuali.rice.krad.uif.util.ViewModelUtils; 052 import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 053 import org.kuali.rice.krad.uif.view.View; 054 import org.kuali.rice.krad.web.form.UifFormBase; 055 import org.springframework.util.Assert; 056 057 import javax.servlet.ServletRequest; 058 import javax.servlet.http.HttpServletRequest; 059 import java.lang.reflect.Constructor; 060 import java.lang.reflect.Method; 061 import java.security.GeneralSecurityException; 062 import java.text.MessageFormat; 063 import java.text.NumberFormat; 064 import java.util.ArrayList; 065 import java.util.Arrays; 066 import java.util.Collection; 067 import java.util.HashMap; 068 import java.util.Iterator; 069 import java.util.List; 070 import java.util.Map; 071 import java.util.Properties; 072 import java.util.regex.Pattern; 073 074 /** 075 * Miscellaneous Utility Methods 076 * 077 * @author Kuali Rice Team (rice.collab@kuali.org) 078 */ 079 public final class KRADUtils { 080 private static KualiModuleService kualiModuleService; 081 082 private static final KualiDecimal ONE_HUNDRED = new KualiDecimal("100.00"); 083 084 /** 085 * Prevent instantiation of the class. 086 */ 087 private KRADUtils() { 088 throw new UnsupportedOperationException("do not call"); 089 } 090 091 /** 092 * Retrieve the title for a business object class 093 * 094 * <p> 095 * The title is a nicely formatted version of the simple class name. 096 * </p> 097 * 098 * @param clazz business object class 099 * @return title of the business object class 100 */ 101 public final static String getBusinessTitleForClass(Class<? extends Object> clazz) { 102 if (clazz == null) { 103 throw new IllegalArgumentException( 104 "The getBusinessTitleForClass method of KRADUtils requires a non-null class"); 105 } 106 String className = clazz.getSimpleName(); 107 108 StringBuffer label = new StringBuffer(className.substring(0, 1)); 109 for (int i = 1; i < className.length(); i++) { 110 if (Character.isLowerCase(className.charAt(i))) { 111 label.append(className.charAt(i)); 112 } else { 113 label.append(" ").append(className.charAt(i)); 114 } 115 } 116 return label.toString().trim(); 117 } 118 119 /** 120 * Picks off the filename from the full path 121 * 122 * <p> 123 * The different OS path separators are being taken into consideration. 124 * </p> 125 * 126 * @param fullFileNames file name with path 127 * @return file name 128 */ 129 public final static List<String> getFileNameFromPath(List<String> fullFileNames) { 130 List<String> fileNameList = new ArrayList<String>(); 131 132 for (String fullFileName : fullFileNames) { 133 if (StringUtils.contains(fullFileName, "/")) { 134 fileNameList.add(StringUtils.substringAfterLast(fullFileName, "/")); 135 } else { 136 fileNameList.add(StringUtils.substringAfterLast(fullFileName, "\\")); 137 } 138 } 139 140 return fileNameList; 141 } 142 143 /** 144 * Convert the given money amount into an integer string. 145 * 146 * <p> 147 * Since the return string cannot have decimal point, multiplies the amount by 100 so the decimal places 148 * are not lost, for example, 320.15 is converted into 32015. 149 * </p> 150 * 151 * @param decimalNumber decimal number to be converted 152 * @return an integer string of the given money amount through multiplying by 100 and removing the fraction 153 * portion. 154 */ 155 public final static String convertDecimalIntoInteger(KualiDecimal decimalNumber) { 156 KualiDecimal decimalAmount = decimalNumber.multiply(ONE_HUNDRED); 157 NumberFormat formatter = NumberFormat.getIntegerInstance(); 158 String formattedAmount = formatter.format(decimalAmount); 159 160 return StringUtils.replace(formattedAmount, ",", ""); 161 } 162 163 /** 164 * Return the integer value of a string 165 * 166 * <p> 167 * If the string contains a decimal value everything after the decimal point is dropped. 168 * </p> 169 * 170 * @param numberStr string 171 * @return integer representation of the given string 172 */ 173 public static Integer getIntegerValue(String numberStr) { 174 Integer numberInt = null; 175 try { 176 numberInt = new Integer(numberStr); 177 } catch (NumberFormatException nfe) { 178 Double numberDbl = new Double(numberStr); 179 numberInt = new Integer(numberDbl.intValue()); 180 } 181 return numberInt; 182 } 183 184 /** 185 * Attempt to coerce a String attribute value to the given propertyType. If the transformation can't be made, 186 * either because the propertyType is null or because the transformation required exceeds this method's very small 187 * bag of tricks, then null is returned. 188 * 189 * @param propertyType the Class to coerce the attributeValue to 190 * @param attributeValue the String value to coerce 191 * @return an instance of the propertyType class, or null the transformation can't be made. 192 */ 193 public static Object hydrateAttributeValue(Class<?> propertyType, String attributeValue) { 194 Object attributeValueObject = null; 195 if (propertyType != null && attributeValue != null) { 196 if (String.class.equals(propertyType)) { 197 // it's already a String 198 attributeValueObject = attributeValue; 199 } // KULRICE-6808: Kim Role Maintenance - Custom boolean role qualifier values are not being converted properly 200 else if (Boolean.class.equals(propertyType) || Boolean.TYPE.equals(propertyType)) { 201 attributeValueObject = Truth.strToBooleanIgnoreCase(attributeValue); 202 } else { 203 // try to create one with KRADUtils for other misc data types 204 attributeValueObject = KRADUtils.createObject(propertyType, new Class[]{String.class}, 205 new Object[]{attributeValue}); 206 // if that didn't work, we'll get a null back 207 } 208 } 209 return attributeValueObject; 210 } 211 212 public static Object createObject(Class<?> clazz, Class<?>[] argumentClasses, Object[] argumentValues) { 213 if (clazz == null) { 214 return null; 215 } 216 if (argumentClasses.length == 1 && argumentClasses[0] == String.class) { 217 if (argumentValues.length == 1 && argumentValues[0] != null) { 218 if (clazz == String.class) { 219 // this means we're trying to create a String from a String 220 // don't new up Strings, it's a bad idea 221 return argumentValues[0]; 222 } else { 223 // maybe it's a type that supports valueOf? 224 Method valueOfMethod = null; 225 try { 226 valueOfMethod = clazz.getMethod("valueOf", String.class); 227 } catch (NoSuchMethodException e) { 228 // ignored 229 } 230 if (valueOfMethod != null) { 231 try { 232 return valueOfMethod.invoke(null, argumentValues[0]); 233 } catch (Exception e) { 234 // ignored 235 } 236 } 237 } 238 } 239 } 240 try { 241 Constructor<?> constructor = clazz.getConstructor(argumentClasses); 242 return constructor.newInstance(argumentValues); 243 } catch (Exception e) { 244 // ignored 245 } 246 return null; 247 } 248 249 /** 250 * Creates a comma separated String representation of the given list. 251 * 252 * <p> 253 * For example 'a','b',c'. 254 * </p> 255 * 256 * @param list 257 * @return the joined String, empty if the list is null or has no elements 258 */ 259 public static String joinWithQuotes(List<String> list) { 260 if (list == null || list.size() == 0) { 261 return ""; 262 } 263 264 return KRADConstants.SINGLE_QUOTE + 265 StringUtils.join(list.iterator(), KRADConstants.SINGLE_QUOTE + "," + KRADConstants.SINGLE_QUOTE) + 266 KRADConstants.SINGLE_QUOTE; 267 } 268 269 private static KualiModuleService getKualiModuleService() { 270 if (kualiModuleService == null) { 271 kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService(); 272 } 273 return kualiModuleService; 274 } 275 276 /** 277 * TODO this method will probably need to be exposed in a public KRADUtils class as it is used 278 * by several different modules. That will have to wait until ModuleService and KualiModuleService are moved 279 * to core though. 280 */ 281 public static String getNamespaceCode(Class<? extends Object> clazz) { 282 ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(clazz); 283 if (moduleService == null) { 284 return KRADConstants.DEFAULT_NAMESPACE; 285 } 286 return moduleService.getModuleConfiguration().getNamespaceCode(); 287 } 288 289 public static Map<String, String> getNamespaceAndComponentSimpleName(Class<? extends Object> clazz) { 290 Map<String, String> map = new HashMap<String, String>(); 291 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 292 map.put(KRADConstants.COMPONENT_NAME, getComponentSimpleName(clazz)); 293 return map; 294 } 295 296 public static Map<String, String> getNamespaceAndComponentFullName(Class<? extends Object> clazz) { 297 Map<String, String> map = new HashMap<String, String>(); 298 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 299 map.put(KRADConstants.COMPONENT_NAME, getComponentFullName(clazz)); 300 return map; 301 } 302 303 public static Map<String, String> getNamespaceAndActionClass(Class<? extends Object> clazz) { 304 Map<String, String> map = new HashMap<String, String>(); 305 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 306 map.put(KRADConstants.ACTION_CLASS, clazz.getName()); 307 return map; 308 } 309 310 private static String getComponentSimpleName(Class<? extends Object> clazz) { 311 return clazz.getSimpleName(); 312 } 313 314 private static String getComponentFullName(Class<? extends Object> clazz) { 315 return clazz.getName(); 316 } 317 318 /** 319 * Parses a string that is in map format (commas separating map entries, colon separates 320 * map key/value) to a new map instance 321 * 322 * @param parameter - string parameter to parse 323 * @return Map<String, String> instance populated from string parameter 324 */ 325 public static Map<String, String> convertStringParameterToMap(String parameter) { 326 Map<String, String> map = new HashMap<String, String>(); 327 328 if (StringUtils.isNotBlank(parameter)) { 329 if (StringUtils.contains(parameter, ",")) { 330 String[] fieldConversions = StringUtils.split(parameter, ","); 331 332 for (int i = 0; i < fieldConversions.length; i++) { 333 String fieldConversionStr = fieldConversions[i]; 334 if (StringUtils.isNotBlank(fieldConversionStr)) { 335 if (StringUtils.contains(fieldConversionStr, ":")) { 336 String[] fieldConversion = StringUtils.split(fieldConversionStr, ":"); 337 map.put(fieldConversion[0], fieldConversion[1]); 338 } else { 339 map.put(fieldConversionStr, fieldConversionStr); 340 } 341 } 342 } 343 } else if (StringUtils.contains(parameter, ":")) { 344 String[] fieldConversion = StringUtils.split(parameter, ":"); 345 map.put(fieldConversion[0], fieldConversion[1]); 346 } else { 347 map.put(parameter, parameter); 348 } 349 } 350 351 return map; 352 } 353 354 /** 355 * Parses a string that is in list format (commas separating list entries) to a new List instance 356 * 357 * @param parameter - string parameter to parse 358 * @return List<String> instance populated from string parameter 359 */ 360 public static List<String> convertStringParameterToList(String parameter) { 361 List<String> list = new ArrayList<String>(); 362 363 if (StringUtils.isNotBlank(parameter)) { 364 if (StringUtils.contains(parameter, ",")) { 365 String[] parameters = StringUtils.split(parameter, ","); 366 List arraysList = Arrays.asList(parameters); 367 list.addAll(arraysList); 368 } else { 369 list.add(parameter); 370 } 371 } 372 373 return list; 374 } 375 376 /** 377 * Translates characters in the given string like brackets that will cause 378 * problems with binding to characters that do not affect the binding 379 * 380 * @param key - string to translate 381 * @return String translated string 382 */ 383 public static String translateToMapSafeKey(String key) { 384 String safeKey = key; 385 386 safeKey = StringUtils.replace(safeKey, "[", "_"); 387 safeKey = StringUtils.replace(safeKey, "]", "_"); 388 389 return safeKey; 390 } 391 392 /** 393 * Builds a string from the given map by joining each entry with a comma and 394 * each key/value pair with a colon 395 * 396 * @param map - map instance to build string for 397 * @return String of map entries 398 */ 399 public static String buildMapParameterString(Map<String, String> map) { 400 String parameterString = ""; 401 402 for (Map.Entry<String, String> entry : map.entrySet()) { 403 if (StringUtils.isNotBlank(parameterString)) { 404 parameterString += ","; 405 } 406 407 parameterString += entry.getKey() + ":" + entry.getValue(); 408 } 409 410 return parameterString; 411 } 412 413 /** 414 * Parses the given string into a Map by splitting on the comma to get the 415 * map entries and within each entry splitting by colon to get the key/value 416 * pairs 417 * 418 * @param parameterString - string to parse into map 419 * @return Map<String, String> map from string 420 */ 421 public static Map<String, String> getMapFromParameterString(String parameterString) { 422 Map<String, String> map = new HashMap<String, String>(); 423 424 String[] entries = parameterString.split(","); 425 for (int i = 0; i < entries.length; i++) { 426 String[] keyValue = entries[i].split(":"); 427 if (keyValue.length != 2) { 428 throw new RuntimeException("malformed field conversion pair: " + Arrays.toString(keyValue)); 429 } 430 431 map.put(keyValue[0], keyValue[1]); 432 } 433 434 return map; 435 } 436 437 /** 438 * Retrieves value for the given parameter name in the request and attempts to convert to a Boolean using 439 * the <code>BooleanFormatter</code> 440 * 441 * @param request - servlet request containing parameters 442 * @param parameterName - name of parameter to retrieve value for 443 * @return Boolean set to value of parameter, or null if parameter was not found in request 444 */ 445 public static Boolean getRequestParameterAsBoolean(ServletRequest request, String parameterName) { 446 Boolean parameterValue = null; 447 448 String parameterValueStr = request.getParameter(parameterName); 449 if (StringUtils.isNotBlank(parameterValueStr)) { 450 parameterValue = (Boolean) new BooleanFormatter().convertFromPresentationFormat(parameterValueStr); 451 } 452 453 return parameterValue; 454 } 455 456 /** 457 * Translates the given Map of String keys and String array values to a Map 458 * of String key and values. If the String array contains more than one 459 * value, the single string is built by joining the values with the vertical 460 * bar character 461 * 462 * @param requestParameters - Map of request parameters to translate 463 * @return Map<String, String> translated Map 464 */ 465 public static Map<String, String> translateRequestParameterMap(Map<String, String[]> requestParameters) { 466 Map<String, String> parameters = new HashMap<String, String>(); 467 468 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 469 String parameterValue = ""; 470 if (parameter.getValue().length > 1) { 471 parameterValue = StringUtils.join(parameter.getValue(), "|"); 472 } else { 473 parameterValue = parameter.getValue()[0]; 474 } 475 parameters.put(parameter.getKey(), parameterValue); 476 } 477 478 return parameters; 479 } 480 481 /** 482 * Retrieves parameter values from the request that match the requested 483 * names. In addition, based on the object class an authorization check is 484 * performed to determine if the values are secure and should be decrypted. 485 * If true, the value is decrypted before returning 486 * 487 * @param parameterNames - names of the parameters whose values should be retrieved 488 * from the request 489 * @param parentObjectClass - object class that contains the parameter names as properties 490 * and should be consulted for security checks 491 * @param requestParameters - all request parameters to pull from 492 * @return Map<String, String> populated with parameter name/value pairs 493 * pulled from the request 494 */ 495 public static Map<String, String> getParametersFromRequest(List<String> parameterNames, Class<?> parentObjectClass, 496 Map<String, String> requestParameters) { 497 Map<String, String> parameterValues = new HashMap<String, String>(); 498 499 for (Iterator<String> iter = parameterNames.iterator(); iter.hasNext(); ) { 500 String keyPropertyName = iter.next(); 501 502 if (requestParameters.get(keyPropertyName) != null) { 503 String keyValue = requestParameters.get(keyPropertyName); 504 505 // Check if this element was encrypted, if it was decrypt it 506 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService() 507 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(parentObjectClass, keyPropertyName)) { 508 try { 509 keyValue = StringUtils.removeEnd(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX); 510 keyValue = CoreApiServiceLocator.getEncryptionService().decrypt(keyValue); 511 } catch (GeneralSecurityException e) { 512 throw new RuntimeException(e); 513 } 514 } 515 516 parameterValues.put(keyPropertyName, keyValue); 517 } 518 } 519 520 return parameterValues; 521 } 522 523 /** 524 * Builds a Map containing a key/value pair for each property given in the property names list, general 525 * security is checked to determine if the value needs to be encrypted along with applying formatting to 526 * the value 527 * 528 * @param propertyNames - list of property names to get key/value pairs for 529 * @param dataObject - object instance containing the properties for which the values will be pulled 530 * @return Map<String, String> containing entry for each property name with the property name as the map key 531 * and the property value as the value 532 */ 533 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, 534 Object dataObject) { 535 Map<String, String> propertyKeyValues = new HashMap<String, String>(); 536 537 if (dataObject == null) { 538 return propertyKeyValues; 539 } 540 541 // iterate through properties and add a map entry for each 542 for (String propertyName : propertyNames) { 543 Object propertyValue = ObjectPropertyUtils.getPropertyValue(dataObject, propertyName); 544 if (propertyValue == null) { 545 propertyValue = StringUtils.EMPTY; 546 } 547 548 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService() 549 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObject.getClass(), propertyName)) { 550 try { 551 if (CoreApiServiceLocator.getEncryptionService().isEnabled()) { 552 propertyValue = CoreApiServiceLocator.getEncryptionService().encrypt(propertyValue) 553 + EncryptionService.ENCRYPTION_POST_PREFIX; 554 } 555 } catch (GeneralSecurityException e) { 556 throw new RuntimeException("Exception while trying to encrypt value for key/value map.", e); 557 } 558 } 559 560 // TODO: need to apply formatting to return value once util class is ready 561 propertyKeyValues.put(propertyName, propertyValue.toString()); 562 } 563 564 return propertyKeyValues; 565 } 566 567 /** 568 * Utility method to convert a Map to a Properties object 569 * 570 * @param parameters - map to convert 571 * @return Properties object containing all the map entries 572 */ 573 public static Properties convertMapToProperties(Map<String, String> parameters) { 574 Properties properties = new Properties(); 575 576 if (parameters != null) { 577 for (Map.Entry<String, String> parameter : parameters.entrySet()) { 578 properties.put(parameter.getKey(), parameter.getValue()); 579 } 580 } 581 582 return properties; 583 } 584 585 /** 586 * Utility method to convert a Request Parameters Map to a Properties object 587 * 588 * <p> 589 * Multiple values for a parameter are joined together with comma delimiter 590 * </p> 591 * 592 * @param requestParameters - map to convert 593 * @return Properties object containing all the map entries 594 */ 595 public static Properties convertRequestMapToProperties(Map<String, String[]> requestParameters) { 596 Properties properties = new Properties(); 597 598 if (requestParameters != null) { 599 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 600 String[] parameterValue = parameter.getValue(); 601 String parameterValueString = StringUtils.join(parameterValue, ","); 602 603 properties.put(parameter.getKey(), parameterValueString); 604 } 605 } 606 607 return properties; 608 } 609 610 /** 611 * Check if data might be sensitive 612 * 613 * <p> 614 * The sensitivity of the data is checked by matching it against the sensitive data patterns that are specified 615 * in the system parameter table. 616 * </p> 617 * 618 * @param fieldValue data to be checked for sensitivity 619 * @return true if the data matches the sensitive data pattern, false otherwise 620 */ 621 public static boolean containsSensitiveDataPatternMatch(String fieldValue) { 622 if (StringUtils.isBlank(fieldValue)) { 623 return false; 624 } 625 626 ParameterService parameterService = CoreFrameworkServiceLocator.getParameterService(); 627 Collection<String> sensitiveDataPatterns = parameterService.getParameterValuesAsString( 628 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT, 629 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS); 630 631 for (String pattern : sensitiveDataPatterns) { 632 if (Pattern.compile(pattern).matcher(fieldValue).find()) { 633 return true; 634 } 635 } 636 637 return false; 638 } 639 640 /** 641 * Strips out common patterns used in cross side scripting 642 * 643 * @param value string to strip patterns from 644 * @return cleaned string 645 */ 646 public static String stripXSSPatterns(String value) { 647 if (value == null) { 648 return null; 649 } 650 651 // Avoid null characters 652 value = value.replaceAll("", ""); 653 654 // Avoid anything between script tags 655 Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE); 656 value = scriptPattern.matcher(value).replaceAll(""); 657 658 // Avoid anything in a src='...' type of expression 659 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", 660 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 661 value = scriptPattern.matcher(value).replaceAll(""); 662 663 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", 664 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 665 value = scriptPattern.matcher(value).replaceAll(""); 666 667 // Remove any lonesome </script> tag 668 scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE); 669 value = scriptPattern.matcher(value).replaceAll(""); 670 671 // Remove any lonesome <script ...> tag 672 scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 673 value = scriptPattern.matcher(value).replaceAll(""); 674 675 // Avoid eval(...) expressions 676 scriptPattern = Pattern.compile("eval\\((.*?)\\)", 677 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 678 value = scriptPattern.matcher(value).replaceAll(""); 679 680 // Avoid expression(...) expressions 681 scriptPattern = Pattern.compile("expression\\((.*?)\\)", 682 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 683 value = scriptPattern.matcher(value).replaceAll(""); 684 685 // Avoid javascript:... expressions 686 scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); 687 value = scriptPattern.matcher(value).replaceAll(""); 688 689 // Avoid vbscript:... expressions 690 scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); 691 value = scriptPattern.matcher(value).replaceAll(""); 692 693 // Avoid onload= expressions 694 scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 695 value = scriptPattern.matcher(value).replaceAll(""); 696 697 return value; 698 } 699 700 /** 701 * Gets the UserSession object from the HttpServletRequest object's 702 * associated session. 703 * 704 * <p> 705 * In some cases (different threads) the UserSession cannot be retrieved 706 * from GlobalVariables but can still be accessed via the session object 707 * </p> 708 */ 709 public static final UserSession getUserSessionFromRequest(HttpServletRequest request) { 710 return (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY); 711 } 712 713 /** 714 * Check if current deployment is the production environment 715 * 716 * @return true if the deploy environment is production, false otherwise 717 */ 718 public static boolean isProductionEnvironment() { 719 return CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 720 KRADConstants.PROD_ENVIRONMENT_CODE_KEY).equalsIgnoreCase( 721 CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 722 KRADConstants.ENVIRONMENT_KEY)); 723 } 724 725 /** 726 * Gets the message associated with ErrorMessage object passed in, using message service. 727 * The prefix and suffix will be appended to the retrieved message if processPrefixSuffix is true and if those 728 * settings are set on the ErrorMessage passed in. 729 * 730 * @param errorMessage the ErrorMessage object containing the message key(s) 731 * @param processPrefixSuffix if true appends the prefix and suffix to the message if they exist on ErrorMessage 732 * @return the converted/retrieved message 733 */ 734 public static String getMessageText(ErrorMessage errorMessage, boolean processPrefixSuffix) { 735 String message = ""; 736 if (errorMessage != null && errorMessage.getErrorKey() != null) { 737 MessageService messageService = KRADServiceLocatorWeb.getMessageService(); 738 739 // find message by key 740 message = messageService.getMessageText(errorMessage.getNamespaceCode(), errorMessage.getComponentCode(), 741 errorMessage.getErrorKey()); 742 if (message == null) { 743 message = "Intended message with key: " + errorMessage.getErrorKey() + " not found."; 744 } 745 746 if (errorMessage.getMessageParameters() != null && StringUtils.isNotBlank(message)) { 747 message = message.replace("'", "''"); 748 message = MessageFormat.format(message, (Object[]) errorMessage.getMessageParameters()); 749 } 750 751 // add prefix 752 if (StringUtils.isNotBlank(errorMessage.getMessagePrefixKey()) && processPrefixSuffix) { 753 String prefix = messageService.getMessageText(errorMessage.getNamespaceCode(), 754 errorMessage.getComponentCode(), errorMessage.getMessagePrefixKey()); 755 756 if (errorMessage.getMessagePrefixParameters() != null && StringUtils.isNotBlank(prefix)) { 757 prefix = prefix.replace("'", "''"); 758 prefix = MessageFormat.format(prefix, (Object[]) errorMessage.getMessagePrefixParameters()); 759 } 760 761 if (StringUtils.isNotBlank(prefix)) { 762 message = prefix + " " + message; 763 } 764 } 765 766 // add suffix 767 if (StringUtils.isNotBlank(errorMessage.getMessageSuffixKey()) && processPrefixSuffix) { 768 String suffix = messageService.getMessageText(errorMessage.getNamespaceCode(), 769 errorMessage.getComponentCode(), errorMessage.getMessageSuffixKey()); 770 771 if (errorMessage.getMessageSuffixParameters() != null && StringUtils.isNotBlank(suffix)) { 772 suffix = suffix.replace("'", "''"); 773 suffix = MessageFormat.format(suffix, (Object[]) errorMessage.getMessageSuffixParameters()); 774 } 775 776 if (StringUtils.isNotBlank(suffix)) { 777 message = message + " " + suffix; 778 } 779 } 780 } 781 782 return message; 783 } 784 785 /** 786 * Generate the request parameter portion of the url based on the map of key value pairs passed in 787 * 788 * @param requestParameters the request parameters to use in the string 789 * @return a request parameter string starting with "?" with "&" separators, or blank if the mapped passed in is 790 * blank 791 */ 792 public static String getRequestStringFromMap(Map<String, String> requestParameters) { 793 String requestString = ""; 794 795 if (requestParameters.isEmpty()) { 796 return requestString; 797 } 798 799 URLCodec urlCodec = new URLCodec(KRADConstants.DEFAULT_ENCODING); 800 801 for (String key : requestParameters.keySet()) { 802 String value = null; 803 try { 804 value = urlCodec.encode(requestParameters.get(key)); 805 } catch (EncoderException e) { 806 throw new RuntimeException("Unable to encode parameter name or value: " + key + "=" + value, e); 807 } 808 809 if (StringUtils.isNotBlank(requestString)) { 810 requestString = requestString + "&"; 811 } 812 813 requestString = requestString + key + "=" + value; 814 } 815 816 return "?" + requestString; 817 } 818 819 /** 820 * Helper method for building a URL that will invoke the given controller and render the given 821 * KRAD view 822 * 823 * @param baseUrl base url (domain, port) 824 * @param controllerMapping mapping for the controller that should be invoked 825 * @param viewId id for the view that should be rendered 826 * @return url for invoking the view 827 */ 828 public static String buildViewUrl(String baseUrl, String controllerMapping, String viewId) { 829 Assert.hasLength(baseUrl, "base url is null or empty"); 830 Assert.hasLength(controllerMapping, "controller mapping is null or empty"); 831 Assert.hasLength(viewId, "view id is null or empty"); 832 833 StringBuffer url = new StringBuffer(); 834 835 url.append(baseUrl); 836 837 if (!baseUrl.endsWith("/")) { 838 url.append("/"); 839 } 840 841 url.append(controllerMapping); 842 843 url.append("?"); 844 url.append(UifParameters.VIEW_ID); 845 url.append("="); 846 url.append(viewId); 847 848 return url.toString(); 849 } 850 851 /** 852 * Removes parameters from the given properties object that are request specific (useful when manupulating the 853 * current URL to invoke something else) 854 * 855 * @param requestParameters properties instance containing the parameters to clean 856 */ 857 public static void cleanRequestParameters(Properties requestParameters) { 858 requestParameters.remove(UifParameters.SESSION_ID); 859 requestParameters.remove(UifParameters.AJAX_REQUEST); 860 requestParameters.remove(UifParameters.AJAX_RETURN_TYPE); 861 requestParameters.remove(UifParameters.FORM_KEY); 862 requestParameters.remove(UifParameters.JUMP_TO_ID); 863 requestParameters.remove(UifParameters.FOCUS_ID); 864 } 865 866 /** 867 * Get the full url for a request (requestURL + queryString) 868 * 869 * @param request the request 870 * @return the fullUrl 871 */ 872 public static String getFullURL(HttpServletRequest request) { 873 if (request == null) { 874 return null; 875 } 876 877 StringBuffer requestURL = request.getRequestURL(); 878 String queryString = request.getQueryString(); 879 880 if (queryString == null) { 881 return requestURL.toString(); 882 } else { 883 return requestURL.append('?').append(queryString).toString(); 884 } 885 } 886 887 /** 888 * Attempts to generate a unique view title by combining the View's headerText with the title attribute for the 889 * dataObjectClass found through the DataObjectMetaDataService. If the title attribute cannot be found, just the 890 * headerText is returned. 891 * 892 * @param form the form 893 * @param view the view 894 * @return the headerText with the title attribute in parenthesis or just the headerText if it title attribute 895 * cannot be determined 896 */ 897 public static String generateUniqueViewTitle(UifFormBase form, View view) { 898 String title = view.getHeader().getHeaderText(); 899 900 String viewLabelPropertyName = ""; 901 902 Class<?> dataObjectClass; 903 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 904 dataObjectClass = ObjectPropertyUtils.getPropertyType(form, view.getDefaultBindingObjectPath()); 905 } else { 906 dataObjectClass = view.getFormClass(); 907 } 908 909 DataObjectMetaDataService mds = KRADServiceLocatorWeb.getDataObjectMetaDataService(); 910 if (dataObjectClass != null) { 911 viewLabelPropertyName = mds.getTitleAttribute(dataObjectClass); 912 } 913 914 String viewLabelPropertyPath = ""; 915 if (StringUtils.isNotBlank(viewLabelPropertyName)) { 916 // adjust binding prefix 917 if (!viewLabelPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) { 918 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 919 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + viewLabelPropertyName; 920 } 921 } else { 922 viewLabelPropertyPath = StringUtils.removeStart(viewLabelPropertyName, 923 UifConstants.NO_BIND_ADJUST_PREFIX); 924 } 925 } else { 926 // attempt to get title attribute 927 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 928 dataObjectClass = ViewModelUtils.getObjectClassForMetadata(view, form, 929 view.getDefaultBindingObjectPath()); 930 } else { 931 dataObjectClass = view.getFormClass(); 932 } 933 934 if (dataObjectClass != null) { 935 String titleAttribute = mds.getTitleAttribute(dataObjectClass); 936 if (StringUtils.isNotBlank(titleAttribute)) { 937 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + titleAttribute; 938 } 939 } 940 } 941 942 Object viewLabelPropertyValue = null; 943 if (StringUtils.isNotBlank(viewLabelPropertyPath) && ObjectPropertyUtils.isReadableProperty(form, 944 viewLabelPropertyPath)) { 945 viewLabelPropertyValue = ObjectPropertyUtils.getPropertyValue(form, viewLabelPropertyPath); 946 } 947 948 if (viewLabelPropertyValue != null && StringUtils.isNotBlank(viewLabelPropertyValue.toString()) && StringUtils 949 .isNotBlank(title)) { 950 return title + " (" + viewLabelPropertyValue.toString() + ")"; 951 } else { 952 return title; 953 } 954 } 955 956 /** 957 * Attempts to extract a string value out of the field passed in, varies depending on field type 958 * 959 * <p>If the field is a dataField, it will use its propertyName to retrieve a value, otherwise it will try to 960 * retrieve textual content out of various component types. If the field is a FieldGroup, only the first 961 * component's determined value will be used. This function is used for sorting.</p> 962 * 963 * @param model the current model 964 * @param field the field to get a value from 965 * @return the field's String value, false if it cant be determined 966 */ 967 public static String getSimpleFieldValue(Object model, Field field) { 968 if (field == null) { 969 return null; 970 } 971 972 String value = null; 973 // check for what type of field this is 974 if (field instanceof DataField) { 975 String propertyPath = ((DataField) field).getBindingInfo().getBindingPath(); 976 Object valueObject = null; 977 978 if (field.isHidden()) { 979 return ""; 980 } 981 982 // check if readable 983 if (ObjectPropertyUtils.isReadableProperty(model, propertyPath)) { 984 valueObject = ObjectPropertyUtils.getPropertyValue(model, propertyPath); 985 } 986 987 // use object's string value 988 if (valueObject != null && !((DataField) field).isApplyMask()) { 989 value = valueObject.toString(); 990 } else if (valueObject != null && ((DataField) field).isApplyMask()) { 991 value = ((DataField) field).getMaskFormatter().maskValue(valueObject); 992 } 993 } else if (field instanceof ActionField) { 994 value = ((ActionField) field).getActionLabel(); 995 996 // use image alt text if any 997 if (StringUtils.isBlank(value) && ((ActionField) field).getActionImage() != null) { 998 value = ((ActionField) field).getActionImage().getAltText(); 999 } 1000 } else if (field instanceof LinkField) { 1001 value = ((LinkField) field).getLinkText(); 1002 } else if (field instanceof ImageField) { 1003 value = ((ImageField) field).getAltText(); 1004 } else if (field instanceof MessageField && ((MessageField) field).getMessage() != null) { 1005 value = ((MessageField) field).getMessage().getMessageText(); 1006 } else if (field instanceof SpaceField) { 1007 value = ""; 1008 } else if (field instanceof FieldGroup 1009 && ((FieldGroup) field).getGroup() != null 1010 && ((FieldGroup) field).getGroup().getItems() != null 1011 && !((FieldGroup) field).getGroup().getItems().isEmpty()) { 1012 // using first components type for assumed value 1013 Component firstComponent = ((FieldGroup) field).getGroup().getItems().get(0); 1014 1015 // check first component type to extract value 1016 if (firstComponent != null && firstComponent instanceof Field) { 1017 value = getSimpleFieldValue(model, field); 1018 } else if (firstComponent instanceof Action && StringUtils.isNotBlank( 1019 ((Action) firstComponent).getActionLabel())) { 1020 value = ((Action) firstComponent).getActionLabel(); 1021 } else if (firstComponent instanceof Action && ((Action) firstComponent).getActionImage() != null) { 1022 value = ((Action) firstComponent).getActionImage().getAltText(); 1023 } else if (firstComponent instanceof Link) { 1024 value = ((Link) firstComponent).getLinkText(); 1025 } else if (firstComponent instanceof Image) { 1026 value = ((Image) firstComponent).getAltText(); 1027 } else if (firstComponent instanceof org.kuali.rice.krad.uif.element.Message) { 1028 value = ((org.kuali.rice.krad.uif.element.Message) firstComponent).getMessageText(); 1029 } else { 1030 value = null; 1031 } 1032 } 1033 1034 return value; 1035 } 1036 1037 /** 1038 * Helper method to change common characters into HTML attribute safe characters 1039 * 1040 * @param message the string to convert 1041 * @return the converted string with quotes, sing quotes, and slash replaced 1042 */ 1043 public static String convertToHTMLAttributeSafeString(String message) { 1044 if (StringUtils.isBlank(message)) { 1045 return message; 1046 } 1047 1048 if (message.contains("\"")) { 1049 message = message.replace("\"", """); 1050 } 1051 if (message.contains("'")) { 1052 message = message.replace("'", "'"); 1053 } 1054 if (message.contains("\\")) { 1055 message = message.replace("\\", "\"); 1056 } 1057 1058 return message; 1059 } 1060 1061 /** 1062 * Get the rowCss for the line specified, by evaluating the conditionalRowCssClasses map for that row 1063 * 1064 * @param conditionalRowCssClasses the conditionalRowCssClass map, where key is the condition and value is 1065 * the class(es) 1066 * @param lineIndex the line/row index 1067 * @param isOdd true if the row is considered odd 1068 * @param lineContext the lineContext for expressions, pass null if not applicable 1069 * @param expressionEvaluator the expressionEvaluator, pass null if not applicable 1070 * @return row csss class String for the class attribute of this row 1071 */ 1072 public static String generateRowCssClassString(Map<String, String> conditionalRowCssClasses, int lineIndex, 1073 boolean isOdd, Map<String, Object> lineContext, ExpressionEvaluator expressionEvaluator) { 1074 String rowCss = ""; 1075 if (conditionalRowCssClasses == null || conditionalRowCssClasses.isEmpty()) { 1076 return rowCss; 1077 } 1078 1079 for (String cssRule : conditionalRowCssClasses.keySet()) { 1080 if (cssRule.startsWith(UifConstants.EL_PLACEHOLDER_PREFIX) && lineContext != null && 1081 expressionEvaluator != null) { 1082 String outcome = expressionEvaluator.evaluateExpressionTemplate(lineContext, cssRule); 1083 if (outcome != null && Boolean.parseBoolean(outcome)) { 1084 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1085 } 1086 } else if (cssRule.equals(UifConstants.RowSelection.ALL)) { 1087 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1088 } else if (cssRule.equals(UifConstants.RowSelection.EVEN) && !isOdd) { 1089 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1090 } else if (cssRule.equals(UifConstants.RowSelection.ODD) && isOdd) { 1091 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1092 } else if (StringUtils.isNumeric(cssRule) && (lineIndex + 1) == Integer.parseInt(cssRule)) { 1093 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1094 } 1095 } 1096 1097 rowCss = StringUtils.removeStart(rowCss, " "); 1098 1099 return rowCss; 1100 } 1101 }