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