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 }