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.DbDataUtil.ExportBuilder;
import ch.inftec.ju.util.AssertUtil;
import ch.inftec.ju.util.IOUtil;
import ch.inftec.ju.util.JuException;
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.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 Logger logger = LoggerFactory.getLogger(DbTestAnnotationHandler.class);
 
  private final List<DataSet> dataSetAnnos;
  private final List<DataSetExport> dataSetExportAnnos;
  private final List<PostServerCode> postServerCodeAnnos;
  private final List<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) {
    // Get all annotations for the method and the declaring class (including super classes, but
    // excluding overridden methods)
    this.dataSetAnnos = ReflectUtils.getAnnotations(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.getAnnotations(method, DataSetExport.class, true, true, true);
    this.postServerCodeAnnos = ReflectUtils.getAnnotations(method, PostServerCode.class, true, false, false);
    this.dataVerifyAnnos = ReflectUtils.getAnnotations(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]);
//  }
 
  public final void executePreTestAnnotations(JuEmUtil emUtil) throws Exception {
    // Load test data as defined by annotations

    DbDataUtil du = new DbDataUtil(emUtil);
    Integer sequenceValue = null;
    for (DataSet dataSet : this.dataSetAnnos) {
      // Run pre initializer
      this.runInitializer(dataSet.preInitializer(), emUtil.getEm());
     
      if (DataSet.NO_CLEAN_INSERT.equals(dataSet.value())) {
        // Skip clean-insert       
      } else {
        // Perform clean-insert of value resource
        URL resourceUrl = this.resourceToUrl(dataSet.value(), dataSet.resourceDir());
        du.buildImport()
          .from(resourceUrl)
          .executeCleanInsert();
      }
     
      // Perform inserts for inserts resources
      for (String insertResource : dataSet.inserts()) {
        URL resourceUrl = this.resourceToUrl(insertResource, dataSet.resourceDir());
        du.buildImport()
          .from(resourceUrl)
          .executeInsert();
      }
     
      sequenceValue = dataSet.sequenceValue();
     
      // Run post initializer
      this.runInitializer(dataSet.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 (PostServerCode code : this.postServerCodeAnnos) {
      Class<?> codeClass = null;
      if (code.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 = code.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) {
      DataSetExport dataSetExport = this.dataSetExportAnnos.get(0);
     
      // 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 = new DbDataUtil(emUtil).buildExport()
          .addTablesByDataSet(tablesDataSestUrl, true);
     
      doc = eb.writeToXmlDocument();
     
      if (dataSetExport.doPhysicalExport()) {
        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 {
        // Log XML
        if (logger.isInfoEnabled()) {
          XString xs = new XString(targetFileName);
          xs.newLine();
          xs.addLine(XmlUtils.toString(doc, true, true));
          logger.info(xs.toString());
        }
      }
     
      if (this.dataSetExportAnnos.size() > 1) {
        logger.warn("Ignoring DataSetExport annotations as only first is processed");
      }
    }
   
    // Run data verifiers (provided the test method and data set export has succeeded)
    List<DataVerifier> verifiers = new ArrayList<DataVerifier>();
   
    // Check for programmatic verifiers
    for (DataVerify verify : this.dataVerifyAnnos) {
      Class<?> verifierClass = null;
      if (verify.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 = verify.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());
      }

      // Use cause (if possible / serializable)
      Throwable cause = IOUtil.isSerializable(t.getCause())
          ? t.getCause()
          : new JuException("Original cause was non-serializable. Check server for details.");
         
      throw new JuRuntimeException("%s (Original Exception %s was non-serializable)", cause, t.getMessage(), t.getClass().getName());
    } 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.