com.ms.commons.test.BaseTestCase.java Source code

Java tutorial

Introduction

Here is the source code for com.ms.commons.test.BaseTestCase.java

Source

/*
 * Copyright 2011-2016 ZXC.com All right reserved. This software is the confidential and proprietary information of
 * ZXC.com ("Confidential Information"). You shall not disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into with ZXC.com.
 */
package com.ms.commons.test;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import junit.framework.TestCase;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.test.AbstractTransactionalSpringContextTests;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.ms.commons.core.CommonServiceLocator;
import com.ms.commons.test.annotation.Mock;
import com.ms.commons.test.annotation.PrepareAnnotationTool;
import com.ms.commons.test.annotation.SecondaryJdbcSetting;
import com.ms.commons.test.annotation.TestCaseInfo;
import com.ms.commons.test.assertion.Assert;
import com.ms.commons.test.assertion.exception.AssertException;
import com.ms.commons.test.assertion.impl.DataBaseAssertion;
import com.ms.commons.test.assertion.impl.ForEachAssertion;
import com.ms.commons.test.assertion.impl.JavaBeanAssertion;
import com.ms.commons.test.assertion.impl.ParameterizedAssertion;
import com.ms.commons.test.cache.BuiltInCacheKey;
import com.ms.commons.test.cache.ThreadContextCache;
import com.ms.commons.test.common.ExceptionUtil;
import com.ms.commons.test.common.ReflectUtil;
import com.ms.commons.test.common.SortUtil;
import com.ms.commons.test.common.StringUtil;
import com.ms.commons.test.common.comparator.Comparator;
import com.ms.commons.test.common.dbencoding.DbEncodingUtil;
import com.ms.commons.test.common.dbencoding.impl.info.CnStringDbEncodingInfo;
import com.ms.commons.test.common.dbencoding.impl.info.UTF8StringDbEncodingInfo;
import com.ms.commons.test.common.task.Task;
import com.ms.commons.test.common.task.TaskUtil;
import com.ms.commons.test.constants.IntlTestGlobalConstants;
import com.ms.commons.test.context.TestCaseRuntimeInfo;
import com.ms.commons.test.database.JdbcManagementTool;
import com.ms.commons.test.database.JdbcManagementToolBuilder;
import com.ms.commons.test.database.SecondaryPreareFilter;
import com.ms.commons.test.database.SecondarySetting;
import com.ms.commons.test.datareader.DataReaderType;
import com.ms.commons.test.datareader.DataReaderUtil;
import com.ms.commons.test.datareader.exception.ResourceNotFoundException;
import com.ms.commons.test.datareader.impl.BaseReaderUtil;
import com.ms.commons.test.integration.junit4.internal.IntlTestBlockJUnit4ClassRunner;
import com.ms.commons.test.memorydb.MemoryDatabase;
import com.ms.commons.test.mock.AliMock;
import com.ms.commons.test.mock.MockBeanPostProcessor;
import com.ms.commons.test.mock.inject.proxy.ProxyDepackageUtil;
import com.ms.commons.test.prepare.PrepareUtil;
import com.ms.commons.test.prepare.event.PrepareEventUtil;
import com.ms.commons.test.prepare.event.impl.ClearSpecialDataPrepareEvent;
import com.ms.commons.test.prepare.impl.DataBasePreparation;
import com.ms.commons.test.prepare.impl.ForEachPreparation;
import com.ms.commons.test.prepare.impl.JavaBeanPreparation;
import com.ms.commons.test.prepare.impl.YamlPreparation;
import com.ms.commons.test.treedb.JsonObjectUtils;
import com.ms.commons.test.treedb.TreeDatabase;
import com.ms.commons.test.treedb.TreeObject;
import com.ms.commons.test.treedb.TreeObjectAssert;

/**
 * @author zxc Apr 13, 2013 10:56:47 PM
 */
@RunWith(IntlTestBlockJUnit4ClassRunner.class)
public abstract class BaseTestCase extends AbstractBaseTestCase {

    protected final Logger log = Logger.getLogger(getClass());

    protected static final String DEFAULT_CLASS_SUFFIX = "Data";

    protected static final String TESTCASE_BASE_PATH = "testcase.base.path";

    protected static volatile boolean firstTimeRan = false;
    protected static volatile Map<List<Object>, Object> objectMethodMap = new HashMap<List<Object>, Object>();

    static {
        long BEGIN_TIME = System.currentTimeMillis();

        Locale.setDefault(Locale.ENGLISH);
        System.setProperty("ark.trace.sql.stack", "false");
        System.setProperty("ark.trace.sql", "SUDI");
        // System.setProperty("com.ms.ark.trace.TraceHandler", "com.ms.ark.trace.LoggingTraceHandler");

        System.err.println("TEST STATIC INIT  TIME " + (System.currentTimeMillis() - BEGIN_TIME));
    }

    protected JdbcManagementTool jdbcManagementTool = null;

    public BaseTestCase() {
        System.setProperty(TESTCASE_BASE_PATH, getAbstractBasePath());
    }

    protected Object getService(String name) {
        return applicationContext.getBean(name);
    }

    /**
     * Spring?IDServiceID
     */
    @Override
    protected Object contextKey() {
        TestCaseInfo testCaseInfo = this.getClass().getAnnotation(TestCaseInfo.class);
        if (!testCaseInfo.autoWire()) {
            return null;
        }
        if ((testCaseInfo != null) && (testCaseInfo.contextKey() != null)
                && (testCaseInfo.contextKey().length() != 0)) {
            return testCaseInfo.contextKey();
        } else {
            if (testCaseInfo.useDataSourceContextKey()) {
                return "DatasourceServicesLocator";
            }
            throw new RuntimeException(
                    "You SHOULD override contextKey() or add TestCaseInfo to the test class or set autoWire false or add useDataSourceContextKey=true.");
        }
    }

    /**
     * ??Spring
     */
    @Override
    protected ConfigurableApplicationContext loadContext(Object key) throws Exception {
        return (ConfigurableApplicationContext) CommonServiceLocator.getApplicationContext();
    }

    /**
     * ?
     * 
     * <pre>
     *  {@link TestCaseInfo#autoWire()}{@link AbstractDependencyInjectionSpringContextTests#prepareTestInstance()} ?
     * dataSourcetranscationManager.
     * </pre>
     */
    @Override
    protected void prepareTestInstance() throws Exception {
        TestCaseInfo testCaseInfo = this.getClass().getAnnotation(TestCaseInfo.class);

        this.initMoke();
        this.setDependencyCheck(false);

        // ??
        if (testCaseInfo.autoWire()) {
            this.setAutowireMode(testCaseInfo.autoWireMode());
        } else {
            this.setAutowireMode(AUTOWIRE_NO);
        }

        // jdbcExtractor
        // if ((testCaseInfo == null) || testCaseInfo.useJdbcExtractor()) {
        // this.applicationContext.getBean("jdbcExtractor");
        // }

        // AbstractDependencyInjectionSpringContextTests ?
        super.prepareTestInstance();
        afterPrepareTestInstance();
        // Mock current test instance.
        if (isMockOn()) {
            AliMock.mockObject(this);
        }

    }

    protected void afterPrepareTestInstance() {

    }

    /**
     * Service???
     * 
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    protected void initMoke() throws InstantiationException, IllegalAccessException {
        // Mock once on startup
        if (isMockOn()) {

            if (log.isDebugEnabled()) {
                log.debug("================>initMock!");
            }
            ConfigurableApplicationContext context = (ConfigurableApplicationContext) CommonServiceLocator
                    .getApplicationContext();
            context.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {

                public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                        throws BeansException {
                    beanFactory.addBeanPostProcessor(new MockBeanPostProcessor());
                }
            });
            context.refresh();
        }
    }

    @Override
    protected void onSetUpBeforeTransaction() throws Exception {
        if (getClass().getAnnotation(TestCaseInfo.class) != null) {
            if (!getClass().getAnnotation(TestCaseInfo.class).defaultRollBack()) {
                log.warn("Set default rollback to false.");
            }
            setDefaultRollback(getClass().getAnnotation(TestCaseInfo.class).defaultRollBack());
        }

        if ((getClass().getAnnotation(SecondaryJdbcSetting.class) != null) && (jdbcManagementTool == null)) {
            SecondaryJdbcSetting setting = getClass().getAnnotation(SecondaryJdbcSetting.class);
            if (setting.jdbcManagementToolBuilder() == JdbcManagementToolBuilder.class) {
                jdbcManagementTool = new JdbcManagementTool(setting);
                jdbcManagementTool
                        .setDefaultRollback(getClass().getAnnotation(TestCaseInfo.class).defaultRollBack());
            } else {
                jdbcManagementTool = ReflectUtil.newInstance(setting.jdbcManagementToolBuilder())
                        .buildJdbcManagementTool(this);
            }
        }

        if (jdbcManagementTool != null) {
            jdbcManagementTool.startTransaction();
            SecondarySetting secondarySetting = null;

            Method method = ThreadContextCache.get(Method.class, BuiltInCacheKey.Method);
            final PrepareAnnotationTool prepareAnnotation = new PrepareAnnotationTool(method);
            if (prepareAnnotation != null) {
                if (prepareAnnotation.secondaryPrepareFilter() != SecondaryPreareFilter.class) {
                    final SecondaryPreareFilter secondaryPreareFilter = ReflectUtil
                            .newInstance(prepareAnnotation.secondaryPrepareFilter());
                    secondarySetting = new SecondarySetting(jdbcManagementTool, secondaryPreareFilter);
                }
            }
            if (secondarySetting == null) {
                SecondaryJdbcSetting secondaryJdbcSetting = getClass().getAnnotation(SecondaryJdbcSetting.class);
                secondarySetting = new SecondarySetting(jdbcManagementTool,
                        ReflectUtil.newInstance(secondaryJdbcSetting.secondaryPrepareFilter()));
            }
            ThreadContextCache.put(BuiltInCacheKey.SecondarySetting, secondarySetting);
        }

    }

    /**
     * ???
     */
    // @Override
    // protected void startNewTransaction() throws TransactionException {
    // // 
    // super.startNewTransaction();
    // // ??
    // try {
    // before();
    // } catch (Exception e) {
    // throw new RuntimeException("Init Test Data Failed", e);
    // }
    // }

    @Override
    protected void onTearDownAfterTransaction() throws Exception {
        if (jdbcManagementTool != null) {
            jdbcManagementTool.finishTrasaction();
            ThreadContextCache.put(BuiltInCacheKey.SecondarySetting, null);
        }
    }

    @Before
    public void before() throws Exception {
        log.debug("BaseTestCase.before()");
        TestCaseInfo testCaseInfo = this.getClass().getAnnotation(TestCaseInfo.class);
        if (testCaseInfo.autoWire()) {
            this.applicationContext = getContext(contextKey());
            prepareTestInstance();
        }
        onSetUp();

        initOnBeforeEveryTestCase();

        if (!firstTimeRan) {
            // ?
            firstTimeRan = true;
            runOnceBeforeAll();
        }
    }

    @After
    @SuppressWarnings("unchecked")
    public void after() throws Exception {
        log.debug("=========================> BaseTestCase.after()");
        if (ThreadContextCache.get(BuiltInCacheKey.Finally) != null) {

            for (Runnable runnable : (List<Runnable>) ThreadContextCache.get(List.class, BuiltInCacheKey.Finally)) {
                try {
                    runnable.run();
                } catch (Exception e) {
                    log.error("Error occured in clear data.", e);
                }
            }
        }

        final List<Task> taskList = (List<Task>) TestCaseRuntimeInfo.current().getContext()
                .get(ClearSpecialDataPrepareEvent.__finish__task__list__);
        if (taskList != null) {
            if (TestCaseRuntimeInfo.current().getPrepare().newThreadTransactionImport()) {
                runInBlockedThread(new Runnable() {

                    public void run() {
                        log.warn("Clear data in new thread (transaction).");
                        TransactionStatus localTransactionStatus = transactionManager
                                .getTransaction(new DefaultTransactionDefinition());
                        try {
                            TaskUtil.runTasks(taskList);
                            transactionManager.commit(localTransactionStatus);
                        } catch (Throwable t) {
                            log.error("Clear data transaction has be rolled back.", t);
                            transactionManager.rollback(localTransactionStatus);
                            throw new RuntimeException(t);
                        }
                    }
                });
            } else {
                TaskUtil.runTasks(taskList);
            }
        }

        super.tearDown();

        if (isMockOn()) {
            AliMock.clearMock();
        }
        objectMethodMap.clear();
        ThreadContextCache.clear();
    }

    // ?
    // ================================================================================

    /**
     * ??
     */
    protected void runOnceBeforeAll() {
    }

    // ================================================================================

    /**
     * ??
     */
    protected Class<?> clazz() {
        TestCaseInfo tci = this.getClass().getAnnotation(TestCaseInfo.class);
        if ((tci == null) || (tci.testFor() == TestCase.class)) {
            String testClassName = this.getClass().getName();
            if (testClassName.endsWith("Test")) {
                try {
                    return Class.forName(testClassName.substring(0, testClassName.length() - 4));
                } catch (ClassNotFoundException e) {
                    System.err.println(
                            "Load class failed: " + testClassName.substring(0, testClassName.length() - 4));
                }
            }
            throw new RuntimeException("No TestCaseInfo or not assigned For class.");
        }
        return tci.testFor();
    }

    /**
     * ?(non-static )
     */
    protected Object invokeMethod(Object object, String methodName, Class<?>[] parameterTypes,
            Object[] parameters) {
        if (object instanceof Proxy) {
            object = ProxyDepackageUtil.getLastDepackageObject(object);
        }
        Class<?> clazz = object.getClass();
        return ReflectUtil.invokeMethod(clazz, object, methodName, parameterTypes, parameters);
    }

    /**
     * {@link BaseTestCase#invokeMethod(Object, String, Class[], Object[])}??
     * 
     * @param object
     * @param methodName
     * @return
     */
    protected Object invokeMethod(Object object, String methodName) {
        return invokeMethod(object, methodName, null, null);
    }

    /**
     * ?(static )
     */
    protected Object invokeMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes,
            Object[] parameters) {
        return ReflectUtil.invokeMethod(clazz, null, methodName, parameterTypes, parameters);
    }

    /**
     * {@link BaseTestCase#invokeMethod(Class, String, Class[], Object[])}??
     * 
     * @param clazz
     * @param methodName
     * @return
     */
    protected Object invokeMethod(Class<?> clazz, String methodName) {
        return invokeMethod(clazz, methodName, null, null);
    }

    /**
     * 
     * 
     * @return
     */
    protected Object[] nulls() {
        return new Object[] { null };
    }

    /**
     * 
     * 
     * @param methodName
     * @param params
     * @return
     */
    protected Object callStatic(String methodName, Object... params) {
        return callMethod(null, methodName, params);
    }

    /**
     * 
     * 
     * @param obj
     * @param methodName
     * @param params
     * @return
     */
    protected Object callMethod(Object obj, String methodName, Object... params) {
        Class<?> clazz = (obj == null) ? clazz() : ((obj instanceof Class<?>) ? (Class<?>) obj : obj.getClass());
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                if (method.getParameterTypes().length == params.length) {
                    method.setAccessible(true);
                    try {
                        if ((obj == null) || (obj instanceof Class<?>)) {
                            return method.invoke(null, params);
                        } else {
                            return method.invoke(obj, params);
                        }
                    } catch (Exception e) {
                        throw ExceptionUtil.wrapToRuntimeException(e);
                    }
                }
            }
        }
        throw new RuntimeException("Cannot find method '" + methodName + "' in '" + clazz + "'.");
    }

    // ================================================================================

    /**
     * 
     */
    protected File requireTempFile(String fileName) {
        return new File(IntlTestGlobalConstants.TESTCASE_USER_TEMP_DIR + File.separator + fileName);
    }

    /**
     * ?
     * 
     * @param fileName
     * @return
     */
    protected File requireDataFile(String fileName) {
        return new File(BaseReaderUtil.getBasePath() + fileName);
    }

    // ================================================================================

    /**
     * ???
     */
    protected void sortList(List<?> list, String sortField) {
        SortUtil.sortList(list, sortField, true);
    }

    /**
     * ???
     */
    protected void sortList(List<?> list, String sortField, boolean asc) {
        SortUtil.sortList(list, sortField, asc);
    }

    /**
     * ()
     */
    @SuppressWarnings("unchecked")
    protected <T> T getObject(Object object, String fieldName) {
        Class<?> clazz = (object.getClass() == Class.class) ? (Class<?>) object : object.getClass();
        try {
            Field field = ReflectUtil.getDeclaredField(clazz, fieldName);
            field.setAccessible(true);
            if (Modifier.isStatic(field.getModifiers())) {
                return (T) field.get(null);
            } else {
                return (T) field.get(object);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * ()
     */
    protected void setObject(Object object, String fieldName, Object value) {
        Class<?> clazz = (object.getClass() == Class.class) ? (Class<?>) object : object.getClass();
        try {
            Field field = ReflectUtil.getDeclaredField(clazz, fieldName);
            field.setAccessible(true);
            if (Modifier.isStatic(field.getModifiers())) {
                field.set(null, value);
            } else {
                field.set(object, value);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * ???
     * 
     * @param object
     * @param fieldName
     * @param value
     */
    protected void replaceObjectInTestCase(final Object object, final String fieldName, Object value) {

        final Object oldValue = getObject(object, fieldName);
        setObject(object, fieldName, value);

        List<Object> listKey = Arrays.asList(object, fieldName);
        if (objectMethodMap.get(listKey) == null) {
            objectMethodMap.put(listKey, Boolean.TRUE);
            // ??
            clearDataAtFinally(new Runnable() {

                public void run() {
                    setObject(object, fieldName, oldValue);
                }
            });
        }
    }

    // ================================================================================

    /**
     * prepare???
     */
    protected void prepareDataBase(String... tables) {
        PrepareUtil.prepareDataBase(jdbcTemplate, getMemoryDB(), tables);
    }

    protected <T> T prepareObject(Class<T> clazz, String table) {
        return PrepareUtil.prepareObject(getMemoryDB(), clazz, table);
    }

    /**
     * prepare?java pojo
     */
    protected <T> List<T> prepareObjectList(Class<T> clazz, String table) {
        return PrepareUtil.prepareObjectList(getMemoryDB(), clazz, table);
    }

    /**
     * prepare?java pojo
     */
    protected <T> T prepareObject(Class<T> clazz, String database, String table) {
        return PrepareUtil.prepareObject(getMemoryDB(database), clazz, table);
    }

    /**
     * database?java pojo
     */
    protected <T> List<T> prepareObjectList(Class<T> clazz, String database, String table) {
        return PrepareUtil.prepareObjectList(getMemoryDB(database), clazz, table);
    }

    // ================================================================================

    /**
     * database?java pojo
     */
    private <T> boolean checkBothNull(List<T> expected, List<T> actual) {
        if (expected == null) {
            assertNull("expected is null but actual is not null", actual);
            return true;
        } else if (actual == null) {
            fail("expected is not null but actual is null");
        }
        return false;
    }

    /**
     * expectedactual??null
     */
    protected <T> void assertListEquals(List<T> expected, List<T> actual) {
        if (checkBothNull(expected, actual))
            return;
        assertEquals("Size not equals", expected.size(), actual.size());
        for (int i = 0; i < expected.size(); i++) {
            assertEquals("Index " + i + " assert failed", expected.get(i), actual.get(i));
        }
    }

    /**
     * list??list(ArrayListLinkedList)
     */
    protected <T> void assertListEquals(List<T> expected, List<T> actual, Comparator<T> comparator) {
        if (checkBothNull(expected, actual))
            return;
        assertEquals("Size not equals", expected.size(), actual.size());
        for (int i = 0; i < expected.size(); i++) {
            assertTrue("Index " + i + " assert failed", comparator.compare(expected.get(i), actual.get(i)));
        }
    }

    /**
     * list???listComparable?IntegerString
     */
    protected <T extends Comparable<? super T>> void assertListSortedEquals(List<T> expected, List<T> actual) {
        if (checkBothNull(expected, actual))
            return;
        assertEquals("Size not equals", expected.size(), actual.size());
        Collections.sort(expected);
        Collections.sort(actual);
        for (int i = 0; i < expected.size(); i++) {
            assertEquals("Index " + i + " assert failed", expected.get(i), actual.get(i));
        }
    }

    /**
     * list???list???java.util.Comparator??
     */
    protected <T> void assertListSortedEquals(List<T> expected, List<T> actual,
            java.util.Comparator<T> comparator) {
        if (checkBothNull(expected, actual))
            return;
        assertEquals("Size not equals", expected.size(), actual.size());
        Collections.sort(expected, comparator);
        Collections.sort(actual, comparator);
        for (int i = 0; i < expected.size(); i++) {
            assertEquals("Index " + i + " assert failed", expected.get(i), actual.get(i));
        }
    }

    /**
     * list???list???sortField??????
     */
    protected <T> void assertListSortedEquals(List<T> expected, List<T> actual, String sortField) {
        assertListSortedEquals(expected, actual, sortField, true);
    }

    /**
     * list???list???sortField????asc??/??
     */
    protected <T> void assertListSortedEquals(List<T> expected, List<T> actual, String sortField, boolean asc) {
        if (checkBothNull(expected, actual))
            return;
        assertEquals("Size not equals", expected.size(), actual.size());
        SortUtil.sortList(expected, sortField, asc);
        SortUtil.sortList(actual, sortField, asc);
        for (int i = 0; i < expected.size(); i++) {
            assertEquals("Index " + i + " assert failed", expected.get(i), actual.get(i));
        }
    }

    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ???API <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    /**
     * ? *
     * 
     * @return
     */
    protected JavaBeanPreparation getObjPreparation() {
        return (new JavaBeanPreparation()).database(getMemoryDB());
    }

    protected YamlPreparation getYamlPreparation(String dataFileName) {
        return new YamlPreparation().relativePath(getRelativePath(DataReaderType.Yaml, dataFileName));
    }

    /**
     * ?? *
     * 
     * @return
     */
    protected DataBasePreparation getDBPreparation() {
        return (new DataBasePreparation()).jdbcTemplate(jdbcTemplate).database(getMemoryDB());
    }

    /**
     * ForEach? *
     * 
     * @param <T>
     * @return
     */
    protected <T> ForEachPreparation<T> getForPreparation() {
        return new ForEachPreparation<T>();
    }

    /**
     * ??? *
     * 
     * @return
     */
    protected DataBaseAssertion getDBAssertion() {
        return (new DataBaseAssertion()).jdbcTemplate(jdbcTemplate)
                .database(getMemoryDB(BuiltInCacheKey.Result.getValue()));
    }

    /**
     * ?? *
     * 
     * @return
     */
    protected JavaBeanAssertion getObjAssertion() {
        return (new JavaBeanAssertion()).database(getMemoryDB(BuiltInCacheKey.Result.getValue()));
    }

    /**
     * ??memorydatabase?
     * 
     * @return
     */
    protected ParameterizedAssertion getParamAssertion() {
        return (new ParameterizedAssertion()).database(getMemoryDB());
    }

    /**
     * ForEach?? *
     * 
     * @param <T>
     * @return
     */
    protected <T> ForEachAssertion<T> getForAssertion() {
        return new ForEachAssertion<T>();
    }

    /**
     * ??
     */
    protected void assertSuccessed() {
        // do nothing
    }

    /**
     * Assert string is blank: whitespace, empty ("") or null
     */
    protected void assertBlank(String str) {
        assertTrue(StringUtils.isBlank(str));
    }

    /**
     * Assert string is empty ("") or null
     */
    protected void assertEmpty(String str) {
        assertTrue(StringUtils.isEmpty(str));
    }

    /**
     * Assert string is not blank
     */
    protected void assertNotBlank(String str) {
        assertTrue(StringUtils.isNotBlank(str));
    }

    /**
     * Assert string is not empty
     */
    protected void assertNotEmpty(String str) {
        assertTrue(StringUtils.isNotEmpty(str));
    }

    /**
     * Assert string contains only unicode digits.
     */
    protected void assertNumeric(String str) {
        assertTrue(StringUtils.isNumeric(str));
    }

    /**
     * Assert string contains only unicode digits or space(' ').
     */
    protected void assertNumericSpace(String str) {
        assertTrue(StringUtils.isNumericSpace(str));
    }

    /**
     * Assert string contains only whitespace.
     */
    protected void assertWhitespace(String str) {
        assertTrue(StringUtils.isWhitespace(str));
    }

    /**
     * Assert array is null or empty(length==0)
     */
    protected <T> void assertNullOrEmpty(T[] array) {
        assertTrue(array == null || array.length == 0);
    }

    /**
     * Assert list is null or empty
     */
    protected <T> void assertNullOrEmpty(List<T> list) {
        assertTrue(list == null || list.isEmpty());
    }

    /**
     * result?(??)java pojo
     */
    protected void assertResult(Object bean) {
        Assert.assertResult(getMemoryDB(BuiltInCacheKey.Result.getValue()), bean);
    }

    /**
     * result?java pojo
     * 
     * @param bean
     * @param table
     */
    protected void assertResult(Object bean, String table) {
        Assert.assertResult(getMemoryDB(BuiltInCacheKey.Result.getValue()), bean, table);
    }

    /**
     * result?(??)java pojo
     */
    protected void assertResultList(List<?> beanList) {
        Assert.assertResultList(getMemoryDB(BuiltInCacheKey.Result.getValue()), beanList);
    }

    /**
     * result?java pojo
     */
    protected void assertResultList(List<?> beanList, String table) {
        Assert.assertResultList(getMemoryDB(BuiltInCacheKey.Result.getValue()), beanList, table);
    }

    /**
     * result?java pojo,?
     */
    protected void assertResultList(List<?> beanList, String table, String[] columns) {
        Assert.assertResultList(getMemoryDB(BuiltInCacheKey.Result.getValue()), beanList, table, columns);
    }

    /**
     * result?(??)??java pojolist
     */
    protected void assertResultListSorted(List<?> beanList, String sortField) {
        assertResultListSorted(beanList, sortField, true);
    }

    /**
     * result?(??)??java pojolist
     */
    protected void assertResultListSorted(List<?> beanList, String sortField, boolean asc) {
        SortUtil.sortList(beanList, sortField, asc);
        assertResultList(beanList);
    }

    /**
     * result???java pojolist
     */
    protected void assertResultListSorted(List<?> beanList, String table, String sortField) {
        assertResultListSorted(beanList, table, sortField, true);
    }

    /**
     * result???java pojolist
     */
    protected void assertResultListSorted(List<?> beanList, String table, String sortField, boolean asc) {
        SortUtil.sortList(beanList, sortField, asc);
        assertResultList(beanList, table);
    }

    /**
     * result???java pojolist,?
     */
    protected void assertResultListSorted(List<?> beanList, String table, String[] columns, String sortField) {
        assertResultListSorted(beanList, table, columns, sortField, true);
    }

    /**
     * result???java pojolist,?
     */
    protected void assertResultListSorted(List<?> beanList, String table, String[] columns, String sortField,
            boolean asc) {
        SortUtil.sortList(beanList, sortField, asc);
        assertResultList(beanList, table, columns);
    }

    /**
     * database?(??)java pojo
     */
    public void assertResultList(String database, List<?> beanList) {
        Assert.assertResultList(getMemoryDB(database), beanList);
    }

    /**
     * database?java pojo
     */
    protected void assertResultList(String database, List<?> beanList, String table) {
        Assert.assertResultList(getMemoryDB(database), beanList, table);
    }

    /**
     * database?(??)??java pojolist
     */
    public void assertResultListSorted(String database, List<?> beanList, String sortField) {
        assertResultListSorted(database, beanList, sortField, true);
    }

    /**
     * database?(??)??java pojolist
     */
    public void assertResultListSorted(String database, List<?> beanList, String sortField, boolean asc) {
        SortUtil.sortList(beanList, sortField, asc);
        assertResultList(database, beanList);
    }

    /**
     * database???java pojolist
     */
    protected void assertResultListSorted(String database, List<?> beanList, String table, String sortField) {
        assertResultListSorted(database, beanList, table, sortField, true);
    }

    /**
     * database???java pojolist
     */
    protected void assertResultListSorted(String database, List<?> beanList, String table, String sortField,
            boolean asc) {
        SortUtil.sortList(beanList, sortField, asc);
        assertResultList(database, beanList, table);
    }

    /**
     * result???
     */
    protected void assertResultTable(String table) {
        Assert.assertResultTable(jdbcTemplate, getMemoryDB(BuiltInCacheKey.Result.getValue()), table);
    }

    /**
     * result???,?
     */
    protected void assertResultTable(String table, String[] columns) {
        Assert.assertResultTable(jdbcTemplate, getMemoryDB(BuiltInCacheKey.Result.getValue()), table, columns);
    }

    /**
     * result???(????)
     */
    protected void assertResultTable(String table, String whereSql, String sortSql, Object[] args) {
        Assert.assertResultTable(jdbcTemplate, getMemoryDB(BuiltInCacheKey.Result.getValue()), table, whereSql,
                sortSql, args);
    }

    /**
     * result???(????)
     */
    protected void assertResultTable(String table, String whereSql, String sortSql, Object[] args,
            String[] columns) {
        Assert.assertResultTable(jdbcTemplate, getMemoryDB(BuiltInCacheKey.Result.getValue()), table, whereSql,
                sortSql, args, columns);
    }

    /**
     * database?(??)java pojo
     */
    protected void assertResult(String database, Object bean) {
        Assert.assertResult(getMemoryDB(database), bean);
    }

    /**
     * database?java pojo
     * 
     * @param bean
     * @param table
     */
    protected void assertResult(String database, Object bean, String table) {
        Assert.assertResult(getMemoryDB(database), bean, table);
    }

    /**
     * database???
     */
    protected void assertResultTable(String database, String table) {
        Assert.assertResultTable(jdbcTemplate, getMemoryDB(database), table);
    }

    /**
     * * database???(????)
     */

    protected void assertResultTable(String database, String table, String whereSql, String sortSql,
            Object[] args) {
        Assert.assertResultTable(jdbcTemplate, getMemoryDB(database), table, whereSql, sortSql, args);
    }

    /**
     * ??
     */
    protected void assertResultTableEmpty(String table) {
        assertResultTableCount(table, 0);
    }

    /**
     * ???
     */
    protected void assertResultTableEmpty(String table, String where) {
        assertResultTableCount(table, where, 0);
    }

    /**
     * ??
     */
    protected void assertResultTableCount(String table, int count) {
        assertResultTableCount(table, null, count);
    }

    /**
     * ???
     */
    protected void assertResultTableCount(String table, String where, int count) {
        String w = ((where == null) || (where.length() == 0)) ? "1=1" : where;
        String we = (where == null) ? "" : where;
        int c = jdbcTemplate.queryForInt("select count(*) from " + table + " where " + w);
        if (c != count) {
            throw new AssertException("Aspect table `" + table + "` have " + count + " recoreds on `" + we
                    + "` but actual have " + c + " records.");
        }
    }

    // ================================================================================

    /**
     * ?prepare??
     */
    protected MemoryDatabase getMemoryDB() {
        MemoryDatabase database = ThreadContextCache.get(MemoryDatabase.class, BuiltInCacheKey.Prepare.getValue());
        if (database == null) {
            throw new RuntimeException("Memory database not prepared or prepare file not exists.");
        }
        return database;
    }

    /**
     * ?????
     */
    protected MemoryDatabase getMemoryDB(String dataFileName) {
        Method method = ThreadContextCache.get(Method.class, BuiltInCacheKey.Method);
        return getMemoryDB(getDataReaderType(method), dataFileName);
    }

    /**
     * ??????
     */
    protected MemoryDatabase getMemoryDB(DataReaderType dataType, String dataFileName) {
        return getMemoryDB(dataType, dataFileName, true);
    }

    /**
     * * ??????(????)
     */
    protected MemoryDatabase getMemoryDB(DataReaderType dataType, String dataFileName, boolean checkFileExists) {

        MemoryDatabase cachedDataBase = ThreadContextCache.get(MemoryDatabase.class, dataFileName);
        if (cachedDataBase != null) {
            return cachedDataBase;
        }

        String relativePath = getRelativePath(dataType, dataFileName);
        try {
            MemoryDatabase database = (MemoryDatabase) DataReaderUtil.readData(dataType, relativePath);
            ThreadContextCache.put(dataFileName, database);

            return database;
        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
            if (checkFileExists) {
                throw e;
            } else {
                return null;
            }
        }
    }

    // ================================================================================

    /**
     * ??
     */
    protected void clearTables(String... tables) {
        for (String table : tables) {
            clearTableData(table);
        }
    }

    /**
     * ??<br>
     * ?spring??deleteFromTables
     * 
     * @see AbstractTransactionalDataSourceSpringContextTests#deleteFromTables(String[])
     */
    protected void clearTableData(String table) {
        clearTableData(table, "1=1");
    }

    /**
     * ??(whereSql ?1=1)<br>
     * ?spring??deleteFromTables
     * 
     * @see AbstractTransactionalDataSourceSpringContextTests#deleteFromTables(String[])
     */
    protected void clearTableData(String table, String whereSql) {
        if ((whereSql == null) || (whereSql.length() == 0)) {
            throw new RuntimeException("Where sql cannot be empty.");
        }
        int count = jdbcTemplate.update("delete from " + table + " where " + whereSql);
        log.info("Table `" + table + "` 's `" + count + "` records has been cleared on `" + whereSql + "`.");
    }

    // ================================================================================

    /**
     * ??
     */
    protected void runInBlockedThread(final Runnable runnable) {
        final List<Throwable> throwable = new ArrayList<Throwable>();

        Thread blockedThread = new Thread(new Runnable() {

            public void run() {
                try {
                    runnable.run();
                } catch (Throwable t) {
                    throwable.add(t);
                }
            }
        });
        blockedThread.start();
        try {
            blockedThread.join();
        } catch (InterruptedException e) {
            log.error("Thread interrupted error occured in blocked thread runing.", e);
            throw new RuntimeException(e);
        }
        if (throwable.size() > 0) {
            Throwable t = throwable.get(0);
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new RuntimeException(throwable.get(0));
            }
        }
    }

    /**
     * 
     */
    @SuppressWarnings("unchecked")
    protected void clearDataAtFinally(Runnable runnable) {
        if (ThreadContextCache.get(BuiltInCacheKey.Finally) == null) {
            ThreadContextCache.put(BuiltInCacheKey.Finally, new ArrayList<Runnable>());
        }
        ThreadContextCache.get(List.class, BuiltInCacheKey.Finally).add(runnable);
    }

    // ================================================================================

    // {{{{{{{{{{{{{{{{ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ }}}}}}}}}}}}}}}} //

    protected void initOnBeforeEveryTestCase() {
        log.debug("=========================> BaseTestCase.initOnBeforeEveryTestCase() init db data");
        String basePath = getClassBasePath();
        if (basePath.contains("com.mountainminds.eclemma.core")) { // if run in eclemma
            System.err.println("Run in eclemma we try to change base path!");
            String userDir = IntlTestGlobalConstants.USER_DIR;

            if (new File(FilenameUtils.concat(userDir, "src/java")).exists()) {
                basePath = getFinalClassBasePath(userDir + "/src/java.test/");
            } else if (new File(FilenameUtils.concat(userDir, "src/main")).exists()) {
                basePath = getFinalClassBasePath(userDir + "/src/test/java");
            } else {
                System.err.println("Oh, we cannot detect is antx or maven.");
            }
        }
        log.info("Base path set to `" + basePath + "`.");
        BaseReaderUtil.setBasePath(basePath);

        initMemoryDatabaseOnBeforeEveryTestCase();
    }

    // called by prepare class runner
    protected void initMemoryDatabaseOnBeforeEveryTestCase() {
        Method method = ThreadContextCache.get(Method.class, BuiltInCacheKey.Method);
        if (!isNeedPrepare(method)) {
            return;
        }
        // ?handle
        DbEncodingUtil.clearInfo();

        final PrepareAnnotationTool prepareAnnotation = new PrepareAnnotationTool(method);
        // xml_tree???MemoryDatabase
        if (DataReaderType.TreeXml == prepareAnnotation.type()) {
            return;
        }

        // ?
        DbEncodingUtil.applyInfo(new CnStringDbEncodingInfo(prepareAnnotation.cnStringEncoding()));
        DbEncodingUtil.applyInfo(new UTF8StringDbEncodingInfo(prepareAnnotation.utf8StringEncoding()));

        // ?Sheet
        final String[] importTables = StringUtil.splitAndTrimByComma(prepareAnnotation.importTables());

        PrepareEventUtil.clearRegister();
        if (prepareAnnotation.autoClearExistsData() || prepareAnnotation.autoClearImportDataOnFinish()) {
            log.info("Import data clear exists ones on.");
            PrepareEventUtil.register(new ClearSpecialDataPrepareEvent(prepareAnnotation.primaryKey()));
        }

        final MemoryDatabase prepare = getMemoryDB(prepareAnnotation.type(), BuiltInCacheKey.Prepare.getValue(),
                true);
        getMemoryDB(prepareAnnotation.type(), BuiltInCacheKey.Result.getValue(), false);

        // common prepare (for test case class)
        MemoryDatabase commonPrepare = null;
        if (prepareAnnotation.autoImportCommonData()) {
            commonPrepare = getMemoryDB(prepareAnnotation.type(), ":prepare", false);
        }
        final MemoryDatabase finalCommonPrepare = commonPrepare;

        if (prepareAnnotation.autoImport()) {

            boolean isDefaultRollback = isDefaultRollback();
            if (prepareAnnotation.autoClear()) {
                if ((!isDefaultRollback) || (prepareAnnotation.newThreadTransactionImport())) {
                    if (!prepareAnnotation.forceClear()) {
                        throw new RuntimeException("Cannot import data when default rollback was"
                                + " turned off and no force clear flag.");
                    }
                }
            }
            log.info("Auto import data on flag autoImport:" + prepareAnnotation.autoImport() + ", autoClear:"
                    + prepareAnnotation.autoClear() + ", defaultRollback:" + isDefaultRollback + ", forceClear:"
                    + prepareAnnotation.forceClear() + ", newThreadTransactionImport:"
                    + prepareAnnotation.newThreadTransactionImport());
            if (prepareAnnotation.newThreadTransactionImport()) {
                runInBlockedThread(new Runnable() {

                    public void run() {
                        log.warn("Import data in new thread (transaction).");
                        TransactionStatus localTransactionStatus = transactionManager
                                .getTransaction(new DefaultTransactionDefinition());
                        try {
                            if (prepareAnnotation.autoImportCommonData() && (finalCommonPrepare != null)) {
                                PrepareUtil.prepareDataBase(jdbcTemplate, finalCommonPrepare,
                                        prepareAnnotation.autoClear());
                            }

                            if (importTables != null) {
                                PrepareUtil.prepareDataBase(jdbcTemplate, prepare, prepareAnnotation.autoClear(),
                                        importTables);
                            } else {
                                PrepareUtil.prepareDataBase(jdbcTemplate, prepare, prepareAnnotation.autoClear());
                            }

                            log.info("Import data transaction has be commited.");
                            transactionManager.commit(localTransactionStatus);
                        } catch (Throwable t) {
                            log.error("Import data transaction has be rolled back.", t);
                            transactionManager.rollback(localTransactionStatus);
                            throw new RuntimeException(t);
                        }
                    }
                });
            } else {
                if (prepareAnnotation.autoImportCommonData() && (finalCommonPrepare != null)) {
                    PrepareUtil.prepareDataBase(jdbcTemplate, finalCommonPrepare, prepareAnnotation.autoClear());
                }
                if (importTables != null) {
                    PrepareUtil.prepareDataBase(jdbcTemplate, prepare, prepareAnnotation.autoClear(), importTables);
                } else {
                    PrepareUtil.prepareDataBase(jdbcTemplate, prepare, prepareAnnotation.autoClear());
                }
            }
        }
    }

    protected String getAbstractBasePath() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String slashClassName = this.getClass().getName().replace('.', '/');
        String classPath = classLoader.getResource(slashClassName + ".class").getPath();
        int classIndex = classPath.indexOf(slashClassName);
        String basePart = classPath.substring(0, classIndex);
        return basePart;
    }

    protected String getClassBasePath() {

        TestCaseInfo info = this.getClass().getAnnotation(TestCaseInfo.class);
        if ((info != null) && (info.basePath().length() != 0)) {
            return info.basePath();
        }

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String slashClassName = this.getClass().getName().replace('.', '/');
        String classPath = classLoader.getResource(slashClassName + ".class").getPath();
        int classIndex = classPath.indexOf(slashClassName);
        String basePart = classPath.substring(0, classIndex);

        return getFinalClassBasePath(basePart);
    }

    protected String getFinalClassBasePath(String basePart) {
        String slashClassName = this.getClass().getName().replace('.', '/');

        String finalPath = basePart + "/" + getResourcePrefix() + "/" + slashClassName + getClassSuffix();
        finalPath = replaceDoubleSlash(finalPath);

        if ((new File(finalPath)).exists()) {
            return finalPath;
        } else {
            String newPath = basePart + "/" + getResourcePrefix() + "/" + nomalnizeClassPath(slashClassName)
                    + getClassSuffix();
            return replaceDoubleSlash(newPath);
        }
    }

    protected String replaceDoubleSlash(String path) {
        while (path.indexOf("//") >= 0) {
            path = path.replace("//", "/");
        }
        return path;
    }

    protected String nomalnizeClassPath(String path) {
        if (path == null) {
            return null;
        }

        String[] parts = path.split("[\\/]");
        StringBuilder newPath = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            if ((part != null) && part.length() > 0) {
                newPath.append(part.substring(0, 1).toLowerCase() + part.substring(1));
            }

            if (i < (parts.length - 1)) {
                newPath.append("/");
            }
        }
        return newPath.toString();
    }

    protected String getRelativePath(DataReaderType dataType, String dataFileName) {
        if (dataFileName.startsWith(":")) {
            dataFileName = dataFileName.substring(1);
        } else {
            String methodName = getMethodName();
            if (dataFileName.indexOf('_') <= 0) {
                dataFileName = methodName + "_" + dataFileName;
            }
        }
        String defaultExt = DataReaderUtil.getDefaultExt(dataType);
        String relativePath = dataFileName;
        if (!relativePath.endsWith(defaultExt)) {
            relativePath = relativePath + defaultExt;
        }
        return relativePath;
    }

    protected String getMethodName() {
        Method method = ThreadContextCache.get(Method.class, BuiltInCacheKey.Method);
        PrepareAnnotationTool prepare = new PrepareAnnotationTool(method);
        if ((prepare != null) && (prepare.methodName().length() != 0)) {
            return prepare.methodName();
        } else {
            return ThreadContextCache.get(Method.class, BuiltInCacheKey.Method).getName();
        }
    }

    protected String getResourcePrefix() {
        TestCaseInfo info = this.getClass().getAnnotation(TestCaseInfo.class);
        return ((info == null) || (info.defaultPath())) ? "" : info.pathPrefix();
    }

    protected String getClassSuffix() {
        TestCaseInfo info = this.getClass().getAnnotation(TestCaseInfo.class);
        return ((info == null) || (info.defaultPath())) ? DEFAULT_CLASS_SUFFIX : info.classSuffix();
    }

    protected DataReaderType getDataReaderType(Method method) {
        PrepareAnnotationTool prepare = new PrepareAnnotationTool(method);
        return prepare.type();
    }

    protected boolean isNeedPrepare(Method method) {
        return new PrepareAnnotationTool(method).isPrepareEnable();
    }

    protected boolean isMockOn() {
        boolean isMockFlagOn = (this.getClass().getAnnotation(Mock.class) != null);
        // ?MOCKMock
        return isMockFlagOn;
    }

    protected boolean isDefaultRollback() {
        try {
            Field defaultRollbackField = AbstractTransactionalSpringContextTests.class
                    .getDeclaredField("defaultRollback");
            defaultRollbackField.setAccessible(true);
            return ((Boolean) defaultRollbackField.get(this)).booleanValue();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // ================================================================================
    // ???,?memorydatabase?memorydatabase
    protected TreeDatabase getTreeDB() {
        return getTreeDB(BuiltInCacheKey.Prepare.getValue());
    }

    protected TreeDatabase getTreeDB(String dataFileName) {
        return getTreeDB(dataFileName, true);
    }

    protected TreeDatabase getTreeDB(String dataFileName, boolean checkFileExists) {
        String relativePath = getRelativePath(DataReaderType.TreeXml, dataFileName);
        try {
            TreeDatabase database = (TreeDatabase) DataReaderUtil.readData(DataReaderType.TreeXml, relativePath);
            return database;
        } catch (ResourceNotFoundException e) {
            if (checkFileExists) {
                throw e;
            } else {
                return null;
            }
        }
    }

    /**
     * ????
     * 
     * @param database getTreeDB()
     * @param objectName TreeObject.name
     * @return
     */
    protected static Object prepareObject(TreeDatabase database, String objectName) {
        List<?> treeObjectList = prepareObjectList(database, objectName);
        if (treeObjectList != null && treeObjectList.size() > 0) {
            return treeObjectList.get(0);
        }
        return null;
    }

    /**
     * ???
     * 
     * @param database getTreeDB()
     * @param objectName TreeObject.name
     * @return
     */
    protected static List<?> prepareObjectList(TreeDatabase database, String objectName) {
        TreeObject treeTable = database.getObject(objectName);
        if (treeTable != null) {
            return treeTable.getTreeObjectList();
        }
        return null;
    }

    /**
     * <strong><font color=red> NEW FEATURE?</font></strong><code>?<br>
     * 
     * <pre>
     * PersonProfile{
     * String firstName;
     * String lastName;
     * Date gmtCreate;
     * List&lt;Integer&gt; list;
     * };
     * Person{
     * Integer id;
     * Date gmtModified;
     * PersonProfile personProfile;
     * };
     * </pre>
     * 
     * @param bean ?result
     * @param objectName result??
     * @param properties : ?Person?:
     * {"id","personProfile.gmtCreate","personProfile.list"}??inclusiveexclusive??
     * @param exclusive true<strong><font color=red>?</font></strong><code>properties</code>
     * false<strong><font color=red>?</font></strong> <code>properties</code>
     */
    protected void assertTreeObjectResult(Object bean, String objectName, String[] properties, boolean exclusive) {
        TreeObjectAssert.assertResult(getTreeDB(BuiltInCacheKey.Result.getValue()), bean, objectName, properties,
                exclusive);
    }

    /**
     * <strong><font color=red> NEW FEATURE?</font></strong><code>?<br>
     * 
     * <pre>
     * PersonProfile{
     *  String          firstName;
     *  String          lastName;
     *  Date gmtCreate;
     *  List&lt;Integer&gt; list; 
     * };
     * Person{
     *  Integer id;
     *  Date gmtModified;
     *  PersonProfile   personProfile;
     * };
     * </pre>
     * 
     * @param beanlist ?result
     * @param objectName result??
     * @param properties : ?Person?:
     * {"id","personProfile.gmtCreate","personProfile.list"}??inclusiveexclusive??
     * @param exclusive true<strong><font color=red>?</font></strong><code>properties</code>
     * false<strong><font color=red>?</font></strong> <code>properties</code>
     */

    protected void assertTreeObjectResultList(List<?> beanList, String objectName, String[] properties,
            boolean exclusive) {
        TreeObjectAssert.assertResultList(getTreeDB(BuiltInCacheKey.Result.getValue()), beanList, objectName,
                properties, exclusive);

    }

    /**
     * <strong><font color=red> NEW FEATURE?</font></strong><code>?<br>
     * 
     * <pre>
     * PersonProfile{
     *  String          firstName;
     *  String          lastName;
     *  Date gmtCreate;
     *  List&lt;Integer&gt; list; 
     * };
     * Person{
     *  Integer id;
     *  Date gmtModified;
     *  PersonProfile   personProfile;
     * };
     * </pre>
     * 
     * @param object1
     * @param object2
     * @param properties : ?Person?:
     * {"id","personProfile.gmtCreate","personProfile.list"}??inclusiveexclusive??
     * @param exclusive true<strong><font color=red>?</font></strong><code>properties</code>
     * false<strong><font color=red>?</font></strong> <code>properties</code>
     */
    protected void assertObjectEquals(Object object1, Object object2, String[] properties, boolean exclusive) {
        if (!JsonObjectUtils.isSameObject(object1, object2, properties, exclusive)) {
            throw new AssertException("Not equals!");
        }
    }
}