001/** 002 * Copyright 2005-2015 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 */ 016package org.kuali.rice.krad.util; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Constructor; 022import java.lang.reflect.InvocationTargetException; 023import java.lang.reflect.Method; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.security.GeneralSecurityException; 027import java.text.MessageFormat; 028import java.text.NumberFormat; 029import java.text.ParseException; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Collection; 033import java.util.Collections; 034import java.util.HashMap; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Map; 038import java.util.Properties; 039import java.util.regex.Pattern; 040 041import javax.servlet.ServletRequest; 042import javax.servlet.http.HttpServletRequest; 043import javax.servlet.http.HttpServletResponse; 044 045import org.apache.commons.beanutils.PropertyUtils; 046import org.apache.commons.codec.EncoderException; 047import org.apache.commons.codec.net.URLCodec; 048import org.apache.commons.lang.StringUtils; 049import org.apache.log4j.Logger; 050import org.kuali.rice.core.api.CoreApiServiceLocator; 051import org.kuali.rice.core.api.encryption.EncryptionService; 052import org.kuali.rice.core.api.search.SearchOperator; 053import org.kuali.rice.core.api.util.RiceKeyConstants; 054import org.kuali.rice.core.api.util.Truth; 055import org.kuali.rice.core.api.util.type.KualiDecimal; 056import org.kuali.rice.core.api.util.type.TypeUtils; 057import org.kuali.rice.core.web.format.BooleanFormatter; 058import org.kuali.rice.core.web.format.FormatException; 059import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 060import org.kuali.rice.coreservice.framework.parameter.ParameterConstants; 061import org.kuali.rice.coreservice.framework.parameter.ParameterService; 062import org.kuali.rice.krad.UserSession; 063import org.kuali.rice.krad.data.KradDataServiceLocator; 064import org.kuali.rice.krad.messages.MessageService; 065import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 066import org.kuali.rice.krad.service.KualiModuleService; 067import org.kuali.rice.krad.service.ModuleService; 068import org.kuali.rice.krad.uif.UifConstants; 069import org.kuali.rice.krad.uif.UifParameters; 070import org.kuali.rice.krad.uif.component.ClientSideState; 071import org.kuali.rice.krad.uif.component.Component; 072import org.kuali.rice.krad.uif.element.Action; 073import org.kuali.rice.krad.uif.element.Image; 074import org.kuali.rice.krad.uif.element.Link; 075import org.kuali.rice.krad.uif.field.ActionField; 076import org.kuali.rice.krad.uif.field.DataField; 077import org.kuali.rice.krad.uif.field.Field; 078import org.kuali.rice.krad.uif.field.FieldGroup; 079import org.kuali.rice.krad.uif.field.ImageField; 080import org.kuali.rice.krad.uif.field.LinkField; 081import org.kuali.rice.krad.uif.field.MessageField; 082import org.kuali.rice.krad.uif.field.SpaceField; 083import org.kuali.rice.krad.uif.util.CopyUtils; 084import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 085import org.kuali.rice.krad.uif.util.ViewModelUtils; 086import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 087import org.kuali.rice.krad.uif.view.View; 088import org.kuali.rice.krad.web.form.UifFormBase; 089import org.springframework.beans.PropertyAccessorUtils; 090import org.springframework.util.Assert; 091import org.springframework.util.FileCopyUtils; 092 093/** 094 * Miscellaneous Utility Methods. 095 * 096 * @author Kuali Rice Team (rice.collab@kuali.org) 097 */ 098public 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 = fieldConversionStr.split(":",-1); 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 = parameter.split(":",-1); 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 security on field 522 boolean isSecure = isSecure(keyPropertyName, parentObjectClass); 523 524 if (StringUtils.endsWith(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX)) { 525 keyValue = StringUtils.removeEnd(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX); 526 isSecure = true; 527 } 528 529 // decrypt if the value is secure 530 if (isSecure) { 531 try { 532 if (CoreApiServiceLocator.getEncryptionService().isEnabled()) { 533 keyValue = CoreApiServiceLocator.getEncryptionService().decrypt(keyValue); 534 } 535 } catch (GeneralSecurityException e) { 536 String message = "Data object class " + parentObjectClass + " property " + keyPropertyName 537 + " should have been encrypted, but there was a problem decrypting it."; 538 LOG.error(message); 539 540 throw new RuntimeException(message, e); 541 } 542 } 543 544 parameterValues.put(keyPropertyName, keyValue); 545 } 546 } 547 548 return parameterValues; 549 } 550 551 /** 552 * Returns a map containing a key/value pair for each property given in the property names list, ignoring any values 553 * considered to be secure. 554 * 555 * @param propertyNames the list of property names for which to get key/value pairs 556 * @param dataObject the object instance containing the properties for which the values will be pulled 557 * 558 * @return a map containing an entry for each property name with the property name as the map key 559 * and the property value as the value 560 */ 561 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, Object dataObject) { 562 return getPropertyKeyValuesFromDataObject(propertyNames, Collections.<String>emptyList(), dataObject); 563 } 564 565 /** 566 * Returns a map containing a key/value pair for each property given in the property names list, ignoring any values 567 * considered to be secure, including those in {@code securePropertyNames}. 568 * 569 * @param propertyNames the list of property names for which to get key/value pairs 570 * @param securePropertyNames list of additional secure property names to match for encryption 571 * @param dataObject the object instance containing the properties for which the values will be pulled 572 * 573 * @return a map containing an entry for each property name with the property name as the map key 574 * and the property value as the value 575 */ 576 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, 577 List<String> securePropertyNames, Object dataObject) { 578 Map<String, String> propertyKeyValues = new HashMap<String, String>(); 579 580 if (dataObject == null) { 581 return propertyKeyValues; 582 } 583 584 // iterate through properties and add a map entry for each 585 for (String propertyName : propertyNames) { 586 String propertyValue = ObjectPropertyUtils.getPropertyValueAsText(dataObject, propertyName); 587 588 if (propertyValue == null) { 589 propertyValue = StringUtils.EMPTY; 590 } 591 592 // add the property if it is not secure 593 if (!isSecure(propertyName, securePropertyNames, dataObject.getClass())) { 594 propertyKeyValues.put(propertyName, propertyValue); 595 } 596 } 597 598 return propertyKeyValues; 599 } 600 601 /** 602 * Determines whether a property name should be secured, either based on installed sensitive data patterns, a list 603 * of secure property name patterns, or attributes in the Data Dictionary. 604 * 605 * @param propertyName the property name to check for security 606 * @param dataObjectClass the class of the object containing this property 607 * 608 * @return true if the property needs to be secure, false otherwise 609 */ 610 public static boolean isSecure(String propertyName, Class<?> dataObjectClass) { 611 return isSecure(propertyName, Collections.<String>emptyList(), dataObjectClass); 612 } 613 614 /** 615 * Determines whether a property name should be secured, either based on installed sensitive data patterns, a list 616 * of secure property name patterns, or attributes in the Data Dictionary. 617 * 618 * @param propertyName the property name to check for security 619 * @param securePropertyNames the secure property name patterns to check 620 * @param dataObjectClass the class of the object containing this property 621 * 622 * @return true if the property needs to be secure, false otherwise 623 */ 624 public static boolean isSecure(String propertyName, List<String> securePropertyNames, Class<?> dataObjectClass) { 625 if (containsSecurePropertyName(propertyName, securePropertyNames)) { 626 return true; 627 } 628 629 return KRADServiceLocatorWeb.getDataObjectAuthorizationService() 630 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, propertyName); 631 } 632 633 /** 634 * Helper method to identify if propertyName contains a secure property name element. 635 * Check handles simple or compound names and ignores partial matches. 636 * 637 * @param propertyName property name as a single term or compound term (i.e. items[0].propertyName) 638 * @param securePropertyNames list of secure property names to match 639 * @return true if any of the secure property names are found in the property name, false otherwise 640 */ 641 private static boolean containsSecurePropertyName(String propertyName, List<String> securePropertyNames) { 642 if (securePropertyNames == null) { 643 return false; 644 } 645 646 for (String securePropertyName : securePropertyNames) { 647 // pattern prefix and suffix used to handle compound names and ignore partial name matches 648 if (Pattern.compile("(?:\\.|^)" + Pattern.quote(securePropertyName) + "(?:\\.|\\[|$)").matcher(propertyName) 649 .find()) { 650 return true; 651 } 652 } 653 654 return false; 655 } 656 657 /** 658 * Utility method to convert a Map to a Properties object 659 * 660 * @param parameters - map to convert 661 * @return Properties object containing all the map entries 662 */ 663 public static Properties convertMapToProperties(Map<String, String> parameters) { 664 Properties properties = new Properties(); 665 666 if (parameters != null) { 667 for (Map.Entry<String, String> parameter : parameters.entrySet()) { 668 properties.put(parameter.getKey(), parameter.getValue()); 669 } 670 } 671 672 return properties; 673 } 674 675 /** 676 * Utility method to convert a Request Parameters Map to a Properties object 677 * 678 * <p> 679 * Multiple values for a parameter are joined together with comma delimiter 680 * </p> 681 * 682 * @param requestParameters - map to convert 683 * @return Properties object containing all the map entries 684 */ 685 public static Properties convertRequestMapToProperties(Map<String, String[]> requestParameters) { 686 Properties properties = new Properties(); 687 688 if (requestParameters != null) { 689 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 690 String[] parameterValue = parameter.getValue(); 691 String parameterValueString = StringUtils.join(parameterValue, ","); 692 693 properties.put(parameter.getKey(), parameterValueString); 694 } 695 } 696 697 return properties; 698 } 699 700 /** 701 * This method converts request parameters coming in as String to native types in case of Boolean, Number or 702 * java.sql.Date. 703 * For boolean the conversion is performed only if an @Converter annotation is set on the JPA entity field. 704 * 705 * @param dataObjectClass - business object class 706 * @param parameters - map of request parameters with field values as String for the fields in the dataObjectClass 707 * @return Map <String,Object> converted values 708 */ 709 public static Map<String, Object> coerceRequestParameterTypes(Class<?> dataObjectClass, 710 Map<String, String> parameters) { 711 Map<String, Object> filteredFieldValues = new HashMap<String, Object>(); 712 List<java.lang.reflect.Field> allFields = ObjectPropertyUtils.getAllFields( 713 new ArrayList<java.lang.reflect.Field>(), dataObjectClass, Object.class); 714 715 for (String fieldName : parameters.keySet()) { 716 Class<?> propertyType = ObjectPropertyUtils.getPropertyType(dataObjectClass, fieldName); 717 718 String strValue = parameters.get(fieldName); 719 720 if (TypeUtils.isBooleanClass(propertyType) && isConvertAnnotationPresent(allFields, fieldName)) { 721 filteredFieldValues.put(fieldName, Truth.strToBooleanIgnoreCase(strValue)); 722 } else if (TypeUtils.isIntegralClass(propertyType) || TypeUtils.isDecimalClass(propertyType)) { 723 try { 724 filteredFieldValues.put(fieldName, hydrateAttributeValue(propertyType, strValue)); 725 } catch (Exception nfe) { 726 GlobalVariables.getMessageMap().putError("parameters[" + fieldName + "]", 727 RiceKeyConstants.ERROR_NUMBER, strValue); 728 throw new RuntimeException("Could not parse property value into Number for " + fieldName); 729 } 730 } else if (TypeUtils.isTemporalClass(propertyType)) { 731 try { 732 filteredFieldValues.put(fieldName, CoreApiServiceLocator.getDateTimeService().convertToSqlDate( 733 strValue)); 734 } catch (ParseException pe) { 735 GlobalVariables.getMessageMap().putError("parameters[" + fieldName + "]", 736 RiceKeyConstants.ERROR_DATE_TIME, strValue); 737 throw new RuntimeException("Could not parse property value into java.sql.Date for " + fieldName); 738 } 739 } 740 741 // If value not converted set the value from parameters 742 if (filteredFieldValues.get(fieldName) == null) { 743 filteredFieldValues.put(fieldName, parameters.get(fieldName)); 744 } 745 746 } 747 return filteredFieldValues; 748 749 } 750 751 /** 752 * Checks to see if the specified field from the list of allFields has the @Convert annotation set on it 753 * 754 * @param allFields List of all fields on the entity 755 * @param fieldName Field name to check for @Convert annotation 756 * @return true if annotation is present else false 757 */ 758 private static boolean isConvertAnnotationPresent(List<java.lang.reflect.Field> allFields, String fieldName) { 759 //Check if there is a @Convert annotation on the field 760 boolean convertAnnotationFound = false; 761 for (java.lang.reflect.Field f : allFields) { 762 if (f.getName().equalsIgnoreCase(fieldName)) { 763 if (f.getAnnotation(javax.persistence.Convert.class) != null) { 764 convertAnnotationFound = true; 765 } 766 break; 767 } 768 } 769 770 return convertAnnotationFound; 771 } 772 773 /** 774 * Check if data might be sensitive 775 * 776 * <p> 777 * The sensitivity of the data is checked by matching it against the sensitive data patterns that are specified 778 * in the system parameter table. 779 * </p> 780 * 781 * @param fieldValue data to be checked for sensitivity 782 * @return true if the data matches the sensitive data pattern, false otherwise 783 */ 784 public static boolean containsSensitiveDataPatternMatch(String fieldValue) { 785 if (StringUtils.isBlank(fieldValue)) { 786 return false; 787 } 788 789 ParameterService parameterService = CoreFrameworkServiceLocator.getParameterService(); 790 Collection<String> sensitiveDataPatterns = parameterService.getParameterValuesAsString( 791 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT, 792 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS); 793 794 for (String pattern : sensitiveDataPatterns) { 795 if (Pattern.compile(pattern).matcher(fieldValue).find()) { 796 return true; 797 } 798 } 799 800 return false; 801 } 802 803 /** 804 * Strips out common patterns used in cross side scripting. 805 * 806 * @param values string array to strip patterns from 807 * @return cleaned string array 808 */ 809 public static String[] stripXSSPatterns(String[] values) { 810 ArrayList<String> strippedValues = new ArrayList<String>(); 811 812 for (String value : values) { 813 strippedValues.add(stripXSSPatterns(value)); 814 } 815 816 return strippedValues.toArray(new String[]{}); 817 } 818 819 /** 820 * Strips out common patterns used in cross side scripting. 821 * 822 * @param value string to strip patterns from 823 * @return cleaned string 824 */ 825 public static String stripXSSPatterns(String value) { 826 if (value == null) { 827 return null; 828 } 829 830 // Avoid null characters 831 value = value.replaceAll("", ""); 832 833 // Avoid anything between script tags 834 Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE); 835 value = scriptPattern.matcher(value).replaceAll(""); 836 837 // Avoid anything in a src='...' type of expression 838 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", 839 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 840 value = scriptPattern.matcher(value).replaceAll(""); 841 842 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", 843 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 844 value = scriptPattern.matcher(value).replaceAll(""); 845 846 // Remove any lonesome </script> tag 847 scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE); 848 value = scriptPattern.matcher(value).replaceAll(""); 849 850 // Remove any lonesome <script ...> tag 851 scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 852 value = scriptPattern.matcher(value).replaceAll(""); 853 854 // Avoid eval(...) expressions 855 scriptPattern = Pattern.compile("eval\\((.*?)\\)", 856 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 857 value = scriptPattern.matcher(value).replaceAll(""); 858 859 // Avoid expression(...) expressions 860 scriptPattern = Pattern.compile("expression\\((.*?)\\)", 861 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 862 value = scriptPattern.matcher(value).replaceAll(""); 863 864 // Avoid javascript:... expressions 865 scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); 866 value = scriptPattern.matcher(value).replaceAll(""); 867 868 // Avoid vbscript:... expressions 869 scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); 870 value = scriptPattern.matcher(value).replaceAll(""); 871 872 // Avoid onload= expressions 873 scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 874 value = scriptPattern.matcher(value).replaceAll(""); 875 876 return value; 877 } 878 879 /** 880 * Gets the UserSession object from the HttpServletRequest object's 881 * associated session. 882 * 883 * <p> 884 * In some cases (different threads) the UserSession cannot be retrieved 885 * from GlobalVariables but can still be accessed via the session object 886 * </p> 887 * 888 * @param request servlet request 889 * @return user session found in the request's servlet session 890 */ 891 public static final UserSession getUserSessionFromRequest(HttpServletRequest request) { 892 return (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY); 893 } 894 895 /** 896 * Check if current deployment is the production environment 897 * 898 * @return true if the deploy environment is production, false otherwise 899 */ 900 public static boolean isProductionEnvironment() { 901 return CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 902 KRADConstants.PROD_ENVIRONMENT_CODE_KEY).equalsIgnoreCase( 903 CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 904 KRADConstants.ENVIRONMENT_KEY)); 905 } 906 907 /** 908 * Gets the message associated with ErrorMessage object passed in, using message service. 909 * The prefix and suffix will be appended to the retrieved message if processPrefixSuffix is true and if those 910 * settings are set on the ErrorMessage passed in. 911 * 912 * @param errorMessage the ErrorMessage object containing the message key(s) 913 * @param processPrefixSuffix if true appends the prefix and suffix to the message if they exist on ErrorMessage 914 * @return the converted/retrieved message 915 */ 916 public static String getMessageText(ErrorMessage errorMessage, boolean processPrefixSuffix) { 917 String message = ""; 918 if (errorMessage != null && errorMessage.getErrorKey() != null) { 919 MessageService messageService = KRADServiceLocatorWeb.getMessageService(); 920 921 // find message by key 922 message = messageService.getMessageText(errorMessage.getNamespaceCode(), errorMessage.getComponentCode(), 923 errorMessage.getErrorKey()); 924 if (message == null) { 925 message = "Intended message with key: " + errorMessage.getErrorKey() + " not found."; 926 } 927 928 if (errorMessage.getMessageParameters() != null && StringUtils.isNotBlank(message)) { 929 message = message.replace("'", "''"); 930 message = MessageFormat.format(message, (Object[]) errorMessage.getMessageParameters()); 931 } 932 933 // add prefix 934 if (StringUtils.isNotBlank(errorMessage.getMessagePrefixKey()) && processPrefixSuffix) { 935 String prefix = messageService.getMessageText(errorMessage.getNamespaceCode(), 936 errorMessage.getComponentCode(), errorMessage.getMessagePrefixKey()); 937 938 if (errorMessage.getMessagePrefixParameters() != null && StringUtils.isNotBlank(prefix)) { 939 prefix = prefix.replace("'", "''"); 940 prefix = MessageFormat.format(prefix, (Object[]) errorMessage.getMessagePrefixParameters()); 941 } 942 943 if (StringUtils.isNotBlank(prefix)) { 944 message = prefix + " " + message; 945 } 946 } 947 948 // add suffix 949 if (StringUtils.isNotBlank(errorMessage.getMessageSuffixKey()) && processPrefixSuffix) { 950 String suffix = messageService.getMessageText(errorMessage.getNamespaceCode(), 951 errorMessage.getComponentCode(), errorMessage.getMessageSuffixKey()); 952 953 if (errorMessage.getMessageSuffixParameters() != null && StringUtils.isNotBlank(suffix)) { 954 suffix = suffix.replace("'", "''"); 955 suffix = MessageFormat.format(suffix, (Object[]) errorMessage.getMessageSuffixParameters()); 956 } 957 958 if (StringUtils.isNotBlank(suffix)) { 959 message = message + " " + suffix; 960 } 961 } 962 } 963 964 return message; 965 } 966 967 /** 968 * Logs the error messages if any in the message map 969 */ 970 public static void logErrors() { 971 if (!GlobalVariables.getMessageMap().hasErrors()) { 972 return; 973 } 974 975 for (Iterator<Map.Entry<String, List<ErrorMessage>>> i = 976 GlobalVariables.getMessageMap().getAllPropertiesAndErrors().iterator(); i.hasNext(); ) { 977 Map.Entry<String, List<ErrorMessage>> e = i.next(); 978 979 StringBuffer logMessage = buildMessage(e); 980 LOG.error(logMessage); 981 } 982 } 983 984 /** 985 * Builds the message for a given entry in the messageMap. The entry could have multiple messages for a given key. 986 * The messages are appended separated by a ; 987 * 988 * @param e Map entry of property and errors for that property 989 * @return logMessage 990 */ 991 private static StringBuffer buildMessage(Map.Entry<String, List<ErrorMessage>> e) { 992 StringBuffer logMessage = new StringBuffer(); 993 logMessage.append("[" + e.getKey() + "] "); 994 boolean first = true; 995 996 List<ErrorMessage> errorList = e.getValue(); 997 for (Iterator<ErrorMessage> j = errorList.iterator(); j.hasNext(); ) { 998 ErrorMessage em = j.next(); 999 1000 // if its the first message for the key 1001 if (first) { 1002 first = false; 1003 } else { 1004 logMessage.append(";"); 1005 } 1006 logMessage.append(em); 1007 } 1008 return logMessage; 1009 } 1010 1011 /** 1012 * Generate the request parameter portion of the url based on the map of key value pairs passed in 1013 * 1014 * @param requestParameters the request parameters to use in the string 1015 * @return a request parameter string starting with "?" with "&" separators, or blank if the mapped passed in is 1016 * blank 1017 */ 1018 public static String getRequestStringFromMap(Map<String, String> requestParameters) { 1019 String requestString = ""; 1020 1021 if (requestParameters.isEmpty()) { 1022 return requestString; 1023 } 1024 1025 URLCodec urlCodec = new URLCodec(KRADConstants.DEFAULT_ENCODING); 1026 1027 for (String key : requestParameters.keySet()) { 1028 String value = null; 1029 try { 1030 value = urlCodec.encode(requestParameters.get(key)); 1031 } catch (EncoderException e) { 1032 throw new RuntimeException("Unable to encode parameter name or value: " + key + "=" + value, e); 1033 } 1034 1035 if (StringUtils.isNotBlank(requestString)) { 1036 requestString = requestString + "&"; 1037 } 1038 1039 requestString = requestString + key + "=" + value; 1040 } 1041 1042 return "?" + requestString; 1043 } 1044 1045 /** 1046 * Adds the header and content of an attachment to the response. 1047 * 1048 * @param response HttpServletResponse instance 1049 * @param contentType the content type of the attachment 1050 * @param inputStream the content of the attachment 1051 * @param fileName the file name of the attachment 1052 * @param fileSize the size of the attachment 1053 * @throws IOException if attachment to the results fails due to an IO error 1054 */ 1055 public static void addAttachmentToResponse(HttpServletResponse response, InputStream inputStream, 1056 String contentType, String fileName, long fileSize) throws IOException { 1057 1058 // If there are quotes in the name, we should replace them to avoid issues. 1059 // The filename will be wrapped with quotes below when it is set in the header 1060 String updateFileName; 1061 if (fileName.contains("\"")) { 1062 updateFileName = fileName.replaceAll("\"", ""); 1063 } else { 1064 updateFileName = fileName; 1065 } 1066 1067 // set response 1068 response.setContentType(contentType); 1069 response.setContentLength(org.springframework.util.NumberUtils.convertNumberToTargetClass(fileSize, 1070 Integer.class)); 1071 response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\""); 1072 response.setHeader("Expires", "0"); 1073 response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); 1074 response.setHeader("Pragma", "public"); 1075 1076 // Copy the input stream to the response 1077 if (inputStream != null) { 1078 FileCopyUtils.copy(inputStream, response.getOutputStream()); 1079 } 1080 } 1081 1082 /** 1083 * Helper method for building a URL that will invoke the given controller and render the given 1084 * KRAD view 1085 * 1086 * @param baseUrl base url (domain, port) 1087 * @param controllerMapping mapping for the controller that should be invoked 1088 * @param viewId id for the view that should be rendered 1089 * @return url for invoking the view 1090 */ 1091 public static String buildViewUrl(String baseUrl, String controllerMapping, String viewId) { 1092 Assert.hasLength(baseUrl, "base url is null or empty"); 1093 Assert.hasLength(controllerMapping, "controller mapping is null or empty"); 1094 Assert.hasLength(viewId, "view id is null or empty"); 1095 1096 StringBuffer url = new StringBuffer(); 1097 1098 url.append(baseUrl); 1099 1100 if (!baseUrl.endsWith("/")) { 1101 url.append("/"); 1102 } 1103 1104 url.append(controllerMapping); 1105 1106 url.append("?"); 1107 url.append(UifParameters.VIEW_ID); 1108 url.append("="); 1109 url.append(viewId); 1110 1111 return url.toString(); 1112 } 1113 1114 /** 1115 * Removes parameters from the given properties object that are request specific (useful when manupulating the 1116 * current URL to invoke something else) 1117 * 1118 * @param requestParameters properties instance containing the parameters to clean 1119 */ 1120 public static void cleanRequestParameters(Properties requestParameters) { 1121 requestParameters.remove(UifParameters.SESSION_ID); 1122 requestParameters.remove(UifParameters.AJAX_REQUEST); 1123 requestParameters.remove(UifParameters.AJAX_RETURN_TYPE); 1124 requestParameters.remove(UifParameters.FORM_KEY); 1125 requestParameters.remove(UifParameters.JUMP_TO_ID); 1126 requestParameters.remove(UifParameters.FOCUS_ID); 1127 } 1128 1129 /** 1130 * Get the full url for a request (requestURL + queryString) 1131 * 1132 * @param request the request 1133 * @return the fullUrl 1134 */ 1135 public static String getFullURL(HttpServletRequest request) { 1136 if (request == null) { 1137 return null; 1138 } 1139 1140 StringBuffer requestURL = request.getRequestURL(); 1141 String queryString = request.getQueryString(); 1142 1143 if (queryString == null) { 1144 return requestURL.toString(); 1145 } else { 1146 return requestURL.append('?').append(queryString).toString(); 1147 } 1148 } 1149 1150 /** 1151 * Determines whether two URLs point at the same domain. 1152 * 1153 * @param firstDomain first URL string to compare 1154 * @param secondDomain second URL string to compare 1155 * @return true if the domains are different, false otherwise 1156 */ 1157 public static boolean areDifferentDomains(String firstDomain, String secondDomain) { 1158 try { 1159 URL urlOne = new URL(firstDomain.toLowerCase()); 1160 URL urlTwo = new URL(secondDomain.toLowerCase()); 1161 1162 if (urlOne.getHost().equals(urlTwo.getHost())) { 1163 LOG.debug("Hosts " 1164 + urlOne.getHost() 1165 + " of domains " 1166 + firstDomain 1167 + " and " 1168 + secondDomain 1169 + " were determined to be equal"); 1170 1171 return false; 1172 } else { 1173 LOG.debug("Hosts " 1174 + urlOne.getHost() 1175 + " of domains " 1176 + firstDomain 1177 + " and " 1178 + secondDomain 1179 + " are not equal"); 1180 1181 return true; 1182 } 1183 } catch (MalformedURLException mue) { 1184 LOG.error("Unable to successfully compare domains " + firstDomain + " and " + secondDomain); 1185 } 1186 1187 return true; 1188 } 1189 1190 /** 1191 * Attempts to generate a unique view title by combining the View's headerText with the title attribute for the 1192 * dataObjectClass found through the DataObjectMetaDataService. If the title attribute cannot be found, just the 1193 * headerText is returned. 1194 * 1195 * @param form the form 1196 * @param view the view 1197 * @return the headerText with the title attribute in parenthesis or just the headerText if it title attribute 1198 * cannot be determined 1199 */ 1200 public static String generateUniqueViewTitle(UifFormBase form, View view) { 1201 String title = view.getHeader().getHeaderText(); 1202 1203 String viewLabelPropertyName = ""; 1204 1205 Class<?> dataObjectClass; 1206 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 1207 dataObjectClass = ObjectPropertyUtils.getPropertyType(form, view.getDefaultBindingObjectPath()); 1208 } else { 1209 dataObjectClass = view.getFormClass(); 1210 } 1211 1212 if (dataObjectClass != null) { 1213 viewLabelPropertyName = KRADServiceLocatorWeb.getLegacyDataAdapter().getTitleAttribute(dataObjectClass); 1214 } 1215 1216 String viewLabelPropertyPath = ""; 1217 if (StringUtils.isNotBlank(viewLabelPropertyName)) { 1218 // adjust binding prefix 1219 if (!viewLabelPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) { 1220 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 1221 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + viewLabelPropertyName; 1222 } 1223 } else { 1224 viewLabelPropertyPath = StringUtils.removeStart(viewLabelPropertyName, 1225 UifConstants.NO_BIND_ADJUST_PREFIX); 1226 } 1227 } else { 1228 // attempt to get title attribute 1229 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 1230 dataObjectClass = ViewModelUtils.getObjectClassForMetadata(view, form, 1231 view.getDefaultBindingObjectPath()); 1232 } else { 1233 dataObjectClass = view.getFormClass(); 1234 } 1235 1236 if (dataObjectClass != null) { 1237 String titleAttribute = KRADServiceLocatorWeb.getLegacyDataAdapter().getTitleAttribute(dataObjectClass); 1238 if (StringUtils.isNotBlank(titleAttribute)) { 1239 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + titleAttribute; 1240 } 1241 } 1242 } 1243 1244 Object viewLabelPropertyValue = null; 1245 if (StringUtils.isNotBlank(viewLabelPropertyPath) && ObjectPropertyUtils.isReadableProperty(form, 1246 viewLabelPropertyPath)) { 1247 viewLabelPropertyValue = ObjectPropertyUtils.getPropertyValueAsText(form, viewLabelPropertyPath); 1248 } 1249 1250 if (viewLabelPropertyValue != null && StringUtils.isNotBlank(viewLabelPropertyValue.toString()) && StringUtils 1251 .isNotBlank(title)) { 1252 return title + " (" + viewLabelPropertyValue.toString() + ")"; 1253 } else { 1254 return title; 1255 } 1256 } 1257 1258 /** 1259 * Helper method for building title text for an element and a map of key/value pairs, 1260 * 1261 * <p> 1262 * Each key of the key value map is assumed to be an attribute for the given element class. The label is then 1263 * retrieved for the attribute from the data dictionary and used in the title (instead of the key) 1264 * </p> 1265 * 1266 * @param prependText text to prepend to the title 1267 * @param element element class the title is being generated for, used as the parent for getting the key labels 1268 * @param keyValueMap map of key value pairs to add to the title text 1269 * @return title string 1270 */ 1271 public static String buildAttributeTitleString(String prependText, Class<?> element, 1272 Map<String, String> keyValueMap) { 1273 StringBuffer titleText = new StringBuffer(prependText); 1274 1275 for (String key : keyValueMap.keySet()) { 1276 String fieldVal = keyValueMap.get(key).toString(); 1277 1278 titleText.append(" " 1279 + KRADServiceLocatorWeb.getDataDictionaryService().getAttributeLabel(element, key) 1280 + "=" 1281 + fieldVal.toString()); 1282 } 1283 1284 return titleText.toString(); 1285 } 1286 1287 /** 1288 * Attempts to extract a string value out of the field passed in, varies depending on field type 1289 * 1290 * <p>If the field is a dataField, it will use its propertyName to retrieve a value, otherwise it will try to 1291 * retrieve textual content out of various component types. If the field is a FieldGroup, only the first 1292 * component's determined value will be used. This function is used for sorting.</p> 1293 * 1294 * @param model the current model 1295 * @param field the field to get a value from 1296 * @return the field's String value, false if it cant be determined 1297 */ 1298 public static String getSimpleFieldValue(Object model, Field field) { 1299 if (field == null) { 1300 return null; 1301 } 1302 1303 String value = null; 1304 // check for what type of field this is 1305 if (field instanceof DataField) { 1306 String propertyPath = ((DataField) field).getBindingInfo().getBindingPath(); 1307 Object valueObject = null; 1308 1309 if (field.isHidden()) { 1310 return ""; 1311 } 1312 1313 // check if readable 1314 if (ObjectPropertyUtils.isReadableProperty(model, propertyPath)) { 1315 valueObject = ObjectPropertyUtils.getPropertyValueAsText(model, propertyPath); 1316 } 1317 1318 // use object's string value 1319 if (valueObject != null && !((DataField) field).isApplyMask()) { 1320 value = valueObject.toString(); 1321 } else if (valueObject != null && ((DataField) field).isApplyMask()) { 1322 value = ((DataField) field).getMaskFormatter().maskValue(valueObject); 1323 } 1324 } else if (field instanceof ActionField) { 1325 value = ((ActionField) field).getActionLabel(); 1326 1327 // use image alt text if any 1328 if (StringUtils.isBlank(value) && ((ActionField) field).getActionImage() != null) { 1329 value = ((ActionField) field).getActionImage().getAltText(); 1330 } 1331 } else if (field instanceof LinkField) { 1332 value = ((LinkField) field).getLinkText(); 1333 } else if (field instanceof ImageField) { 1334 value = ((ImageField) field).getAltText(); 1335 } else if (field instanceof MessageField && ((MessageField) field).getMessage() != null) { 1336 value = ((MessageField) field).getMessage().getMessageText(); 1337 } else if (field instanceof SpaceField) { 1338 value = ""; 1339 } else if (field instanceof FieldGroup 1340 && ((FieldGroup) field).getGroup() != null 1341 && ((FieldGroup) field).getGroup().getItems() != null 1342 && !((FieldGroup) field).getGroup().getItems().isEmpty()) { 1343 // using first components type for assumed value 1344 Component firstComponent = ((FieldGroup) field).getGroup().getItems().get(0); 1345 1346 // check first component type to extract value 1347 if (firstComponent != null && firstComponent instanceof Field) { 1348 value = getSimpleFieldValue(model, (Field) firstComponent); 1349 } else if (firstComponent instanceof Action && StringUtils.isNotBlank( 1350 ((Action) firstComponent).getActionLabel())) { 1351 value = ((Action) firstComponent).getActionLabel(); 1352 } else if (firstComponent instanceof Action && ((Action) firstComponent).getActionImage() != null) { 1353 value = ((Action) firstComponent).getActionImage().getAltText(); 1354 } else if (firstComponent instanceof Link) { 1355 value = ((Link) firstComponent).getLinkText(); 1356 } else if (firstComponent instanceof Image) { 1357 value = ((Image) firstComponent).getAltText(); 1358 } else if (firstComponent instanceof org.kuali.rice.krad.uif.element.Message) { 1359 value = ((org.kuali.rice.krad.uif.element.Message) firstComponent).getMessageText(); 1360 } else { 1361 value = null; 1362 } 1363 } 1364 1365 return value; 1366 } 1367 1368 /** 1369 * Helper method to change common characters into HTML attribute safe characters 1370 * 1371 * @param message the string to convert 1372 * @return the converted string with quotes, sing quotes, and slash replaced 1373 */ 1374 public static String convertToHTMLAttributeSafeString(String message) { 1375 if (StringUtils.isBlank(message)) { 1376 return message; 1377 } 1378 1379 if (message.contains("\"")) { 1380 message = message.replace("\"", """); 1381 } 1382 if (message.contains("'")) { 1383 message = message.replace("'", "'"); 1384 } 1385 if (message.contains("\\")) { 1386 message = message.replace("\\", "\"); 1387 } 1388 1389 return message; 1390 } 1391 1392 /** 1393 * Get the rowCss for the line specified, by evaluating the conditionalRowCssClasses map for that row 1394 * 1395 * @param conditionalRowCssClasses the conditionalRowCssClass map, where key is the condition and value is 1396 * the class(es) 1397 * @param lineIndex the line/row index 1398 * @param isOdd true if the row is considered odd 1399 * @param lineContext the lineContext for expressions, pass null if not applicable 1400 * @param expressionEvaluator the expressionEvaluator, pass null if not applicable 1401 * @return row csss class String for the class attribute of this row 1402 */ 1403 public static String generateRowCssClassString(Map<String, String> conditionalRowCssClasses, int lineIndex, 1404 boolean isOdd, Map<String, Object> lineContext, ExpressionEvaluator expressionEvaluator) { 1405 String rowCss = ""; 1406 if (conditionalRowCssClasses == null || conditionalRowCssClasses.isEmpty()) { 1407 return rowCss; 1408 } 1409 1410 for (String cssRule : conditionalRowCssClasses.keySet()) { 1411 if (cssRule.startsWith(UifConstants.EL_PLACEHOLDER_PREFIX) && lineContext != null && 1412 expressionEvaluator != null) { 1413 String outcome = expressionEvaluator.evaluateExpressionTemplate(lineContext, cssRule); 1414 if (outcome != null && Boolean.parseBoolean(outcome)) { 1415 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1416 } 1417 } else if (cssRule.equals(UifConstants.RowSelection.ALL)) { 1418 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1419 } else if (cssRule.equals(UifConstants.RowSelection.EVEN) && !isOdd) { 1420 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1421 } else if (cssRule.equals(UifConstants.RowSelection.ODD) && isOdd) { 1422 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1423 } else if (StringUtils.isNumeric(cssRule) && (lineIndex + 1) == Integer.parseInt(cssRule)) { 1424 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1425 } 1426 } 1427 1428 rowCss = StringUtils.removeStart(rowCss, " "); 1429 1430 return rowCss; 1431 } 1432 1433 /** 1434 * LegacyCase - This method simply uses PojoPropertyUtilsBean logic to get the Class of a Class property. 1435 * This method does not have any of the logic needed to obtain the Class of an element of a Collection specified in 1436 * the DataDictionary. 1437 * 1438 * @param object An instance of the Class of which we're trying to get the property Class. 1439 * @param propertyName The name of the property. 1440 * @return property type 1441 * @throws IllegalAccessException 1442 * @throws NoSuchMethodException 1443 * @throws InvocationTargetException 1444 */ 1445 static public Class easyGetPropertyType(Object object, 1446 String propertyName) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { 1447 if (LegacyUtils.useLegacyForObject(object)) { 1448 return PropertyUtils.getPropertyType(object, propertyName); 1449 } 1450 return KradDataServiceLocator.getDataObjectService().wrap(object).getPropertyType(propertyName); 1451 } 1452 1453 /** 1454 * Sets the property of an object with the given value. Converts using the formatter of the type for the property. 1455 * Note: propertyType does not need passed, is found by util method. 1456 * 1457 * @param bo business object 1458 * @param propertyName property name 1459 * @param propertyValue propery value 1460 * @throws IllegalAccessException 1461 * @throws InvocationTargetException 1462 * @throws NoSuchMethodException 1463 */ 1464 public static void setObjectProperty(Object bo, String propertyName, 1465 Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1466 Class propertyType = easyGetPropertyType(bo, propertyName); 1467 setObjectProperty(bo, propertyName, propertyType, propertyValue); 1468 1469 } 1470 1471 /** 1472 * Sets the property of an object with the given value. Converts using the formatter of the given type if one is 1473 * found. 1474 * 1475 * @param bo 1476 * @param propertyName 1477 * @param propertyType 1478 * @param propertyValue 1479 * @throws NoSuchMethodException 1480 * @throws InvocationTargetException 1481 * @throws IllegalAccessException 1482 */ 1483 public static void setObjectProperty(Object bo, String propertyName, Class propertyType, 1484 Object propertyValue) throws FormatException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1485 KRADServiceLocatorWeb.getLegacyDataAdapter().setObjectProperty(bo, propertyName, propertyType, propertyValue); 1486 } 1487 1488 /** 1489 * Removes all query characters from a string. 1490 * 1491 * @param string 1492 * @return Cleaned string 1493 */ 1494 public static String clean(String string) { 1495 for (SearchOperator op : SearchOperator.QUERY_CHARACTERS) { 1496 string = StringUtils.replace(string, op.op(), KRADConstants.EMPTY_STRING); 1497 } 1498 return string; 1499 } 1500 1501 /** 1502 * This method is a confirms if object is null, and unproxies if necessary to determine this 1503 * 1504 * @param object - any object, proxied or not, materialized or not 1505 * @return true if the object (or underlying materialized object) is null, false otherwise 1506 */ 1507 public static boolean isNull(Object object) { 1508 if (object == null) { 1509 return true; 1510 } 1511 return KRADServiceLocatorWeb.getLegacyDataAdapter().isNull(object); 1512 } 1513 1514 /** 1515 * This method is a confirms if object is not null, and unproxies if necessary to determine this 1516 * 1517 * @param object - any object, proxied or not, materialized or not 1518 * @return true if the object (or underlying materialized object) is not null, true if its null 1519 */ 1520 public static boolean isNotNull(Object object) { 1521 return !isNull(object); 1522 } 1523 1524 /** 1525 * Attempts to find the Class for the given potentially proxied object 1526 * 1527 * @param object the potentially proxied object to find the Class of 1528 * @return the best Class which could be found for the given object 1529 */ 1530 public static Class materializeClassForProxiedObject(Object object) { 1531 return KRADServiceLocatorWeb.getLegacyDataAdapter().materializeClassForProxiedObject(object); 1532 } 1533 1534 /** 1535 * This method runs the KRADUtils.isNotNull() method for each item in a list of BOs. KRADUtils.isNotNull() will 1536 * materialize 1537 * the objects if they are currently OJB proxies. 1538 * 1539 * @param possiblyProxiedObjects - a Collection of objects that may be proxies 1540 */ 1541 public static void materializeObjects(Collection possiblyProxiedObjects) { 1542 for (Iterator i = possiblyProxiedObjects.iterator(); i.hasNext(); ) { 1543 KRADUtils.isNotNull(i.next()); 1544 } 1545 } 1546 1547 /** 1548 * Returns the prefix of a nested attribute name, or the empty string if the attribute name is not nested. 1549 * 1550 * @param attributeName 1551 * @return everything BEFORE the last "." character in attributeName 1552 */ 1553 public static String getNestedAttributePrefix(String attributeName) { 1554 int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(attributeName); 1555 1556 return lastIndex != -1 ? StringUtils.substring(attributeName, 0, lastIndex) : StringUtils.EMPTY; 1557 } 1558 1559 /** 1560 * Returns the primitive part of an attribute name string. 1561 * 1562 * @param attributeName 1563 * @return everything AFTER the last "." character in attributeName 1564 */ 1565 public static String getNestedAttributePrimitive(String attributeName) { 1566 int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(attributeName); 1567 1568 return lastIndex != -1 ? StringUtils.substring(attributeName, lastIndex + 1) : attributeName; 1569 } 1570 1571 /** 1572 * This method safely extracts either simple values OR nested values. For example, if the bo is SubAccount, and the 1573 * fieldName is 1574 * a21SubAccount.subAccountTypeCode, this thing makes sure it gets the value off the very end attribute, no matter 1575 * how deeply 1576 * nested it is. The code would be slightly simpler if this was done recursively, but this is safer, and consumes a 1577 * constant 1578 * amount of memory, no matter how deeply nested it goes. 1579 * 1580 * @param bo 1581 * @param fieldName 1582 * @return The field value if it exists. If it doesnt, and the name is invalid, and 1583 */ 1584 public static Object getNestedValue(Object bo, String fieldName) { 1585 return KRADServiceLocatorWeb.getLegacyDataAdapter().getNestedValue(bo, fieldName); 1586 } 1587 1588 /** 1589 * This method safely creates a object from a class 1590 * Convenience method to create new object and throw a runtime exception if it cannot 1591 * If the class is an {@link org.kuali.rice.krad.bo.ExternalizableBusinessObject}, this method will determine the 1592 * interface for the EBO and 1593 * query the 1594 * appropriate module service to create a new instance. 1595 * 1596 * @param clazz 1597 * @return a newInstance() of clazz 1598 */ 1599 public static <T> T createNewObjectFromClass(Class<T> clazz) { 1600 if (clazz == null) { 1601 throw new RuntimeException("BO class was passed in as null"); 1602 } 1603 return (T) KRADServiceLocatorWeb.getLegacyDataAdapter().createNewObjectFromClass(clazz); 1604 } 1605 1606 private static KualiModuleService getKualiModuleService() { 1607 if (kualiModuleService == null) { 1608 kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService(); 1609 } 1610 return kualiModuleService; 1611 } 1612 1613 /** 1614 * Updates the properties of the given component instance with the value found from the 1615 * corresponding map of client state (if found) 1616 * 1617 * @param component component instance to update 1618 * @param clientSideState map of state to sync with 1619 */ 1620 public static void syncClientSideStateForComponent(Component component, Map<String, Object> clientSideState) { 1621 // find the map of state that was sent for component (if any) 1622 Map<String, Object> componentState = null; 1623 if (component instanceof View) { 1624 componentState = clientSideState; 1625 } else { 1626 if (clientSideState.containsKey(component.getId())) { 1627 componentState = (Map<String, Object>) clientSideState.get(component.getId()); 1628 } 1629 } 1630 1631 // if state was sent, match with fields on the component that are annotated to have client state 1632 if ((componentState != null) && (!componentState.isEmpty())) { 1633 Map<String, Annotation> annotatedFields = CopyUtils.getFieldsWithAnnotation(component.getClass(), 1634 ClientSideState.class); 1635 1636 for (Map.Entry<String, Annotation> annotatedField : annotatedFields.entrySet()) { 1637 ClientSideState clientSideStateAnnot = (ClientSideState) annotatedField.getValue(); 1638 1639 String variableName = clientSideStateAnnot.variableName(); 1640 if (StringUtils.isBlank(variableName)) { 1641 variableName = annotatedField.getKey(); 1642 } 1643 1644 if (componentState.containsKey(variableName)) { 1645 Object value = componentState.get(variableName); 1646 ObjectPropertyUtils.setPropertyValue(component, annotatedField.getKey(), value); 1647 } 1648 } 1649 } 1650 } 1651 1652}