diff options
26 files changed, 3322 insertions, 2 deletions
| diff --git a/pdf-as-lib/build.gradle b/pdf-as-lib/build.gradle index 06018df8..a5d90939 100644 --- a/pdf-as-lib/build.gradle +++ b/pdf-as-lib/build.gradle @@ -42,7 +42,7 @@ dependencies {  	compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.3.1'  	compile group: 'org.apache.pdfbox', name: 'pdfbox', version: '1.8.5'  	compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.1' -	compile group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' +	compile group: 'commons-io', name: 'commons-io', version: '2.4'  	compile group: 'commons-collections', name: 'commons-collections', version: '3.2'  	compile group: 'org.apache.axis2', name: 'axis2', version: '1.6.2'  	compile group: 'org.apache.axis2', name: 'axis2-jaxws', version: '1.6.2' 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 diff --git a/settings.gradle b/settings.gradle index 5def47c1..46b968fc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include "pdf-as-common", "signature-standards:sigs-pkcs7detached", "signature-standards:sigs-pades", "pdf-as-lib", "pdf-as-cli", "pdf-as-legacy", "pdf-as-web", "pdf-as-web-client" +include "pdf-as-common", "signature-standards:sigs-pkcs7detached", "signature-standards:sigs-pades", "pdf-as-lib", "pdf-as-tests", "pdf-as-cli", "pdf-as-legacy", "pdf-as-web", "pdf-as-web-client" | 
