/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.PersistenceService; import org.springframework.core.io.ClassPathResource; /** * provide with a set of utilities that can be used to prepare test data for unit testing. The core idea is to convert Java * properties into a list of specified business objects or search criteria. */ public class TestDataPreparator { public static final String DEFAULT_FIELD_NAMES = "fieldNames"; public static final String DEFAULT_DELIMINATOR = "deliminator"; /** * load properties from the given class path resource. The class path is different than the absolute path. If a resource is * located at /project/test/org/kuali/kfs/util/message.properties, then its class path is org/kuali/kfs/util/message.properties, * which is the fully-qualified Java package name plus the resource name. * * @param classPath the given class path of a resource * @return properties loaded from the given resource. */ public static Properties loadPropertiesFromClassPath(String classPath) { Properties properties = new Properties(); try { properties.load(TestDataPreparator.class.getClassLoader().getResourceAsStream(classPath)); } catch (IOException e) { throw new RuntimeException("Invalid class path: " + classPath + e); } return properties; } /** * build a list of objects of type "clazz" from the test data provided by the given properties. The default fieldNames and * deliminator are used. * * @param clazz the the specified object type * @param properties the given properties that contain the test data * @param propertyKeyPrefix the test data with the given key prefix can be used to construct the return objects * @param numberOfData the number of test data matching the search criteria * @return a list of objects of type "clazz" from the test data provided by the given properties */ public static <T> List<T> buildTestDataList(Class<? extends T> clazz, Properties properties, String propertyKeyPrefix, int numberOfData) { String fieldNames = properties.getProperty(DEFAULT_FIELD_NAMES); String deliminator = properties.getProperty(DEFAULT_DELIMINATOR); return buildTestDataList(clazz, properties, propertyKeyPrefix, fieldNames, deliminator, numberOfData); } /** * build a list of objects of type "clazz" from the test data provided by the given properties * * @param clazz the the specified object type * @param properties the given properties that contain the test data * @param propertyKeyPrefix the test data with the given key prefix can be used to construct the return objects * @param fieldNames the field names of the test data columns * @param deliminator the deliminator that is used to separate the field from each other * @param numberOfData the number of test data matching the search criteria * @return a list of objects of type "clazz" from the test data provided by the given properties */ public static <T> List<T> buildTestDataList(Class<? extends T> clazz, Properties properties, String propertyKeyPrefix, String fieldNames, String deliminator, int numberOfData) { List<T> testDataList = new ArrayList<T>(); for (int i = 1; i <= numberOfData; i++) { String propertyKey = propertyKeyPrefix + i; T testData = buildTestDataObject(clazz, properties, propertyKey, fieldNames, deliminator); testDataList.add(testData); } return testDataList; } /** * build an object of type "clazz" from the test data provided by the given properties * * @param clazz the the specified object type * @param properties the given properties that contain the test data * @param propertyKey the test data with the given key * @param fieldNames the field names of the test data columns * @param deliminator the deliminator that is used to separate the field from each other * @return an object of type "clazz" from the test data provided by the given properties */ public static <T> T buildTestDataObject(Class<? extends T> clazz, Properties properties, String propertyKey, String fieldNames, String deliminator) { T testData = null; try { testData = clazz.newInstance(); ObjectUtil.populateBusinessObject(testData, properties, propertyKey, fieldNames, deliminator); } catch (Exception e) { e.printStackTrace(); } return testData; } /** * build an object of type "clazz" from the test data provided by the given properties * * @param clazz the the specified object type * @param properties the given properties that contain the test data * @param propertyKey the test data with the given key * @return an object of type "clazz" from the test data provided by the given properties */ public static <T> T buildTestDataObject(Class<? extends T> clazz, Properties properties, String propertyKey) { String fieldNames = properties.getProperty(DEFAULT_FIELD_NAMES); String deliminator = properties.getProperty(DEFAULT_DELIMINATOR); T testData = null; try { testData = clazz.newInstance(); ObjectUtil.populateBusinessObject(testData, properties, propertyKey, fieldNames, deliminator); } catch (Exception e) { e.printStackTrace(); } return testData; } /** * build a list of objects of type "clazz" from the expected results provided by the given properties. The default fieldNames * and deliminator are used. * * @param clazz the the specified object type. The instance of this type should be comparable through overriding Object.equals() * @param properties the given properties that contain the expected results * @param propertyKeyPrefix the expected results with the given key prefix can be used to construct the return objects * @param numberOfData the number of the expected results matching the search criteria * @return a list of objects of type "clazz" from the expected results provided by the given properties */ public static <T> List<T> buildExpectedValueList(Class<? extends T> clazz, Properties properties, String propertyKeyPrefix, int numberOfData) { String fieldNames = properties.getProperty(DEFAULT_FIELD_NAMES); String deliminator = properties.getProperty(DEFAULT_DELIMINATOR); return buildExpectedValueList(clazz, properties, propertyKeyPrefix, fieldNames, deliminator, numberOfData); } /** * build a list of objects of type "clazz" from the expected results provided by the given properties * * @param clazz the the specified object type. The instance of this type should be comparable through overriding Object.equals() * @param properties the given properties that contain the expected results * @param propertyKeyPrefix the expected results with the given key prefix can be used to construct the return objects * @param fieldNames the field names of the expected results columns * @param deliminator the deliminator that is used to separate the field from each other * @param numberOfData the number of the expected results matching the search criteria * @return a list of objects of type "clazz" from the expected results provided by the given properties */ public static <T> List<T> buildExpectedValueList(Class<? extends T> clazz, Properties properties, String propertyKeyPrefix, String fieldNames, String deliminator, int numberOfData) { List<T> expectedDataList = new ArrayList<T>(); for (int i = 1; i <= numberOfData; i++) { String propertyKey = propertyKeyPrefix + i; try { T expectedData = TestDataPreparator.buildTestDataObject(clazz, properties, propertyKey, fieldNames, deliminator); if (!expectedDataList.contains(expectedData)) { expectedDataList.add(expectedData); } } catch (Exception e) { e.printStackTrace(); } } return expectedDataList; } /** * build the cleanup criteria for "clazz" from the given properties. The default fieldNames and deliminator are used. * * @param clazz the the specified object type. * @param properties the given properties that contain the cleanup criteria fields and values * @param propertyKey the given property whose value provides the cleanup criteria values * @return the cleanup criteria for "clazz" from the given properties */ public static <T> Map<String, Object> buildCleanupCriteria(Class<? extends T> clazz, Properties properties, String propertyKey) { String fieldNames = properties.getProperty(DEFAULT_FIELD_NAMES); String deliminator = properties.getProperty(DEFAULT_DELIMINATOR); return buildCleanupCriteria(clazz, properties, propertyKey, fieldNames, deliminator); } /** * build the cleanup criteria for "clazz" from the given properties. * * @param clazz the the specified object type. * @param properties the given properties that contain the cleanup criteria fields and values * @param propertyKey the given property whose value provides the cleanup criteria values * @param fieldNames the field names of the cleanup columns * @param deliminator the deliminator that is used to separate the field from each other * @return the cleanup criteria for "clazz" from the given properties */ public static <T> Map<String, Object> buildCleanupCriteria(Class<? extends T> clazz, Properties properties, String propertyKey, String fieldNames, String deliminator) { T instanceOfClazz = TestDataPreparator.buildTestDataObject(clazz, properties, propertyKey, fieldNames, deliminator); return ObjectUtil.buildPropertyMap(instanceOfClazz, Arrays.asList(StringUtils.split(fieldNames, deliminator))); } /** * persist the given data object if it is not in the persistent store * * @param dataObject the given data object * @return return the data object persisted into the data store */ public static <T extends PersistableBusinessObject> T persistDataObject(T dataObject) { T existingDataObject = (T) getBusinessObjectService().retrieve(dataObject); if (existingDataObject == null) { getBusinessObjectService().save(dataObject); getPersistenceService().retrieveNonKeyFields(dataObject); return dataObject; } else { ObjectUtil.buildObject(existingDataObject, dataObject); } return existingDataObject; } /** * persist the given data object if it is not in the persistent store * * @param dataObject the given data object * @return return the data object persisted into the data store */ public static <T extends PersistableBusinessObject> void persistDataObject(List<T> dataObjects) { for (T dataObject : dataObjects) { persistDataObject(dataObject); } } /** * remove the existing data from the database so that they cannot affact the test results */ public static <T extends PersistableBusinessObject> void doCleanUpWithoutReference(Class<T> clazz, Properties properties, String propertykey, String fieldNames, String deliminator) throws Exception { Map<String, Object> fieldValues = buildFieldValues(clazz, properties, propertykey, fieldNames, deliminator); getBusinessObjectService().deleteMatching(clazz, fieldValues); } /** * remove the existing data from the database so that they cannot affact the test results */ public static <T extends PersistableBusinessObject> void doCleanUpWithReference(Class<T> clazz, Properties properties, String propertykey, String fieldNames, String deliminator) throws Exception { List<T> dataObjects = findMatching(clazz, properties, propertykey, fieldNames, deliminator); for (T object : dataObjects) { getBusinessObjectService().delete(object); } } /** * remove the existing data from the database so that they cannot affact the test results */ public static <T extends PersistableBusinessObject> List<T> findMatching(Class<T> clazz, Properties properties, String propertykey, String fieldNames, String deliminator) throws Exception { Map<String, Object> fieldValues = buildFieldValues(clazz, properties, propertykey, fieldNames, deliminator); return (List<T>) getBusinessObjectService().findMatching(clazz, fieldValues); } /** * build the field name and value pairs from the given properties in the specified properties file */ public static <T> Map<String, Object> buildFieldValues(Class<T> clazz, Properties properties, String propertykey, String fieldNames, String deliminator) throws Exception { T cleanup = clazz.newInstance(); ObjectUtil.populateBusinessObject(cleanup, properties, propertykey, fieldNames, deliminator); return ObjectUtil.buildPropertyMap(cleanup, Arrays.asList(StringUtils.split(fieldNames, deliminator))); } /** * test if the given object is in the given collection. The given key fields can be used for the comparison. * * @return true if the given object is in the given collection; otherwise, false */ public static <T> boolean contains(List<T> collection, T object, List<String> keyFields) { for (T objectInCollection : collection) { boolean contains = ObjectUtil.equals(objectInCollection, object, keyFields); if (contains) { return true; } } return false; } /** * test if the given two collections contain the exactly same elements. The given key fields can be used for the comparison. * * @return true if the given two collections contain the exactly same elements; otherwise, false */ public static <T> boolean hasSameElements(List<T> collection1, List<T> collection2, List<String> keyFields) { if (collection1 == collection2) { return true; } if (collection1 == null || collection2 == null) { return false; } if (collection1.size() != collection2.size()) { return false; } for (T object : collection2) { boolean contains = contains(collection1, object, keyFields); if (!contains) { return false; } } return true; } /** * Generates transaction data for a business object from properties * * @param businessObject the transction business object * @return the transction business object with data * @throws Exception thrown if an exception is encountered for any reason */ public static <T> T buildTestDataObject(Class<? extends T> clazz, Properties properties) { T testData = null; try { testData = clazz.newInstance(); Iterator propsIter = properties.keySet().iterator(); while (propsIter.hasNext()) { String propertyName = (String) propsIter.next(); String propertyValue = (String) properties.get(propertyName); // if searchValue is empty and the key is not a valid property ignore if (StringUtils.isBlank(propertyValue) || !(PropertyUtils.isWriteable(testData, propertyName))) { continue; } String propertyType = PropertyUtils.getPropertyType(testData, propertyName).getSimpleName(); Object finalPropertyValue = ObjectUtil.valueOf(propertyType, propertyValue); if (finalPropertyValue != null) { PropertyUtils.setProperty(testData, propertyName, finalPropertyValue); } } } catch (Exception e) { throw new RuntimeException("Cannot build a test data object with the given data. " + e); } return testData; } /** * get an instant of BusinessObjectService * * @return an instant of BusinessObjectService */ private static BusinessObjectService getBusinessObjectService() { return SpringContext.getBean(BusinessObjectService.class); } /** * get an instant of PersistenceService * * @return an instant of PersistenceService */ private static PersistenceService getPersistenceService() { return SpringContext.getBean(PersistenceService.class); } }