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