001 /** 002 * Copyright 2005-2014 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 java.io.IOException; 019 import java.io.InputStream; 020 import java.lang.annotation.Annotation; 021 import java.lang.reflect.Constructor; 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.net.MalformedURLException; 025 import java.net.URL; 026 import java.security.GeneralSecurityException; 027 import java.text.MessageFormat; 028 import java.text.NumberFormat; 029 import java.text.ParseException; 030 import java.util.ArrayList; 031 import java.util.Arrays; 032 import java.util.Collection; 033 import java.util.Collections; 034 import java.util.HashMap; 035 import java.util.Iterator; 036 import java.util.List; 037 import java.util.Map; 038 import java.util.Properties; 039 import java.util.regex.Pattern; 040 041 import javax.servlet.ServletRequest; 042 import javax.servlet.http.HttpServletRequest; 043 import javax.servlet.http.HttpServletResponse; 044 045 import org.apache.commons.beanutils.PropertyUtils; 046 import org.apache.commons.codec.EncoderException; 047 import org.apache.commons.codec.net.URLCodec; 048 import org.apache.commons.lang.StringUtils; 049 import org.apache.log4j.Logger; 050 import org.kuali.rice.core.api.CoreApiServiceLocator; 051 import org.kuali.rice.core.api.encryption.EncryptionService; 052 import org.kuali.rice.core.api.search.SearchOperator; 053 import org.kuali.rice.core.api.util.RiceKeyConstants; 054 import org.kuali.rice.core.api.util.Truth; 055 import org.kuali.rice.core.api.util.type.KualiDecimal; 056 import org.kuali.rice.core.api.util.type.TypeUtils; 057 import org.kuali.rice.core.web.format.BooleanFormatter; 058 import org.kuali.rice.core.web.format.FormatException; 059 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 060 import org.kuali.rice.coreservice.framework.parameter.ParameterConstants; 061 import org.kuali.rice.coreservice.framework.parameter.ParameterService; 062 import org.kuali.rice.krad.UserSession; 063 import org.kuali.rice.krad.data.KradDataServiceLocator; 064 import org.kuali.rice.krad.messages.MessageService; 065 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 066 import org.kuali.rice.krad.service.KualiModuleService; 067 import org.kuali.rice.krad.service.ModuleService; 068 import org.kuali.rice.krad.uif.UifConstants; 069 import org.kuali.rice.krad.uif.UifParameters; 070 import org.kuali.rice.krad.uif.component.ClientSideState; 071 import org.kuali.rice.krad.uif.component.Component; 072 import org.kuali.rice.krad.uif.element.Action; 073 import org.kuali.rice.krad.uif.element.Image; 074 import org.kuali.rice.krad.uif.element.Link; 075 import org.kuali.rice.krad.uif.field.ActionField; 076 import org.kuali.rice.krad.uif.field.DataField; 077 import org.kuali.rice.krad.uif.field.Field; 078 import org.kuali.rice.krad.uif.field.FieldGroup; 079 import org.kuali.rice.krad.uif.field.ImageField; 080 import org.kuali.rice.krad.uif.field.LinkField; 081 import org.kuali.rice.krad.uif.field.MessageField; 082 import org.kuali.rice.krad.uif.field.SpaceField; 083 import org.kuali.rice.krad.uif.util.CopyUtils; 084 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 085 import org.kuali.rice.krad.uif.util.ViewModelUtils; 086 import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 087 import org.kuali.rice.krad.uif.view.View; 088 import org.kuali.rice.krad.web.form.UifFormBase; 089 import org.springframework.beans.PropertyAccessorUtils; 090 import org.springframework.util.Assert; 091 import org.springframework.util.FileCopyUtils; 092 093 /** 094 * Miscellaneous Utility Methods. 095 * 096 * @author Kuali Rice Team (rice.collab@kuali.org) 097 */ 098 public final class KRADUtils { 099 private static final Logger LOG = Logger.getLogger(KRADUtils.class); 100 101 private static KualiModuleService kualiModuleService; 102 private static final KualiDecimal ONE_HUNDRED = new KualiDecimal("100.00"); 103 104 /** 105 * Prevent instantiation of the class. 106 */ 107 private KRADUtils() { 108 throw new UnsupportedOperationException("do not call"); 109 } 110 111 /** 112 * Retrieve the title for a business object class 113 * 114 * <p> 115 * The title is a nicely formatted version of the simple class name. 116 * </p> 117 * 118 * @param clazz business object class 119 * @return title of the business object class 120 */ 121 public final static String getBusinessTitleForClass(Class<? extends Object> clazz) { 122 if (clazz == null) { 123 throw new IllegalArgumentException( 124 "The getBusinessTitleForClass method of KRADUtils requires a non-null class"); 125 } 126 String className = clazz.getSimpleName(); 127 128 StringBuffer label = new StringBuffer(className.substring(0, 1)); 129 for (int i = 1; i < className.length(); i++) { 130 if (Character.isLowerCase(className.charAt(i))) { 131 label.append(className.charAt(i)); 132 } else { 133 label.append(" ").append(className.charAt(i)); 134 } 135 } 136 return label.toString().trim(); 137 } 138 139 /** 140 * Picks off the filename from the full path 141 * 142 * <p> 143 * The different OS path separators are being taken into consideration. 144 * </p> 145 * 146 * @param fullFileNames file name with path 147 * @return file name 148 */ 149 public final static List<String> getFileNameFromPath(List<String> fullFileNames) { 150 List<String> fileNameList = new ArrayList<String>(); 151 152 for (String fullFileName : fullFileNames) { 153 if (StringUtils.contains(fullFileName, "/")) { 154 fileNameList.add(StringUtils.substringAfterLast(fullFileName, "/")); 155 } else { 156 fileNameList.add(StringUtils.substringAfterLast(fullFileName, "\\")); 157 } 158 } 159 160 return fileNameList; 161 } 162 163 /** 164 * Convert the given money amount into an integer string. 165 * 166 * <p> 167 * Since the return string cannot have decimal point, multiplies the amount by 100 so the decimal places 168 * are not lost, for example, 320.15 is converted into 32015. 169 * </p> 170 * 171 * @param decimalNumber decimal number to be converted 172 * @return an integer string of the given money amount through multiplying by 100 and removing the fraction 173 * portion. 174 */ 175 public final static String convertDecimalIntoInteger(KualiDecimal decimalNumber) { 176 KualiDecimal decimalAmount = decimalNumber.multiply(ONE_HUNDRED); 177 NumberFormat formatter = NumberFormat.getIntegerInstance(); 178 String formattedAmount = formatter.format(decimalAmount); 179 180 return StringUtils.replace(formattedAmount, ",", ""); 181 } 182 183 /** 184 * Return the integer value of a string 185 * 186 * <p> 187 * If the string contains a decimal value everything after the decimal point is dropped. 188 * </p> 189 * 190 * @param numberStr string 191 * @return integer representation of the given string 192 */ 193 public static Integer getIntegerValue(String numberStr) { 194 Integer numberInt = null; 195 try { 196 numberInt = new Integer(numberStr); 197 } catch (NumberFormatException nfe) { 198 Double numberDbl = new Double(numberStr); 199 numberInt = new Integer(numberDbl.intValue()); 200 } 201 return numberInt; 202 } 203 204 /** 205 * Attempt to coerce a String attribute value to the given propertyType. If the transformation can't be made, 206 * either because the propertyType is null or because the transformation required exceeds this method's very small 207 * bag of tricks, then null is returned. 208 * 209 * @param propertyType the Class to coerce the attributeValue to 210 * @param attributeValue the String value to coerce 211 * @return an instance of the propertyType class, or null the transformation can't be made. 212 */ 213 public static Object hydrateAttributeValue(Class<?> propertyType, String attributeValue) { 214 Object attributeValueObject = null; 215 if (propertyType != null && attributeValue != null) { 216 if (String.class.equals(propertyType)) { 217 // it's already a String 218 attributeValueObject = attributeValue; 219 } // KULRICE-6808: Kim Role Maintenance - Custom boolean role qualifier values are not being converted properly 220 else if (Boolean.class.equals(propertyType) || Boolean.TYPE.equals(propertyType)) { 221 attributeValueObject = Truth.strToBooleanIgnoreCase(attributeValue); 222 } else { 223 // try to create one with KRADUtils for other misc data types 224 attributeValueObject = KRADUtils.createObject(propertyType, new Class[]{String.class}, 225 new Object[]{attributeValue}); 226 // if that didn't work, we'll get a null back 227 } 228 } 229 return attributeValueObject; 230 } 231 232 public static Object createObject(Class<?> clazz, Class<?>[] argumentClasses, Object[] argumentValues) { 233 if (clazz == null) { 234 return null; 235 } 236 if (argumentClasses.length == 1 && argumentClasses[0] == String.class) { 237 if (argumentValues.length == 1 && argumentValues[0] != null) { 238 if (clazz == String.class) { 239 // this means we're trying to create a String from a String 240 // don't new up Strings, it's a bad idea 241 return argumentValues[0]; 242 } else { 243 // maybe it's a type that supports valueOf? 244 Method valueOfMethod = null; 245 try { 246 valueOfMethod = clazz.getMethod("valueOf", String.class); 247 } catch (NoSuchMethodException e) { 248 // ignored 249 } 250 if (valueOfMethod != null) { 251 try { 252 return valueOfMethod.invoke(null, argumentValues[0]); 253 } catch (Exception e) { 254 // ignored 255 } 256 } 257 } 258 } 259 } 260 try { 261 Constructor<?> constructor = clazz.getConstructor(argumentClasses); 262 return constructor.newInstance(argumentValues); 263 } catch (Exception e) { 264 // ignored 265 } 266 return null; 267 } 268 269 /** 270 * Creates a comma separated String representation of the given list. 271 * 272 * <p> 273 * For example 'a','b',c'. 274 * </p> 275 * 276 * @param list 277 * @return the joined String, empty if the list is null or has no elements 278 */ 279 public static String joinWithQuotes(List<String> list) { 280 if (list == null || list.size() == 0) { 281 return ""; 282 } 283 284 return KRADConstants.SINGLE_QUOTE + 285 StringUtils.join(list.iterator(), KRADConstants.SINGLE_QUOTE + "," + KRADConstants.SINGLE_QUOTE) + 286 KRADConstants.SINGLE_QUOTE; 287 } 288 289 /** 290 * TODO this method will probably need to be exposed in a public KRADUtils class as it is used 291 * by several different modules. That will have to wait until ModuleService and KualiModuleService are moved 292 * to core though. 293 * 294 * @param clazz class to get a namespace code for 295 * @return namespace code 296 */ 297 public static String getNamespaceCode(Class<? extends Object> clazz) { 298 ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(clazz); 299 if (moduleService == null) { 300 return KRADConstants.DEFAULT_NAMESPACE; 301 } 302 return moduleService.getModuleConfiguration().getNamespaceCode(); 303 } 304 305 public static Map<String, String> getNamespaceAndComponentSimpleName(Class<? extends Object> clazz) { 306 Map<String, String> map = new HashMap<String, String>(); 307 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 308 map.put(KRADConstants.COMPONENT_NAME, getComponentSimpleName(clazz)); 309 return map; 310 } 311 312 public static Map<String, String> getNamespaceAndComponentFullName(Class<? extends Object> clazz) { 313 Map<String, String> map = new HashMap<String, String>(); 314 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 315 map.put(KRADConstants.COMPONENT_NAME, getComponentFullName(clazz)); 316 return map; 317 } 318 319 public static Map<String, String> getNamespaceAndActionClass(Class<? extends Object> clazz) { 320 Map<String, String> map = new HashMap<String, String>(); 321 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 322 map.put(KRADConstants.ACTION_CLASS, clazz.getName()); 323 return map; 324 } 325 326 private static String getComponentSimpleName(Class<? extends Object> clazz) { 327 return clazz.getSimpleName(); 328 } 329 330 private static String getComponentFullName(Class<? extends Object> clazz) { 331 return clazz.getName(); 332 } 333 334 /** 335 * Parses a string that is in map format (commas separating map entries, colon separates 336 * map key/value) to a new map instance 337 * 338 * @param parameter - string parameter to parse 339 * @return Map<String, String> instance populated from string parameter 340 */ 341 public static Map<String, String> convertStringParameterToMap(String parameter) { 342 Map<String, String> map = new HashMap<String, String>(); 343 344 if (StringUtils.isNotBlank(parameter)) { 345 if (StringUtils.contains(parameter, ",")) { 346 String[] fieldConversions = StringUtils.split(parameter, ","); 347 348 for (int i = 0; i < fieldConversions.length; i++) { 349 String fieldConversionStr = fieldConversions[i]; 350 if (StringUtils.isNotBlank(fieldConversionStr)) { 351 if (StringUtils.contains(fieldConversionStr, ":")) { 352 String[] fieldConversion = StringUtils.split(fieldConversionStr, ":"); 353 map.put(fieldConversion[0], fieldConversion[1]); 354 } else { 355 map.put(fieldConversionStr, fieldConversionStr); 356 } 357 } 358 } 359 } else if (StringUtils.contains(parameter, ":")) { 360 String[] fieldConversion = StringUtils.split(parameter, ":"); 361 map.put(fieldConversion[0], fieldConversion[1]); 362 } else { 363 map.put(parameter, parameter); 364 } 365 } 366 367 return map; 368 } 369 370 /** 371 * Parses a string that is in list format (commas separating list entries) to a new List instance 372 * 373 * @param parameter - string parameter to parse 374 * @return List<String> instance populated from string parameter 375 */ 376 public static List<String> convertStringParameterToList(String parameter) { 377 List<String> list = new ArrayList<String>(); 378 379 if (StringUtils.isNotBlank(parameter)) { 380 if (StringUtils.contains(parameter, ",")) { 381 String[] parameters = StringUtils.split(parameter, ","); 382 List arraysList = Arrays.asList(parameters); 383 list.addAll(arraysList); 384 } else { 385 list.add(parameter); 386 } 387 } 388 389 return list; 390 } 391 392 /** 393 * Translates characters in the given string like brackets that will cause 394 * problems with binding to characters that do not affect the binding 395 * 396 * @param key - string to translate 397 * @return String translated string 398 */ 399 public static String translateToMapSafeKey(String key) { 400 String safeKey = key; 401 402 safeKey = StringUtils.replace(safeKey, "[", "_"); 403 safeKey = StringUtils.replace(safeKey, "]", "_"); 404 405 return safeKey; 406 } 407 408 /** 409 * Builds a string from the given map by joining each entry with a comma and 410 * each key/value pair with a colon 411 * 412 * @param map - map instance to build string for 413 * @return String of map entries 414 */ 415 public static String buildMapParameterString(Map<String, String> map) { 416 String parameterString = ""; 417 418 for (Map.Entry<String, String> entry : map.entrySet()) { 419 if (StringUtils.isNotBlank(parameterString)) { 420 parameterString += ","; 421 } 422 423 parameterString += entry.getKey() + ":" + entry.getValue(); 424 } 425 426 return parameterString; 427 } 428 429 /** 430 * Parses the given string into a Map by splitting on the comma to get the 431 * map entries and within each entry splitting by colon to get the key/value 432 * pairs 433 * 434 * @param parameterString - string to parse into map 435 * @return Map<String, String> map from string 436 */ 437 public static Map<String, String> getMapFromParameterString(String parameterString) { 438 Map<String, String> map = new HashMap<String, String>(); 439 440 String[] entries = parameterString.split(","); 441 for (int i = 0; i < entries.length; i++) { 442 String[] keyValue = entries[i].split(":"); 443 if (keyValue.length != 2) { 444 throw new RuntimeException("malformed field conversion pair: " + Arrays.toString(keyValue)); 445 } 446 447 map.put(keyValue[0], keyValue[1]); 448 } 449 450 return map; 451 } 452 453 /** 454 * Retrieves value for the given parameter name in the request and attempts to convert to a Boolean using 455 * the <code>BooleanFormatter</code> 456 * 457 * @param request - servlet request containing parameters 458 * @param parameterName - name of parameter to retrieve value for 459 * @return Boolean set to value of parameter, or null if parameter was not found in request 460 */ 461 public static Boolean getRequestParameterAsBoolean(ServletRequest request, String parameterName) { 462 Boolean parameterValue = null; 463 464 String parameterValueStr = request.getParameter(parameterName); 465 if (StringUtils.isNotBlank(parameterValueStr)) { 466 parameterValue = (Boolean) new BooleanFormatter().convertFromPresentationFormat(parameterValueStr); 467 } 468 469 return parameterValue; 470 } 471 472 /** 473 * Translates the given Map of String keys and String array values to a Map 474 * of String key and values. If the String array contains more than one 475 * value, the single string is built by joining the values with the vertical 476 * bar character 477 * 478 * @param requestParameters - Map of request parameters to translate 479 * @return Map<String, String> translated Map 480 */ 481 public static Map<String, String> translateRequestParameterMap(Map<String, String[]> requestParameters) { 482 Map<String, String> parameters = new HashMap<String, String>(); 483 484 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 485 String parameterValue = ""; 486 if (parameter.getValue().length > 1) { 487 parameterValue = StringUtils.join(parameter.getValue(), "|"); 488 } else { 489 parameterValue = parameter.getValue()[0]; 490 } 491 parameters.put(parameter.getKey(), parameterValue); 492 } 493 494 return parameters; 495 } 496 497 /** 498 * Retrieves parameter values from the request that match the requested 499 * names. In addition, based on the object class an authorization check is 500 * performed to determine if the values are secure and should be decrypted. 501 * If true, the value is decrypted before returning 502 * 503 * @param parameterNames - names of the parameters whose values should be retrieved 504 * from the request 505 * @param parentObjectClass - object class that contains the parameter names as properties 506 * and should be consulted for security checks 507 * @param requestParameters - all request parameters to pull from 508 * @return Map<String, String> populated with parameter name/value pairs 509 * pulled from the request 510 */ 511 public static Map<String, String> getParametersFromRequest(List<String> parameterNames, Class<?> parentObjectClass, 512 Map<String, String> requestParameters) { 513 Map<String, String> parameterValues = new HashMap<String, String>(); 514 515 for (Iterator<String> iter = parameterNames.iterator(); iter.hasNext(); ) { 516 String keyPropertyName = iter.next(); 517 518 if (requestParameters.get(keyPropertyName) != null) { 519 String keyValue = requestParameters.get(keyPropertyName); 520 521 // Check if this element was encrypted, if it was decrypt it 522 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService() 523 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(parentObjectClass, keyPropertyName)) { 524 try { 525 keyValue = StringUtils.removeEnd(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX); 526 keyValue = CoreApiServiceLocator.getEncryptionService().decrypt(keyValue); 527 } catch (GeneralSecurityException e) { 528 throw new RuntimeException(e); 529 } 530 } 531 532 parameterValues.put(keyPropertyName, keyValue); 533 } 534 } 535 536 return parameterValues; 537 } 538 539 /** 540 * Builds a Map containing a key/value pair for each property given in the property names list, general 541 * security is checked to determine if the value needs to be encrypted along with applying formatting to 542 * the value 543 * 544 * @param propertyNames - list of property names to get key/value pairs for 545 * @param dataObject - object instance containing the properties for which the values will be pulled 546 * @return Map<String, String> containing entry for each property name with the property name as the map key 547 * and the property value as the value 548 */ 549 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, 550 Object dataObject) { 551 return getPropertyKeyValuesFromDataObject(propertyNames, Collections.<String>emptyList(), dataObject); 552 } 553 554 /** 555 * Builds a Map containing a key/value pair for each property given in the property names list, general 556 * security is checked to determine if the value needs to be encrypted along with applying formatting to 557 * the value 558 * 559 * @param propertyNames - list of property names to get key/value pairs for 560 * @param securePropertyNames - list of secure property names to match for encryption 561 * @param dataObject - object instance containing the properties for which the values will be pulled 562 * @return Map<String, String> containing entry for each property name with the property name as the map key 563 * and the property value as the value 564 */ 565 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, 566 List<String> securePropertyNames, Object dataObject) { 567 Map<String, String> propertyKeyValues = new HashMap<String, String>(); 568 569 if (dataObject == null) { 570 return propertyKeyValues; 571 } 572 573 // iterate through properties and add a map entry for each 574 for (String propertyName : propertyNames) { 575 Object propertyValue = ObjectPropertyUtils.getPropertyValue(dataObject, propertyName); 576 if (propertyValue == null) { 577 propertyValue = StringUtils.EMPTY; 578 } 579 580 // secure values are not returned 581 if (!isSecure(propertyName, securePropertyNames, dataObject, propertyValue)) { 582 propertyKeyValues.put(propertyName, propertyValue.toString()); 583 } 584 585 } 586 587 return propertyKeyValues; 588 } 589 590 /** 591 * Determines whether a property name should be secured, either based on installed sensitive data patterns, a list 592 * of secure property name patterns, or attributes in the Data Dictionary. 593 * 594 * @param propertyName The property name to check for security 595 * @param securePropertyNames The secure property name patterns to check 596 * @param dataObject The object containing this property 597 * @param propertyValue The value of the property 598 * @return true if the property needs to be secure, false otherwise 599 */ 600 private static boolean isSecure(String propertyName, List<String> securePropertyNames, Object dataObject, 601 Object propertyValue) { 602 if (propertyValue instanceof String && containsSensitiveDataPatternMatch((String) propertyValue)) { 603 return true; 604 } 605 606 if (containsSecurePropertyName(propertyName, securePropertyNames)) { 607 return true; 608 } 609 610 return KRADServiceLocatorWeb.getDataObjectAuthorizationService() 611 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObject.getClass(), propertyName); 612 } 613 614 /** 615 * Helper method to identify if propertyName contains a secure property name element. 616 * Check handles simple or compound names and ignores partial matches. 617 * 618 * @param propertyName property name as a single term or compound term (i.e. items[0].propertyName) 619 * @param securePropertyNames list of secure property names to match 620 * @return true if any of the secure property names are found in the property name, false otherwise 621 */ 622 private static boolean containsSecurePropertyName(String propertyName, List<String> securePropertyNames) { 623 if (securePropertyNames == null) { 624 return false; 625 } 626 627 for (String securePropertyName : securePropertyNames) { 628 // pattern prefix and suffix used to handle compound names and ignore partial name matches 629 if (Pattern.compile("(?:\\.|^)" + Pattern.quote(securePropertyName) + "(?:\\.|\\[|$)").matcher(propertyName) 630 .find()) { 631 return true; 632 } 633 } 634 635 return false; 636 } 637 638 /** 639 * Utility method to convert a Map to a Properties object 640 * 641 * @param parameters - map to convert 642 * @return Properties object containing all the map entries 643 */ 644 public static Properties convertMapToProperties(Map<String, String> parameters) { 645 Properties properties = new Properties(); 646 647 if (parameters != null) { 648 for (Map.Entry<String, String> parameter : parameters.entrySet()) { 649 properties.put(parameter.getKey(), parameter.getValue()); 650 } 651 } 652 653 return properties; 654 } 655 656 /** 657 * Utility method to convert a Request Parameters Map to a Properties object 658 * 659 * <p> 660 * Multiple values for a parameter are joined together with comma delimiter 661 * </p> 662 * 663 * @param requestParameters - map to convert 664 * @return Properties object containing all the map entries 665 */ 666 public static Properties convertRequestMapToProperties(Map<String, String[]> requestParameters) { 667 Properties properties = new Properties(); 668 669 if (requestParameters != null) { 670 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 671 String[] parameterValue = parameter.getValue(); 672 String parameterValueString = StringUtils.join(parameterValue, ","); 673 674 properties.put(parameter.getKey(), parameterValueString); 675 } 676 } 677 678 return properties; 679 } 680 681 /** 682 * This method converts request parameters coming in as String to native types in case of Boolean, Number or 683 * java.sql.Date. 684 * For boolean the conversion is performed only if an @Converter annotation is set on the JPA entity field. 685 * 686 * @param dataObjectClass - business object class 687 * @param parameters - map of request parameters with field values as String for the fields in the dataObjectClass 688 * @return Map <String,Object> converted values 689 */ 690 public static Map<String, Object> coerceRequestParameterTypes(Class<?> dataObjectClass, 691 Map<String, String> parameters) { 692 Map<String, Object> filteredFieldValues = new HashMap<String, Object>(); 693 List<java.lang.reflect.Field> allFields = ObjectPropertyUtils.getAllFields( 694 new ArrayList<java.lang.reflect.Field>(), dataObjectClass, Object.class); 695 696 for (String fieldName : parameters.keySet()) { 697 Class<?> propertyType = ObjectPropertyUtils.getPropertyType(dataObjectClass, fieldName); 698 699 String strValue = parameters.get(fieldName); 700 701 if (TypeUtils.isBooleanClass(propertyType) && isConvertAnnotationPresent(allFields, fieldName)) { 702 filteredFieldValues.put(fieldName, Truth.strToBooleanIgnoreCase(strValue)); 703 } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) { 704 try { 705 filteredFieldValues.put(fieldName, hydrateAttributeValue(propertyType, strValue)); 706 } catch (Exception nfe) { 707 GlobalVariables.getMessageMap().putError("parameters[" + fieldName + "]", 708 RiceKeyConstants.ERROR_NUMBER, strValue); 709 throw new RuntimeException("Could not parse property value into Number for " + fieldName); 710 } 711 } else if (TypeUtils.isTemporalClass(propertyType)) { 712 try { 713 filteredFieldValues.put(fieldName, CoreApiServiceLocator.getDateTimeService().convertToSqlDate( 714 strValue)); 715 } catch (ParseException pe) { 716 GlobalVariables.getMessageMap().putError("parameters[" + fieldName + "]", 717 RiceKeyConstants.ERROR_DATE_TIME, strValue); 718 throw new RuntimeException("Could not parse property value into java.sql.Date for " + fieldName); 719 } 720 } 721 722 // If value not converted set the value from parameters 723 if (filteredFieldValues.get(fieldName) == null) { 724 filteredFieldValues.put(fieldName, parameters.get(fieldName)); 725 } 726 727 } 728 return filteredFieldValues; 729 730 } 731 732 /** 733 * Checks to see if the specified field from the list of allFields has the @Convert annotation set on it 734 * 735 * @param allFields List of all fields on the entity 736 * @param fieldName Field name to check for @Convert annotation 737 * @return true if annotation is present else false 738 */ 739 private static boolean isConvertAnnotationPresent(List<java.lang.reflect.Field> allFields, String fieldName) { 740 //Check if there is a @Convert annotation on the field 741 boolean convertAnnotationFound = false; 742 for (java.lang.reflect.Field f : allFields) { 743 if (f.getName().equalsIgnoreCase(fieldName)) { 744 if (f.getAnnotation(javax.persistence.Convert.class) != null) { 745 convertAnnotationFound = true; 746 } 747 break; 748 } 749 } 750 751 return convertAnnotationFound; 752 } 753 754 /** 755 * Check if data might be sensitive 756 * 757 * <p> 758 * The sensitivity of the data is checked by matching it against the sensitive data patterns that are specified 759 * in the system parameter table. 760 * </p> 761 * 762 * @param fieldValue data to be checked for sensitivity 763 * @return true if the data matches the sensitive data pattern, false otherwise 764 */ 765 public static boolean containsSensitiveDataPatternMatch(String fieldValue) { 766 if (StringUtils.isBlank(fieldValue)) { 767 return false; 768 } 769 770 ParameterService parameterService = CoreFrameworkServiceLocator.getParameterService(); 771 Collection<String> sensitiveDataPatterns = parameterService.getParameterValuesAsString( 772 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT, 773 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS); 774 775 for (String pattern : sensitiveDataPatterns) { 776 if (Pattern.compile(pattern).matcher(fieldValue).find()) { 777 return true; 778 } 779 } 780 781 return false; 782 } 783 784 /** 785 * Strips out common patterns used in cross side scripting. 786 * 787 * @param values string array to strip patterns from 788 * @return cleaned string array 789 */ 790 public static String[] stripXSSPatterns(String[] values) { 791 ArrayList<String> strippedValues = new ArrayList<String>(); 792 793 for (String value : values) { 794 strippedValues.add(stripXSSPatterns(value)); 795 } 796 797 return strippedValues.toArray(new String[]{}); 798 } 799 800 /** 801 * Strips out common patterns used in cross side scripting. 802 * 803 * @param value string to strip patterns from 804 * @return cleaned string 805 */ 806 public static String stripXSSPatterns(String value) { 807 if (value == null) { 808 return null; 809 } 810 811 // Avoid null characters 812 value = value.replaceAll("", ""); 813 814 // Avoid anything between script tags 815 Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE); 816 value = scriptPattern.matcher(value).replaceAll(""); 817 818 // Avoid anything in a src='...' type of expression 819 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", 820 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 821 value = scriptPattern.matcher(value).replaceAll(""); 822 823 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", 824 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 825 value = scriptPattern.matcher(value).replaceAll(""); 826 827 // Remove any lonesome </script> tag 828 scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE); 829 value = scriptPattern.matcher(value).replaceAll(""); 830 831 // Remove any lonesome <script ...> tag 832 scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 833 value = scriptPattern.matcher(value).replaceAll(""); 834 835 // Avoid eval(...) expressions 836 scriptPattern = Pattern.compile("eval\\((.*?)\\)", 837 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 838 value = scriptPattern.matcher(value).replaceAll(""); 839 840 // Avoid expression(...) expressions 841 scriptPattern = Pattern.compile("expression\\((.*?)\\)", 842 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 843 value = scriptPattern.matcher(value).replaceAll(""); 844 845 // Avoid javascript:... expressions 846 scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); 847 value = scriptPattern.matcher(value).replaceAll(""); 848 849 // Avoid vbscript:... expressions 850 scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); 851 value = scriptPattern.matcher(value).replaceAll(""); 852 853 // Avoid onload= expressions 854 scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 855 value = scriptPattern.matcher(value).replaceAll(""); 856 857 return value; 858 } 859 860 /** 861 * Gets the UserSession object from the HttpServletRequest object's 862 * associated session. 863 * 864 * <p> 865 * In some cases (different threads) the UserSession cannot be retrieved 866 * from GlobalVariables but can still be accessed via the session object 867 * </p> 868 * 869 * @param request servlet request 870 * @return user session found in the request's servlet session 871 */ 872 public static final UserSession getUserSessionFromRequest(HttpServletRequest request) { 873 return (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY); 874 } 875 876 /** 877 * Check if current deployment is the production environment 878 * 879 * @return true if the deploy environment is production, false otherwise 880 */ 881 public static boolean isProductionEnvironment() { 882 return CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 883 KRADConstants.PROD_ENVIRONMENT_CODE_KEY).equalsIgnoreCase( 884 CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 885 KRADConstants.ENVIRONMENT_KEY)); 886 } 887 888 /** 889 * Gets the message associated with ErrorMessage object passed in, using message service. 890 * The prefix and suffix will be appended to the retrieved message if processPrefixSuffix is true and if those 891 * settings are set on the ErrorMessage passed in. 892 * 893 * @param errorMessage the ErrorMessage object containing the message key(s) 894 * @param processPrefixSuffix if true appends the prefix and suffix to the message if they exist on ErrorMessage 895 * @return the converted/retrieved message 896 */ 897 public static String getMessageText(ErrorMessage errorMessage, boolean processPrefixSuffix) { 898 String message = ""; 899 if (errorMessage != null && errorMessage.getErrorKey() != null) { 900 MessageService messageService = KRADServiceLocatorWeb.getMessageService(); 901 902 // find message by key 903 message = messageService.getMessageText(errorMessage.getNamespaceCode(), errorMessage.getComponentCode(), 904 errorMessage.getErrorKey()); 905 if (message == null) { 906 message = "Intended message with key: " + errorMessage.getErrorKey() + " not found."; 907 } 908 909 if (errorMessage.getMessageParameters() != null && StringUtils.isNotBlank(message)) { 910 message = message.replace("'", "''"); 911 message = MessageFormat.format(message, (Object[]) errorMessage.getMessageParameters()); 912 } 913 914 // add prefix 915 if (StringUtils.isNotBlank(errorMessage.getMessagePrefixKey()) && processPrefixSuffix) { 916 String prefix = messageService.getMessageText(errorMessage.getNamespaceCode(), 917 errorMessage.getComponentCode(), errorMessage.getMessagePrefixKey()); 918 919 if (errorMessage.getMessagePrefixParameters() != null && StringUtils.isNotBlank(prefix)) { 920 prefix = prefix.replace("'", "''"); 921 prefix = MessageFormat.format(prefix, (Object[]) errorMessage.getMessagePrefixParameters()); 922 } 923 924 if (StringUtils.isNotBlank(prefix)) { 925 message = prefix + " " + message; 926 } 927 } 928 929 // add suffix 930 if (StringUtils.isNotBlank(errorMessage.getMessageSuffixKey()) && processPrefixSuffix) { 931 String suffix = messageService.getMessageText(errorMessage.getNamespaceCode(), 932 errorMessage.getComponentCode(), errorMessage.getMessageSuffixKey()); 933 934 if (errorMessage.getMessageSuffixParameters() != null && StringUtils.isNotBlank(suffix)) { 935 suffix = suffix.replace("'", "''"); 936 suffix = MessageFormat.format(suffix, (Object[]) errorMessage.getMessageSuffixParameters()); 937 } 938 939 if (StringUtils.isNotBlank(suffix)) { 940 message = message + " " + suffix; 941 } 942 } 943 } 944 945 return message; 946 } 947 948 /** 949 * Logs the error messages if any in the message map 950 */ 951 public static void logErrors() { 952 953 if (!GlobalVariables.getMessageMap().hasErrors()) { 954 return; 955 } 956 957 for (Iterator<Map.Entry<String, List<ErrorMessage>>> i = 958 GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) { 959 Map.Entry<String, List<ErrorMessage>> e = i.next(); 960 961 StringBuffer logMessage = buildMessage(e); 962 LOG.error(logMessage); 963 } 964 } 965 966 /** 967 * Builds the message for a given entry in the messageMap. The entry could have multiple messages for a given key. 968 * The messages are appended separated by a ; 969 * 970 * @param e Map entry of property and errors for that property 971 * @return logMessage 972 */ 973 private static StringBuffer buildMessage(Map.Entry<String, List<ErrorMessage>> e) { 974 StringBuffer logMessage = new StringBuffer(); 975 logMessage.append("[" + e.getKey() + "] "); 976 boolean first = true; 977 978 List<ErrorMessage> errorList = e.getValue(); 979 for (Iterator<ErrorMessage> j = errorList.iterator(); j.hasNext(); ) { 980 ErrorMessage em = j.next(); 981 982 // if its the first message for the key 983 if (first) { 984 first = false; 985 } else { 986 logMessage.append(";"); 987 } 988 logMessage.append(em); 989 } 990 return logMessage; 991 } 992 993 /** 994 * Generate the request parameter portion of the url based on the map of key value pairs passed in 995 * 996 * @param requestParameters the request parameters to use in the string 997 * @return a request parameter string starting with "?" with "&" separators, or blank if the mapped passed in is 998 * blank 999 */ 1000 public static String getRequestStringFromMap(Map<String, String> requestParameters) { 1001 String requestString = ""; 1002 1003 if (requestParameters.isEmpty()) { 1004 return requestString; 1005 } 1006 1007 URLCodec urlCodec = new URLCodec(KRADConstants.DEFAULT_ENCODING); 1008 1009 for (String key : requestParameters.keySet()) { 1010 String value = null; 1011 try { 1012 value = urlCodec.encode(requestParameters.get(key)); 1013 } catch (EncoderException e) { 1014 throw new RuntimeException("Unable to encode parameter name or value: " + key + "=" + value, e); 1015 } 1016 1017 if (StringUtils.isNotBlank(requestString)) { 1018 requestString = requestString + "&"; 1019 } 1020 1021 requestString = requestString + key + "=" + value; 1022 } 1023 1024 return "?" + requestString; 1025 } 1026 1027 /** 1028 * Adds the header and content of an attachment to the response. 1029 * 1030 * @param response HttpServletResponse instance 1031 * @param contentType the content type of the attachment 1032 * @param inputStream the content of the attachment 1033 * @param fileName the file name of the attachment 1034 * @param fileSize the size of the attachment 1035 * @throws IOException if attachment to the results fails due to an IO error 1036 */ 1037 public static void addAttachmentToResponse(HttpServletResponse response, InputStream inputStream, 1038 String contentType, String fileName, long fileSize) throws IOException { 1039 1040 // If there are quotes in the name, we should replace them to avoid issues. 1041 // The filename will be wrapped with quotes below when it is set in the header 1042 String updateFileName; 1043 if (fileName.contains("\"")) { 1044 updateFileName = fileName.replaceAll("\"", ""); 1045 } else { 1046 updateFileName = fileName; 1047 } 1048 1049 // set response 1050 response.setContentType(contentType); 1051 response.setContentLength(org.springframework.util.NumberUtils.convertNumberToTargetClass(fileSize, 1052 Integer.class)); 1053 response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\""); 1054 response.setHeader("Expires", "0"); 1055 response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); 1056 response.setHeader("Pragma", "public"); 1057 1058 // Copy the input stream to the response 1059 FileCopyUtils.copy(inputStream, response.getOutputStream()); 1060 } 1061 1062 /** 1063 * Helper method for building a URL that will invoke the given controller and render the given 1064 * KRAD view 1065 * 1066 * @param baseUrl base url (domain, port) 1067 * @param controllerMapping mapping for the controller that should be invoked 1068 * @param viewId id for the view that should be rendered 1069 * @return url for invoking the view 1070 */ 1071 public static String buildViewUrl(String baseUrl, String controllerMapping, String viewId) { 1072 Assert.hasLength(baseUrl, "base url is null or empty"); 1073 Assert.hasLength(controllerMapping, "controller mapping is null or empty"); 1074 Assert.hasLength(viewId, "view id is null or empty"); 1075 1076 StringBuffer url = new StringBuffer(); 1077 1078 url.append(baseUrl); 1079 1080 if (!baseUrl.endsWith("/")) { 1081 url.append("/"); 1082 } 1083 1084 url.append(controllerMapping); 1085 1086 url.append("?"); 1087 url.append(UifParameters.VIEW_ID); 1088 url.append("="); 1089 url.append(viewId); 1090 1091 return url.toString(); 1092 } 1093 1094 /** 1095 * Removes parameters from the given properties object that are request specific (useful when manupulating the 1096 * current URL to invoke something else) 1097 * 1098 * @param requestParameters properties instance containing the parameters to clean 1099 */ 1100 public static void cleanRequestParameters(Properties requestParameters) { 1101 requestParameters.remove(UifParameters.SESSION_ID); 1102 requestParameters.remove(UifParameters.AJAX_REQUEST); 1103 requestParameters.remove(UifParameters.AJAX_RETURN_TYPE); 1104 requestParameters.remove(UifParameters.FORM_KEY); 1105 requestParameters.remove(UifParameters.JUMP_TO_ID); 1106 requestParameters.remove(UifParameters.FOCUS_ID); 1107 } 1108 1109 /** 1110 * Get the full url for a request (requestURL + queryString) 1111 * 1112 * @param request the request 1113 * @return the fullUrl 1114 */ 1115 public static String getFullURL(HttpServletRequest request) { 1116 if (request == null) { 1117 return null; 1118 } 1119 1120 StringBuffer requestURL = request.getRequestURL(); 1121 String queryString = request.getQueryString(); 1122 1123 if (queryString == null) { 1124 return requestURL.toString(); 1125 } else { 1126 return requestURL.append('?').append(queryString).toString(); 1127 } 1128 } 1129 1130 /** 1131 * Determines whether two URLs point at the same domain. 1132 * 1133 * @param firstDomain first URL string to compare 1134 * @param secondDomain second URL string to compare 1135 * @return true if the domains are different, false otherwise 1136 */ 1137 public static boolean areDifferentDomains(String firstDomain, String secondDomain) { 1138 try { 1139 URL urlOne = new URL(firstDomain.toLowerCase()); 1140 URL urlTwo = new URL(secondDomain.toLowerCase()); 1141 1142 if (urlOne.getHost().equals(urlTwo.getHost())) { 1143 LOG.debug("Hosts " 1144 + urlOne.getHost() 1145 + " of domains " 1146 + firstDomain 1147 + " and " 1148 + secondDomain 1149 + " were determined to be equal"); 1150 1151 return false; 1152 } else { 1153 LOG.debug("Hosts " 1154 + urlOne.getHost() 1155 + " of domains " 1156 + firstDomain 1157 + " and " 1158 + secondDomain 1159 + " are not equal"); 1160 1161 return true; 1162 } 1163 } catch (MalformedURLException mue) { 1164 LOG.error("Unable to successfully compare domains " + firstDomain + " and " + secondDomain); 1165 } 1166 1167 return true; 1168 } 1169 1170 /** 1171 * Attempts to generate a unique view title by combining the View's headerText with the title attribute for the 1172 * dataObjectClass found through the DataObjectMetaDataService. If the title attribute cannot be found, just the 1173 * headerText is returned. 1174 * 1175 * @param form the form 1176 * @param view the view 1177 * @return the headerText with the title attribute in parenthesis or just the headerText if it title attribute 1178 * cannot be determined 1179 */ 1180 public static String generateUniqueViewTitle(UifFormBase form, View view) { 1181 String title = view.getHeader().getHeaderText(); 1182 1183 String viewLabelPropertyName = ""; 1184 1185 Class<?> dataObjectClass; 1186 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 1187 dataObjectClass = ObjectPropertyUtils.getPropertyType(form, view.getDefaultBindingObjectPath()); 1188 } else { 1189 dataObjectClass = view.getFormClass(); 1190 } 1191 1192 if (dataObjectClass != null) { 1193 viewLabelPropertyName = KRADServiceLocatorWeb.getLegacyDataAdapter().getTitleAttribute(dataObjectClass); 1194 } 1195 1196 String viewLabelPropertyPath = ""; 1197 if (StringUtils.isNotBlank(viewLabelPropertyName)) { 1198 // adjust binding prefix 1199 if (!viewLabelPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) { 1200 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 1201 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + viewLabelPropertyName; 1202 } 1203 } else { 1204 viewLabelPropertyPath = StringUtils.removeStart(viewLabelPropertyName, 1205 UifConstants.NO_BIND_ADJUST_PREFIX); 1206 } 1207 } else { 1208 // attempt to get title attribute 1209 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 1210 dataObjectClass = ViewModelUtils.getObjectClassForMetadata(view, form, 1211 view.getDefaultBindingObjectPath()); 1212 } else { 1213 dataObjectClass = view.getFormClass(); 1214 } 1215 1216 if (dataObjectClass != null) { 1217 String titleAttribute = KRADServiceLocatorWeb.getLegacyDataAdapter().getTitleAttribute(dataObjectClass); 1218 if (StringUtils.isNotBlank(titleAttribute)) { 1219 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + titleAttribute; 1220 } 1221 } 1222 } 1223 1224 Object viewLabelPropertyValue = null; 1225 if (StringUtils.isNotBlank(viewLabelPropertyPath) && ObjectPropertyUtils.isReadableProperty(form, 1226 viewLabelPropertyPath)) { 1227 viewLabelPropertyValue = ObjectPropertyUtils.getPropertyValue(form, viewLabelPropertyPath); 1228 } 1229 1230 if (viewLabelPropertyValue != null && StringUtils.isNotBlank(viewLabelPropertyValue.toString()) && StringUtils 1231 .isNotBlank(title)) { 1232 return title + " (" + viewLabelPropertyValue.toString() + ")"; 1233 } else { 1234 return title; 1235 } 1236 } 1237 1238 /** 1239 * Helper method for building title text for an element and a map of key/value pairs, 1240 * 1241 * <p> 1242 * Each key of the key value map is assumed to be an attribute for the given element class. The label is then 1243 * retrieved for the attribute from the data dictionary and used in the title (instead of the key) 1244 * </p> 1245 * 1246 * @param prependText text to prepend to the title 1247 * @param element element class the title is being generated for, used as the parent for getting the key labels 1248 * @param keyValueMap map of key value pairs to add to the title text 1249 * @return title string 1250 */ 1251 public static String buildAttributeTitleString(String prependText, Class<?> element, 1252 Map<String, String> keyValueMap) { 1253 StringBuffer titleText = new StringBuffer(prependText); 1254 1255 for (String key : keyValueMap.keySet()) { 1256 String fieldVal = keyValueMap.get(key).toString(); 1257 1258 titleText.append(" " 1259 + KRADServiceLocatorWeb.getDataDictionaryService().getAttributeLabel(element, key) 1260 + "=" 1261 + fieldVal.toString()); 1262 } 1263 1264 return titleText.toString(); 1265 } 1266 1267 /** 1268 * Attempts to extract a string value out of the field passed in, varies depending on field type 1269 * 1270 * <p>If the field is a dataField, it will use its propertyName to retrieve a value, otherwise it will try to 1271 * retrieve textual content out of various component types. If the field is a FieldGroup, only the first 1272 * component's determined value will be used. This function is used for sorting.</p> 1273 * 1274 * @param model the current model 1275 * @param field the field to get a value from 1276 * @return the field's String value, false if it cant be determined 1277 */ 1278 public static String getSimpleFieldValue(Object model, Field field) { 1279 if (field == null) { 1280 return null; 1281 } 1282 1283 String value = null; 1284 // check for what type of field this is 1285 if (field instanceof DataField) { 1286 String propertyPath = ((DataField) field).getBindingInfo().getBindingPath(); 1287 Object valueObject = null; 1288 1289 if (field.isHidden()) { 1290 return ""; 1291 } 1292 1293 // check if readable 1294 if (ObjectPropertyUtils.isReadableProperty(model, propertyPath)) { 1295 valueObject = ObjectPropertyUtils.getPropertyValue(model, propertyPath); 1296 } 1297 1298 // use object's string value 1299 if (valueObject != null && !((DataField) field).isApplyMask()) { 1300 value = valueObject.toString(); 1301 } else if (valueObject != null && ((DataField) field).isApplyMask()) { 1302 value = ((DataField) field).getMaskFormatter().maskValue(valueObject); 1303 } 1304 } else if (field instanceof ActionField) { 1305 value = ((ActionField) field).getActionLabel(); 1306 1307 // use image alt text if any 1308 if (StringUtils.isBlank(value) && ((ActionField) field).getActionImage() != null) { 1309 value = ((ActionField) field).getActionImage().getAltText(); 1310 } 1311 } else if (field instanceof LinkField) { 1312 value = ((LinkField) field).getLinkText(); 1313 } else if (field instanceof ImageField) { 1314 value = ((ImageField) field).getAltText(); 1315 } else if (field instanceof MessageField && ((MessageField) field).getMessage() != null) { 1316 value = ((MessageField) field).getMessage().getMessageText(); 1317 } else if (field instanceof SpaceField) { 1318 value = ""; 1319 } else if (field instanceof FieldGroup 1320 && ((FieldGroup) field).getGroup() != null 1321 && ((FieldGroup) field).getGroup().getItems() != null 1322 && !((FieldGroup) field).getGroup().getItems().isEmpty()) { 1323 // using first components type for assumed value 1324 Component firstComponent = ((FieldGroup) field).getGroup().getItems().get(0); 1325 1326 // check first component type to extract value 1327 if (firstComponent != null && firstComponent instanceof Field) { 1328 value = getSimpleFieldValue(model, (Field) firstComponent); 1329 } else if (firstComponent instanceof Action && StringUtils.isNotBlank( 1330 ((Action) firstComponent).getActionLabel())) { 1331 value = ((Action) firstComponent).getActionLabel(); 1332 } else if (firstComponent instanceof Action && ((Action) firstComponent).getActionImage() != null) { 1333 value = ((Action) firstComponent).getActionImage().getAltText(); 1334 } else if (firstComponent instanceof Link) { 1335 value = ((Link) firstComponent).getLinkText(); 1336 } else if (firstComponent instanceof Image) { 1337 value = ((Image) firstComponent).getAltText(); 1338 } else if (firstComponent instanceof org.kuali.rice.krad.uif.element.Message) { 1339 value = ((org.kuali.rice.krad.uif.element.Message) firstComponent).getMessageText(); 1340 } else { 1341 value = null; 1342 } 1343 } 1344 1345 return value; 1346 } 1347 1348 /** 1349 * Helper method to change common characters into HTML attribute safe characters 1350 * 1351 * @param message the string to convert 1352 * @return the converted string with quotes, sing quotes, and slash replaced 1353 */ 1354 public static String convertToHTMLAttributeSafeString(String message) { 1355 if (StringUtils.isBlank(message)) { 1356 return message; 1357 } 1358 1359 if (message.contains("\"")) { 1360 message = message.replace("\"", """); 1361 } 1362 if (message.contains("'")) { 1363 message = message.replace("'", "'"); 1364 } 1365 if (message.contains("\\")) { 1366 message = message.replace("\\", "\"); 1367 } 1368 1369 return message; 1370 } 1371 1372 /** 1373 * Get the rowCss for the line specified, by evaluating the conditionalRowCssClasses map for that row 1374 * 1375 * @param conditionalRowCssClasses the conditionalRowCssClass map, where key is the condition and value is 1376 * the class(es) 1377 * @param lineIndex the line/row index 1378 * @param isOdd true if the row is considered odd 1379 * @param lineContext the lineContext for expressions, pass null if not applicable 1380 * @param expressionEvaluator the expressionEvaluator, pass null if not applicable 1381 * @return row csss class String for the class attribute of this row 1382 */ 1383 public static String generateRowCssClassString(Map<String, String> conditionalRowCssClasses, int lineIndex, 1384 boolean isOdd, Map<String, Object> lineContext, ExpressionEvaluator expressionEvaluator) { 1385 String rowCss = ""; 1386 if (conditionalRowCssClasses == null || conditionalRowCssClasses.isEmpty()) { 1387 return rowCss; 1388 } 1389 1390 for (String cssRule : conditionalRowCssClasses.keySet()) { 1391 if (cssRule.startsWith(UifConstants.EL_PLACEHOLDER_PREFIX) && lineContext != null && 1392 expressionEvaluator != null) { 1393 String outcome = expressionEvaluator.evaluateExpressionTemplate(lineContext, cssRule); 1394 if (outcome != null && Boolean.parseBoolean(outcome)) { 1395 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1396 } 1397 } else if (cssRule.equals(UifConstants.RowSelection.ALL)) { 1398 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1399 } else if (cssRule.equals(UifConstants.RowSelection.EVEN) && !isOdd) { 1400 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1401 } else if (cssRule.equals(UifConstants.RowSelection.ODD) && isOdd) { 1402 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1403 } else if (StringUtils.isNumeric(cssRule) && (lineIndex + 1) == Integer.parseInt(cssRule)) { 1404 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1405 } 1406 } 1407 1408 rowCss = StringUtils.removeStart(rowCss, " "); 1409 1410 return rowCss; 1411 } 1412 1413 /** 1414 * LegacyCase - This method simply uses PojoPropertyUtilsBean logic to get the Class of a Class property. 1415 * This method does not have any of the logic needed to obtain the Class of an element of a Collection specified in 1416 * the DataDictionary. 1417 * 1418 * @param object An instance of the Class of which we're trying to get the property Class. 1419 * @param propertyName The name of the property. 1420 * @return property type 1421 * @throws IllegalAccessException 1422 * @throws NoSuchMethodException 1423 * @throws InvocationTargetException 1424 */ 1425 static public Class easyGetPropertyType(Object object, 1426 String propertyName) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { 1427 if (LegacyUtils.useLegacyForObject(object)) { 1428 return PropertyUtils.getPropertyType(object, propertyName); 1429 } 1430 return KradDataServiceLocator.getDataObjectService().wrap(object).getPropertyType(propertyName); 1431 } 1432 1433 /** 1434 * Sets the property of an object with the given value. Converts using the formatter of the type for the property. 1435 * Note: propertyType does not need passed, is found by util method. 1436 * 1437 * @param bo business object 1438 * @param propertyName property name 1439 * @param propertyValue propery value 1440 * @throws IllegalAccessException 1441 * @throws InvocationTargetException 1442 * @throws NoSuchMethodException 1443 */ 1444 public static void setObjectProperty(Object bo, String propertyName, 1445 Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1446 Class propertyType = easyGetPropertyType(bo, propertyName); 1447 setObjectProperty(bo, propertyName, propertyType, propertyValue); 1448 1449 } 1450 1451 /** 1452 * Sets the property of an object with the given value. Converts using the formatter of the given type if one is 1453 * found. 1454 * 1455 * @param bo 1456 * @param propertyName 1457 * @param propertyType 1458 * @param propertyValue 1459 * @throws NoSuchMethodException 1460 * @throws InvocationTargetException 1461 * @throws IllegalAccessException 1462 */ 1463 public static void setObjectProperty(Object bo, String propertyName, Class propertyType, 1464 Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1465 KRADServiceLocatorWeb.getLegacyDataAdapter().setObjectProperty(bo, propertyName, propertyType, propertyValue); 1466 } 1467 1468 /** 1469 * Removes all query characters from a string. 1470 * 1471 * @param string 1472 * @return Cleaned string 1473 */ 1474 public static String clean(String string) { 1475 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 1476 string = StringUtils.replace(string, op.op(), KRADConstants.EMPTY_STRING); 1477 } 1478 return string; 1479 } 1480 1481 /** 1482 * This method is a confirms if object is null, and unproxies if necessary to determine this 1483 * 1484 * @param object - any object, proxied or not, materialized or not 1485 * @return true if the object (or underlying materialized object) is null, false otherwise 1486 */ 1487 public static boolean isNull(Object object) { 1488 if (object == null) { 1489 return true; 1490 } 1491 return KRADServiceLocatorWeb.getLegacyDataAdapter().isNull(object); 1492 } 1493 1494 /** 1495 * This method is a confirms if object is not null, and unproxies if necessary to determine this 1496 * 1497 * @param object - any object, proxied or not, materialized or not 1498 * @return true if the object (or underlying materialized object) is not null, true if its null 1499 */ 1500 public static boolean isNotNull(Object object) { 1501 return !isNull(object); 1502 } 1503 1504 /** 1505 * Attempts to find the Class for the given potentially proxied object 1506 * 1507 * @param object the potentially proxied object to find the Class of 1508 * @return the best Class which could be found for the given object 1509 */ 1510 public static Class materializeClassForProxiedObject(Object object) { 1511 return KRADServiceLocatorWeb.getLegacyDataAdapter().materializeClassForProxiedObject(object); 1512 } 1513 1514 /** 1515 * This method runs the KRADUtils.isNotNull() method for each item in a list of BOs. KRADUtils.isNotNull() will 1516 * materialize 1517 * the objects if they are currently OJB proxies. 1518 * 1519 * @param possiblyProxiedObjects - a Collection of objects that may be proxies 1520 */ 1521 public static void materializeObjects(Collection possiblyProxiedObjects) { 1522 for (Iterator i = possiblyProxiedObjects.iterator(); i.hasNext(); ) { 1523 KRADUtils.isNotNull(i.next()); 1524 } 1525 } 1526 1527 /** 1528 * Returns the prefix of a nested attribute name, or the empty string if the attribute name is not nested. 1529 * 1530 * @param attributeName 1531 * @return everything BEFORE the last "." character in attributeName 1532 */ 1533 public static String getNestedAttributePrefix(String attributeName) { 1534 int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(attributeName); 1535 1536 return lastIndex != -1 ? StringUtils.substring(attributeName, 0, lastIndex) : StringUtils.EMPTY; 1537 } 1538 1539 /** 1540 * Returns the primitive part of an attribute name string. 1541 * 1542 * @param attributeName 1543 * @return everything AFTER the last "." character in attributeName 1544 */ 1545 public static String getNestedAttributePrimitive(String attributeName) { 1546 int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(attributeName); 1547 1548 return lastIndex != -1 ? StringUtils.substring(attributeName, lastIndex + 1) : attributeName; 1549 } 1550 1551 /** 1552 * This method safely extracts either simple values OR nested values. For example, if the bo is SubAccount, and the 1553 * fieldName is 1554 * a21SubAccount.subAccountTypeCode, this thing makes sure it gets the value off the very end attribute, no matter 1555 * how deeply 1556 * nested it is. The code would be slightly simpler if this was done recursively, but this is safer, and consumes a 1557 * constant 1558 * amount of memory, no matter how deeply nested it goes. 1559 * 1560 * @param bo 1561 * @param fieldName 1562 * @return The field value if it exists. If it doesnt, and the name is invalid, and 1563 */ 1564 public static Object getNestedValue(Object bo, String fieldName) { 1565 return KRADServiceLocatorWeb.getLegacyDataAdapter().getNestedValue(bo, fieldName); 1566 } 1567 1568 /** 1569 * This method safely creates a object from a class 1570 * Convenience method to create new object and throw a runtime exception if it cannot 1571 * If the class is an {@link org.kuali.rice.krad.bo.ExternalizableBusinessObject}, this method will determine the 1572 * interface for the EBO and 1573 * query the 1574 * appropriate module service to create a new instance. 1575 * 1576 * @param clazz 1577 * @return a newInstance() of clazz 1578 */ 1579 public static <T> T createNewObjectFromClass(Class<T> clazz) { 1580 if (clazz == null) { 1581 throw new RuntimeException("BO class was passed in as null"); 1582 } 1583 return (T) KRADServiceLocatorWeb.getLegacyDataAdapter().createNewObjectFromClass(clazz); 1584 } 1585 1586 private static KualiModuleService getKualiModuleService() { 1587 if (kualiModuleService == null) { 1588 kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService(); 1589 } 1590 return kualiModuleService; 1591 } 1592 1593 /** 1594 * Updates the properties of the given component instance with the value found from the 1595 * corresponding map of client state (if found) 1596 * 1597 * @param component component instance to update 1598 * @param clientSideState map of state to sync with 1599 */ 1600 public static void syncClientSideStateForComponent(Component component, Map<String, Object> clientSideState) { 1601 // find the map of state that was sent for component (if any) 1602 Map<String, Object> componentState = null; 1603 if (component instanceof View) { 1604 componentState = clientSideState; 1605 } else { 1606 if (clientSideState.containsKey(component.getId())) { 1607 componentState = (Map<String, Object>) clientSideState.get(component.getId()); 1608 } 1609 } 1610 1611 // if state was sent, match with fields on the component that are annotated to have client state 1612 if ((componentState != null) && (!componentState.isEmpty())) { 1613 Map<String, Annotation> annotatedFields = CopyUtils.getFieldsWithAnnotation(component.getClass(), 1614 ClientSideState.class); 1615 1616 for (Map.Entry<String, Annotation> annotatedField : annotatedFields.entrySet()) { 1617 ClientSideState clientSideStateAnnot = (ClientSideState) annotatedField.getValue(); 1618 1619 String variableName = clientSideStateAnnot.variableName(); 1620 if (StringUtils.isBlank(variableName)) { 1621 variableName = annotatedField.getKey(); 1622 } 1623 1624 if (componentState.containsKey(variableName)) { 1625 Object value = componentState.get(variableName); 1626 ObjectPropertyUtils.setPropertyValue(component, annotatedField.getKey(), value); 1627 } 1628 } 1629 } 1630 } 1631 1632 }