Package ch.inftec.ju.testing.db

Source Code of ch.inftec.ju.testing.db.DbTestAnnotationHandler

package ch.inftec.ju.testing.db;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;

import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import ch.inftec.ju.db.JuEmUtil;
import ch.inftec.ju.testing.db.DataSetExport.ExportType;
import ch.inftec.ju.testing.db.DbDataUtil.ExportBuilder;
import ch.inftec.ju.util.AssertUtil;
import ch.inftec.ju.util.IOUtil;
import ch.inftec.ju.util.JuObjectUtils;
import ch.inftec.ju.util.JuRuntimeException;
import ch.inftec.ju.util.JuStringUtils;
import ch.inftec.ju.util.JuUrl;
import ch.inftec.ju.util.JuUtils;
import ch.inftec.ju.util.ReflectUtils;
import ch.inftec.ju.util.ReflectUtils.AnnotationInfo;
import ch.inftec.ju.util.SystemPropertyTempSetter;
import ch.inftec.ju.util.XString;
import ch.inftec.ju.util.xml.XmlUtils;

/**
* Helper class to handle test annotations like @DataSet and @DataVerify.
* <p>
* When calling the execute... methods, the client is responsible that a valid transaction is present.
* @author Martin
*
*/
public class DbTestAnnotationHandler implements Serializable {
  private final static Logger logger = LoggerFactory.getLogger(DbTestAnnotationHandler.class);
 
  private final List<AnnotationInfo<DbDataUtilConfig>> dbDataUtilConfigAnnos;
  private final List<AnnotationInfo<JuTestEnv>> testEnvAnnos;
  private final List<AnnotationInfo<DataSet>> dataSetAnnos;
  private final List<AnnotationInfo<DataSetExport>> dataSetExportAnnos;
  private final List<AnnotationInfo<PostServerCode>> postServerCodeAnnos;
  private final List<AnnotationInfo<DataVerify>> dataVerifyAnnos;
 
  protected final String testClassName;
  protected final String testMethodName;
 
  /**
   * Readable name of the test method, may be testMethod[0] for parameterized tests.
   */
  private final String testMethodReadableName;
 
  public DbTestAnnotationHandler(Method method, Description description) {
    this.dbDataUtilConfigAnnos = ReflectUtils.getAnnotationsWithInfo(method.getDeclaringClass(), DbDataUtilConfig.class, true);
   
    // Get all annotations for the method and declaring class (exlucing overridden methods)
    // in reverse order, i.e. starting from class to method
    this.testEnvAnnos = ReflectUtils.getAnnotationsWithInfo(method, JuTestEnv.class, false, true, true);
    Collections.reverse(this.testEnvAnnos);
   
    // Get all annotations for the method and the declaring class (including super classes, but
    // excluding overridden methods)
    this.dataSetAnnos = ReflectUtils.getAnnotationsWithInfo(method, DataSet.class, false, true, true);
    // Reverse the list as we want to start with the base class, then class and method last
    Collections.reverse(this.dataSetAnnos);
   
    this.dataSetExportAnnos = ReflectUtils.getAnnotationsWithInfo(method, DataSetExport.class, true, true, true);
    this.postServerCodeAnnos = ReflectUtils.getAnnotationsWithInfo(method, PostServerCode.class, true, false, false);
    this.dataVerifyAnnos = ReflectUtils.getAnnotationsWithInfo(method, DataVerify.class, true, false, false);
   
    this.testClassName = method.getDeclaringClass().getName();
    this.testMethodName = method.getName();
    this.testMethodReadableName = description.getMethodName();
  }
 
  private Class<?> getTestClass() {
    try {
      return Class.forName(this.testClassName);
    } catch (Exception ex) {
      throw new JuRuntimeException("Couldn't get test class. Make sure it's on the classpath: " + this.testClassName);
    }
  }
 
 
//  private Method getTestMethod() {
//    return ReflectUtils.getMethod(this.getTestClass(), this.testMethodName, new Class<?>[0]);
//  }
 
  /**
   * Initializes the environment for the test case.
   * @return A SystemPropertyTempSetter that can be used to restore the original system property state
   */
  public SystemPropertyTempSetter initContainerTestEnv() {
    return DbTestAnnotationHandler.setTestEnvProperties(this.testEnvAnnos);
  }
 
  /**
   * Sets the system properties as configured in the JuTestEnv annotations provided and returns
   * a SystemPropertyTempSetter that can be used to reset the properties.
   * @param testEnvAnnos List of JuTestEnv annotations containing systemProperties settings
   * @return SystemPropertyTempSetter that must be closed as soon as the test context is left
   */
  public static SystemPropertyTempSetter setTestEnvProperties(List<AnnotationInfo<JuTestEnv>> testEnvAnnos) {
    @SuppressWarnings("resource") // We'll close it in another method...
    SystemPropertyTempSetter tempSetter = new SystemPropertyTempSetter();
   
    try {
      for (AnnotationInfo<JuTestEnv> testEnvInfo : testEnvAnnos) {
        logger.debug("Processing Annotation (Setting system property): {}", testEnvInfo);
       
        for (String keyValStr : testEnvInfo.getAnnotation().systemProperties()) {
          if (!StringUtils.isEmpty(keyValStr) && keyValStr.contains("=")) {
            int ind = keyValStr.indexOf("=");
            String key = keyValStr.substring(0, ind);
            String val = keyValStr.length() > ind - 1
                ? keyValStr.substring(ind + 1)
                : "";
           
            logger.debug("Setting system property for test context: {}={}", key, val);
            tempSetter.setProperty(key, val);
          } else {
            throw new JuRuntimeException("SystemProperty String must be of type key=val, but was: %s", keyValStr);
          }
        }
      }
    } catch (Exception ex) {
      // When an exception occurrs, make sure we reset the properties to their original values
      tempSetter.close();
      throw ex;
    }
   
    return tempSetter;
  }
 
  private DbDataUtil getDbDataUtil(JuEmUtil emUtil) {
    if (this.dbDataUtilConfigAnnos.size() > 0) {
      AnnotationInfo<DbDataUtilConfig> annoInfo = this.dbDataUtilConfigAnnos.get(0);
      Class<? extends DbDataUtilProvider> providerClass = annoInfo.getAnnotation().value();
     
      logger.debug("Retrieving DbDataUtil from provider {} (defined in Annotation {}", providerClass, annoInfo);
     
      DbDataUtilProvider provider = ReflectUtils.newInstance(providerClass, false);
      DbDataUtil dbDataUtil = provider.getDbDataUtil();
      if (dbDataUtil == null) {
        logger.warn("DbDataUtilProvider.getDbDataUtil() returned null. Creating DbDataUtil using JuEmUtil.");
      } else {
        return provider.getDbDataUtil();
      }
    }
   
    return new DbDataUtil(emUtil);
  }
 
  public final void executePreTestAnnotations(JuEmUtil emUtil) throws Exception {
    // Load test data as defined by annotations

    DbDataUtil du = this.getDbDataUtil(emUtil);
    Integer sequenceValue = null;
    for (AnnotationInfo<DataSet> dataSeInfo : this.dataSetAnnos) {
      logger.debug("Processing Annotation (Loading Data Sets): {}", dataSeInfo);
     
      // Run pre initializer
      this.runInitializer(dataSeInfo.getAnnotation().preInitializer(), emUtil.getEm());
     
      if (DataSet.NO_CLEAN_INSERT.equals(dataSeInfo.getAnnotation().value())) {
        // Skip clean-insert       
      } else {
        // Perform clean-insert of value resource
        URL resourceUrl = this.resourceToUrl(dataSeInfo.getAnnotation().value(), dataSeInfo.getAnnotation().resourceDir());
        du.buildImport()
          .from(resourceUrl)
          .executeCleanInsert();
      }
     
      // Perform inserts for inserts resources
      for (String insertResource : dataSeInfo.getAnnotation().inserts()) {
        URL resourceUrl = this.resourceToUrl(insertResource, dataSeInfo.getAnnotation().resourceDir());
        du.buildImport()
          .from(resourceUrl)
          .executeInsert();
      }
     
      sequenceValue = dataSeInfo.getAnnotation().sequenceValue();
     
      // Run post initializer
      this.runInitializer(dataSeInfo.getAnnotation().postInitializer(), emUtil.getEm());
    }

    // Reset the sequences
    if (sequenceValue != null) {
      emUtil.resetIdentityGenerationOrSequences(sequenceValue);
    }
  }
 
  private void runInitializer(Class<? extends ServerCode> clazz, EntityManager em) throws Exception {
    ServerCode initializer = ReflectUtils.newInstance(clazz, false);
    initializer.init(em);
    initializer.execute();   
  }

  /**
   * Converts a resourceUrl string to an URL. This also performs paramterized placeholder replacement
   * if necessary.
   * @param resource Resource path
   * @param resourceDir Resource directory in case we need to lookup the resource in the file system
   * @return Actual resource URL
   * @throws JuRuntimeException If the resource is not valid
   */
  private URL resourceToUrl(String resource, String resourceDir) {
    String actualResource = resource;
    // Perform {param} placeholder replacement
    if (resource.indexOf(DataSet.PARAM_POSTFIX) > 0) {
      String parameterizedTestName = this.getParameterizedTestName();
      AssertUtil.assertNotNull("Doesn't seem to be parameterized test: " + this.testMethodReadableName, parameterizedTestName);
     
      actualResource = actualResource.replace(DataSet.PARAM_POSTFIX, "[" + parameterizedTestName + "]");
    }
   
    URL url = null;
    if (!JuUtils.getJuPropertyChain().get("ju-testing.export.compareToResource", Boolean.class)
        && !StringUtils.isEmpty(resourceDir)) {
      // Lookup resource in file system
      Path p = Paths.get(this.getLocalRoot(), resourceDir, actualResource);
      url = JuUrl.toUrl(p);
    } else {
      // Lookup resource as (classpath) resource
      url = JuUrl.resource().relativeTo(this.getTestClass()).get(actualResource);
      if (url == null) url = JuUrl.resource(actualResource);
    }
   
    if (url == null) {
      throw new JuRuntimeException(String.format("Couldn't find resource %s, relative to class %s"
          , actualResource
          , this.getTestClass()));
    }
   
    return url;
  }

  /**
   * Gets the local root directory used to resolve resource locations.
   * <p>
   * Can be overridden by extending classes to provide a different root.
   * @return Root location for resource lookup on the filesystem
   */
  protected String getLocalRoot() {
    return ".";
  }
 
  /**
   * Get the name of the parameterized test.
   * @return Parameterized test name or null if the test is not parameterized.
   */
  private String getParameterizedTestName() {
    if (this.testMethodReadableName.indexOf("[") < 0 || !this.testMethodReadableName.endsWith("]")) {
      return null;
    } else {
      return this.testMethodReadableName.substring(this.testMethodReadableName.indexOf("[") + 1
          , this.testMethodReadableName.length() - 1);
    }
  }
 
//  /**
//   * Extending classes can override this method to perform initialization on the
//   * test class before the test method is invoked.
//   * @param instance
//   */
//  protected void initTestClass(Object instance) {
//  }
 
  public final void executePostServerCode(JuEmUtil emUtil) throws Exception {
    // Execute post server code
    for (AnnotationInfo<PostServerCode> codeInfo : this.postServerCodeAnnos) {
      logger.debug("Processing Annotation (Executing Post Server Code): {}", codeInfo);
     
      Class<?> codeClass = null;
      if (codeInfo.getAnnotation().value() == PostServerCode.DEFAULT_SERVER_CODE.class) {
        String verifierName = StringUtils.capitalize(this.testMethodName + "_code");
        Class<?> defaultVerifier = ReflectUtils.getInnerClass(this.getTestClass(), verifierName);
        AssertUtil.assertNotNull(String.format("Couldn't find Verifier %s as inner class of %s. Make sure it exists and is public static."
            , verifierName, this.getTestClass())
            , defaultVerifier);

        codeClass = defaultVerifier;
      } else {
        codeClass = codeInfo.getAnnotation().value();
      }

      this.runServerCode(codeClass, emUtil.getEm());
    }
  }
 
  public final void executePostTestAnnotations(JuEmUtil emUtil) throws Exception {
    // Process DataSetExport annotation. We'll just consider the first annotation.
    Document doc = null;
    if (this.dataSetExportAnnos.size() > 0) {
      logger.debug("Processing Annotation (Exporting Data Set): {}", this.dataSetExportAnnos.get(0));
      DataSetExport dataSetExport = this.dataSetExportAnnos.get(0).getAnnotation();
     
      if (dataSetExport.exportType() != ExportType.NONE) {
        // Get file name
        String targetFileName = dataSetExport.exportName();
        if (StringUtils.isEmpty(targetFileName)) {
          // Construct name using class and method name
          targetFileName = String.format("%s_%s.xml"
            ,this.getTestClass().getSimpleName()
            , JuStringUtils.removeNonAlphabeticalLeadingCharacters(this.testMethodReadableName));
        }
       
        URL tablesDataSestUrl = JuUrl.resource().relativeTo(this.getTestClass()).get(dataSetExport.tablesDataSet());
        if (tablesDataSestUrl == null) tablesDataSestUrl = JuUrl.resource(dataSetExport.tablesDataSet());
       
        ExportBuilder eb = this.getDbDataUtil(emUtil).buildExport()
            .addTablesByDataSet(tablesDataSestUrl, true);
       
        doc = eb.writeToXmlDocument();
       
        if (dataSetExport.exportType() == ExportType.PHYSICAL) {
          if (JuUtils.getJuPropertyChain().get("ju-testing.export.compareToResource", Boolean.class, true)) {
            // Perform export in-memory and compare to resource
            String resourcePrefix = dataSetExport.resourcePrefix();
            String resourcePath = resourcePrefix + "/" + targetFileName;
            URL resourceUrl = JuUrl.singleResource(resourcePath);
            String resourceString = new IOUtil().loadTextFromUrl(resourceUrl);
           
            String xmlString = eb.writeToXmlString();
           
            logger.debug("Comparing DB export to resource {}", resourceUrl);
            Assert.assertEquals(resourceString, xmlString);
          } else {
            // Perform export to file
            String targetDirName = dataSetExport.targetDir();
            // Create target directory
            Path targetDirPath = Paths.get(this.getLocalRoot(), targetDirName);
            Files.createDirectories(targetDirPath);
           
            // Build file path
            Path targetFilePath = targetDirPath.resolve(targetFileName);
            eb.writeToXmlFile(targetFilePath);
          }
        } else if (dataSetExport.exportType() == ExportType.MEMORY) {
          // Log XML
          if (logger.isInfoEnabled()) {
            XString xs = new XString(targetFileName);
            xs.newLine();
            xs.addLine(XmlUtils.toString(doc, true, true));
            logger.info(xs.toString());
          }
        } else {
          // Shouldn't happen
          throw new IllegalArgumentException("Unsupported export type: " + dataSetExport.exportType());
        }
      }
     
      if (this.dataSetExportAnnos.size() > 1) {
        logger.debug("Ignoring DataSetExport annotations as only first is processed:");
        for (int i = 1; i < this.dataSetExportAnnos.size(); i++) {
          logger.debug("Ignoring Annotation: {}", this.dataSetExportAnnos.get(i));
        }
      }
    }
   
    // Run data verifiers (provided the test method and data set export has succeeded)
    List<DataVerifier> verifiers = new ArrayList<DataVerifier>();
   
    // Check for programmatic verifiers
    for (AnnotationInfo<DataVerify> verifyInfo : this.dataVerifyAnnos) {
      logger.debug("Processing Annotation (Data Verifying): {}", verifyInfo);
     
      Class<?> verifierClass = null;
      if (verifyInfo.getAnnotation().value() == DataVerify.DEFAULT_DATA_VERIFIER.class) {
        String verifierName = StringUtils.capitalize(JuStringUtils.removeNonAlphabeticalLeadingCharacters(this.testMethodName));
        Class<?> defaultVerifier = ReflectUtils.getInnerClass(this.getTestClass(), verifierName);
        AssertUtil.assertNotNull(
            String.format("Couldn't find Verifier %s as inner class of %s. Make sure it exists and is public static."
            , verifierName, this.getTestClass())
            , defaultVerifier);
       
        verifierClass = defaultVerifier;
      } else {
        verifierClass = verifyInfo.getAnnotation().value();
      }
     
      verifiers.add(this.createVerifier(verifierClass, emUtil.getEm(), doc));
    }
   
    // Run verifiers
    for (DataVerifier verifier : verifiers) {
      verifier.verify();
    }
  }
 
  private void runServerCode(Class<?> codeClass, EntityManager em) throws Exception {
    AssertUtil.assertTrue("Code class must be of type ServerCode: " + codeClass.getName(), ServerCode.class.isAssignableFrom(codeClass));
   
    ServerCode code = (ServerCode) ReflectUtils.newInstance(codeClass, false);
    code.init(em);
   
    try {
      code.execute();
    } catch (Exception ex) {
      this.handleServerThrowable(ex);
    }
  }
 
  /**
   * Handle Server throwables to make sure we can send them to the client.
   * @param t Throwable
   * @throws T Handled throwable (may be the same or a converted Throwable)
   */
  protected final <T extends Throwable> void handleServerThrowable(T t) throws T {
    // Handle non-serializable exceptions
    if (!IOUtil.isSerializable(t)) {
      // If we have an assumption failure wrapped in an InvocationTargetException, rethrow a new
      // AssumptionViolatedException that just containing the message
      InvocationTargetException ite = JuObjectUtils.as(t, InvocationTargetException.class);
      if (ite != null && ite.getTargetException() instanceof AssumptionViolatedException) {
        throw new AssumptionViolatedException(ite.getTargetException().getMessage());
      }

     
      XString causes = new XString("%s (Original Exception %s not serializable. Resolving chain"
          , t.getMessage()
          , t.getClass().getName());
         
      Throwable tChain = t;
      // Use cause (if possible / serializable)
      if (ite != null) {
        causes.addLineFormatted("%s [Target: %s]", ite.getMessage(), ite.getTargetException());
        if (ite.getTargetException() != null) {
          tChain = ite.getTargetException();
        }
      }
     
      Throwable cause = tChain.getCause();
      while (cause != null) {
        if (!IOUtil.isSerializable(cause)) {
                   
          causes.addLineFormatted("%s [Caused by: %s (non-serializable)", cause.getMessage(), cause.getClass().getName());
          cause = cause.getCause();
        } else {
          break;
        }
      }
     
      if (cause != null) {
        throw new JuRuntimeException(causes.toString(), cause);
      } else {
        causes.addLine("Check Server log for more details");
        throw new JuRuntimeException(causes.toString());
      }
    } else {
      throw t;
    }
  }
 
  private DataVerifier createVerifier(Class<?> verifierClass, EntityManager em, Document doc) {
    AssertUtil.assertTrue("Verifier must be of type DataVerifier: " + verifierClass.getName(), DataVerifier.class.isAssignableFrom(verifierClass));
   
    DataVerifier verifier = (DataVerifier) ReflectUtils.newInstance(verifierClass, false);
    verifier.init(em, doc);
    this.initVerifier(verifier);

    return verifier;
  }
 
  /**
   * Extending classes can override this method to perform additional initialization on the DataVerifier.
   * @param verifier DataVerifier
   */
  protected void initVerifier(DataVerifier verifier) {
  }
 
  @Override
  public String toString() {
    return String.format("%s.%s()", this.testClassName, this.testMethodReadableName);
  }
}
TOP

Related Classes of ch.inftec.ju.testing.db.DbTestAnnotationHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.