aboutsummaryrefslogtreecommitdiff
path: root/pdf-as-tests
diff options
context:
space:
mode:
authorAndreas Fitzek <andreas.fitzek@iaik.tugraz.at>2014-08-29 15:09:54 +0200
committerAndreas Fitzek <andreas.fitzek@iaik.tugraz.at>2014-08-29 15:09:54 +0200
commitffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f (patch)
tree73a685d2c6becb3a274522fb31b898c8a9b0903c /pdf-as-tests
parent7a983c6687e8045efcc918c273bc43798319423b (diff)
downloadpdf-as-4-ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f.tar.gz
pdf-as-4-ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f.tar.bz2
pdf-as-4-ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f.zip
Integrated PDF-AS Testing library
Diffstat (limited to 'pdf-as-tests')
-rw-r--r--pdf-as-tests/build.gradle147
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java166
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java137
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java337
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java176
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java121
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java472
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java131
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java54
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java34
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java110
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java254
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java170
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java45
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java204
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java150
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java147
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java176
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java41
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java138
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java76
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java10
-rw-r--r--pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java9
-rw-r--r--pdf-as-tests/src/test/resources/log4j.properties15
24 files changed, 3320 insertions, 0 deletions
diff --git a/pdf-as-tests/build.gradle b/pdf-as-tests/build.gradle
new file mode 100644
index 00000000..f2bce693
--- /dev/null
+++ b/pdf-as-tests/build.gradle
@@ -0,0 +1,147 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+jar {
+ manifest {
+ attributes 'Implementation-Title': 'PDF-AS-4 Test Library', 'JARMANIFEST': 'PDF-AS-LIB'
+ }
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven { url "http://anonsvn.icesoft.org/repo/maven2/releases/" }
+}
+
+dependencies {
+ compile project (':pdf-as-lib')
+ compile project (':signature-standards:sigs-pkcs7detached')
+ compile project (':signature-standards:sigs-pades')
+ compile group: 'log4j', name: 'log4j', version: '1.2.17'
+ compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.5'
+ testCompile group: 'junit', name: 'junit', version: '4.+'
+ testCompile group: 'org.apache.pdfbox', name: 'preflight', version: '1.8.5'
+ testCompile group: 'org.apache.pdfbox', name: 'pdfbox', version: '1.8.6'
+ testCompile group: 'org.icepdf', name: 'icepdf-core', version: '5.0.7'
+}
+
+logger.info("Building tasks for Test suites")
+def suiteDir = new File(projectDir, "src/test/test-suites")
+logger.info("Searching: " + suiteDir.absolutePath)
+
+suiteDir.eachDir { File subDir ->
+ def dirname = subDir.name
+ logger.info("Test Suite " + subDir.name + " found in " + subDir.absolutePath)
+
+ task "runTestSuite${dirname.capitalize()}"(type: Test) {
+ description "runs tests from Test Suite: " + dirname
+ systemProperties 'test.dir': subDir.absolutePath
+ include '**/ParameterizedSignatureTestSuite.class'
+
+ beforeSuite { TestDescriptor descriptor ->
+ if(descriptor.getParent() == null) {
+ logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+ }
+ if(descriptor.getClassName().equals("at.gv.egiz.param_tests.ParameterizedSignatureTestSuite")) {
+ logger.quiet("Starting suite: " + dirname + " (" + descriptor.getName() + ")")
+ }
+ }
+
+ afterSuite { TestDescriptor descriptor, TestResult result ->
+ if(descriptor.getClassName().equals("at.gv.egiz.param_tests.ParameterizedSignatureTestSuite")) {
+ logger.quiet("------------------");
+ logger.quiet("Ending suite: " + dirname);
+ logger.quiet("\tResult (SUCCESS/ERROR/SKIPPED/TOTAL): " +
+ result.getSuccessfulTestCount() + "/" +
+ result.getFailedTestCount() + "/" +
+ result.getSkippedTestCount() + "/" +
+ result.getTestCount());
+ float duration_ms = result.getEndTime() - result.getStartTime()
+ float duration_sec = duration_ms / 1000.0f
+ logger.quiet("\tDuration: " + duration_sec + " s [" + duration_ms + " ms]")
+ logger.quiet("\tReport @ file://" + subDir.absolutePath + "/index.html")
+ }
+ if(descriptor.getParent() == null) {
+ logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+ }
+ }
+
+ beforeTest { TestDescriptor descriptor ->
+ logger.quiet("------------------");
+ logger.info("Running test: " + descriptor.getName())
+ }
+
+ afterTest { TestDescriptor descriptor, TestResult result ->
+ float duration_ms = result.getEndTime() - result.getStartTime()
+ float duration_sec = duration_ms / 1000.0f
+ logger.quiet(result.getResultType().toString() + " => [" +
+ descriptor.getName() + "] took " + duration_sec + " s [" + duration_ms + " ms]")
+ if(TestResult.ResultType.FAILURE.equals(result.getResultType())) {
+ if(result.getException() != null) {
+ logger.error("Failed test: " + result.getException().getMessage());
+ result.getException().printStackTrace();
+ } else {
+ logger.error("Failed test provided no exception");
+ }
+ }
+ }
+ }
+
+ test.dependsOn tasks.getByPath("runTestSuite${dirname.capitalize()}")
+
+
+ task "cleanTestSuite${dirname.capitalize()}"(type: Delete) {
+ outputs.upToDateWhen { false }
+ delete fileTree (dir: subDir.absolutePath, include: "index.html")
+ delete fileTree (dir: subDir.absolutePath, include: "**/test_result.html")
+ }
+
+ task "cleanOutFolders${dirname.capitalize()}"() << {
+ subDir.eachDir { File tcDir ->
+ File outDir = new File(tcDir, "out");
+ if(outDir.exists() && outDir.isDirectory()) {
+ outDir.eachFileRecurse { File mfile ->
+ mfile.delete()
+ }
+ outDir.delete()
+ }
+ }
+ }
+
+ clean.dependsOn tasks.getByPath("cleanOutFolders${dirname.capitalize()}")
+ clean.dependsOn tasks.getByPath("cleanTestSuite${dirname.capitalize()}")
+}
+
+tasks.getByPath(":pdf-as-lib:test").dependsOn test
+
+test {
+ include '**/DummyTest.class'
+
+ /*beforeSuite { TestDescriptor descriptor ->
+ logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+ logger.quiet("Starting suite: " + descriptor.getName())
+ }
+
+ afterSuite { TestDescriptor descriptor, TestResult result ->
+ logger.quiet("Ending suite: " + descriptor.getName());
+ logger.quiet("\tResult (SUCCESS/ERROR/SKIPPED/TOTAL): " +
+ result.getSuccessfulTestCount() + "/" +
+ result.getFailedTestCount() + "/" +
+ result.getSkippedTestCount() + "/" +
+ result.getTestCount());
+ logger.quiet("\tDuration: " + (result.getEndTime() - result.getStartTime()) + " MS");
+ if(descriptor.getClassName().equals("at.gv.egiz.param_tests.ParameterizedSignatureTestSuite")) {
+ logger.quiet("\tReport @ file:///" + subDir.absolutePath + "/index.html")
+ }
+ logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+ }*/
+
+ beforeTest { TestDescriptor descriptor ->
+ logger.quiet("------------------");
+ logger.quiet("Running test: " + descriptor.getName())
+ }
+
+ afterTest { TestDescriptor descriptor, TestResult result ->
+ logger.quiet("Ending test: " + descriptor.getName() + " result " + result)
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java
new file mode 100644
index 00000000..e5c8e1be
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java
@@ -0,0 +1,166 @@
+package at.gv.egiz.param_tests;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.util.Collection;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.pdfbox.preflight.PreflightDocument;
+import org.apache.pdfbox.preflight.ValidationResult;
+import org.apache.pdfbox.preflight.exception.SyntaxValidationException;
+import org.apache.pdfbox.preflight.parser.PreflightParser;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.param_tests.provider.BaseSignatureTestData;
+import at.gv.egiz.param_tests.provider.PDFAProvider;
+import at.gv.egiz.param_tests.serialization.SerializiationManager;
+import at.gv.egiz.param_tests.serialization.html.PDFAHTMLSerizalier;
+import at.gv.egiz.param_tests.testinfo.PDFATestInfo;
+import at.gv.egiz.pdfas.common.exceptions.PdfAsException;
+
+/**
+ * Parameterized unit-test for checking if a PDF-file is PDFA conform after
+ * signing. The test result is inconclusive if the file is already not conform
+ * before signing.
+ *
+ * @author mtappler
+ *
+ */
+@RunWith(Parameterized.class)
+public class PDFASignatureTest extends SignatureTest {
+
+ /**
+ * the logger for this class
+ */
+ private static final Logger logger = LoggerFactory
+ .getLogger(PDFASignatureTest.class);
+
+ /**
+ * Sets up this class, which includes registering a serializer for it,
+ * respectively the test type.
+ */
+ @BeforeClass
+ public static void setUpClass() {
+ SerializiationManager.getInstance().registerSerializer(
+ new PDFAHTMLSerizalier(), PDFATestInfo.class);
+ }
+
+ /**
+ * Constructor which sets the parameter for this parameterized unit test.
+ *
+ * @param testDirectory
+ * name of the directory of this test
+ * @param testName
+ * the name of this test
+ * @param testData
+ * basic test data, like connector, or signature profile
+ */
+ public PDFASignatureTest(String testDirectory, String testName,
+ BaseSignatureTestData testData) {
+ this.baseTestData = testData;
+ }
+
+ /**
+ * Static data-function, which is needed for JUnit's parameterized tests. It
+ * returns one collection item per test, which contains one array element
+ * per constructor parameter.
+ *
+ * @return the parameterized test data
+ */
+ //@Parameters(name = "{index}-pdfa signature test:<{0}> - {1}")
+ @Parameters(name = "{index}-{1}")
+ public static Collection<Object[]> data() {
+ return new PDFAProvider().gatherData();
+ }
+
+ /**
+ * The actual unit test which is executed, it checks if the input is
+ * PDFA-conform, signs the PDF file and checks if the signing result is
+ * PDFA-conform. If the input file is not PDF-conform the test is skipped as
+ * being inconclusive.
+ *
+ * @throws CertificateException
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @throws PdfAsException
+ */
+ @Test
+ public void pdfaTest() throws CertificateException, FileNotFoundException,
+ IOException, PdfAsException {
+ PDFATestInfo testInfo = SerializiationManager.getInstance()
+ .createTestInfo(PDFATestInfo.class, baseTestData);
+ assertTrue("No input file given", baseTestData.getPdfFile() != null);
+ File inputFile = new File(baseTestData.getPdfFile());
+ assertTrue("Given input file must exists", inputFile.exists());
+ Pair<ValidationResult, Throwable> resultBeforeSign = checkPDFAConformance(inputFile);
+ testInfo.setResultBeforeSign(resultBeforeSign);
+ Assume.assumeTrue("Input should conform to PDF-A standard",
+ resultBeforeSign.getLeft() != null
+ && resultBeforeSign.getLeft().isValid());
+ File outputPdfFile = signPDFFile();
+ logger.debug("Signed document " + baseTestData.getOutputFile());
+
+ Pair<ValidationResult, Throwable> resultAfterSign = checkPDFAConformance(outputPdfFile);
+ testInfo.setResultAfterSign(resultAfterSign);
+ assertTrue("Output file must be PDF-A conform",
+ resultAfterSign.getLeft() != null
+ && resultAfterSign.getLeft().isValid());
+ }
+
+ /**
+ * Helper method for checking PDFA-conformance.
+ *
+ * @param fd
+ * the File object corresponding to the file, which should be
+ * checked.
+ * @return a pair consisting of the validation result and an exception,
+ * which might have been thrown during the validation (both possibly
+ * null)
+ */
+ private Pair<ValidationResult, Throwable> checkPDFAConformance(File fd) {
+ PreflightDocument document = null;
+ ValidationResult result = null;
+ try {
+ PreflightParser parser = new PreflightParser(fd);
+ parser.parse();
+ document = parser.getPreflightDocument();
+ document.validate();
+ document.close();
+ result = document.getResult();
+ return new ImmutablePair<ValidationResult, Throwable>(result, null);
+ } catch (SyntaxValidationException e) {
+ logger.debug("The file " + fd.getName()
+ + " is syntactically invalid.", e);
+ return new ImmutablePair<ValidationResult, Throwable>(result, e);
+ } catch (IOException e) {
+ logger.debug("An IOException (" + e.getMessage()
+ + ") occurred, while validating the PDF-A conformance of "
+ + fd.getName(), e);
+ return new ImmutablePair<ValidationResult, Throwable>(result, e);
+ } catch (RuntimeException e) {
+ logger.debug("An RuntimeException (" + e.getMessage()
+ + ") occurred, while validating the PDF-A conformance of "
+ + fd.getName(), e);
+ return new ImmutablePair<ValidationResult, Throwable>(result, e);
+ } finally {
+ if (document != null) {
+ IOUtils.closeQuietly((Closeable)document);
+ }
+ }
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java
new file mode 100644
index 00000000..a8e03b5e
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java
@@ -0,0 +1,137 @@
+package at.gv.egiz.param_tests;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.net.URL;
+
+import org.apache.commons.io.output.TeeOutputStream;
+import org.apache.log4j.PropertyConfigurator;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+
+import at.gv.egiz.param_tests.serialization.SerializiationManager;
+import at.gv.egiz.param_tests.serialization.html.HTMLTestSummaryWriter;
+
+/**
+ * Test suite which groups all available parameterized tests in
+ * <code>Suite.SuiteClasses</code>-annotation.
+ *
+ * @author mtappler
+ *
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({ PDFASignatureTest.class, SignaturePositionTest.class })
+public class ParameterizedSignatureTestSuite {
+ /**
+ * variable to save the standard output <code>PrintStream</code> after
+ * redirecting it, to be able to reset it afterwards, this should only be
+ * necessary if this class is loaded and run dynamically (not using right
+ * mouse -> Run As -> JUnit Test)
+ */
+ private static PrintStream stdOutSave;
+ /**
+ * variable to save the standard error <code>PrintStream</code> after
+ * redirecting it
+ */
+ private static PrintStream stderrSave;
+ /**
+ * stream which logs everything which is written to stdout during test
+ * execution
+ */
+ private static ByteArrayOutputStream stdoutLog;
+ /**
+ * stream which logs everything which is written to stderr during test
+ * execution
+ */
+ private static ByteArrayOutputStream stderrLog;
+
+ /**
+ * the location of the log4j configuration
+ */
+ private static final URL log4j = ParameterizedSignatureTestSuite.class
+ .getResource("/log4j.properties");
+ /**
+ * If set to true, logging will be configured, when this class is loaded.
+ * This ensures that logging will be available in tests before logging is
+ * configured by pdf-as.
+ */
+ private static final boolean CONFIGURE_LOGGING = true;
+
+ /**
+ * Getter
+ *
+ * @return the byte array output stream which captures stdout
+ */
+ public static ByteArrayOutputStream getStdoutLog() {
+ // to avoid null NPE, if a test is started without testsuite
+ return stdoutLog != null ? stdoutLog : new ByteArrayOutputStream();
+ }
+
+ /**
+ * Getter
+ *
+ * @return the byte array output stream which captures stderr
+ */
+ public static ByteArrayOutputStream getStderrLog() {
+ // to avoid null NPE
+ return stderrLog != null ? stderrLog : new ByteArrayOutputStream();
+ }
+
+ static {
+ String testDir = System.getProperty("test.dir");
+ System.out.println("Running Tests from: " + testDir);
+ }
+
+ /**
+ * Sets up the test run by redirecting stdout and stderr such that both are
+ * written to the console and to a byte array output stream and configures
+ * logging if the corresponding flag is set.
+ *
+ * @throws InitializationError
+ * if no test directory is given via the system property
+ * "test.dir"
+ */
+ @BeforeClass
+ public static void setUpClass() throws InitializationError {
+ stdoutLog = new ByteArrayOutputStream();
+ TeeOutputStream teeStdOut = new TeeOutputStream(System.out, stdoutLog);
+ stdOutSave = System.out;
+ System.setOut(new PrintStream(teeStdOut));
+ stderrLog = new ByteArrayOutputStream();
+ TeeOutputStream teeStdErr = new TeeOutputStream(System.err, stderrLog);
+ stderrSave = System.err;
+ System.setErr(new PrintStream(teeStdErr));
+ if (CONFIGURE_LOGGING && log4j != null) {
+ try {
+ PropertyConfigurator.configure(new FileInputStream(log4j
+ .getFile()));
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ String testDir = System.getProperty("test.dir");
+ if (testDir == null)
+ throw new InitializationError("Test directory is not set");
+
+ SerializiationManager.getInstance().setTestSummaryWriter(
+ new HTMLTestSummaryWriter(testDir));
+ }
+
+ /**
+ * Resets stdout and stderr redirection and serializes all test results.
+ */
+ @AfterClass
+ public static void tearDownClass() {
+ System.setOut(stdOutSave);
+ System.setErr(stderrSave);
+ SerializiationManager.getInstance().serializeAll();
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java
new file mode 100644
index 00000000..282eeb05
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java
@@ -0,0 +1,337 @@
+package at.gv.egiz.param_tests;
+
+import static org.junit.Assert.*;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.print.PrinterException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.util.Collection;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import org.icepdf.core.pobjects.Document;
+import org.icepdf.core.pobjects.PDimension;
+import org.icepdf.core.pobjects.Page;
+import org.icepdf.core.util.GraphicsRenderingHints;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import at.gv.egiz.param_tests.provider.BaseSignatureTestData;
+import at.gv.egiz.param_tests.provider.SignaturePositionProvider;
+import at.gv.egiz.param_tests.serialization.SerializiationManager;
+import at.gv.egiz.param_tests.serialization.html.SignaturePositionHTMLSerializer;
+import at.gv.egiz.param_tests.testinfo.SignaturePositionTestInfo;
+import at.gv.egiz.pdfas.common.exceptions.PdfAsException;
+
+/**
+ * Parameterized unit test for checking the correct positioning of the signature
+ * block, or for capturing reference images (if it is known that the current
+ * implementation is correct).
+ *
+ * @author mtappler
+ *
+ */
+@RunWith(Parameterized.class)
+public class SignaturePositionTest extends SignatureTest {
+
+ /**
+ * Zoom value which controls the resolution of the image taken for image
+ * comparison. It must be the same as for capturing the reference picture.
+ */
+ private static final float ZOOM = 4;
+ /**
+ * The page number of the page, which shows the signature block.
+ */
+ private int sigPageNumber = -1;
+ /**
+ * A list of rectangular areas, which will be ignored for image comparison
+ */
+ private List<Rectangle> ignoredAreas = null;
+ /**
+ * the file name of the reference file for image comparison
+ */
+ private String refImageFileName = null;
+ /**
+ * if set to true, a reference image will be captured, but no actual
+ * comparison will be performed
+ */
+ private boolean captureReference = false;
+
+ /**
+ * Set up method to perform, when the class is loaded, which registers a
+ * serializer for it.
+ */
+ @BeforeClass
+ public static void setUpClass() {
+ SerializiationManager.getInstance().registerSerializer(
+ new SignaturePositionHTMLSerializer(),
+ SignaturePositionTestInfo.class);
+ }
+
+ /**
+ * Constructor for a single parameterized unit test, constructor parameters
+ * are parameters for the test.
+ *
+ * @param testDirectory
+ * directory, which defines this test
+ * @param testName
+ * name of this test
+ * @param baseTestData
+ * basic test data common to all parameterized signature tests
+ * @param positionString
+ * string specifying the position of the signature block
+ * @param sigPageNumber
+ * number of the page, on which the signature block should be
+ * shown
+ * @param ignoredAreas
+ * areas, which are ignored during the image comparison
+ * @param refImageFileName
+ * file name of the reference image
+ * @param captureReference
+ * if set to true, a reference image will be captured, but no
+ * actual comparison will be performed
+ */
+ public SignaturePositionTest(String testDirectory, String testName,
+ BaseSignatureTestData baseTestData, String positionString,
+ int sigPageNumber, List<Rectangle> ignoredAreas,
+ String refImageFileName, boolean captureReference) {
+ this.baseTestData = baseTestData;
+ this.positionString = positionString;
+ this.sigPageNumber = sigPageNumber;
+ this.ignoredAreas = ignoredAreas;
+ if (baseTestData.getPdfFile() != null) { // should not happen actually
+ this.refImageFileName = extractDirectoryString(baseTestData
+ .getPdfFile()) + refImageFileName;
+ } else {
+ this.refImageFileName = refImageFileName;
+ }
+ this.captureReference = captureReference;
+ }
+
+ /**
+ * This methods truncates a file such that the base name of the given is cut
+ * off, i.e. it does something like Unix' dirname, but adds a "/" at the
+ * end.
+ *
+ * @param fileName
+ * the name of the file
+ * @return the directory name part of the file
+ */
+ private String extractDirectoryString(String fileName) {
+ try {
+ return new File(fileName).getCanonicalFile().getParent() + "/";
+ } catch (IOException e) {
+ // fall back
+ return fileName.substring(0, fileName.lastIndexOf('/') + 1);
+ }
+ }
+
+ /**
+ * Static data-function, which is needed for JUnit's parameterized tests. It
+ * returns one collection item per test, which contains one array element
+ * per constructor parameter.
+ *
+ * @return the parameterized test data
+ */
+ //@Parameters(name = "{index}-signature position test:<{0}> - {1}")
+ @Parameters(name = "{index}-{1}")
+ public static Collection<Object[]> data() {
+ return new SignaturePositionProvider().gatherData();
+ }
+
+ /**
+ * Helper method, which captures a reference image, i.e. an image of the
+ * page with page number specified by <code>sigPageNumber</code> of the
+ * PDF-file after signing. The captured file is saved, as well as a modified
+ * version of it. The modified version contains black rectangles for the
+ * ignored areas.
+ *
+ * @param testInfo
+ * a test info object, which is used to store the location of the
+ * reference image, as well as the location of the reference
+ * image with ignored areas
+ * @throws IOException
+ */
+ private void captureReferenceImage(SignaturePositionTestInfo testInfo)
+ throws IOException {
+ String pdfName = baseTestData.getOutputFile();
+ String referenceOutputFile = refImageFileName;
+ int pageNumber = sigPageNumber;
+ BufferedImage image = captureImage(pdfName, pageNumber);
+ ImageIO.write(image, "png", new File(referenceOutputFile));
+ Graphics refImageGraphics = image.createGraphics();
+ ignoreAreas(refImageGraphics, image.getHeight());
+ String refImageIgnored = extractDirectoryString(baseTestData
+ .getOutputFile()) + "refImage_ignored.png";
+ ImageIO.write(image, "png", new File(refImageIgnored));
+ testInfo.setRefImageIgnored(refImageIgnored);
+ refImageGraphics.dispose();
+ image.flush();
+ }
+
+ /**
+ * The actual test method, which captures an image of the signature block
+ * page of the signed PDFs and compares it pixel by pixel with the reference
+ * image.
+ *
+ * @throws FileNotFoundException
+ * @throws CertificateException
+ * @throws IOException
+ * @throws PdfAsException
+ * @throws IndexOutOfBoundsException
+ * @throws PrinterException
+ */
+ @Test
+ public void signaturePositionTest() throws FileNotFoundException,
+ CertificateException, IOException, PdfAsException,
+ IndexOutOfBoundsException, PrinterException {
+ SignaturePositionTestInfo testInfo = SerializiationManager
+ .getInstance().createTestInfo(SignaturePositionTestInfo.class,
+ baseTestData);
+ testInfo.getAdditionParameters().setCaptureReferenceImage(
+ captureReference);
+ testInfo.getAdditionParameters().setPositionString(positionString);
+ testInfo.getAdditionParameters().setIgnoredAreas(ignoredAreas);
+ testInfo.getAdditionParameters().setRefImageFileName(refImageFileName);
+ testInfo.getAdditionParameters().setSigPageNumber(sigPageNumber);
+
+ Assume.assumeNotNull(positionString);
+ Assume.assumeNotNull(refImageFileName);
+ Assume.assumeFalse("A page number must be specified",
+ sigPageNumber == -1);
+ signPDFFile();
+ if (captureReference) {
+ captureReferenceImage(testInfo);
+ return;
+ }
+ BufferedImage sigPageImage = captureImage(baseTestData.getOutputFile(),
+ sigPageNumber);
+ assertNotNull("Could not get image of page", sigPageImage);
+ BufferedImage refImage = ImageIO.read(new File(refImageFileName));
+ assertNotNull("Could not get reference image", sigPageImage);
+
+ assertEquals("Width of image differs from reference",
+ refImage.getWidth(), sigPageImage.getWidth());
+ assertEquals("Height of image differs from reference",
+ refImage.getHeight(), sigPageImage.getHeight());
+
+ Graphics sigPageGraphics = sigPageImage.getGraphics();
+ Graphics refImageGraphics = refImage.createGraphics();
+ int imageHeight = sigPageImage.getHeight();
+ ignoreAreas(sigPageGraphics, imageHeight);
+ ignoreAreas(refImageGraphics, imageHeight);
+
+ String refImageIgnored = extractDirectoryString(baseTestData
+ .getOutputFile()) + "refImage_ignored.png";
+ ImageIO.write(refImage, "png", new File(refImageIgnored));
+ testInfo.setRefImageIgnored(refImageIgnored);
+ String sigPageImageIgnored = extractDirectoryString(baseTestData
+ .getOutputFile()) + "sigPageImage_ignored.png";
+ ImageIO.write(sigPageImage, "png", new File(sigPageImageIgnored));
+ testInfo.setSigPageImageIgnored(sigPageImageIgnored);
+
+ // now perform the pixel by pixel comparison
+ boolean same = true;
+ BufferedImage differenceImage = new BufferedImage(refImage.getWidth(),
+ refImage.getHeight(), refImage.getType());
+ Graphics differenceGraphics = differenceImage.createGraphics();
+ differenceGraphics.setColor(Color.WHITE);
+ differenceGraphics.fillRect(0, 0, differenceImage.getWidth(),
+ differenceImage.getHeight());
+ for (int x = 0; x < refImage.getWidth(); x++) {
+ for (int y = 0; y < refImage.getHeight(); y++) {
+ // since both reference and signature page image are
+ // produced in the same (use same color model) and we
+ // want a pixel by pixel comparison, this should work
+ boolean samePixel = refImage.getRGB(x, y) == sigPageImage
+ .getRGB(x, y);
+ if (!samePixel) {
+ same = false;
+ differenceImage.setRGB(x, y, Color.RED.getRGB());
+ }
+ }
+ }
+ String diffImage = extractDirectoryString(baseTestData.getOutputFile())
+ + "difference.png";
+ ImageIO.write(differenceImage, "png", new File(diffImage));
+ testInfo.setDiffImage(diffImage);
+
+ differenceGraphics.dispose();
+ differenceImage.flush();
+ sigPageGraphics.dispose();
+ sigPageImage.flush();
+ refImageGraphics.dispose();
+ refImage.flush();
+ assertTrue("Images must be the same", same);
+ }
+
+ /**
+ * Helper method, which "clears" all areas specified by
+ * <code>ignoredAreas</code>. "Cleared" are colored in the background color
+ * of the image (black).
+ *
+ * @param graphics
+ * Graphics object corresponding to an image
+ * @param imageHeight
+ * the height of the image
+ */
+ protected void ignoreAreas(Graphics graphics, int imageHeight) {
+ for (Rectangle r : ignoredAreas) {
+ int effectiveX = (int) (r.x * ZOOM);
+ // in awt 0 is at top, in PDF-AS 0 is at bottom
+ int effectiveY = imageHeight - (int) (r.y * ZOOM);
+ int effectiveWidth = (int) (r.width * ZOOM);
+ int effectiveHeight = (int) (r.height * ZOOM);
+ graphics.clearRect(effectiveX, effectiveY, effectiveWidth,
+ effectiveHeight);
+ }
+ }
+
+ /**
+ * This method captures an image of a page of a PDF document. This is done
+ * using the rendering capabilities of ICEPDF.
+ *
+ * @param fileName
+ * the name of the PDF file
+ * @param pageNumber
+ * the page number which should be captured
+ * @return the captured image
+ */
+ private BufferedImage captureImage(String fileName, int pageNumber) {
+ Document document = new Document();
+ try {
+ document.setFile(fileName);
+ } catch (Exception e) {
+ document.dispose();
+ fail(String
+ .format("Not possible to capture page %d of file %s, because of %s.",
+ pageNumber, fileName, e.getMessage()));
+ }
+ Page page = document.getPageTree().getPage(pageNumber - 1);
+ page.init();
+ PDimension sz = page.getSize(Page.BOUNDARY_CROPBOX, 0, ZOOM);
+
+ int pageWidth = (int) sz.getWidth();
+ int pageHeight = (int) sz.getHeight();
+
+ BufferedImage image = new BufferedImage(pageWidth, pageHeight,
+ BufferedImage.TYPE_4BYTE_ABGR);
+ Graphics g = image.createGraphics();
+ page.paint(g, GraphicsRenderingHints.PRINT, Page.BOUNDARY_CROPBOX, 0,
+ ZOOM);
+ document.dispose();
+ return image;
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java
new file mode 100644
index 00000000..97e7e819
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java
@@ -0,0 +1,176 @@
+package at.gv.egiz.param_tests;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.param_tests.provider.BaseSignatureDataProvider;
+import at.gv.egiz.param_tests.provider.BaseSignatureTestData;
+import at.gv.egiz.pdfas.common.exceptions.PdfAsException;
+import at.gv.egiz.pdfas.common.utils.StreamUtils;
+import at.gv.egiz.pdfas.lib.api.ByteArrayDataSink;
+import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource;
+import at.gv.egiz.pdfas.lib.api.Configuration;
+import at.gv.egiz.pdfas.lib.api.DataSource;
+import at.gv.egiz.pdfas.lib.api.PdfAs;
+import at.gv.egiz.pdfas.lib.api.PdfAsFactory;
+import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner;
+import at.gv.egiz.pdfas.lib.api.sign.SignParameter;
+import at.gv.egiz.pdfas.sigs.pades.PAdESSigner;
+import at.gv.egiz.pdfas.sigs.pades.PAdESSignerKeystore;
+import at.gv.egiz.sl.util.BKUSLConnector;
+import at.gv.egiz.sl.util.MOAConnector;
+
+/**
+ * Base class for parameterized signature tests, which provides fields and
+ * methods common to all parameterized signature test.
+ *
+ * @author mtappler
+ *
+ */
+public class SignatureTest {
+
+ /**
+ * test data common to all test types, like connector type
+ */
+ protected BaseSignatureTestData baseTestData;
+ /**
+ * positioning string which specifies the position of the signature block
+ */
+ protected String positionString;
+ /**
+ * logger for this class
+ */
+ private static final Logger logger = LoggerFactory
+ .getLogger(PDFASignatureTest.class);
+
+ /**
+ * a JUnit TestWatcher, this object gets notified several times a
+ * subclass of this class is run as test
+ */
+ @Rule
+ public TestRule watchman = new SignatureTestWatcher();
+
+ /**
+ * PdfAs used for signing and creating a configuration
+ */
+ protected PdfAs pdfAs;
+ /**
+ * the sign parameter used for signing in the test
+ */
+ protected SignParameter signParameter;
+ /**
+ * the connector used for signing in the test
+ */
+ protected IPlainSigner slConnector;
+
+ /**
+ * This method signs a file, all parameter like input file name are specified
+ * through fields of this class.
+ *
+ * @return the signed file File-object
+ * @throws IOException
+ * @throws FileNotFoundException
+ * @throws CertificateException
+ * @throws PdfAsException
+ */
+ protected File signPDFFile() throws IOException, FileNotFoundException,
+ CertificateException, PdfAsException {
+ File inputFile = new File(baseTestData.getPdfFile());
+ File outputPdfFile = new File(baseTestData.getOutputFile());
+ DataSource dataSource = new ByteArrayDataSource(
+ StreamUtils.inputStreamToByteArray(new FileInputStream(
+ inputFile)));
+ ByteArrayDataSink dataSink = new ByteArrayDataSink();
+ pdfAs = null;
+
+ pdfAs = PdfAsFactory.createPdfAs(new File(baseTestData
+ .getConfigurationFile()));
+ Configuration configuration = pdfAs.getConfiguration();
+
+ signParameter = PdfAsFactory.createSignParameter(configuration,
+ dataSource);
+
+ String id = UUID.randomUUID().toString();
+ signParameter.setTransactionId(id);
+ logger.debug("Transaction: " + id);
+
+ slConnector = null;
+
+ if (baseTestData.getConnectorData() != null) {
+ if (baseTestData.getConnectorData().getConnectorType()
+ .equals("bku")) {
+ slConnector = new PAdESSigner(new BKUSLConnector(configuration));
+ } else if (baseTestData.getConnectorData().getConnectorType()
+ .equals("moa")) {
+ slConnector = new PAdESSigner(new MOAConnector(configuration));
+ } else if (baseTestData.getConnectorData().getConnectorType()
+ .equals("ks")) {
+ Map<String, String> params = baseTestData.getConnectorData()
+ .getConnectorParameters();
+ String keystoreFilename = params
+ .get(BaseSignatureDataProvider.KS_FILE_NAME);
+ String keystoreAlias = params
+ .get(BaseSignatureDataProvider.KS_ALIAS);
+ String keystoreType = params
+ .get(BaseSignatureDataProvider.KS_TYPE);
+ String keystoreStorepass = params
+ .get(BaseSignatureDataProvider.KS_PASS);
+ String keystoreKeypass = params
+ .get(BaseSignatureDataProvider.KS_KEY_PASS);
+
+ assertNotNull(
+ "You need to provide a keystore file if using ks connector",
+ keystoreFilename);
+ assertNotNull(
+ "You need to provide a key alias if using ks connector",
+ keystoreAlias);
+
+ slConnector = new PAdESSignerKeystore(keystoreFilename,
+ keystoreAlias, keystoreStorepass, keystoreKeypass,
+ keystoreType);
+ }
+ }
+ if (slConnector == null) {
+ slConnector = new PAdESSigner(new BKUSLConnector(configuration));
+ }
+
+ signParameter.setOutput(dataSink);
+ signParameter.setPlainSigner(slConnector);
+ signParameter.setDataSource(dataSource);
+ // this is not needed for PDF-A test
+ if (positionString != null)
+ signParameter.setSignaturePosition(positionString);
+ signParameter.setSignatureProfileId(baseTestData.getProfilID());
+ logger.debug("Starting signature for " + baseTestData.getPdfFile());
+ logger.debug("Selected signature Profile " + baseTestData.getProfilID());
+ /* SignResult result = */pdfAs.sign(signParameter);
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(outputPdfFile, false);
+ fos.write(dataSink.getData());
+ fos.close();
+ } catch (IOException e) {
+ logger.debug("IO exception occured while writing PDF output file",
+ e);
+ throw e;
+ } finally {
+ IOUtils.closeQuietly(fos);
+ }
+
+ return outputPdfFile;
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java
new file mode 100644
index 00000000..104dab7c
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java
@@ -0,0 +1,121 @@
+package at.gv.egiz.param_tests;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import at.gv.egiz.param_tests.serialization.SerializiationManager;
+
+/**
+ * A test watcher, which provides some basic information about executed tests,
+ * i.e. verdicts and console output. Basically a test watcher is some class,
+ * which gets notified every time a test started or stopped.
+ *
+ * @author mtappler
+ *
+ */
+public class SignatureTestWatcher extends TestWatcher {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return super.apply(base, description);
+ }
+
+ /**
+ * This method tells the serialization manager that a test succeeded
+ *
+ * @param description
+ * the description of the test, which contains the display name,
+ * which in turn contains the directory of the test, which
+ * uniquely identifies the test
+ */
+ @Override
+ protected void succeeded(Description description) {
+ super.succeeded(description);
+ SerializiationManager.getInstance().setSucceeded(
+ extractDirectoryName(description.getDisplayName()));
+
+ }
+
+ /**
+ * Helper method which extracts the directory name of a test given its
+ * display name. By convention, the display name shall include directory
+ * names enclosed in angle brackets.
+ *
+ * @param displayName
+ * the name which is displayed for a test
+ * @return the directory of the test
+ */
+ private String extractDirectoryName(String displayName) {
+ return displayName.substring(displayName.indexOf('[') + 1,
+ displayName.lastIndexOf(']'));
+
+ //return displayName.substring(displayName.indexOf('<') + 1,
+ // displayName.lastIndexOf('>'));
+ }
+
+ /**
+ * This method tells the serialization manager that a test failed
+ *
+ * @param description
+ * the description of the test, which contains the display name,
+ * which in turn contains the directory of the test, which
+ * uniquely identifies the test
+ */
+ @Override
+ protected void failed(Throwable e, Description description) {
+ super.failed(e, description);
+ SerializiationManager.getInstance().setFailed(
+ extractDirectoryName(description.getDisplayName()), e);
+
+ }
+
+ /**
+ * This method tells the serialization manager that a test was skipped,
+ * because of a failed assumption.
+ *
+ * @param description
+ * the description of the test, which contains the display name,
+ * which in turn contains the directory of the test, which
+ * uniquely identifies the test
+ */
+ protected void skipped(AssumptionViolatedException e,
+ Description description) {
+ SerializiationManager.getInstance().setInconclusive(
+ extractDirectoryName(description.getDisplayName()), e);
+ }
+
+ /**
+ * This method resets the logging of stdout and stderr, to be able to
+ * retrieve it after the test has finished.
+ *
+ * @param description
+ * description of a test which was just started
+ */
+ @Override
+ protected void starting(Description description) {
+ super.starting(description);
+ ParameterizedSignatureTestSuite.getStdoutLog().reset();
+ ParameterizedSignatureTestSuite.getStderrLog().reset();
+ }
+
+ /**
+ * This method sets stdout and stderr, which was logged during the execution
+ * of a test.
+ *
+ * @param description
+ * the description of the test, which contains the display name,
+ * which in turn contains the directory of the test, which
+ * uniquely identifies the test
+ */
+ @Override
+ protected void finished(Description description) {
+ super.finished(description);
+ SerializiationManager.getInstance().setStdOut(
+ extractDirectoryName(description.getDisplayName()),
+ ParameterizedSignatureTestSuite.getStdoutLog().toString());
+ SerializiationManager.getInstance().setStdErr(
+ extractDirectoryName(description.getDisplayName()),
+ ParameterizedSignatureTestSuite.getStderrLog().toString());
+ }
+} \ No newline at end of file
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java
new file mode 100644
index 00000000..1ce78e14
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java
@@ -0,0 +1,472 @@
+package at.gv.egiz.param_tests.provider;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.filefilter.WildcardFileFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.pdfas.lib.api.PdfAsFactory;
+
+/**
+ * Abstract base class for test data providers. The subclasses of it shall read
+ * configuration properties and provide test parameters for parameterized unit
+ * tests. This offers some functionality, which is needed by all tests, e.g. it
+ * reads basic configuration data, which is needed for signing a PDF file, which
+ * is done by all tests.
+ *
+ * @author mtappler
+ *
+ */
+public abstract class BaseSignatureDataProvider {
+
+ /**
+ * config-property key for specifying the type of the test
+ */
+ public static final String TEST_TYPE = "test.type";
+
+ /**
+ * config-property key for specifying the name of the test, if no name is
+ * specified, the test directory will be used as name
+ */
+ public static final String TEST_NAME = "test.name";
+
+ /**
+ * config-property for specifying signature profile
+ */
+ public static final String PROFILE_ID = "profile.id";
+
+ /**
+ * config-property key for specifying the name of the output file
+ */
+ public static final String OUTPUT_FILE = "output.file";
+ /**
+ * config-property key for specifying the type of connector, which is used
+ * for signing
+ */
+ public static final String CONNECTOR = "connector";
+ /**
+ * config-property key for PDF-AS configuration file location
+ */
+ public static final String CONFIG_FILE = "config.file";
+ /**
+ * config-property key for PDF input file name, the file which is signed
+ */
+ public static final String INPUT_FILE = "input.file";
+
+ public static final String PARENT_CFG = "parent";
+
+ /**
+ * default location for PDF-AS config file
+ */
+ public static final String STANDARD_CONFIG_LOCATION = System
+ .getProperty("user.home") + "/.pdfas/";
+
+ /**
+ * config-property key for the filename of the keystore, required if ks
+ * (keystore) is used as connector
+ */
+ public static final String KS_FILE_NAME = "ks.filename";
+
+ /**
+ * config-property key for the key alias, required if ks (keystore) is used
+ * as connector
+ */
+ public static final String KS_ALIAS = "ks.alias";
+
+ /**
+ * config-property key for the keystore type, optional value if ks
+ * (keystore) is used as connector
+ */
+ public static final String KS_TYPE = "ks.type";
+
+ /**
+ * config-property key for the password of the keystore, required if ks
+ * (keystore) is used as connector
+ */
+ public static final String KS_PASS = "ks.pass";
+
+ /**
+ * config-property key for the password of the key, required if ks
+ * (keystore) is used as connector
+ */
+ public static final String KS_KEY_PASS = "ks.keypass";
+
+ /**
+ * logger for this class
+ */
+ private static final Logger logger = LoggerFactory
+ .getLogger(BaseSignatureDataProvider.class);
+
+ /**
+ * Interface for test classes: This method should be invoked to get the data
+ * for parameterized unit tests. It calls two template method style (design
+ * pattern) methods to perform different actions for different test types.
+ * Generally it loops over all directories in the test directory and filters
+ * using a wildcard-filter and then calls abstract methods, one to decide if
+ * the subclass is a provider the given test type
+ * <code>isSupportedTestType</code> and one to extract configuration data
+ * <code>extractDataFromConfig</code>.
+ *
+ * @return parameters for the parameterized unit tests
+ */
+ public List<Object[]> gatherData() {
+ String testDir = System.getProperty("test.dir");
+ logger.info("Data from: " + testDir);
+ String testFilter = System.getProperty("test.filter");
+ List<Object[]> result = new ArrayList<Object[]>();
+ File testDirFile = new File(testDir);
+ File rootConfigFile = new File(testDirFile, "config.properties");
+ Properties rootProp = new Properties();
+ InputStream rootIn = null;
+ try {
+ rootIn = new FileInputStream(rootConfigFile);
+ rootProp.load(rootIn);
+ } catch (IOException e) {
+ // if we can't get root properties, we just take empty root
+ // properties
+ rootProp = new Properties();
+ } finally {
+ IOUtils.closeQuietly(rootIn);
+ }
+
+ File[] childFiles = null;
+ if (testFilter == null) {
+ childFiles = testDirFile.listFiles();
+ } else {
+ String[] wildcards = testFilter.split(";");
+ childFiles = testDirFile
+ .listFiles((FilenameFilter) new WildcardFileFilter(
+ wildcards));
+ }
+ int idx = 0;
+ for (File child : childFiles) {
+ if (child.isDirectory() && directoryContainsConfig(child)) {
+ File configFile = new File(child, "config.properties");
+ Properties prop = new Properties();
+ InputStream in = null;
+ try {
+ in = new FileInputStream(configFile);
+ prop.load(in);
+
+ String parent = prop.getProperty(PARENT_CFG);
+ if(parent != null) {
+ File parentFile = new File(child, parent);
+ if(parentFile.exists()) {
+ prop.clear();
+ prop.load(new FileInputStream(parentFile));
+ prop.load(new FileInputStream(configFile));
+ }
+ }
+
+ if (isSupportedTestType(prop)) {
+ Object[] data = extractDataFromConfig(configFile,
+ rootProp, prop);
+ result.add(data);
+ String testName = idx + "-" + data[1].toString();
+ ((BaseSignatureTestData)data[2]).setUniqueUnitTestName(testName);
+ idx++;
+ }
+ } catch (IOException e) {
+ logger.warn(
+ "Could not run test with config:"
+ + configFile.getAbsolutePath(), e);
+ } finally {
+ if (in != null)
+ IOUtils.closeQuietly(in);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Method to extract test-type specific data/parameters from a config file.
+ *
+ * @param configFile
+ * the File object corresponding to the config file
+ * @param rootProps
+ * the properties read from the root config file
+ * @param configProps
+ * the properties read from the config file
+ * @return parameters for one parameterized unit test
+ */
+ protected abstract Object[] extractDataFromConfig(File configFile,
+ Properties rootProps, Properties configProps);
+
+ /**
+ * Utility method to extract basic configuration data, which is needed by
+ * all parameterized unit tests, i.e. test directory, test name and one
+ * instance of <code>BaseSignatureTestData</code>.
+ *
+ * @param configFile
+ * the File object corresponding to the config file
+ * @param rootProps
+ * the properties read from the root config file
+ * @param configProps
+ * the properties read from the config file
+ * @return basic parameters for one parameterized unit test
+ */
+ protected Object[] extractStandardDataFromConfig(File configFile,
+ Properties rootProps, Properties configProps) {
+ String testDirectory = null;
+ try {
+ testDirectory = configFile.getParentFile().getCanonicalPath();
+ } catch (IOException e) {
+ // getCanonicalPath() might again throw an exception
+ testDirectory = configFile.getAbsolutePath().replace(
+ "/config.properties", "");
+ }
+ String testName = getTestName(configFile, configProps);
+ String configurationFile = provideConfigFile(rootProps, configProps);
+ String profilID = getProperty(rootProps, configProps, PROFILE_ID);
+ String pdfFile = getPDFFileName(configFile, configProps);
+ String outputFile = getOutputFileName(pdfFile, configFile, configProps);
+ ConnectorData connectorData = provideConnectorData(rootProps,
+ configProps);
+ return new Object[] {
+ testDirectory,
+ testName,
+ new BaseSignatureTestData(testDirectory, testName,
+ configurationFile, profilID, pdfFile, outputFile,
+ connectorData) };
+ }
+
+ /**
+ * Test if the concrete provider implementation supports a test given by
+ * properties.
+ *
+ * @param prop
+ * the properties file of this test
+ * @return true if the test is suppported, false otherwise
+ */
+ protected abstract boolean isSupportedTestType(Properties prop);
+
+ /**
+ * Helper method to check if a directory contains a file called
+ * "config.properties".
+ *
+ * @param file
+ * the File object corresponding to the directory
+ * @return true if the directory contains a config file
+ */
+ private boolean directoryContainsConfig(File file) {
+ return Arrays.asList(file.list()).contains("config.properties");
+ }
+
+ /**
+ * Retrieves the test name, which should either be specified in the config
+ * file for the test case (not in the root properties) or be equal to the
+ * directory name of the test.
+ *
+ * @param configFile
+ * file object for the config file
+ * @param configProps
+ * the properties defined in the config file
+ * @return the test name
+ */
+ protected String getTestName(File configFile, Properties configProps) {
+ if (configProps.containsKey(TEST_NAME)) {
+ return configProps.getProperty(TEST_NAME);
+ } else {
+ int lastSlash = configFile.getAbsolutePath().lastIndexOf('/');
+ int secondToLastSlash = configFile.getAbsolutePath().lastIndexOf(
+ '/', lastSlash - 1);
+ return configFile.getAbsolutePath().substring(
+ secondToLastSlash + 1, lastSlash);
+ }
+ }
+
+ /**
+ * This method retrieves the configured location of the PDF-AS configuration
+ * file. It may return the standard configuration location if no location is
+ * specified.
+ *
+ * @param rootProps
+ * root properties, i.e. properties specified in the test
+ * directory
+ * @param configProps
+ * properties specific for one test case
+ * @return location of the property file
+ */
+ protected String provideConfigFile(Properties rootProps,
+ Properties configProps) {
+ String configurationFile = getProperty(rootProps, configProps,
+ CONFIG_FILE);
+ if (configurationFile == null) {
+ configurationFile = STANDARD_CONFIG_LOCATION;
+ deployConfigIfNotexisting();
+ }
+ return configurationFile;
+ }
+
+ /**
+ * Deploys a PDF-AS config in the standard location if it does not already
+ * exist, same as in pdf-as-cli (Main.java).
+ */
+ private static void deployConfigIfNotexisting() {
+ File configurationLocation = new File(STANDARD_CONFIG_LOCATION);
+ try {
+ if (!configurationLocation.exists()) {
+ PdfAsFactory.deployDefaultConfiguration(configurationLocation);
+ }
+ } catch (Exception e) {
+ logger.warn("Failed to deploy default confiuration to "
+ + configurationLocation.getAbsolutePath(), e);
+ }
+ }
+
+ /**
+ * Retrieves the file name of the PDF input file for a test, i.e. the file
+ * which should be signed. If the configuration properties do not specify an
+ * input file, the first file found in the test case directory is used.
+ *
+ * @param configFile
+ * the config file for the test
+ * @param configProps
+ * the config properties for the test
+ * @return the input file name
+ */
+ protected String getPDFFileName(File configFile, Properties configProps) {
+ String dirString = configFile.getParent() + "/";
+ if (configProps.containsKey(INPUT_FILE))
+ return dirString + configProps.getProperty(INPUT_FILE);
+ else {
+ // just return the first pdf file found in the TC directory
+ // or null for which we need to check in the TC
+ String[] pdfsInDir = configFile.getParentFile().list(
+ new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".pdf");
+ }
+ });
+ if (pdfsInDir.length == 0)
+ return null;
+ else
+ return dirString + pdfsInDir[0];
+
+ }
+ }
+
+ /**
+ * Retrieves the file name of the PDF output file for a test, i.e. the file
+ * which is signed. If the configuration properties do not specify an output
+ * file, "_signed" is appended to the name of the input file and used as
+ * name.
+ *
+ * @param configFile
+ * the config file for the test
+ * @param configProps
+ * the config properties for the test
+ * @return the output file name
+ */
+ protected String getOutputFileName(String pdfFile, File configFile,
+ Properties configProps) {
+ String outputFile = configProps.getProperty(OUTPUT_FILE);
+ if (outputFile == null) {
+ if (pdfFile.endsWith(".pdf")) {
+ outputFile = pdfFile.subSequence(0,
+ pdfFile.length() - ".pdf".length())
+ + "_signed.pdf";
+ } else {
+ outputFile = pdfFile + "_signed.pdf";
+ }
+ outputFile = outputFile.substring(outputFile.lastIndexOf('/') + 1,
+ outputFile.length());
+ }
+ new File(configFile.getParent() + "/out/").mkdir();
+ return configFile.getParent() + "/out/" + outputFile;
+
+ }
+
+ /**
+ * This method reads configuration properties and returns connector data,
+ * i.e. data specifying which connector should be used together with which
+ * parameters.
+ *
+ * @param rootProps
+ * root properties, i.e. properties specified in the test
+ * directory
+ * @param configProps
+ * properties specific for one test case
+ * @return connector data
+ */
+ protected ConnectorData provideConnectorData(Properties rootProps,
+ Properties configProps) {
+ if (getProperty(rootProps, configProps, CONNECTOR) != null) {
+ String connectorType = getProperty(rootProps, configProps,
+ CONNECTOR);
+ if (connectorType.equalsIgnoreCase("bku")) {
+ return new ConnectorData("bku",
+ Collections.<String, String> emptyMap());
+ } else if (connectorType.equalsIgnoreCase("moa")) {
+ return new ConnectorData("moa",
+ Collections.<String, String> emptyMap());
+ } else if (connectorType.equalsIgnoreCase("ks")) {
+ Map<String, String> connectorParamaters = new HashMap<String, String>();
+ connectorParamaters.put(KS_FILE_NAME,
+ getProperty(rootProps, configProps, KS_FILE_NAME));
+ connectorParamaters.put(KS_ALIAS,
+ getProperty(rootProps, configProps, KS_ALIAS));
+ if (getProperty(rootProps, configProps, KS_TYPE) != null) {
+ connectorParamaters.put(KS_TYPE,
+ getProperty(rootProps, configProps, KS_TYPE));
+ } else {
+ connectorParamaters.put(KS_TYPE, "PKCS12");
+ logger.debug("Defaulting to PKCS12 keystore type.");
+ }
+ if (getProperty(rootProps, configProps, KS_PASS) != null) {
+ connectorParamaters.put(KS_PASS,
+ getProperty(rootProps, configProps, KS_PASS));
+ } else {
+ connectorParamaters.put(KS_PASS, "");
+ }
+ if (getProperty(rootProps, configProps, KS_KEY_PASS) != null) {
+ connectorParamaters.put(KS_KEY_PASS,
+ getProperty(rootProps, configProps, KS_KEY_PASS));
+ } else {
+ connectorParamaters.put(KS_KEY_PASS, "");
+ }
+ return new ConnectorData("ks", connectorParamaters);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Helper method which looks in the Properties-maps for the value of a given
+ * key. If the value is found in <code>configProps</code> then this value is
+ * used, otherwise the value found in <code>rootProps</code> is used. This
+ * way, test properties can override root properties.
+ *
+ * @param rootProps
+ * root properties, i.e. properties specified in the test
+ * directory
+ * @param configProps
+ * properties specific for one test case
+ * @param key
+ * a config-property key
+ * @return the value for the key
+ */
+ protected String getProperty(Properties rootProps, Properties configProps,
+ String key) {
+ if (configProps.containsKey(key))
+ return configProps.getProperty(key);
+ else if (rootProps.containsKey(key))
+ return rootProps.getProperty(key);
+ else
+ return null;
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java
new file mode 100644
index 00000000..c8074b4f
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java
@@ -0,0 +1,131 @@
+package at.gv.egiz.param_tests.provider;
+
+/**
+ * Class holding data, which is used by all parameterized signature tests, e.g.
+ * basic data needed for signing. It does not contain any logic at all
+ *
+ * @author mtappler
+ *
+ */
+public class BaseSignatureTestData {
+
+ /**
+ * location of the PDF-AS configuration file
+ */
+ private String configurationFile;
+ /**
+ * signature profile ID
+ */
+ private String profilID;
+ /**
+ * location of the input PDF file which should be signed
+ */
+ private String pdfFile;
+ /**
+ * location of the output PDF file which is the signed version of the input
+ * file
+ */
+ private String outputFile;
+ /**
+ * connector data, which is used to determine the signer
+ * (IPlainSigner-interface)
+ */
+ private ConnectorData connectorData;
+ /**
+ * the name of the test
+ */
+ private String testName;
+ /**
+ * directory name of the test
+ */
+ private String testDirectory;
+
+ private String uniqueUnitTestName;
+
+ /**
+ * Constructor initializing all attributes.
+ *
+ * @param testDirectory
+ * @param testName
+ * @param configurationFile
+ * @param profilID
+ * @param pdfFile
+ * @param outputFile
+ * @param connectorData
+ */
+ public BaseSignatureTestData(String testDirectory, String testName,
+ String configurationFile, String profilID, String pdfFile,
+ String outputFile, ConnectorData connectorData) {
+ this.testDirectory = testDirectory;
+ this.testName = testName;
+ this.configurationFile = configurationFile;
+ this.profilID = profilID;
+ this.pdfFile = pdfFile;
+ this.outputFile = outputFile;
+ this.connectorData = connectorData;
+ }
+
+ public String getConfigurationFile() {
+ return configurationFile;
+ }
+
+ public void setConfigurationFile(String configurationFile) {
+ this.configurationFile = configurationFile;
+ }
+
+ public String getProfilID() {
+ return profilID;
+ }
+
+ public void setProfilID(String profilID) {
+ this.profilID = profilID;
+ }
+
+ public String getPdfFile() {
+ return pdfFile;
+ }
+
+ public void setPdfFile(String pdfFile) {
+ this.pdfFile = pdfFile;
+ }
+
+ public String getOutputFile() {
+ return outputFile;
+ }
+
+ public void setOutputFile(String outputFile) {
+ this.outputFile = outputFile;
+ }
+
+ public ConnectorData getConnectorData() {
+ return connectorData;
+ }
+
+ public void setConnectorData(ConnectorData connectorData) {
+ this.connectorData = connectorData;
+ }
+
+ public String getTestName() {
+ return testName;
+ }
+
+ public void setTestName(String testName) {
+ this.testName = testName;
+ }
+
+ public String getTestDirectory() {
+ return testDirectory;
+ }
+
+ public void setTestDirectory(String testDirectory) {
+ this.testDirectory = testDirectory;
+ }
+
+ public String getUniqueUnitTestName() {
+ return uniqueUnitTestName;
+ }
+
+ public void setUniqueUnitTestName(String uniqueUnitTestName) {
+ this.uniqueUnitTestName = uniqueUnitTestName;
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java
new file mode 100644
index 00000000..8216358c
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java
@@ -0,0 +1,54 @@
+package at.gv.egiz.param_tests.provider;
+
+import java.util.Map;
+
+/**
+ * Connector data class which specifies which
+ * connector/IPlainSigner-implementation to use.
+ *
+ * @author mtappler
+ *
+ */
+public class ConnectorData {
+ /**
+ * the type of the connector, named the same as in the CLI
+ */
+ private String connectorType;
+ /**
+ * additional parameters like key alias if a keystore is used
+ */
+ private Map<String, String> connectorParameters;
+
+ /**
+ * Constructor initializing both attributes.
+ *
+ * @param connectorType
+ * type of the connector
+ * @param connectorParameters
+ * additional parameters
+ */
+ public ConnectorData(String connectorType,
+ Map<String, String> connectorParameters) {
+ super();
+ this.connectorType = connectorType;
+ this.connectorParameters = connectorParameters;
+ }
+
+ /**
+ * getter
+ *
+ * @return type of the connector to use
+ */
+ public String getConnectorType() {
+ return connectorType;
+ }
+
+ /**
+ * getter
+ *
+ * @return additional parameters for the connector
+ */
+ public Map<String, String> getConnectorParameters() {
+ return connectorParameters;
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java
new file mode 100644
index 00000000..b8903268
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java
@@ -0,0 +1,34 @@
+package at.gv.egiz.param_tests.provider;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Signature data provider for PDF-A conformance tests
+ *
+ * @author mtappler
+ *
+ */
+public class PDFAProvider extends BaseSignatureDataProvider {
+
+ /**
+ * This method extracts signature test parameters for performing
+ * PDF-A conformance tests. It only provides standard parameters, because
+ * it does not need any other parameters.
+ */
+ @Override
+ protected Object[] extractDataFromConfig(File configFile,
+ Properties rootProps, Properties configProps) {
+ return extractStandardDataFromConfig(configFile, rootProps, configProps);
+ }
+
+ /**
+ * This method checks if this provider supports a given test configuration.
+ * In order to be supported the test type must be "pdfa".
+ */
+ @Override
+ protected boolean isSupportedTestType(Properties configProps) {
+ return configProps.containsKey(TEST_TYPE)
+ && configProps.get(TEST_TYPE).equals("pdfa");
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java
new file mode 100644
index 00000000..34727876
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java
@@ -0,0 +1,110 @@
+package at.gv.egiz.param_tests.provider;
+
+import java.awt.Rectangle;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+public class SignaturePositionProvider extends BaseSignatureDataProvider {
+
+ /**
+ * config-property value for "position.mode", if this mode is chosen, only
+ * reference image is captured and no tests are performed
+ */
+ public static final String CAPTURE_REFERENCE = "capture_reference";
+ /**
+ * config-property key for the positioning-string, which has the same syntax
+ * as in the CLI
+ */
+ public static final String POSITIONING_STRING = "position.positioning_string";
+ /**
+ * config-property key for the number of the page on which the signature is
+ * expected to appear
+ */
+ public static final String SIG_PAGE_NUMBER = "position.page_number";
+ /**
+ * config-property key for the name of the reference image
+ */
+ public static final String REFERENCE_IMAGE = "position.reference_image";
+ /**
+ * config-property key for the areas which are ignored for image comparison
+ */
+ public static final String IGNORED_AREAS = "position.ignored_areas";
+ /**
+ * config-property key for mode to use for the test, it can either be
+ * "capture_reference" or anything else
+ */
+ public static final String MODE = "position.mode";
+
+ /**
+ * This method extracts in addition to the standard parameters also
+ * signature position test parameters from the config properties. The keys
+ * for these properties are defined as string constants.
+ */
+ @Override
+ protected Object[] extractDataFromConfig(File configFile,
+ Properties rootProps, Properties configProps) {
+ Object[] standardData = extractStandardDataFromConfig(configFile,
+ rootProps, configProps);
+ Object[] data = new Object[standardData.length + 5];
+ int i;
+ for (i = 0; i < standardData.length; i++) {
+ data[i] = standardData[i];
+ }
+ data[i++] = getProperty(rootProps, configProps, POSITIONING_STRING);
+ data[i++] = Integer.parseInt(getProperty(rootProps, configProps,
+ SIG_PAGE_NUMBER));
+ String ignoredAreaStringProp = getProperty(rootProps, configProps,
+ IGNORED_AREAS);
+ List<Rectangle> ignoredAreas = new ArrayList<Rectangle>();
+ if (ignoredAreaStringProp != null) {
+ String[] ignoredAreaStringSplit = ignoredAreaStringProp.split(";");
+ for (String ignoredAreaString : ignoredAreaStringSplit) {
+ Rectangle ignoredArea = parseIgnoredArea(ignoredAreaString);
+ if (ignoredArea != null) {
+ ignoredAreas.add(ignoredArea);
+ }
+ }
+ }
+ data[i++] = ignoredAreas;
+ data[i++] = getProperty(rootProps, configProps, REFERENCE_IMAGE);
+ data[i++] = CAPTURE_REFERENCE.equals(getProperty(rootProps,
+ configProps, MODE));
+ return data;
+ }
+
+ /**
+ * This parses one ignored area definition and returns a Rectangle-object
+ * representing it. The definitions have the exact format
+ * "<x>,<y>,<width>,<height>", with x and y specifying the coordinates of
+ * the upper left corner of the area and width and height specifying the
+ * size in pixels (width and height extends to the right and the bottom of
+ * the image).
+ *
+ * @param ignoredAreaString
+ * an ignored area definition
+ * @return a rectangle representing the area
+ */
+ private Rectangle parseIgnoredArea(String ignoredAreaString) {
+ String[] ignoredAreaStringSplit = ignoredAreaString.split(",");
+ if (ignoredAreaStringSplit.length != 4)
+ return null;
+ int x = Integer.parseInt(ignoredAreaStringSplit[0]);
+ int y = Integer.parseInt(ignoredAreaStringSplit[1]);
+ int width = Integer.parseInt(ignoredAreaStringSplit[2]);
+ int height = Integer.parseInt(ignoredAreaStringSplit[3]);
+ return new Rectangle(x, y, width, height);
+ }
+
+ /**
+ * This method checks if this provider supports a given test configuration.
+ * In order to be supported the test type must be "position".
+ */
+ @Override
+ protected boolean isSupportedTestType(Properties configProps) {
+ return configProps.containsKey(TEST_TYPE)
+ && configProps.get(TEST_TYPE).equals("position");
+
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java
new file mode 100644
index 00000000..294084eb
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java
@@ -0,0 +1,254 @@
+package at.gv.egiz.param_tests.serialization;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.AssumptionViolatedException;
+
+import at.gv.egiz.param_tests.provider.BaseSignatureTestData;
+import at.gv.egiz.param_tests.testinfo.TestInfo;
+import at.gv.egiz.param_tests.testinfo.TestVerdict;
+
+/**
+ * Singleton managing the serialization of test data. Generally there is one
+ * serializer prototype per test type and one serializer clone per test which
+ * was run. Each serializer clone holds the data of exactly one test and is able
+ * to serizalize in some format. Additionally to that there is also a
+ * TestSummaryWriter-object which writes a summary of all test results.
+ *
+ * @author mtappler
+ *
+ */
+public class SerializiationManager {
+
+ /**
+ * the singleton instance
+ */
+ private static SerializiationManager instance = null;
+
+ /**
+ * The singleton accessor method. It is not used synchronized as it is
+ * expected that the parameterized unit tests won't be run in parallel.
+ *
+ * @return the singleton instance
+ */
+ public static SerializiationManager getInstance() {
+ // implement locking if needed
+ if (instance == null) {
+ instance = new SerializiationManager();
+ }
+ return instance;
+ }
+
+ /**
+ * a map of serializer prototypes, the key is the canonical name of the
+ * TestInfo-subclass which the prototype can serializer and the value is the
+ * prototype itself
+ */
+ private Map<String, TestInfoSerializer<? extends TestInfo>> serializerPrototypes = new HashMap<String, TestInfoSerializer<? extends TestInfo>>();
+
+ /**
+ * a list of serializer clones, with one element for each test
+ */
+ private List<TestInfoSerializer<? extends TestInfo>> serializers = new ArrayList<TestInfoSerializer<? extends TestInfo>>();
+ /**
+ * the test summary, which writes the summary of test results all tests
+ */
+ private TestSummaryWriter testSummaryWriter;
+
+ /**
+ * protected constructor
+ */
+ protected SerializiationManager() {
+ }
+
+ /**
+ * This method registers a serializer for a TestInfo-subclass.
+ *
+ * @param serializer
+ * a serializer prototype
+ * @param testInfoClass
+ * the class object corresponding to the TestInfo-subclass
+ */
+ public <T extends TestInfo> void registerSerializer(
+ TestInfoSerializer<T> serializer, Class<T> testInfoClass) {
+ serializerPrototypes.put(testInfoClass.getCanonicalName(), serializer);
+ }
+
+ /**
+ * This methods creates an instance of a TestInfo-subclass and returns.
+ * Actually a serializer is clone and the clone creates the instance and
+ * holds it to serialize it later.
+ *
+ * @param testInfoClass
+ * the class of the requested test information object
+ * @param baseData
+ * basic test data which is set for all test information object
+ * @return a test information object
+ */
+ public <T extends TestInfo> T createTestInfo(Class<T> testInfoClass,
+ BaseSignatureTestData baseData) {
+ @SuppressWarnings("unchecked")
+ // unfortunately this cast is necessary
+ TestInfoSerializer<T> serializer = (TestInfoSerializer<T>) serializerPrototypes
+ .get(testInfoClass.getCanonicalName());
+ // if there is no test info, the test will fail with a null pointer
+ // exception
+ // if it tries to access the test info
+ if (serializer == null)
+ return null;
+ TestInfoSerializer<T> serializerClone = serializer.cloneSerializer();
+ serializers.add(serializerClone);
+ T tInfo = serializerClone.createTestInfo();
+ tInfo.setBaseTestData(baseData);
+
+ return tInfo;
+ }
+
+ /**
+ * Sets a test given its directory name as succeeded. The directory name is
+ * used, because it uniquely defines the test, as there is only one test per
+ * directory and it is encoded in display name of the test.
+ *
+ * @param directoryName
+ * the name of the directory of the test
+ */
+ public void setSucceeded(String directoryName) {
+ TestInfo testInfo = getTestInfoForDirectory(directoryName);
+ if (testInfo != null) {
+ testInfo.setVerdict(TestVerdict.SUCCEEDED);
+ }
+ }
+
+ /**
+ * Retrieves test information for a test.
+ *
+ * @param directoryName
+ * the name of the directory of the test
+ * @return basic test information
+ */
+ private TestInfo getTestInfoForDirectory(String directoryName) {
+ for (TestInfoSerializer<?> s : serializers) {
+ if (directoryName.startsWith(s.getBaseTestInfo().getBaseTestData()
+ .getUniqueUnitTestName()))
+ return s.getBaseTestInfo();
+ }
+ return null;
+ }
+
+ /**
+ * Sets a test given its directory name as failed and sets a
+ * <code>Throwable</code>-object as fail cause. The directory name is used,
+ * because it uniquely defines the test, as there is only one test per
+ * directory and it is encoded in display name of the test.
+ *
+ * @param directoryName
+ * the name of the directory of the test
+ * @param e
+ * the cause of the fail
+ */
+ public void setFailed(String directoryName, Throwable e) {
+ TestInfo testInfo = getTestInfoForDirectory(directoryName);
+ if (testInfo != null) {
+ testInfo.setVerdict(TestVerdict.FAILED);
+ testInfo.setFailCause(e);
+ }
+ }
+
+ /**
+ * Serializes all test results including a summary. After a call to this
+ * function there will be an index file with the summary in the root test
+ * directory and a test results file in all directories containing tests.
+ */
+ public void serializeAll() {
+ testSummaryWriter.init();
+ testSummaryWriter.writeHeader();
+ Collections.sort(serializers,
+ new Comparator<TestInfoSerializer<? extends TestInfo>>() {
+ public int compare(
+ TestInfoSerializer<? extends TestInfo> o1,
+ TestInfoSerializer<? extends TestInfo> o2) {
+ String firstDir = o1.getBaseTestInfo()
+ .getBaseTestData().getTestDirectory();
+ String secondDir = o2.getBaseTestInfo()
+ .getBaseTestData().getTestDirectory();
+ return firstDir.compareTo(secondDir);
+ }
+ });
+
+ for (TestInfoSerializer<?> s : serializers) {
+ testSummaryWriter.writeSummaryOfTest(s.getBaseTestInfo(),
+ s.getTestType());
+ s.serialize();
+ }
+ testSummaryWriter.writeFooter();
+ testSummaryWriter.close();
+ }
+
+ /**
+ * Sets a test summary writer. This method must be call before
+ * serializeAll() is called, because this method relies on the presence of a
+ * test summary writer.
+ *
+ * @param testSummaryWriter
+ * a concrete implementation of <code>TestSummaryWriter</code>
+ */
+ public void setTestSummaryWriter(TestSummaryWriter testSummaryWriter) {
+ this.testSummaryWriter = testSummaryWriter;
+ }
+
+ /**
+ * Sets the content which was written to the standard output during a test
+ * for a test.
+ *
+ * @param directoryName
+ * the directory name of the test
+ * @param stdOutFromTest
+ * the standard output content
+ */
+ public void setStdOut(String directoryName, String stdOutFromTest) {
+ TestInfo testInfo = getTestInfoForDirectory(directoryName);
+ testInfo.setStdOut(stdOutFromTest);
+ }
+
+ /**
+ * Sets the content which was written to the standard error during a test
+ * for a test.
+ *
+ * @param directoryName
+ * the directory name of the test
+ * @param stdErrFromTest
+ * the standard error content
+ */
+ public void setStdErr(String directoryName, String stdErrFromTest) {
+ TestInfo testInfo = getTestInfoForDirectory(directoryName);
+ testInfo.setStdErr(stdErrFromTest);
+ }
+
+ /**
+ * Sets a test given its directory name as inconclusive and sets a
+ * <code>AssumptionViolatedException</code>-object as cause for the verdict.
+ * The directory name is used, because it uniquely defines the test, as
+ * there is only one test per directory and it is encoded in display name of
+ * the test. A test may be inconclusive if an assumption is violated,e.g. if
+ * PDF-A conformance should be checked after signing and the input file does
+ * not conform to the PDF-A standard then the test cannot perform any
+ * meaningful checks.
+ *
+ * @param directoryName
+ * the name of the directory of the test
+ * @param e
+ * the cause of the fail
+ */
+ public void setInconclusive(String directoryName,
+ AssumptionViolatedException e) {
+ TestInfo testInfo = getTestInfoForDirectory(directoryName);
+ testInfo.setVerdict(TestVerdict.INCONCLUSIVE);
+ testInfo.setFailCause(e);
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java
new file mode 100644
index 00000000..a3014b86
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java
@@ -0,0 +1,170 @@
+package at.gv.egiz.param_tests.serialization;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.param_tests.testinfo.TestInfo;
+
+/**
+ * Base for all test information serializer. It uses template method pattern,
+ * fixing an algorithm for serialization, but delegating the actual steps to the
+ * subclasses. A two-step hierarchy shall be formed, with this class at the
+ * upper-most level, technology-specific (like HTML) serializer at the next and
+ * technology- and test-type-specific serializer at the lowest level.
+ *
+ * @author mtappler
+ *
+ * @param <T>
+ * the type of TestInfo which instance of this class, respectively
+ * subclasses of it can serialize
+ */
+public abstract class TestInfoSerializer<T extends TestInfo> {
+
+ /**
+ * logger for this class
+ */
+ private static final Logger logger = LoggerFactory
+ .getLogger(TestInfoSerializer.class);
+
+ /**
+ * basic test information
+ */
+ protected TestInfo baseTestInfo;
+
+ /**
+ * Method for creating an of a subclass of <code>TestInfo</code>. The test
+ * information created using this method shall be retrieved, when
+ * <code>getBaseTestInfo()</code> is called.
+ *
+ * @return instance of a <code>TestInfo</code>-subclass
+ */
+ public abstract T createTestInfo();
+
+ /**
+ * Clone method for serializer, it shall create a shallow copy of
+ * <code>this</code> and return it. It does not use
+ * <code>Object.clone()</code> for type safety, because this method returns
+ * an <code>Object</code>-instance.
+ *
+ * @return a clone of <code>this</code>
+ */
+ public abstract TestInfoSerializer<T> cloneSerializer();
+
+ /**
+ * getter
+ *
+ * @return basic test information
+ */
+ public TestInfo getBaseTestInfo() {
+ return baseTestInfo;
+ }
+
+ /**
+ * Main serialization method. This method creates a test result file in the
+ * test directory and then calls the methods to perform the serialization
+ * steps. The following parts shall be written:
+ * <ol>
+ * <li>header</li>
+ * <li>test specific parameter</li>
+ * <li>test result</li>
+ * <li>test specific data</li>
+ * <li>footer</li>
+ * </ol>
+ */
+ public void serialize() {
+ File outputFile = new File(baseTestInfo.getBaseTestData()
+ .getTestDirectory() + "/test_result." + fileEnding());
+ FileOutputStream os = null;
+ try {
+ os = new FileOutputStream(outputFile);
+ PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "UTF8"));
+ writeHeader(pw);
+ writeTestSpecificParameters(pw);
+ writeTestResult(pw);
+ writeTestData(pw);
+ writeFooter(pw);
+ pw.flush();
+ } catch (FileNotFoundException e) {
+ logger.warn("File not found for test info serialization.", e);
+ } catch (UnsupportedEncodingException e) {
+ logger.warn("Use unsupported encoding for serialization.", e);
+ } finally {
+ if (os != null)
+ IOUtils.closeQuietly(os);
+ }
+ }
+
+ /**
+ * This writes the test result in some format like HTML to the provided
+ * <code>PrintWriter</code>-object.
+ *
+ * @param pw
+ * the <code>PrintWriter</code>-object whill shall be used for
+ * serialization
+ */
+ protected abstract void writeTestResult(PrintWriter pw);
+
+ /**
+ * This writes test specific parameters in some format like HTML to the
+ * provided <code>PrintWriter</code>-object.
+ *
+ * @param pw
+ * the <code>PrintWriter</code>-object whill shall be used for
+ * serialization
+ */
+ protected abstract void writeTestSpecificParameters(PrintWriter pw);
+
+ /**
+ * This writes the file header in some format like HTML to the provided
+ * <code>PrintWriter</code>-object.
+ *
+ * @param pw
+ * the <code>PrintWriter</code>-object whill shall be used for
+ * serialization
+ */
+ protected abstract void writeHeader(PrintWriter pw);
+
+ /**
+ * This writes test specific data in some format like HTML to the provided
+ * <code>PrintWriter</code>-object.
+ *
+ * @param pw
+ * the <code>PrintWriter</code>-object whill shall be used for
+ * serialization
+ */
+ protected abstract void writeTestData(PrintWriter pw);
+
+ /**
+ * This writes the file footer in some format like HTML to the provided
+ * <code>PrintWriter</code>-object.
+ *
+ * @param pw
+ * the <code>PrintWriter</code>-object whill shall be used for
+ * serialization
+ */
+ protected abstract void writeFooter(PrintWriter pw);
+
+ /**
+ * This method returns the file ending used for the test result file, like
+ * "html" or "xml".
+ *
+ * @return the file ending string
+ */
+ public abstract String fileEnding();
+
+ /**
+ * This method returns a description of the concrete test type, which a
+ * concrete implementation of this class serializes.
+ *
+ * @return a test type description
+ */
+ public abstract String getTestType();
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java
new file mode 100644
index 00000000..ad0dff7d
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java
@@ -0,0 +1,45 @@
+package at.gv.egiz.param_tests.serialization;
+
+import at.gv.egiz.param_tests.testinfo.TestInfo;
+
+/**
+ * interface defining methods for a test summary writer. This shall be
+ * implemented in a technology-dependent way, like for HTML.
+ *
+ * @author mtappler
+ *
+ */
+public interface TestSummaryWriter {
+
+ /**
+ * This method shall write a header to a file.
+ */
+ public void writeHeader();
+
+ /**
+ * This method shall write a short summary of a test to a file.
+ *
+ * @param tInfo
+ * test information for the test
+ * @param testType
+ * the type of the test
+ */
+ public void writeSummaryOfTest(TestInfo tInfo, String testType);
+
+ /**
+ * This method shall write a footer to the file.
+ */
+ public void writeFooter();
+
+ /**
+ * This method shall initialize the writing process, e.g. by creating and
+ * opening a file, to which the summary is written.
+ */
+ public void init();
+
+ /**
+ * This method shall terminate the writing process, e.g. by closing the
+ * summary file.
+ */
+ public void close();
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java
new file mode 100644
index 00000000..9bbb0497
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java
@@ -0,0 +1,204 @@
+package at.gv.egiz.param_tests.serialization.html;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map.Entry;
+
+import at.gv.egiz.param_tests.provider.BaseSignatureTestData;
+import at.gv.egiz.param_tests.serialization.TestInfoSerializer;
+import at.gv.egiz.param_tests.testinfo.TestInfo;
+import at.gv.egiz.param_tests.testinfo.TestVerdict;
+
+/**
+ * A subclass implementing some methods, which can be implemented independent of
+ * concrete test type. It uses the Twitter-bootstrap framework, which must be
+ * provided for the files to be correctly displayed in a browser.
+ *
+ * @author mtappler
+ *
+ * @param <T>
+ * the type of TestInfo which instance of this class, respectively
+ * subclasses of it can serialize
+ */
+public abstract class HTMLSerializer<T extends TestInfo> extends
+ TestInfoSerializer<T> {
+
+ @Override
+ public String fileEnding() {
+ return "html";
+ }
+
+ @Override
+ protected void writeHeader(PrintWriter pw) {
+ BaseSignatureTestData baseData = baseTestInfo.getBaseTestData();
+ pw.println("<!doctype html>");
+ pw.println("<html>");
+ pw.println("<head>");
+ pw.println("<title>Test results for " + baseData.getTestName()
+ + "</title>");
+ pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/bootstrap.min.css\">");
+ pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/bootstrap-theme.min.css\">");
+ pw.println("<meta charset=\"UTF-8\">");
+ pw.println("</head>");
+ pw.println("<body>");
+ pw.println("<div class=\"container\">");
+ pw.println("<div class=\"page-header\">");
+ pw.println("<h1>Detailed test results <small>" + baseData.getTestName()
+ + "</small></h1>");
+ pw.println("</div>");
+ String basicTestDataPanel = createBasicTestData();
+ writePanel(pw, "Basic test data", basicTestDataPanel);
+ }
+
+ /**
+ * Helper method which creates three tabs, one for basic test parameter, one
+ * for the standard output and one for standard error.
+ *
+ * @return HTML-string defining the tabs
+ */
+ protected String createBasicTestData() {
+ BaseSignatureTestData baseData = baseTestInfo.getBaseTestData();
+ String basicParameterBody = createBasicParameterRepresentation(baseData);
+ StringBuilder basicTestDataPanel = new StringBuilder();
+ basicTestDataPanel
+ .append("<ul id=\"tabs\" class=\"nav nav-tabs\" data-tabs=\"tabs\">");
+ basicTestDataPanel
+ .append("<li class=\"active\"><a href=\"#basic-parameters\" "
+ + "data-toggle=\"tab\">Basic parameters</a></li>");
+ basicTestDataPanel
+ .append("<li><a href=\"#std-out\" data-toggle=\"tab\">Standard output</a></li>");
+ basicTestDataPanel
+ .append("<li><a href=\"#std-err\" data-toggle=\"tab\">Standard error</a></li>");
+ basicTestDataPanel.append("</ul>");
+ basicTestDataPanel
+ .append("<div id=\"my-tab-content\" class=\"tab-content\">");
+ basicTestDataPanel
+ .append("<div class=\"tab-pane active\" id=\"basic-parameters\">");
+ basicTestDataPanel.append(basicParameterBody);
+ basicTestDataPanel.append("</div>");
+ basicTestDataPanel.append("<div class=\"tab-pane\" id=\"std-out\">");
+ basicTestDataPanel.append("<pre class=\"pre-scrollable\">"
+ + baseTestInfo.getStdOut() + "</pre>");
+ basicTestDataPanel.append("</div>");
+ basicTestDataPanel.append("<div class=\"tab-pane\" id=\"std-err\">");
+ basicTestDataPanel.append("<pre class=\"pre-scrollable\">"
+ + baseTestInfo.getStdErr() + "</pre>");
+ basicTestDataPanel.append("</div>");
+ basicTestDataPanel.append("</div>");
+ return basicTestDataPanel.toString();
+ }
+
+ /**
+ * This method creates a definition list for basic parameters of a test.
+ *
+ * @param baseData
+ * basic test parameters
+ * @return HTML-string defining a definition list
+ */
+ private String createBasicParameterRepresentation(
+ BaseSignatureTestData baseData) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<dl>");
+ sb.append(createDescription("Input file", baseData.getPdfFile()));
+ sb.append(createDescription("Output file", baseData.getOutputFile()));
+ sb.append(createDescription("Signature profile", baseData.getProfilID()));
+ sb.append(createDescription("Connector Type", baseData
+ .getConnectorData().getConnectorType()));
+ if (baseData.getConnectorData().getConnectorParameters().size() > 0) {
+ StringBuilder connectorParameters = new StringBuilder();
+ connectorParameters.append("<dl class=\"dl-horizontal\">");
+ for (Entry<String, String> e : baseData.getConnectorData()
+ .getConnectorParameters().entrySet())
+ connectorParameters.append(createDescription(e.getKey(),
+ e.getValue()));
+ connectorParameters.append("</dl>");
+ sb.append(createDescription("Connector Parameters",
+ connectorParameters.toString()));
+ }
+ sb.append(createDescription("Test Type", getTestType()));
+ sb.append("</dl>");
+ return sb.toString();
+ }
+
+ /**
+ * Helper for writing a bootstrap panel with some title and content.
+ *
+ * @param pw
+ * <code>PrintWriter</code>-object to which the panel should be
+ * written
+ * @param panelTitle
+ * title of the panel
+ * @param panelBody
+ * panel content
+ */
+ protected void writePanel(PrintWriter pw, String panelTitle,
+ String panelBody) {
+ pw.println("<div class=\"panel panel-default\">");
+ pw.println(" <div class=\"panel-heading\">");
+ pw.println("<h3 class=\"panel-title\">" + panelTitle + "</h3>");
+ pw.println("</div>");
+ pw.println("<div class=\"panel-body\">");
+ pw.println(panelBody);
+ pw.println("</div>");
+ pw.println("</div>");
+ }
+
+ @Override
+ protected void writeTestResult(PrintWriter pw) {
+ StringBuilder panelBody = new StringBuilder();
+ panelBody.append("<h4>"
+ + HTMLTestSummaryWriter.verdictToLabel(baseTestInfo
+ .getVerdict()) + "</h4>");
+ if (baseTestInfo.getVerdict().equals(TestVerdict.FAILED)
+ || baseTestInfo.getVerdict().equals(TestVerdict.INCONCLUSIVE)) {
+ panelBody.append(createExceptionDataString(baseTestInfo
+ .getFailCause()));
+ }
+ writePanel(pw, "Test result", panelBody.toString());
+
+ }
+
+ /**
+ * This method creates a HTML-representation for data contained in a
+ * throwable.
+ *
+ * @param t
+ * <code>Throwable</code>-object which should be displayed
+ * @return HTML-string for the throwable
+ */
+ protected String createExceptionDataString(Throwable t) {
+ StringBuilder exceptionData = new StringBuilder();
+ exceptionData.append("<dl class=\"dl-horizontal\">");
+ exceptionData.append(createDescription("Cause", t.toString()));
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ exceptionData.append(createDescription("Stacktrace", sw.toString()));
+ exceptionData.append("</dl>");
+ return exceptionData.toString();
+ }
+
+ /**
+ * Helper method for creating an item of a definition list.
+ *
+ * @param term
+ * the term of the item (the key)
+ * @param definition
+ * the definition of the item (the value)
+ * @return an HTML-string for the definition list item
+ */
+ protected String createDescription(String term, String definition) {
+ return String.format("<dt>%s</dt><dd>%s</dd>%n", term, definition);
+ }
+
+ @Override
+ protected void writeFooter(PrintWriter pw) {
+ pw.println("<a href=\"../index.html\">Back to summary</a>");
+ pw.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\">"
+ + "</script>");
+ pw.println("<script src=\"../js/bootstrap.min.js\"></script>");
+ pw.println("</div>");
+ pw.println("</body>");
+ pw.println("</html>");
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java
new file mode 100644
index 00000000..0734a3ec
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java
@@ -0,0 +1,150 @@
+package at.gv.egiz.param_tests.serialization.html;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import at.gv.egiz.param_tests.serialization.TestSummaryWriter;
+import at.gv.egiz.param_tests.testinfo.TestInfo;
+import at.gv.egiz.param_tests.testinfo.TestVerdict;
+
+/**
+ * Concrete implementation of the <code>TestSummaryWriter</code>, which creates
+ * HTML output and uses the Twitter-bootstrap framework.
+ *
+ * @author mtappler
+ *
+ */
+public class HTMLTestSummaryWriter implements TestSummaryWriter {
+
+ /**
+ * the location of the test directory
+ */
+ private String testDir;
+ /**
+ * the print writer which is used for writing
+ */
+ private PrintWriter pw;
+ /**
+ * the logger for this class
+ */
+ private static final Logger logger = LoggerFactory
+ .getLogger(HTMLTestSummaryWriter.class);
+
+ /**
+ * Constructor which sets the test directory.
+ *
+ * @param testDir
+ * location of the test directory
+ */
+ public HTMLTestSummaryWriter(String testDir) {
+ this.testDir = testDir;
+ }
+
+ public void writeHeader() {
+ if (pw == null)
+ return;
+ pw.println("<!doctype html>");
+ pw.println("<html>");
+ pw.println("<head>");
+ pw.println("<title>Summary of test results</title>");
+ pw.println("<meta charset=\"UTF-8\">");
+ pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/bootstrap.min.css\">");
+ pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/bootstrap-theme.min.css\">");
+ pw.println("</head>");
+ pw.println("<body>");
+ pw.println("<div class=\"container\">");
+ pw.println("<div class=\"page-header\">");
+ pw.println("<h1>Test result summary</h1>");
+ pw.println("</div>");
+ pw.println("<table class=\"table table-bordered table-striped\">");
+ pw.println("<thead>");
+ pw.println("<tr>");
+ pw.println("<th>Test name</th>");
+ pw.println("<th>Test directory</th>");
+ pw.println("<th>Test type</th>");
+ pw.println("<th>Verdict</th>");
+ pw.println("</tr>");
+ pw.println("</thead>");
+
+ }
+
+ public void writeSummaryOfTest(TestInfo tInfo, String testType) {
+ if (pw == null)
+ return;
+ pw.println("<tr>");
+ pw.println(String.format(
+ "<td><a href=\"%s/test_result.html\">%s</a></td>", tInfo
+ .getBaseTestData().getTestDirectory(), tInfo
+ .getBaseTestData().getTestName()));
+ pw.println(String.format("<td>%s</td>", tInfo.getBaseTestData()
+ .getTestDirectory()));
+ pw.println(String.format("<td>%s</td>", testType));
+ pw.println(String.format("<td>%s</td>",
+ verdictToLabel(tInfo.getVerdict())));
+ pw.println("</tr>");
+ }
+
+ // intentionally package protected
+ /**
+ * Static method for creating bootstrap label for a test verdict. Since it
+ * is technology dependent (HTML + bootstrap) it is defined as package
+ * protected.
+ *
+ * @param verdict
+ * the verdict of a test
+ * @return HTML-string for a verdict label
+ */
+ static String verdictToLabel(TestVerdict verdict) {
+ switch (verdict) {
+ case FAILED:
+ return "<span class=\"label label-danger\">Fail</span>";
+ case INCONCLUSIVE:
+ return "<span class=\"label label-warning\">Inconclusive</span>";
+ case SUCCEEDED:
+ return "<span class=\"label label-success\">Success</span>";
+ default:
+ return "<span class=\"label label-default\">Unknown</span>";
+ }
+ }
+
+ public void writeFooter() {
+ if (pw == null)
+ return;
+
+ pw.println("</table>");
+ pw.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\">"
+ + "</script>");
+ pw.println("<script src=\"js/bootstrap.min.js\"></script>");
+ pw.println("</div>");
+ pw.println("</body>");
+ pw.println("</html>");
+ }
+
+ public void init() {
+ OutputStream os;
+ try {
+ os = new FileOutputStream(testDir + "/index.html");
+ pw = new PrintWriter(new OutputStreamWriter(os, "UTF8"));
+ } catch (FileNotFoundException e) {
+ logger.debug("Could not find output file, not writing any summary",
+ e);
+ } catch (UnsupportedEncodingException e) {
+ logger.debug("Used unsupported encoding for writing summary file.",
+ e);
+ }
+
+ }
+
+ public void close() {
+ if (pw != null)
+ pw.close();
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java
new file mode 100644
index 00000000..fdaa80fa
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java
@@ -0,0 +1,147 @@
+package at.gv.egiz.param_tests.serialization.html;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.pdfbox.preflight.ValidationResult;
+import org.apache.pdfbox.preflight.ValidationResult.ValidationError;
+
+import at.gv.egiz.param_tests.serialization.TestInfoSerializer;
+import at.gv.egiz.param_tests.testinfo.PDFATestInfo;
+
+/**
+ * Concrete test information serializer for PDF-A conformance tests using HTML
+ * and Twitter-bootstrap.
+ *
+ * @author mtappler
+ *
+ */
+public class PDFAHTMLSerizalier extends HTMLSerializer<PDFATestInfo> {
+
+ /**
+ * the test information object which is serialized, it is the same as
+ * <code>TestInfoSerializer.baseTestInfo</code>
+ */
+ private PDFATestInfo testInfo;
+
+ /**
+ * Default contructor used for registering it as prototype.
+ */
+ public PDFAHTMLSerizalier() {
+ this(null);
+ }
+
+ /**
+ * Package protected constructor used for cloning
+ *
+ * @param testInfo
+ */
+ PDFAHTMLSerizalier(PDFATestInfo testInfo) {
+ this.testInfo = testInfo;
+ }
+
+ @Override
+ public PDFATestInfo createTestInfo() {
+ testInfo = new PDFATestInfo();
+ baseTestInfo = testInfo;
+ return testInfo;
+ }
+
+ @Override
+ public TestInfoSerializer<PDFATestInfo> cloneSerializer() {
+ return new PDFAHTMLSerizalier(testInfo);
+ }
+
+ @Override
+ protected void writeTestData(PrintWriter pw) {
+ pw.println("<h2>Validation status before signing</h2>");
+ writeValidationResult(pw, testInfo.getResultBeforeSign());
+ if (testInfo.getResultAfterSign() != null) {
+ pw.println("<h2>Validation status after signing</h2>");
+ writeValidationResult(pw, testInfo.getResultAfterSign());
+ }
+ }
+
+ /**
+ * This method writes the validation result to the given print writer, i.e.
+ * an information about the success of the validation or an exception which
+ * was thrown during the validation or a list of validation errors.
+ *
+ * @param pw
+ * the <code>PrintWriter</code>-object to which the HTML-code is
+ * written
+ * @param validationResult
+ * the pair which defines the result of a validation
+ */
+ private void writeValidationResult(PrintWriter pw,
+ Pair<ValidationResult, Throwable> validationResult) {
+ if (validationResult.getRight() != null) {
+ StringBuilder exceptionString = new StringBuilder();
+ exceptionString
+ .append("p>An exception happened during the validation process:</p>");
+ exceptionString.append(createExceptionDataString(validationResult
+ .getRight()));
+ writePanel(pw, "Validation exception details",
+ exceptionString.toString());
+ }
+ if (validationResult.getLeft() != null) {
+
+ StringBuilder conformanceString = new StringBuilder();
+ if (validationResult.getLeft().isValid()) {
+ conformanceString
+ .append("<p>The document conforms to the PDF-A standard.</p>");
+ } else {
+ List<ValidationError> errors = validationResult.getLeft()
+ .getErrorsList();
+ conformanceString.append("<p>With to the PDF-A standard, "
+ + "the document contains the following errors:</p>");
+ conformanceString
+ .append("<table class=\"table table-bordered table-striped\">");
+ conformanceString.append("<thead>");
+ conformanceString.append("<tr>");
+ conformanceString.append("<th>Error code</th>");
+ conformanceString.append("<th>Error details</th>");
+ conformanceString.append("<th>Warning</th>");
+ conformanceString.append("</tr>");
+ conformanceString.append("</thead>");
+ for (ValidationError error : errors) {
+ writeValidationError(conformanceString, error);
+ }
+ conformanceString.append("</table>");
+ }
+ writePanel(pw, "PDF-A conformance", conformanceString.toString());
+ }
+ }
+
+ /**
+ * Helper method for writing a table line for a single validation error.
+ *
+ * @param conformanceString
+ * the string builder to which the table line is appended
+ * @param error
+ * the error which should be serialized
+ */
+ private void writeValidationError(StringBuilder conformanceString,
+ ValidationError error) {
+ conformanceString.append("<tr>");
+ conformanceString.append(String.format("<td>%s</td>",
+ error.getErrorCode()));
+ conformanceString.append(String.format("<td>%s</td>",
+ error.getDetails()));
+ conformanceString.append(String.format("<td>%s</td>",
+ error.isWarning() ? "x" : ""));
+ conformanceString.append("</tr>");
+ }
+
+ @Override
+ public String getTestType() {
+ return "PDF-A";
+ }
+
+ @Override
+ protected void writeTestSpecificParameters(PrintWriter pw) {
+ // intentionally left blank, no pdf-a specific parameters
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java
new file mode 100644
index 00000000..6cebb9ef
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java
@@ -0,0 +1,176 @@
+package at.gv.egiz.param_tests.serialization.html;
+
+import java.awt.Rectangle;
+import java.io.PrintWriter;
+import java.util.List;
+
+import at.gv.egiz.param_tests.serialization.TestInfoSerializer;
+import at.gv.egiz.param_tests.testinfo.SignaturePositionTestInfo;
+import at.gv.egiz.param_tests.testinfo.SignaturePositionTestInfo.SignaturePositionParameters;
+
+/**
+ * Concrete test information serializer for signature position tests using HTML
+ * and Twitter-bootstrap.
+ *
+ * @author mtappler
+ *
+ */
+public class SignaturePositionHTMLSerializer extends
+ HTMLSerializer<SignaturePositionTestInfo> {
+ /**
+ * the test information object which is serialized, it is the same as
+ * <code>TestInfoSerializer.baseTestInfo</code>
+ */
+ private SignaturePositionTestInfo testInfo;
+
+ /**
+ * Default contructor used for registering it as prototype.
+ */
+ public SignaturePositionHTMLSerializer() {
+ this(null);
+ }
+
+ /**
+ * Package protected constructor used for cloning
+ *
+ * @param testInfo
+ */
+ SignaturePositionHTMLSerializer(SignaturePositionTestInfo testInfo) {
+ this.testInfo = testInfo;
+ }
+
+ @Override
+ public SignaturePositionTestInfo createTestInfo() {
+ testInfo = new SignaturePositionTestInfo();
+ baseTestInfo = testInfo;
+ return testInfo;
+ }
+
+ @Override
+ public TestInfoSerializer<SignaturePositionTestInfo> cloneSerializer() {
+ return new SignaturePositionHTMLSerializer(testInfo);
+ }
+
+ @Override
+ protected void writeTestData(PrintWriter pw) {
+ if (testInfo.getAdditionParameters().isCaptureReferenceImage()) {
+ writeTestDataCapture(pw);
+ } else {
+ writeTestDataTest(pw);
+ }
+ }
+
+ /**
+ * Method for writing test data if only a reference image was captured
+ *
+ * @param pw
+ * object which should be used for writing
+ */
+ private void writeTestDataCapture(PrintWriter pw) {
+ pw.println("<h2>Captured reference image</h2>");
+ writePanel(
+ pw,
+ "Reference image",
+ getImageString(testInfo.getAdditionParameters()
+ .getRefImageFileName(), "reference image"));
+ if (testInfo.getRefImageIgnored() != null) {
+ writePanel(
+ pw,
+ "Reference image with ignored areas",
+ getImageString(testInfo.getRefImageIgnored(),
+ "reference image (ignored)"));
+ }
+ }
+
+ /**
+ * Method for writing test data if an actual signature position test was
+ * performed.
+ *
+ * @param pw
+ * object which should be used for writing
+ */
+ private void writeTestDataTest(PrintWriter pw) {
+ pw.println("<h2>Image data captured during test</h2>");
+ writePanel(
+ pw,
+ "Reference image with ignored areas",
+ getImageString(testInfo.getRefImageIgnored(),
+ "reference image (ignored)"));
+ writePanel(
+ pw,
+ "Signature page image with ignored areas",
+ getImageString(testInfo.getSigPageImageIgnored(),
+ "signature page image (ignored)"));
+ writePanel(pw, "Difference image",
+ getImageString(testInfo.getDiffImage(), "difference image"));
+ }
+
+ /**
+ * This method creates HTML-image-tag for an image file.
+ *
+ * @param imageFileName
+ * location of the image file
+ * @param altText
+ * the alternative text
+ * @return HTML-image-tag
+ */
+ private String getImageString(String imageFileName, String altText) {
+ String imageString;
+ if (imageFileName != null)
+ imageString = String.format(
+ "<img class=\"img-responsive\" src=\"%s\" alt=\"%s\">",
+ imageFileName, altText);
+ else
+ imageString = "<p>This image was not captured correctly. "
+ + "The test may have been aborted before because of an IO error.</p>";
+ return imageString;
+ }
+
+ @Override
+ public String getTestType() {
+ return "Signature Position";
+ }
+
+ @Override
+ protected void writeTestSpecificParameters(PrintWriter pw) {
+
+ SignaturePositionParameters additionalParameters = testInfo
+ .getAdditionParameters();
+ StringBuilder panelBody = new StringBuilder();
+
+ panelBody.append("<dl>");
+ panelBody.append(createDescription("Signature positioning string",
+ additionalParameters.getPositionString()));
+ panelBody.append(createDescription("Signature page number",
+ Integer.toString(additionalParameters.getSigPageNumber())));
+ panelBody.append(createDescription("Reference image file name",
+ additionalParameters.getRefImageFileName()));
+ panelBody.append(createDescription("Ignored Areas",
+ createIgnoredAreasDescription(additionalParameters
+ .getIgnoredAreas())));
+ panelBody.append("</dl>");
+ writePanel(pw, "Test specific parameters", panelBody.toString());
+ }
+
+ /**
+ * This method creates an unordered HTML list for the ignored areas for a
+ * signature position test.
+ *
+ * @param ignoredAreas
+ * list of ignored areas
+ * @return HTML-string representing the ignored areas
+ */
+ private String createIgnoredAreasDescription(List<Rectangle> ignoredAreas) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<ul>");
+ for (Rectangle r : ignoredAreas) {
+ sb.append("<li>");
+ sb.append(String.format("x:%d y:%d width:%d height:%d", r.x, r.y,
+ r.width, r.height));
+ sb.append("</li>");
+ }
+ sb.append("</ul>");
+ return sb.toString();
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java
new file mode 100644
index 00000000..a5f7c190
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java
@@ -0,0 +1,41 @@
+package at.gv.egiz.param_tests.testinfo;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.pdfbox.preflight.ValidationResult;
+
+/**
+ * Test information class for PDF-A conformance tests.
+ *
+ * @author mtappler
+ *
+ */
+public class PDFATestInfo extends TestInfo {
+
+ /**
+ * the validation result before signing for the input PDF file
+ */
+ private Pair<ValidationResult, Throwable> resultBeforeSign;
+ /**
+ * the validation result after signing for the output PDF file
+ */
+ private Pair<ValidationResult, Throwable> resultAfterSign;
+
+ public Pair<ValidationResult, Throwable> getResultBeforeSign() {
+ return resultBeforeSign;
+ }
+
+ public void setResultBeforeSign(
+ Pair<ValidationResult, Throwable> resultBeforeSign) {
+ this.resultBeforeSign = resultBeforeSign;
+ }
+
+ public Pair<ValidationResult, Throwable> getResultAfterSign() {
+ return resultAfterSign;
+ }
+
+ public void setResultAfterSign(
+ Pair<ValidationResult, Throwable> resultAfterSign) {
+ this.resultAfterSign = resultAfterSign;
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java
new file mode 100644
index 00000000..04b75edc
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java
@@ -0,0 +1,138 @@
+package at.gv.egiz.param_tests.testinfo;
+
+import java.awt.Rectangle;
+import java.util.List;
+
+/**
+ * Test information class for signature position test.
+ *
+ * @author mtappler
+ *
+ */
+public class SignaturePositionTestInfo extends TestInfo {
+ /**
+ * Class containing attributes for non-standard parameters of signature
+ * position tests.
+ *
+ * @author mtappler
+ *
+ */
+ public static class SignaturePositionParameters {
+ /**
+ * positioning string which specifies the position of the signature
+ * block
+ */
+ private String positionString;
+ /**
+ * The page number of the page, which shows the signature block.
+ */
+ private int sigPageNumber;
+ /**
+ * A list of rectangular areas, which will be ignored for image
+ * comparison
+ */
+ private List<Rectangle> ignoredAreas;
+ /**
+ * the file name of the reference file for image comparison
+ */
+ private String refImageFileName;
+ /**
+ * if set to true, a reference image is captured during the test, but no
+ * actual comparison will be performed
+ */
+ private boolean captureReferenceImage;
+
+ public String getPositionString() {
+ return positionString;
+ }
+
+ public void setPositionString(String positionString) {
+ this.positionString = positionString;
+ }
+
+ public int getSigPageNumber() {
+ return sigPageNumber;
+ }
+
+ public void setSigPageNumber(int sigPageNumber) {
+ this.sigPageNumber = sigPageNumber;
+ }
+
+ public List<Rectangle> getIgnoredAreas() {
+ return ignoredAreas;
+ }
+
+ public void setIgnoredAreas(List<Rectangle> ignoredAreas) {
+ this.ignoredAreas = ignoredAreas;
+ }
+
+ public String getRefImageFileName() {
+ return refImageFileName;
+ }
+
+ public void setRefImageFileName(String refImageFileName) {
+ this.refImageFileName = refImageFileName;
+ }
+
+ public boolean isCaptureReferenceImage() {
+ return captureReferenceImage;
+ }
+
+ public void setCaptureReferenceImage(boolean captureReferenceImage) {
+ this.captureReferenceImage = captureReferenceImage;
+ }
+ }
+
+ /**
+ * additional/non-standard parameter of signature position tests
+ */
+ private SignaturePositionParameters additionParameters = new SignaturePositionParameters();
+
+ /**
+ * file name of the reference image with ignored areas
+ */
+ private String refImageIgnored;
+
+ /**
+ * file name of the signature page image with ignored areas
+ */
+ private String sigPageImageIgnored;
+
+ /**
+ * file name of difference image
+ */
+ private String diffImage;
+
+ public String getRefImageIgnored() {
+ return refImageIgnored;
+ }
+
+ public String getSigPageImageIgnored() {
+ return sigPageImageIgnored;
+ }
+
+ public String getDiffImage() {
+ return diffImage;
+ }
+
+ public SignaturePositionParameters getAdditionParameters() {
+ return additionParameters;
+ }
+
+ public void setAdditionParameters(
+ SignaturePositionParameters additionParameters) {
+ this.additionParameters = additionParameters;
+ }
+
+ public void setRefImageIgnored(String refImageIgnored) {
+ this.refImageIgnored = refImageIgnored;
+ }
+
+ public void setSigPageImageIgnored(String sigPageImageIgnored) {
+ this.sigPageImageIgnored = sigPageImageIgnored;
+ }
+
+ public void setDiffImage(String diffImage) {
+ this.diffImage = diffImage;
+ }
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java
new file mode 100644
index 00000000..130cece3
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java
@@ -0,0 +1,76 @@
+package at.gv.egiz.param_tests.testinfo;
+
+import at.gv.egiz.param_tests.provider.BaseSignatureTestData;
+
+/**
+ * Abstract test information class containing information common to all tests.
+ * It is declared abstract to enforce that a subclass of it must be created for
+ * each test-type, because serializer prototypes are registered for
+ * <code>TestInfo</code>-subclasses.
+ *
+ * @author mtappler
+ *
+ */
+public abstract class TestInfo {
+ /**
+ * signature test parameters common to all tests
+ */
+ private BaseSignatureTestData baseTestData;
+ /**
+ * the verdict of a test
+ */
+ private TestVerdict verdict = TestVerdict.UNKNOWN;
+ /**
+ * the cause for failure, non-null if the verdict is inconclusive or fail
+ */
+ private Throwable failCause;
+ /**
+ * standard output data written during the test
+ */
+ private String stdOut;
+ /**
+ * standard error data written during the test
+ */
+ private String stdErr;
+
+ public void setBaseTestData(BaseSignatureTestData baseTestData) {
+ this.baseTestData = baseTestData;
+ }
+
+ public Throwable getFailCause() {
+ return failCause;
+ }
+
+ public BaseSignatureTestData getBaseTestData() {
+ return this.baseTestData;
+ }
+
+ public TestVerdict getVerdict() {
+ return verdict;
+ }
+
+ public String getStdOut() {
+ return stdOut;
+ }
+
+ public void setVerdict(TestVerdict result) {
+ this.verdict = result;
+ }
+
+ public void setFailCause(Throwable e) {
+ this.failCause = e;
+ }
+
+ public void setStdOut(String stdOutFromTest) {
+ this.stdOut = stdOutFromTest;
+ }
+
+ public void setStdErr(String stdErrFromTest) {
+ this.stdErr = stdErrFromTest;
+ }
+
+ public String getStdErr() {
+ return stdErr;
+ }
+
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java
new file mode 100644
index 00000000..0fc31a6a
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java
@@ -0,0 +1,10 @@
+package at.gv.egiz.param_tests.testinfo;
+
+/**
+ * Enum defining constants for test verdicts.
+ * @author mtappler
+ *
+ */
+public enum TestVerdict {
+ UNKNOWN, INCONCLUSIVE, FAILED, SUCCEEDED
+}
diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java
new file mode 100644
index 00000000..a5564fce
--- /dev/null
+++ b/pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java
@@ -0,0 +1,9 @@
+package at.gv.egiz.pdfas.tests;
+
+import org.junit.Test;
+
+public class DummyTest {
+ //@Test
+ //public void dummyTest() {
+ //}
+}
diff --git a/pdf-as-tests/src/test/resources/log4j.properties b/pdf-as-tests/src/test/resources/log4j.properties
new file mode 100644
index 00000000..696db3ef
--- /dev/null
+++ b/pdf-as-tests/src/test/resources/log4j.properties
@@ -0,0 +1,15 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+log4j.logger.at.gv.egiz=DEBUG
+#log4j.A1.at.gv.egiz=true
+
+log4j.logger.developer=DEBUG
+#log4j.A1.developer=true
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n \ No newline at end of file