All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.concordion.internal.ConcordionBuilder Maven / Gradle / Ivy

package org.concordion.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.concordion.Concordion;
import org.concordion.api.*;
import org.concordion.api.ConcordionResources.InsertType;
import org.concordion.api.extension.ConcordionExtender;
import org.concordion.api.extension.ConcordionExtension;
import org.concordion.api.extension.ConcordionExtensionFactory;
import org.concordion.api.listener.*;
import org.concordion.api.option.ConcordionOptions;
import org.concordion.api.option.MarkdownExtensions;
import org.concordion.internal.command.*;
import org.concordion.internal.command.executeCommand.ExecuteCommand;
import org.concordion.internal.extension.ExtensionChecker;
import org.concordion.internal.listener.*;
import org.concordion.internal.parser.markdown.MarkdownConverter;
import org.concordion.internal.parser.markdown.XhtmlConverter;
import org.concordion.internal.util.Check;
import org.concordion.internal.util.SimpleFormatter;

public class ConcordionBuilder implements ConcordionExtender {

    private List listeners = new ArrayList();

    public static final String NAMESPACE_CONCORDION_2007 = "http://www.concordion.org/2007/concordion";
    private static final String PROPERTY_OUTPUT_DIR = "concordion.output.dir";
    private static final String PROPERTY_EXTENSIONS = "concordion.extensions";
    private static final String EMBEDDED_STYLESHEET_RESOURCE = "/org/concordion/internal/resource/embedded.css";

    private static File baseOutputDir;
    private SpecificationLocator specificationLocator = new ClassNameBasedSpecificationLocator();
    private Map sources = new HashMap();
    private Target target = null;
    private CommandRegistry commandRegistry = new CommandRegistry();
    private DocumentParser documentParser = new DocumentParser(commandRegistry);
    private SpecificationReader specificationReader;
    private EvaluatorFactory evaluatorFactory = new SimpleEvaluatorFactory();
    private SpecificationCommand specificationCommand = new SpecificationCommand();
    private AssertEqualsCommand assertEqualsCommand = new AssertEqualsCommand();
    private AssertTrueCommand assertTrueCommand = new AssertTrueCommand();
    private AssertFalseCommand assertFalseCommand = new AssertFalseCommand();
    private ExampleCommand exampleCommand = new ExampleCommand();
    private ExecuteCommand executeCommand = new ExecuteCommand();
    private SetCommand setCommand = new SetCommand();
    private RunCommand runCommand = new RunCommand();
    private VerifyRowsCommand verifyRowsCommand = new VerifyRowsCommand();
    private EchoCommand echoCommand = new EchoCommand();
    private ThrowableCaughtPublisher throwableListenerPublisher = new ThrowableCaughtPublisher();
    private LinkedHashMap resourceToCopyMap = new LinkedHashMap();
    private List specificationProcessingListeners = new ArrayList();
    private List throwableCaughtListeners = new ArrayList();
    private List> failFastExceptions = Collections.>emptyList();
    private boolean builtAlready;
    private FixtureType fixtureType;
    private MarkdownConverter markdownConverter = new MarkdownConverter();
    private XhtmlConverter xhtmlConverter = new XhtmlConverter();

    private List specificationTypes = new ArrayList();
    private Set specificationConverters = new HashSet();

    private FileTarget copySourceHtmlTarget;
    private SpecificationProcessingListener pageFooterRenderer;
    private BreadcrumbRenderer breadcrumbRenderer;
    private RunnerFactory runnerFactory;

    {
        ExtensionChecker.checkForOutdatedExtensions();
        commandRegistry.register("", "specification", specificationCommand);

        withSource(new ClassPathSource());

        AssertResultRenderer assertRenderer = new AssertResultRenderer();
        withAssertEqualsListener(assertRenderer);
        withAssertTrueListener(assertRenderer);
        withAssertFalseListener(assertRenderer);
        withVerifyRowsListener(new VerifyRowsResultRenderer());
        withDocumentParsingListener(new DocumentStructureImprover());
        withDocumentParsingListener(new MetadataCreator());
        withSpecificationType("html", null);
        withSpecificationType("xhtml", xhtmlConverter);
        withSpecificationType("md", markdownConverter);
        withSpecificationType("markdown", markdownConverter);
        withPageFooterRenderer(new PageFooterRenderer());
        withRunnerFactory(new SystemPropertiesRunnerFactory());
    }

    public ConcordionBuilder withSource(Source source) {
        for (SourceType sourceType : SourceType.values()) {
            withSource(source, sourceType);
        }
        return this;
    }

    public ConcordionBuilder withSource(Source source, SourceType sourceType) {
        sources.put(sourceType, source);
        return this;
    }

    public ConcordionBuilder withTarget(Target target) {
        this.target = target;
        return this;
    }

    public ConcordionBuilder withPageFooterRenderer(SpecificationProcessingListener pageFooterRenderer) {
        this.pageFooterRenderer = pageFooterRenderer;
        return this;
    }

    public ConcordionBuilder withBreadcrumbRenderer(BreadcrumbRenderer breadcrumbRenderer) {
    	this.breadcrumbRenderer = breadcrumbRenderer;
    	return this;
    }

    public ConcordionBuilder withRunnerFactory(RunnerFactory runnerFactory) {
        this.runnerFactory = runnerFactory;
        return this;
    }

    public ConcordionBuilder withSpecificationLocator(SpecificationLocator specificationLocator) {
        this.specificationLocator = specificationLocator;
        return this;
    }

    public ConcordionBuilder withEvaluatorFactory(EvaluatorFactory evaluatorFactory) {
        this.evaluatorFactory = evaluatorFactory;
        return this;
    }

    public ConcordionBuilder withThrowableListener(ThrowableCaughtListener throwableListener) {
        throwableCaughtListeners.add(throwableListener);
        return this;
    }

    private ConcordionBuilder withThrowableListener(int index, ThrowableCaughtListener throwableListener) {
        throwableCaughtListeners.add(index, throwableListener);
        return this;
    }

    public ConcordionBuilder withAssertEqualsListener(AssertEqualsListener listener) {
        assertEqualsCommand.addAssertEqualsListener(listener);
        return this;
    }

    public ConcordionBuilder withAssertTrueListener(AssertTrueListener listener) {
        assertTrueCommand.addAssertListener(listener);
        return this;
    }

    public ConcordionBuilder withAssertFalseListener(AssertFalseListener listener) {
        assertFalseCommand.addAssertListener(listener);
        return this;
    }

    public ConcordionBuilder withVerifyRowsListener(VerifyRowsListener listener) {
        verifyRowsCommand.addVerifyRowsListener(listener);
        return this;
    }

    public ConcordionBuilder withRunListener(RunListener listener) {
        runCommand.addRunListener(listener);
        return this;
    }

    public ConcordionExtender withRunStrategy(RunStrategy runStrategy) {
        runCommand.setRunStrategy(runStrategy);
        return this;
    }

    public ConcordionBuilder withExecuteListener(ExecuteListener listener) {
        executeCommand.addExecuteListener(listener);
        return this;
    }

    public ConcordionExtender withSetListener(SetListener setListener) {
        setCommand.addSetListener(setListener);
        return this;
    }

    public ConcordionBuilder withDocumentParsingListener(DocumentParsingListener listener) {
        documentParser.addDocumentParsingListener(listener);
        return this;
    }

    public ConcordionBuilder withSpecificationProcessingListener(SpecificationProcessingListener listener) {
        specificationProcessingListeners.add(listener);
        return this;
    }

    public ConcordionBuilder withBuildListener(ConcordionBuildListener listener) {
        listeners.add(listener);
        return this;
    }

    private ConcordionBuilder withApprovedCommand(String namespaceURI, String commandName, Command command) {
        ThrowableCatchingDecorator throwableCatchingDecorator = new ThrowableCatchingDecorator(new LocalTextDecorator(command), failFastExceptions);
        throwableCatchingDecorator.addThrowableListener(throwableListenerPublisher);
        Command decoratedCommand = throwableCatchingDecorator;
        commandRegistry.register(namespaceURI, commandName, decoratedCommand);
        return this;
    }

    public ConcordionBuilder withCommand(String namespaceURI, String commandName, Command command) {
        Check.notEmpty(namespaceURI, "Namespace URI is mandatory");
        Check.notEmpty(commandName, "Command name is mandatory");
        Check.notNull(command, "Command is null");
        Check.isFalse(namespaceURI.contains("concordion.org"),
                "The namespace URI for user-contributed command '" + commandName + "' "
                        + "must not contain 'concordion.org'. Use your own domain name instead.");
        return withApprovedCommand(namespaceURI, commandName, command);
    }

    public ConcordionBuilder withResource(String sourcePath, Resource targetResource) {
        resourceToCopyMap.put(sourcePath, targetResource);
        return this;
    }

    public ConcordionBuilder withEmbeddedCSS(String css) {
        StylesheetEmbedder embedder = new StylesheetEmbedder(css);
        withDocumentParsingListener(embedder);
        return this;
    }

    public ConcordionBuilder withEmbeddedCSS(String css, boolean append) {
        StylesheetEmbedder embedder = new StylesheetEmbedder(css, append);
        withDocumentParsingListener(embedder);
        return this;
    }

    public ConcordionBuilder withLinkedCSS(String cssPath, Resource targetResource) {
        withResource(cssPath, targetResource);
        StylesheetLinker cssLinker = new StylesheetLinker(targetResource);
        withDocumentParsingListener(cssLinker);
        withSpecificationProcessingListener(cssLinker);
        return this;
    }

    public ConcordionBuilder withEmbeddedJavaScript(String javaScript) {
        JavaScriptEmbedder embedder = new JavaScriptEmbedder(javaScript);
        withDocumentParsingListener(embedder);
        return this;
    }

    public ConcordionBuilder withLinkedJavaScript(String jsPath, Resource targetResource) {
        withResource(jsPath, targetResource);
        JavaScriptLinker javaScriptLinker = new JavaScriptLinker(targetResource);
        withDocumentParsingListener(javaScriptLinker);
        withSpecificationProcessingListener(javaScriptLinker);
        return this;
    }

    public Concordion build() throws UnableToBuildConcordionException {
        Check.isFalse(builtAlready, "ConcordionBuilder currently does not support calling build() twice");
        builtAlready = true;

        withApprovedCommand(NAMESPACE_CONCORDION_2007, "run", runCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "execute", executeCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "set", setCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "example", exampleCommand);

        withApprovedCommand(NAMESPACE_CONCORDION_2007, "assert-equals", assertEqualsCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "assertEquals", assertEqualsCommand);

        withApprovedCommand(NAMESPACE_CONCORDION_2007, "assert-true", assertTrueCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "assertTrue", assertTrueCommand);

        withApprovedCommand(NAMESPACE_CONCORDION_2007, "assert-false", assertFalseCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "assertFalse", assertFalseCommand);

        withApprovedCommand(NAMESPACE_CONCORDION_2007, "verify-rows", verifyRowsCommand);
        withApprovedCommand(NAMESPACE_CONCORDION_2007, "verifyRows", verifyRowsCommand);

        withApprovedCommand(NAMESPACE_CONCORDION_2007, "echo", echoCommand);

        Source resourceSource = sources.get(SourceType.RESOURCE);
        Source specificationSource = sources.get(SourceType.SPECIFICATION);

        withThrowableListener(0, new ThrowableRenderer(resourceSource));
        withRunListener(new RunResultRenderer(resourceSource));

        FixtureExampleHook fixtureExampleHook = new FixtureExampleHook();
        withOuterExampleListener(fixtureExampleHook);
        withExampleListener(fixtureExampleHook);

        if (target == null) {
            target = new FileTarget(getBaseOutputDir());
        }
        XMLParser xmlParser = new XMLParser();

        if (breadcrumbRenderer == null) {
        	breadcrumbRenderer = new BreadcrumbRenderer(specificationSource, xmlParser, specificationTypes);
        }
        specificationCommand.addSpecificationListener(breadcrumbRenderer);
        specificationCommand.addSpecificationListener(pageFooterRenderer);

        specificationReader = new XMLSpecificationReader(specificationSource, xmlParser, documentParser);
        specificationReader.setCopySourceHtmlTarget(copySourceHtmlTarget);

        addExtensions();
        copyResources(resourceSource);

        addSpecificationListeners();
        addThrowableListeners();

        SpecificationExporter exporter = new SpecificationExporter(target);
        specificationCommand.addSpecificationListener(exporter);
        specificationCommand.setSpecificationDescriber(exporter);

        exampleCommand.setSpecificationDescriber(exporter);
        runCommand.setRunnerFactory(runnerFactory);

        announceBuildCompleted();

        try {
            return new Concordion(specificationTypes, specificationLocator, specificationReader, evaluatorFactory, fixtureType);
        } catch (IOException e) {
            throw new UnableToBuildConcordionException(e);
        }
    }

    private void announceBuildCompleted() {
		for (ConcordionBuildListener buildListener : listeners) {
			buildListener.concordionBuilt(new ConcordionBuildEvent(target));
		}
	}

	private void addSpecificationListeners() {
        for (SpecificationProcessingListener listener : specificationProcessingListeners) {
            specificationCommand.addSpecificationListener(listener);
        }
    }

    private void addThrowableListeners() {
        for (ThrowableCaughtListener listener : throwableCaughtListeners) {
            throwableListenerPublisher.addThrowableListener(listener);
        }
    }

    private void copyResources(Source resourceSource) {
        for (Entry resourceToCopy : resourceToCopyMap.entrySet()) {
            String sourcePath = resourceToCopy.getKey();
            Resource targetResource = resourceToCopy.getValue();
            try {
                InputStream inputStream = resourceSource.createInputStream(new Resource(sourcePath));
                target.copyTo(targetResource, inputStream);
            } catch (IOException e) {
                throw new RuntimeException("Failed to copy " + sourcePath + " to target " + targetResource, e);
            }
        }
    }

    private void addExtensions() {
        String extensionProp = System.getProperty(PROPERTY_EXTENSIONS);
        if (extensionProp != null) {
            String[] extensions = extensionProp.split("\\s*,\\s*");
            for (String className : extensions) {
                addExtension(className);
            }
        }
    }

    private void addExtension(String className) {
        Class extensionClass;
        try {
            extensionClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Cannot find extension '" + className + "' on classpath", e);
        }
        Object extensionObject;
        try {
            extensionObject = extensionClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot instantiate extension '" + className + "'", e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Extension '" + className + "' or constructor are inaccessible", e);
        }

        ConcordionExtension extension;
        try {
            extension = (ConcordionExtension) extensionObject;
        } catch (ClassCastException e) {
            try {
                ConcordionExtensionFactory factory = (ConcordionExtensionFactory) extensionObject;
                extension = factory.createExtension();
            } catch (ClassCastException e1) {
                String message = SimpleFormatter.format("Extension class '%s' must implement '%s' or '%s'", className,
                        ConcordionExtension.class.getName(), ConcordionExtensionFactory.class.getName());
                throw new RuntimeException(message);
            }
        }
        extension.addTo(this);
    }

    public static File getBaseOutputDir() {
        if (baseOutputDir == null) {
            String outputPath = System.getProperty(PROPERTY_OUTPUT_DIR);
            if (outputPath != null) {
                baseOutputDir = new File(outputPath);
            } else {
                baseOutputDir = new File(System.getProperty("java.io.tmpdir"), "concordion");
            }
        }
        return baseOutputDir;
    }

    public ConcordionBuilder withFailFast(Class[] failFastExceptions) {
        this.failFastExceptions = Arrays.asList(failFastExceptions);
        return this;
    }

    public ConcordionBuilder withFixture(Fixture fixture) {
        this.fixtureType = fixture.getFixtureType();

        withResources(fixtureType);

        if (fixtureType.declaresFailFast()) {
            withFailFast(fixtureType.getDeclaredFailFastExceptions());
        }
        if (fixtureType.declaresFullOGNL()) {
            withEvaluatorFactory(new OgnlEvaluatorFactory());
        }

        return this;
    }

    public ConcordionExtender withExampleListener(ExampleListener listener) {
		exampleCommand.addExampleListener(listener);
		return this;
	}

    @Override
    public ConcordionExtender withImplementationStatusModifier(ImplementationStatusModifier statusModifier) {
        exampleCommand.setImplementationStatusModifier(statusModifier);
        return this;
    }

    public ConcordionExtender withOuterExampleListener(OuterExampleListener listener) {
        specificationCommand.addOuterExampleListener(listener);
        return this;
    }

    public ConcordionBuilder withResources(Fixture fixture) {
        withResources(fixture.getFixtureType());
        return this;
    }

    private void withResources(FixtureType fixtureType) {
        boolean includeDefaultStyling = true;

        Source resourceSource = sources.get(SourceType.RESOURCE);
        if (fixtureType.declaresResources()) {
        	ResourceFinder resources = new ResourceFinder(fixtureType);
        	List sourceFiles = resources.getResourcesToCopy();

        	for (ResourceToCopy sourceFile : sourceFiles) {
    			if (sourceFile.isStyleSheet()) {
    				if (sourceFile.insertType == InsertType.EMBEDDED) {
    					withEmbeddedCSS(resourceSource.readResourceAsString(sourceFile.getResourceName()));
    				} else {
    					withLinkedCSS(sourceFile.getResourceName(), new Resource(sourceFile.getResourceName()));
    				}
    			} else if (sourceFile.isScript()) {
    				if (sourceFile.insertType == InsertType.EMBEDDED) {
    					withEmbeddedJavaScript(resourceSource.readResourceAsString(sourceFile.getResourceName()));
    				} else {
    					withLinkedJavaScript(sourceFile.getResourceName(), new Resource(sourceFile.getResourceName()));
    				}
    			} else {
    				withResource(sourceFile.getResourceName(), new Resource(sourceFile.getResourceName()));
    			}
    		}

        	includeDefaultStyling = resources.includeDefaultStyling();

        	withDocumentParsingListener(new ResourceReferenceRemover(sourceFiles));
        }

        if (includeDefaultStyling) {
        	addDefaultStyling(resourceSource);
        }
    }

    private void addDefaultStyling(Source resourceSource) {
    	String stylesheetContent = resourceSource.readResourceAsString(EMBEDDED_STYLESHEET_RESOURCE);
    	withEmbeddedCSS(stylesheetContent);
    }

	@Override
    public ConcordionBuilder withSpecificationType(String typeSuffix, SpecificationConverter converter) {
	    specificationConverters.add(converter);
	    specificationTypes.add(new SpecificationType(typeSuffix, converter));
	    return this;
	}

    void configureWith(ConcordionOptions options) {
        if (options.markdownExtensions().length > 0) {
            int markdownExtensions = 0;
            for (MarkdownExtensions markdownExtension : options.markdownExtensions()) {
                markdownExtensions = markdownExtensions | markdownExtension.getPegdownExtension();
            }
            markdownConverter.withPegdownExtensions(markdownExtensions);
        }

        if (options.declareNamespaces().length > 0) {
            Map namespaces = ConcordionOptionsParser.convertNamespacePairsToMap(options.declareNamespaces());
            markdownConverter.withNamespaceDeclarations(namespaces);
        }

        String location = options.copySourceHtmlToDir();
        if (!location.isEmpty()) {
            location = expandSystemProperties(location);
            copySourceHtmlTarget = new FileTarget(new File(location));
        }
    }

    private String expandSystemProperties(String location) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
        Matcher matcher = pattern.matcher(location);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String property = matcher.group(1);
            String value = System.getProperty(property);
            if (value == null) {
                throw new RuntimeException(SimpleFormatter.format("Unable to find system property '%s' in @ConcordionOptions setting copySourceHtmlToDir of '%s'", property, location));
            }
            matcher.appendReplacement(sb, value);
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy