Java tutorial
/* scenarioo-api * Copyright (C) 2014, scenarioo.org Development Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules, according * to the GNU General Public License with "Classpath" exception as provided * in the LICENSE file that accompanied this code. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.scenarioo.api; import static org.scenarioo.api.rules.CharacterChecker.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; import org.scenarioo.api.configuration.ScenarioDocuGeneratorConfiguration; import org.scenarioo.api.exception.ScenarioDocuSaveException; import org.scenarioo.api.exception.ScenarioDocuTimeoutException; import org.scenarioo.api.files.ScenarioDocuFiles; import org.scenarioo.api.rules.CharacterChecker; import org.scenarioo.api.rules.DetailsChecker; import org.scenarioo.api.util.xml.ScenarioDocuXMLFileUtil; import org.scenarioo.model.docu.entities.Branch; import org.scenarioo.model.docu.entities.Build; import org.scenarioo.model.docu.entities.Scenario; import org.scenarioo.model.docu.entities.Step; import org.scenarioo.model.docu.entities.StepDescription; import org.scenarioo.model.docu.entities.UseCase; /** * Generator to produce documentation files for a specific build. * * The writer performs all save operations as asynchronous writes, to not block the webtests that are typically calling * the save operations to save documentation content. * * An instance of such a writer needs to be closed after last write operation by using the method {@link #flush()}. * After calling {@link #flush()} once the writer can not be used anymore. */ public class ScenarioDocuWriter { private final ScenarioDocuFiles docuFiles; private final String branchName; private final String buildName; private final ExecutorService asyncWriteExecutor = newAsyncWriteExecutor(); private final List<RuntimeException> caughtExceptions = new ArrayList<RuntimeException>(); /** * Initialize with directory inside which to generate the documentation contents. * * @param destinationDirectory * the directory where the content should be generated (this directory must be precreated by you!). * @param branchName * name of the branch we are generating content for * @param buildName * name of the build (concrete identifier like revision and date) for which we are generating content. */ public ScenarioDocuWriter(final File destinationRootDirectory, final String branchName, final String buildName) { checkIdentifier(branchName); checkIdentifier(buildName); docuFiles = new ScenarioDocuFiles(destinationRootDirectory); this.branchName = branchName; this.buildName = buildName; createBuildDirectoryIfNotYetExists(); } /** * Save the branch description to appropriate directory * * @param branch * the branch description to write. */ public void saveBranchDescription(final Branch branch) { checkIdentifier(branch.getName()); executeAsyncWrite(new Runnable() { @Override public void run() { File destBranchFile = docuFiles.getBranchFile(branchName); ScenarioDocuXMLFileUtil.marshal(branch, destBranchFile); } }); } /** * Save the build description to appropriate directory * * @param build * the build description to write */ public void saveBuildDescription(final Build build) { checkIdentifier(build.getName()); executeAsyncWrite(new Runnable() { @Override public void run() { File destBuildFile = docuFiles.getBuildFile(branchName, buildName); ScenarioDocuXMLFileUtil.marshal(build, destBuildFile); } }); } /** * Save the use case description to appropriate directory and file * * @param useCase * the use case description to write */ public void saveUseCase(final UseCase useCase) { checkIdentifier(useCase.getName()); executeAsyncWrite(new Runnable() { @Override public void run() { File destCaseDir = getUseCaseDirectory(useCase.getName()); createDirectoryIfNotYetExists(destCaseDir); File destCaseFile = docuFiles.getUseCaseFile(branchName, buildName, useCase.getName()); ScenarioDocuXMLFileUtil.marshal(useCase, destCaseFile); } }); } public void saveScenario(final UseCase useCase, final Scenario scenario) { saveScenario(useCase.getName(), scenario); } public void saveScenario(final String useCaseName, final Scenario scenario) { checkIdentifier(useCaseName); checkIdentifier(scenario.getName()); executeAsyncWrite(new Runnable() { @Override public void run() { File destScenarioDir = getScenarioDirectory(useCaseName, scenario.getName()); createDirectoryIfNotYetExists(destScenarioDir); File destScenarioFile = docuFiles.getScenarioFile(branchName, buildName, useCaseName, scenario.getName()); ScenarioDocuXMLFileUtil.marshal(scenario, destScenarioFile); } }); } public void saveStep(final UseCase useCase, final Scenario scenario, final Step step) { saveStep(useCase.getName(), scenario.getName(), step); } /** * The page property of the step is optional, but it is recommended to use it. Page names are a central part of * Scenarioo. */ public void saveStep(final String useCaseName, final String scenarioName, final Step step) { checkSaveStepPreconditions(useCaseName, scenarioName, step); executeAsyncWrite(new Runnable() { @Override public void run() { File destStepsDir = getScenarioStepsDirectory(useCaseName, scenarioName); createDirectoryIfNotYetExists(destStepsDir); calculateScreenshotFileNameIfNotSetWorkaround(useCaseName, scenarioName, step); File destStepFile = docuFiles.getStepFile(branchName, buildName, useCaseName, scenarioName, step.getStepDescription().getIndex()); ScenarioDocuXMLFileUtil.marshal(step, destStepFile); } }); } private void checkSaveStepPreconditions(final String useCaseName, final String scenarioName, final Step step) { CharacterChecker.checkIdentifier(useCaseName); CharacterChecker.checkIdentifier(scenarioName); if (step.getPage() != null) { CharacterChecker.checkIdentifier(step.getPage().getName()); } if (step.getMetadata() != null) { DetailsChecker.checkIdentifiers(step.getMetadata().getDetails()); } if (step.getStepDescription() != null) { DetailsChecker.checkIdentifiers(step.getStepDescription().getDetails()); } } private void calculateScreenshotFileNameIfNotSetWorkaround(final String useCaseName, final String scenarioName, final Step step) { StepDescription stepDescription = step.getStepDescription(); if (stepDescription != null && stepDescription.getScreenshotFileName() == null) { File imageFile = docuFiles.getScreenshotFile(branchName, buildName, useCaseName, scenarioName, stepDescription.getIndex()); stepDescription.setScreenshotFileName(imageFile.getName()); } } /** * In case you want to define your screenshot names differently than by step name, you can save it on your own, into * the following directory for a scenario. */ public File getScreenshotsDirectory(final String usecaseName, final String scenarioName) { return docuFiles.getScreenshotsDirectory(branchName, buildName, checkIdentifier(usecaseName), checkIdentifier(scenarioName)); } /** * Get the file name of the file where the screenshot of a step is stored. */ public File getScreenshotFile(final String usecaseName, final String scenarioName, final int stepIndex) { return docuFiles.getScreenshotFile(branchName, buildName, checkIdentifier(usecaseName), checkIdentifier(scenarioName), stepIndex); } /** * Save the provided PNG image as a PNG file into the correct default file location for a step. * * In case you want to use another image format (e.g. JPEG) or just want to define the image file names for your * scenarios differently, you can do this by using {@link StepDescription#setScreenshotFileName} and saving the * picture on your own, as explained in the documentation of the mentioned method. * * @param pngScreenshot * Screenshot in PNG format. */ public void saveScreenshotAsPng(final String useCaseName, final String scenarioName, final int stepIndex, final byte[] pngScreenshot) { checkIdentifier(useCaseName); checkIdentifier(scenarioName); executeAsyncWrite(new Runnable() { @Override public void run() { final File screenshotFile = docuFiles.getScreenshotFile(branchName, buildName, useCaseName, scenarioName, stepIndex); try { FileUtils.writeByteArrayToFile(screenshotFile, pngScreenshot); } catch (IOException e) { throw new RuntimeException("Could not write image: " + screenshotFile.getAbsolutePath(), e); } } }); } /** * Finish asynchronous writing of all saved files. This has to be called in the end, to ensure all data saved in * this generator is written to the filesystem. * * Will block until writing has finished or timeout occurs. * * @throws ScenarioDocuSaveException * if any of the save commands throwed an exception during asynchronous execution. * @throws ScenarioDocuTimeoutException * if waiting for the saving beeing finished exceeds the configured timeout */ public void flush() { int timeoutInSeconds = ScenarioDocuGeneratorConfiguration.INSTANCE .getTimeoutWaitingForWritingFinishedInSeconds(); asyncWriteExecutor.shutdown(); try { boolean terminated = asyncWriteExecutor.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS); if (!terminated) { asyncWriteExecutor.shutdownNow(); throw new ScenarioDocuTimeoutException( "Timeout occured while waiting for docu files to be written. Writing of files took too long."); } } catch (InterruptedException e) { throw new RuntimeException("Async writing of scenarioo docu files was interrupted", e); } if (!caughtExceptions.isEmpty()) { throw new ScenarioDocuSaveException(caughtExceptions); } } private File getBuildDirectory() { return docuFiles.getBuildDirectory(branchName, buildName); } private File getUseCaseDirectory(final String useCaseName) { return docuFiles.getUseCaseDirectory(branchName, buildName, useCaseName); } private File getScenarioDirectory(final String useCaseName, final String scenarioName) { return docuFiles.getScenarioDirectory(branchName, buildName, useCaseName, scenarioName); } private File getScenarioStepsDirectory(final String useCaseName, final String scenarioName) { return docuFiles.getStepsDirectory(branchName, buildName, useCaseName, scenarioName); } private void createBuildDirectoryIfNotYetExists() { createDirectoryIfNotYetExists(getBuildDirectory()); } private void createDirectoryIfNotYetExists(final File directory) { docuFiles.assertRootDirectoryExists(); if (!directory.exists()) { directory.mkdirs(); } } private void executeAsyncWrite(final Runnable writeTask) { asyncWriteExecutor.execute(new Runnable() { @Override public void run() { try { writeTask.run(); } catch (RuntimeException e) { caughtExceptions.add(e); } } }); } /** * Creates an executor that queues the passed tasks for execution by one single additional thread. The excutor will * start to block further executions as soon as more than the configured write tasks are waiting for execution. */ private static ExecutorService newAsyncWriteExecutor() { return new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>( ScenarioDocuGeneratorConfiguration.INSTANCE.getAsyncWriteBufferSize())); } }