001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.util;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.util.Truth;
020import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
021import org.kuali.rice.core.api.CoreApiServiceLocator;
022import org.kuali.rice.core.api.encryption.EncryptionService;
023import org.kuali.rice.core.api.util.type.KualiDecimal;
024import org.kuali.rice.coreservice.framework.parameter.ParameterConstants;
025import org.kuali.rice.coreservice.framework.parameter.ParameterService;
026import org.kuali.rice.core.web.format.BooleanFormatter;
027import org.kuali.rice.krad.UserSession;
028import org.kuali.rice.krad.service.KRADServiceLocator;
029import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
030import org.kuali.rice.krad.service.KualiModuleService;
031import org.kuali.rice.krad.service.ModuleService;
032import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
033
034import javax.servlet.ServletRequest;
035import javax.servlet.http.HttpServletRequest;
036import java.lang.reflect.Constructor;
037import java.lang.reflect.InvocationTargetException;
038import java.lang.reflect.Method;
039import java.security.GeneralSecurityException;
040import java.text.NumberFormat;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.Collection;
044import java.util.HashMap;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Map;
048import java.util.Properties;
049import java.util.regex.Pattern;
050
051/**
052 * Miscellaneous Utility Methods
053 *
054 * @author Kuali Rice Team (rice.collab@kuali.org)
055 */
056public final class KRADUtils {
057    private static KualiModuleService kualiModuleService;
058
059    private KRADUtils() {
060        throw new UnsupportedOperationException("do not call");
061    }
062
063    public final static String getBusinessTitleForClass(Class<? extends Object> clazz) {
064        if (clazz == null) {
065            throw new IllegalArgumentException(
066                    "The getBusinessTitleForClass method of KRADUtils requires a non-null class");
067        }
068        String className = clazz.getSimpleName();
069
070        StringBuffer label = new StringBuffer(className.substring(0, 1));
071        for (int i = 1; i < className.length(); i++) {
072            if (Character.isLowerCase(className.charAt(i))) {
073                label.append(className.charAt(i));
074            } else {
075                label.append(" ").append(className.charAt(i));
076            }
077        }
078        return label.toString().trim();
079    }
080
081    /**
082     * Picks off the filename from the full path. Takes care of different OS seperators.
083     */
084    public final static List<String> getFileNameFromPath(List<String> fullFileNames) {
085        List<String> fileNameList = new ArrayList<String>();
086
087        for (String fullFileName : fullFileNames) {
088            if (StringUtils.contains(fullFileName, "/")) {
089                fileNameList.add(StringUtils.substringAfterLast(fullFileName, "/"));
090            } else {
091                fileNameList.add(StringUtils.substringAfterLast(fullFileName, "\\"));
092            }
093        }
094
095        return fileNameList;
096    }
097
098    private static final KualiDecimal ONE_HUNDRED = new KualiDecimal("100.00");
099
100    /**
101     * Convert the given monney amount into an interger string. Since the return string cannot have decimal point,
102     * multiplies the
103     * amount by 100 so the decimal places are not lost, for example, 320.15 is converted into 32015.
104     *
105     * @return an integer string of the given money amount through multiplicating by 100 and removing the fraction
106     *         portion.
107     */
108    public final static String convertDecimalIntoInteger(KualiDecimal decimalNumber) {
109        KualiDecimal decimalAmount = decimalNumber.multiply(ONE_HUNDRED);
110        NumberFormat formatter = NumberFormat.getIntegerInstance();
111        String formattedAmount = formatter.format(decimalAmount);
112
113        return StringUtils.replace(formattedAmount, ",", "");
114    }
115
116    public static Integer getIntegerValue(String numberStr) {
117        Integer numberInt = null;
118        try {
119            numberInt = new Integer(numberStr);
120        } catch (NumberFormatException nfe) {
121            Double numberDbl = new Double(numberStr);
122            numberInt = new Integer(numberDbl.intValue());
123        }
124        return numberInt;
125    }
126
127    /**
128     * Attempt to coerce a String attribute value to the given propertyType.  If the transformation can't be made,
129     * either because the propertyType is null or because the transformation required exceeds this method's very small
130     * bag of tricks, then null is returned.
131     *
132     * @param propertyType the Class to coerce the attributeValue to
133     * @param attributeValue the String value to coerce
134     * @return an instance of the propertyType class, or null the transformation can't be made.
135     */
136    public static Object hydrateAttributeValue(Class<?> propertyType, String attributeValue){
137        Object attributeValueObject = null;
138        if ( propertyType!=null && attributeValue!=null ) {
139            if (String.class.equals(propertyType)) {
140                // it's already a String
141                attributeValueObject = attributeValue;
142            } // KULRICE-6808: Kim Role Maintenance - Custom boolean role qualifier values are not being converted properly
143            else if (Boolean.class.equals(propertyType) || Boolean.TYPE.equals(propertyType)) {
144                attributeValueObject = Truth.strToBooleanIgnoreCase(attributeValue);
145            } else {
146                // try to create one with KRADUtils for other misc data types
147                attributeValueObject = KRADUtils.createObject(propertyType, new Class[]{String.class}, new Object[]{attributeValue});
148                // if that didn't work, we'll get a null back
149            }
150        }
151        return attributeValueObject;
152    }
153
154
155    public static Object createObject(Class<?> clazz, Class<?>[] argumentClasses, Object[] argumentValues) {
156        if (clazz == null) {
157            return null;
158        }
159        if (argumentClasses.length == 1 && argumentClasses[0] == String.class) {
160            if (argumentValues.length == 1 && argumentValues[0] != null) {
161                if (clazz == String.class) {
162                    // this means we're trying to create a String from a String
163                    // don't new up Strings, it's a bad idea
164                    return argumentValues[0];
165                } else {
166                    // maybe it's a type that supports valueOf?
167                    Method valueOfMethod = null;
168                    try {
169                        valueOfMethod = clazz.getMethod("valueOf", String.class);
170                    } catch (NoSuchMethodException e) {
171                        // ignored
172                    }
173                    if (valueOfMethod != null) {
174                        try {
175                            return valueOfMethod.invoke(null, argumentValues[0]);
176                        } catch (Exception e) {
177                            // ignored
178                        }
179                    }
180                }
181            }
182        }
183        try {
184            Constructor<?> constructor = clazz.getConstructor(argumentClasses);
185            return constructor.newInstance(argumentValues);
186        } catch (Exception e) {
187            // ignored
188        }
189        return null;
190    }
191
192    public static String joinWithQuotes(List<String> list) {
193        if (list == null || list.size() == 0)
194            return "";
195
196        return KRADConstants.SINGLE_QUOTE +
197                StringUtils.join(list.iterator(), KRADConstants.SINGLE_QUOTE + "," + KRADConstants.SINGLE_QUOTE) +
198                KRADConstants.SINGLE_QUOTE;
199    }
200
201    private static KualiModuleService getKualiModuleService() {
202        if (kualiModuleService == null) {
203            kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService();
204        }
205        return kualiModuleService;
206    }
207
208    /**
209     * TODO this method will probably need to be exposed in a public KRADUtils class as it is used
210     * by several different modules.  That will have to wait until ModuleService and KualiModuleService are moved
211     * to core though.
212     */
213    public static String getNamespaceCode(Class<? extends Object> clazz) {
214        ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(clazz);
215        if (moduleService == null) {
216            return KRADConstants.DEFAULT_NAMESPACE;
217        }
218        return moduleService.getModuleConfiguration().getNamespaceCode();
219    }
220
221    public static Map<String, String> getNamespaceAndComponentSimpleName(Class<? extends Object> clazz) {
222        Map<String, String> map = new HashMap<String, String>();
223        map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz));
224        map.put(KRADConstants.COMPONENT_NAME, getComponentSimpleName(clazz));
225        return map;
226    }
227
228    public static Map<String, String> getNamespaceAndComponentFullName(Class<? extends Object> clazz) {
229        Map<String, String> map = new HashMap<String, String>();
230        map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz));
231        map.put(KRADConstants.COMPONENT_NAME, getComponentFullName(clazz));
232        return map;
233    }
234
235    public static Map<String, String> getNamespaceAndActionClass(Class<? extends Object> clazz) {
236        Map<String, String> map = new HashMap<String, String>();
237        map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz));
238        map.put(KRADConstants.ACTION_CLASS, clazz.getName());
239        return map;
240    }
241
242    private static String getComponentSimpleName(Class<? extends Object> clazz) {
243        return clazz.getSimpleName();
244    }
245
246    private static String getComponentFullName(Class<? extends Object> clazz) {
247        return clazz.getName();
248    }
249
250    /**
251     * Parses a string that is in map format (commas separating map entries, colon separates
252     * map key/value) to a new map instance
253     *
254     * @param parameter - string parameter to parse
255     * @return Map<String, String> instance populated from string parameter
256     */
257    public static Map<String, String> convertStringParameterToMap(String parameter) {
258        Map<String, String> map = new HashMap<String, String>();
259
260        if (StringUtils.isNotBlank(parameter)) {
261            if (StringUtils.contains(parameter, ",")) {
262                String[] fieldConversions = StringUtils.split(parameter, ",");
263
264                for (int i = 0; i < fieldConversions.length; i++) {
265                    String fieldConversionStr = fieldConversions[i];
266                    if (StringUtils.isNotBlank(fieldConversionStr)) {
267                        if (StringUtils.contains(fieldConversionStr, ":")) {
268                            String[] fieldConversion = StringUtils.split(fieldConversionStr, ":");
269                            map.put(fieldConversion[0], fieldConversion[1]);
270                        } else {
271                            map.put(fieldConversionStr, fieldConversionStr);
272                        }
273                    }
274                }
275            } else if (StringUtils.contains(parameter, ":")) {
276                String[] fieldConversion = StringUtils.split(parameter, ":");
277                map.put(fieldConversion[0], fieldConversion[1]);
278            } else {
279                map.put(parameter, parameter);
280            }
281        }
282
283        return map;
284    }
285
286    /**
287     * Parses a string that is in list format (commas separating list entries) to a new List instance
288     *
289     * @param parameter - string parameter to parse
290     * @return List<String> instance populated from string parameter
291     */
292    public static List<String> convertStringParameterToList(String parameter) {
293        List<String> list = new ArrayList<String>();
294
295        if (StringUtils.isNotBlank(parameter)) {
296            if (StringUtils.contains(parameter, ",")) {
297                String[] parameters = StringUtils.split(parameter, ",");
298                List arraysList = Arrays.asList(parameters);
299                list.addAll(arraysList);
300            } else {
301                list.add(parameter);
302            }
303        }
304
305        return list;
306    }
307
308    /**
309     * Translates characters in the given string like brackets that will cause
310     * problems with binding to characters that do not affect the binding
311     *
312     * @param key - string to translate
313     * @return String translated string
314     */
315    public static String translateToMapSafeKey(String key) {
316        String safeKey = key;
317
318        safeKey = StringUtils.replace(safeKey, "[", "_");
319        safeKey = StringUtils.replace(safeKey, "]", "_");
320
321        return safeKey;
322    }
323
324    /**
325     * Builds a string from the given map by joining each entry with a comma and
326     * each key/value pair with a colon
327     *
328     * @param map - map instance to build string for
329     * @return String of map entries
330     */
331    public static String buildMapParameterString(Map<String, String> map) {
332        String parameterString = "";
333
334        for (Map.Entry<String, String> entry : map.entrySet()) {
335            if (StringUtils.isNotBlank(parameterString)) {
336                parameterString += ",";
337            }
338
339            parameterString += entry.getKey() + ":" + entry.getValue();
340        }
341
342        return parameterString;
343    }
344
345    /**
346     * Parses the given string into a Map by splitting on the comma to get the
347     * map entries and within each entry splitting by colon to get the key/value
348     * pairs
349     *
350     * @param parameterString - string to parse into map
351     * @return Map<String, String> map from string
352     */
353    public static Map<String, String> getMapFromParameterString(String parameterString) {
354        Map<String, String> map = new HashMap<String, String>();
355
356        String[] entries = parameterString.split(",");
357        for (int i = 0; i < entries.length; i++) {
358            String[] keyValue = entries[i].split(":");
359            if (keyValue.length != 2) {
360                throw new RuntimeException("malformed field conversion pair: " + Arrays.toString(keyValue));
361            }
362
363            map.put(keyValue[0], keyValue[1]);
364        }
365
366        return map;
367    }
368
369    /**
370     * Retrieves value for the given parameter name in the request and attempts to convert to a Boolean using
371     * the <code>BooleanFormatter</code>
372     *
373     * @param request - servlet request containing parameters
374     * @param parameterName - name of parameter to retrieve value for
375     * @return Boolean set to value of parameter, or null if parameter was not found in request
376     */
377    public static Boolean getRequestParameterAsBoolean(ServletRequest request, String parameterName) {
378        Boolean parameterValue = null;
379
380        String parameterValueStr = request.getParameter(parameterName);
381        if (StringUtils.isNotBlank(parameterValueStr)) {
382            parameterValue = (Boolean) new BooleanFormatter().convertFromPresentationFormat(parameterValueStr);
383        }
384
385        return parameterValue;
386    }
387
388    /**
389     * Translates the given Map of String keys and String array values to a Map
390     * of String key and values. If the String array contains more than one
391     * value, the single string is built by joining the values with the vertical
392     * bar character
393     *
394     * @param requestParameters - Map of request parameters to translate
395     * @return Map<String, String> translated Map
396     */
397    public static Map<String, String> translateRequestParameterMap(Map<String, String[]> requestParameters) {
398        Map<String, String> parameters = new HashMap<String, String>();
399
400        for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) {
401            String parameterValue = "";
402            if (parameter.getValue().length > 1) {
403                parameterValue = StringUtils.join(parameter.getValue(), "|");
404            } else {
405                parameterValue = parameter.getValue()[0];
406            }
407            parameters.put(parameter.getKey(), parameterValue);
408        }
409
410        return parameters;
411    }
412
413    /**
414     * Retrieves parameter values from the request that match the requested
415     * names. In addition, based on the object class an authorization check is
416     * performed to determine if the values are secure and should be decrypted.
417     * If true, the value is decrypted before returning
418     *
419     * @param parameterNames - names of the parameters whose values should be retrieved
420     * from the request
421     * @param parentObjectClass - object class that contains the parameter names as properties
422     * and should be consulted for security checks
423     * @param requestParameters - all request parameters to pull from
424     * @return Map<String, String> populated with parameter name/value pairs
425     *         pulled from the request
426     */
427    public static Map<String, String> getParametersFromRequest(List<String> parameterNames, Class<?> parentObjectClass,
428            Map<String, String> requestParameters) {
429        Map<String, String> parameterValues = new HashMap<String, String>();
430
431        for (Iterator<String> iter = parameterNames.iterator(); iter.hasNext(); ) {
432            String keyPropertyName = iter.next();
433
434            if (requestParameters.get(keyPropertyName) != null) {
435                String keyValue = requestParameters.get(keyPropertyName);
436
437                // Check if this element was encrypted, if it was decrypt it
438                if (KRADServiceLocatorWeb.getDataObjectAuthorizationService()
439                        .attributeValueNeedsToBeEncryptedOnFormsAndLinks(parentObjectClass, keyPropertyName)) {
440                    try {
441                        keyValue = StringUtils.removeEnd(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX);
442                        keyValue = CoreApiServiceLocator.getEncryptionService().decrypt(keyValue);
443                    } catch (GeneralSecurityException e) {
444                        throw new RuntimeException(e);
445                    }
446                }
447
448                parameterValues.put(keyPropertyName, keyValue);
449            }
450        }
451
452        return parameterValues;
453    }
454
455    /**
456     * Builds a Map containing a key/value pair for each property given in the property names list, general
457     * security is checked to determine if the value needs to be encrypted along with applying formatting to
458     * the value
459     *
460     * @param propertyNames - list of property names to get key/value pairs for
461     * @param dataObject - object instance containing the properties for which the values will be pulled
462     * @return Map<String, String> containing entry for each property name with the property name as the map key
463     *         and the property value as the value
464     */
465    public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames,
466            Object dataObject) {
467        Map<String, String> propertyKeyValues = new HashMap<String, String>();
468
469        if (dataObject == null) {
470            return propertyKeyValues;
471        }
472
473        // iterate through properties and add a map entry for each
474        for (String propertyName : propertyNames) {
475            Object propertyValue = ObjectPropertyUtils.getPropertyValue(dataObject, propertyName);
476            if (propertyValue == null) {
477                propertyValue = StringUtils.EMPTY;
478            }
479
480            if (KRADServiceLocatorWeb.getDataObjectAuthorizationService()
481                    .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObject.getClass(), propertyName)) {
482                try {
483                    if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
484                        propertyValue = CoreApiServiceLocator.getEncryptionService().encrypt(propertyValue) +
485                                EncryptionService.ENCRYPTION_POST_PREFIX;
486                    }
487                } catch (GeneralSecurityException e) {
488                    throw new RuntimeException("Exception while trying to encrypt value for key/value map.", e);
489                }
490            }
491 
492            // TODO: need to apply formatting to return value once util class is ready
493            propertyKeyValues.put(propertyName, propertyValue.toString());
494        }
495
496        return propertyKeyValues;
497    }
498
499    /**
500     * Utility method to convert a Map to a Properties object
501     *
502     * @param parameters - map to convert
503     * @return Properties object containing all the map entries
504     */
505    public static Properties convertMapToProperties(Map<String, String> parameters) {
506        Properties properties = new Properties();
507
508        if (parameters != null) {
509            for (Map.Entry<String, String> parameter : parameters.entrySet()) {
510                properties.put(parameter.getKey(), parameter.getValue());
511            }
512        }
513
514        return properties;
515    }
516
517    /**
518     * Utility method to convert a Request Parameters Map to a Properties object
519     *
520     * <p>
521     * Multiple values for a parameter are joined together with comma delimiter
522     * </p>
523     *
524     * @param requestParameters - map to convert
525     * @return Properties object containing all the map entries
526     */
527    public static Properties convertRequestMapToProperties(Map<String, String[]> requestParameters) {
528        Properties properties = new Properties();
529
530        if (requestParameters != null) {
531            for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) {
532                String[] parameterValue = parameter.getValue();
533                String parameterValueString = StringUtils.join(parameterValue, ",");
534
535                properties.put(parameter.getKey(), parameterValueString);
536            }
537        }
538
539        return properties;
540    }
541
542    public static boolean containsSensitiveDataPatternMatch(String fieldValue) {
543        if (StringUtils.isBlank(fieldValue)) {
544            return false;
545        }
546        ParameterService parameterService = CoreFrameworkServiceLocator.getParameterService();
547        Collection<String> sensitiveDataPatterns = parameterService
548                .getParameterValuesAsString(KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT,
549                        KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS);
550        for (String pattern : sensitiveDataPatterns) {
551            if (Pattern.compile(pattern).matcher(fieldValue).find()) {
552                return true;
553            }
554        }
555        return false;
556    }
557
558    /**
559     * Gets the UserSession object from the HttpServletRequest object's
560     * associated session.
561     *
562     * <p>
563     * In some cases (different threads) the UserSession cannot be retrieved
564     * from GlobalVariables but can still be accessed via the session object
565     * </p>
566     */
567    public static final UserSession getUserSessionFromRequest(HttpServletRequest request) {
568        return (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY);
569    }
570
571    /**
572     * @return whether the deploy environment is production
573     */
574    public static boolean isProductionEnvironment() {
575        return KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
576                KRADConstants.PROD_ENVIRONMENT_CODE_KEY)
577                .equalsIgnoreCase(KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
578                        KRADConstants.ENVIRONMENT_KEY));
579    }
580
581}