com.consol.citrus.mvn.plugin.CreateTestMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.consol.citrus.mvn.plugin.CreateTestMojo.java

Source

/*
 * Copyright 2006-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.consol.citrus.mvn.plugin;

import com.consol.citrus.util.TestCaseCreator;
import com.consol.citrus.util.TestCaseCreator.UnitFramework;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.*;
import org.apache.xmlbeans.*;
import org.apache.xmlbeans.impl.xsd2inst.SampleXmlUtil;
import org.codehaus.plexus.components.interactivity.Prompter;
import org.codehaus.plexus.components.interactivity.PrompterException;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Creates new Citrus test cases with empty XML test file and executable Java class.
 * 
 * Mojo offers an interactive mode, where the plugin prompts for parameters during execution. In
 * non-interactive mode the parameters are given as command line arguments.
 *
 * @author Christoph Deppisch
 */
@Mojo(name = "create-test")
public class CreateTestMojo extends AbstractMojo {

    /**
     * Whether to run this command in interactive mode. Defaults to "true".
     */
    @Parameter(property = "interactiveMode", defaultValue = "true")
    private boolean interactiveMode;

    /**
     * The name of the test case (must start with upper case letter).
     */
    @Parameter(property = "name", defaultValue = "")
    private String name;

    /**
     * The name-suffix of all test cases.
     */
    @Parameter(property = "nameSuffix", defaultValue = "_Test")
    private String nameSuffix = "_Test";

    /**
     * The test author
     */
    @Parameter(property = "author", defaultValue = "Unknown")
    private String author;

    /**
     * Describes the test case and its actions
     */
    @Parameter(property = "description", defaultValue = "TODO: Description")
    private String description;

    /**
     * Which package (folder structure) is assigned to this test. Defaults to "com.consol.citrus"
     */
    @Parameter(property = "targetPackage", defaultValue = "com.consol.citrus")
    private String targetPackage;

    /**
     * Which unit test framework to use for test execution (default: testng; options: testng, junit)
     */
    @Parameter(property = "framework", defaultValue = "testng")
    private String framework;

    /**
     * Path of the xsd from which the sample request and response are get from
     */
    @Parameter(property = "xsd", defaultValue = "")
    private String xsd;

    /**
     * Name of the xsd-element used to create the xml-sample-request
     */
    @Parameter(property = "xsdRequestMessage", defaultValue = "")
    private String xsdRequestMessage;

    /**
     * Name of the xsd-element used to create the xml-sample-response
     */
    @Parameter(property = "xsdResponseMessage", defaultValue = "")
    private String xsdResponseMessage;

    /**
     * The path to the wsdl from which the suite is generated.
     */
    @Parameter(property = "wsdl", defaultValue = "")
    private String wsdl;

    @Component
    private Prompter prompter;

    @Override
    public void execute() throws MojoExecutionException {
        try {
            String name = this.name;
            while (interactiveMode && !StringUtils.hasText(name)) {
                name = prompter.prompt("Enter test name:");
            }

            if (!StringUtils.hasText(name)) {
                throw new MojoExecutionException(
                        "Please provide proper test name! Test name must not be empty starting with uppercase letter!");
            }

            String author = this.author;
            String description = this.description;
            String targetPackage = this.targetPackage;
            String framework = this.framework;
            if (interactiveMode) {
                author = prompter.prompt("Enter test author:", this.author);
                description = prompter.prompt("Enter test description:", this.targetPackage);
                targetPackage = prompter.prompt("Enter test package:", this.targetPackage);
                framework = prompter.prompt("Choose unit test framework:",
                        CollectionUtils.arrayToList(new String[] { "testng", "junit" }), this.framework);
            }

            TestCaseCreator creator = getTestCaseCreator().withFramework(UnitFramework.fromString(framework))
                    .withName(name).withAuthor(author).withDescription(description).usePackage(targetPackage);

            if (interactiveMode) {
                String useXsd = prompter.prompt("Create test with XML schema?",
                        CollectionUtils.arrayToList(new String[] { "y", "n" }), "n");

                if (useXsd.equalsIgnoreCase("y")) {
                    createWithXsd(creator);
                    return;
                }

                String useWsdl = prompter.prompt("Create test with WSDL?",
                        CollectionUtils.arrayToList(new String[] { "y", "n" }), "n");

                if (useWsdl.equalsIgnoreCase("y")) {
                    createWithWsdl(creator);
                    return;
                }
            }

            if (interactiveMode) {
                String confirm = prompter.prompt(
                        "Confirm test creation:\n" + "framework: " + framework + "\n" + "name: " + name + "\n"
                                + "author: " + author + "\n" + "description: " + description + "\n" + "package: "
                                + targetPackage + "\n",
                        CollectionUtils.arrayToList(new String[] { "y", "n" }), "y");

                if (confirm.equalsIgnoreCase("n")) {
                    return;
                }
            }

            creator.createTestCase();

            getLog().info("Successfully created new test case " + targetPackage + "." + name);
        } catch (ArrayIndexOutOfBoundsException e) {
            getLog().info(
                    "Wrong parameter usage! See citrus:help for usage details (mvn citrus:help -Ddetail=true -Dgoal=create-test).");
        } catch (PrompterException e) {
            getLog().info(e);
            getLog().info(
                    "Failed to create test! See citrus:help for usage details (mvn citrus:help -Ddetail=true -Dgoal=create-test).");
        }
    }

    /**
     * Creates test case with request and response messages from XML schema.
     * @param creator
     * @throws MojoExecutionException
     */
    public void createWithXsd(TestCaseCreator creator) throws MojoExecutionException {
        try {
            String xsd = this.xsd;
            while (interactiveMode && !StringUtils.hasText(xsd)) {
                xsd = prompter.prompt("Enter path to XSD", this.xsd);
            }

            // compile xsd already here, otherwise later input is useless:
            SchemaTypeSystem sts = compileXsd(xsd);
            SchemaType[] globalElems = sts.documentTypes();

            SchemaType requestElem = null;
            SchemaType responseElem = null;

            String xsdRequestMessage = this.xsdRequestMessage;
            String xsdResponseMessage = this.xsdResponseMessage;
            if (interactiveMode) {
                xsdRequestMessage = prompter.prompt("Enter request element name", this.xsdRequestMessage);

                // try to guess the response-element and the testname from the request:
                String xsdResponseMessageSuggestion = xsdResponseMessage;
                if (xsdRequestMessage.endsWith("Req")) {
                    xsdResponseMessageSuggestion = xsdRequestMessage.substring(0, xsdRequestMessage.indexOf("Req"))
                            + "Res";
                    creator.withName(xsdRequestMessage.substring(0, xsdRequestMessage.indexOf("Req")) + "Test");
                } else if (xsdRequestMessage.endsWith("Request")) {
                    xsdResponseMessageSuggestion = xsdRequestMessage.substring(0,
                            xsdRequestMessage.indexOf("Request")) + "Response";
                    creator.withName(xsdRequestMessage.substring(0, xsdRequestMessage.indexOf("Request")) + "Test");
                } else if (xsdRequestMessage.endsWith("RequestMessage")) {
                    xsdResponseMessageSuggestion = xsdRequestMessage.substring(0,
                            xsdRequestMessage.indexOf("RequestMessage")) + "ResponseMessage";
                    creator.withName(
                            xsdRequestMessage.substring(0, xsdRequestMessage.indexOf("RequestMessage")) + "Test");
                }

                xsdResponseMessage = prompter.prompt("Enter response element name", xsdResponseMessageSuggestion);
            }

            if (interactiveMode) {
                String confirm = prompter.prompt(
                        "Confirm test creation:\n" + "framework: " + creator.getFramework() + "\n" + "name: "
                                + creator.getName() + "\n" + "author: " + creator.getAuthor() + "\n"
                                + "description: " + creator.getDescription() + "\n" + "package: "
                                + creator.getTargetPackage() + "\n",
                        CollectionUtils.arrayToList(new String[] { "y", "n" }), "y");

                if (confirm.equalsIgnoreCase("n")) {
                    return;
                }
            }

            for (SchemaType elem : globalElems) {
                if (elem.getContentModel().getName().getLocalPart().equals(xsdRequestMessage)) {
                    requestElem = elem;
                    break;
                }
            }

            for (SchemaType elem : globalElems) {
                if (elem.getContentModel().getName().getLocalPart().equals(xsdResponseMessage)) {
                    responseElem = elem;
                    break;
                }
            }

            // Now generate it
            creator.withXmlRequest(SampleXmlUtil.createSampleForType(requestElem))
                    .withXmlResponse(SampleXmlUtil.createSampleForType(responseElem));

            creator.createTestCase();

            getLog().info(
                    "Successfully created new test case " + creator.getTargetPackage() + "." + creator.getName());
        } catch (ArrayIndexOutOfBoundsException e) {
            getLog().info(
                    "Wrong parameter usage! See citrus:help for usage details (mvn citrus:help -Ddetail=true -Dgoal=create-test-from-xsd).");
        } catch (PrompterException e) {
            getLog().info(e);
            getLog().info(
                    "Failed to create test! See citrus:help for usage details (mvn citrus:help -Ddetail=true -Dgoal=create-test-from-xsd).");
        } catch (IOException e) {
            getLog().info(e);
        }
    }

    /**
     * Creates test case with request and response messages from XML schema.
     * @param creator
     * @throws MojoExecutionException
     */
    public void createWithWsdl(TestCaseCreator creator) throws MojoExecutionException {
        String separator = "+++++++++++++++++++++++++++++++++++";

        try {
            String wsdl = this.wsdl;
            while (interactiveMode && !StringUtils.hasText(wsdl)) {
                wsdl = prompter.prompt("Enter path to WSDL", this.wsdl);
            }

            if (!StringUtils.hasText(wsdl)) {
                throw new MojoExecutionException("Please provide proper path to WSDL file");
            }

            String wsdlNsDelaration = "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/' ";

            // compile wsdl and xsds right now, otherwise later input is useless:
            XmlObject wsdlObject = compileWsdl(wsdl);
            SchemaTypeSystem schemaTypeSystem = compileXsd(wsdlObject);

            getLog().info(separator);
            getLog().info("WSDL compilation successful");
            String serviceName = evaluateAsString(wsdlObject, wsdlNsDelaration + ".//wsdl:portType/@name");
            getLog().info("Found service: " + serviceName);

            getLog().info(separator);
            getLog().info("Found service operations:");
            XmlObject[] messages = wsdlObject.selectPath(wsdlNsDelaration + ".//wsdl:message");
            XmlObject[] operations = wsdlObject.selectPath(wsdlNsDelaration + ".//wsdl:portType/wsdl:operation");
            for (XmlObject operation : operations) {
                getLog().info(evaluateAsString(operation, wsdlNsDelaration + "./@name"));
            }
            getLog().info(separator);

            getLog().info("Generating test cases for service operations ...");
            String nameSuffix = this.nameSuffix;
            if (interactiveMode) {
                nameSuffix = prompter.prompt("Enter test name suffix", this.nameSuffix);
            }

            if (interactiveMode) {
                String confirm = prompter.prompt(
                        "Confirm test creation:\n" + "framework: " + creator.getFramework() + "\n" + "name: "
                                + creator.getName() + "\n" + "author: " + creator.getAuthor() + "\n"
                                + "description: " + creator.getDescription() + "\n" + "package: "
                                + creator.getTargetPackage() + "\n",
                        CollectionUtils.arrayToList(new String[] { "y", "n" }), "y");

                if (confirm.equalsIgnoreCase("n")) {
                    return;
                }
            }

            for (XmlObject operation : operations) {
                String operationName = evaluateAsString(operation, wsdlNsDelaration + "./@name");
                String inputMessage = removeNsPrefix(
                        evaluateAsString(operation, wsdlNsDelaration + "./wsdl:input/@message"));
                String outputMessage = removeNsPrefix(
                        evaluateAsString(operation, wsdlNsDelaration + "./wsdl:output/@message"));

                String inputElement = null;
                String outputElement = null;
                for (XmlObject message : messages) {
                    String messageName = evaluateAsString(message, wsdlNsDelaration + "./@name");

                    if (messageName.equals(inputMessage)) {
                        inputElement = removeNsPrefix(
                                evaluateAsString(message, wsdlNsDelaration + "./wsdl:part/@element"));
                    }

                    if (messageName.equals(outputMessage)) {
                        outputElement = removeNsPrefix(
                                evaluateAsString(message, wsdlNsDelaration + "./wsdl:part/@element"));
                    }
                }

                SchemaType requestElem = getSchemaType(schemaTypeSystem, operationName, inputElement);
                SchemaType responseElem = getSchemaType(schemaTypeSystem, operationName, outputElement);

                String testName = creator.getName() + operationName + nameSuffix;

                // Now generate it
                creator.withName(testName).withXmlRequest(SampleXmlUtil.createSampleForType(requestElem))
                        .withXmlResponse(SampleXmlUtil.createSampleForType(responseElem));

                creator.createTestCase();

                getLog().info("Successfully created new test case " + creator.getTargetPackage() + "." + testName);
            }

        } catch (ArrayIndexOutOfBoundsException e) {
            getLog().info(e);
            getLog().info(
                    "Wrong parameter usage! See citrus:help for usage details (mvn citrus:help -Ddetail=true -Dgoal=create-suite-from-wsdl).");
        } catch (PrompterException e) {
            getLog().info(e);
            getLog().info(
                    "Failed to create suite! See citrus:help for usage details (mvn citrus:help -Ddetail=true -Dgoal=create-suite-from-wsdl).");
        } catch (IOException e) {
            getLog().info(e);
        }
    }

    /**
     * @param schemaTypeSystem
     * @param operation
     * @param elementName
     * @return
     * @throws MojoExecutionException
     */
    private SchemaType getSchemaType(SchemaTypeSystem schemaTypeSystem, String operation, String elementName)
            throws MojoExecutionException {

        for (SchemaType elem : schemaTypeSystem.documentTypes()) {
            if (elem.getContentModel().getName().getLocalPart().equals(elementName)) {
                return elem;
            }
        }

        throw new MojoExecutionException("Unable to find schema type declaration '" + elementName + "'"
                + " for WSDL operation '" + operation + "'");
    }

    /**
     * Removes namespace prefix if present.
     * @param elementName
     * @return
     */
    private String removeNsPrefix(String elementName) {
        return elementName.indexOf(':') != -1 ? elementName.substring(elementName.indexOf(':') + 1) : elementName;
    }

    /**
     * Compiles WSDL file resource to a XmlObject.
     * @return
     * @throws MojoExecutionException
     * @throws IOException
     */
    private XmlObject compileWsdl(String wsdl) throws MojoExecutionException, IOException {
        File wsdlFile;
        try {
            wsdlFile = new PathMatchingResourcePatternResolver().getResource(wsdl).getFile();
        } catch (FileNotFoundException e) {
            wsdlFile = new File(wsdl);
        }

        if (!wsdlFile.exists()) {
            throw new MojoExecutionException(
                    "Unable to read WSDL - does not exist in " + wsdlFile.getAbsolutePath());
        }

        if (!wsdlFile.canRead()) {
            throw new MojoExecutionException("Unable to read WSDL - could not open in read mode");
        }

        try {
            return XmlObject.Factory.parse(wsdlFile,
                    (new XmlOptions()).setLoadLineNumbers().setLoadMessageDigest().setCompileDownloadUrls());
        } catch (XmlException e) {
            for (Object error : e.getErrors()) {
                getLog().error(((XmlError) error).getLine() + "" + error.toString());
            }
            throw new MojoExecutionException("WSDL could not be parsed", e);
        } catch (Exception e) {
            throw new MojoExecutionException("WSDL could not be parsed", e);
        }
    }

    /**
     * Finds nested XML schema definition and compiles it to a schema type system instance.
     * @param wsdl
     * @return
     * @throws MojoExecutionException
     */
    private SchemaTypeSystem compileXsd(XmlObject wsdl) throws MojoExecutionException {
        // extract namespaces defined on wsdl-level:
        String[] namespacesWsdl = extractNamespacesOnWsdlLevel(wsdl);

        // calc the namespace-prefix of the schema-tag, default ""
        String schemaNsPrefix = extractSchemaNamespacePrefix(wsdl);

        // extract each schema-element and add missing namespaces defined on wsdl-level
        String[] schemas = getNestedSchemas(wsdl, namespacesWsdl, schemaNsPrefix);

        XmlObject[] xsd = new XmlObject[schemas.length];
        try {
            for (int i = 0; i < schemas.length; i++) {
                xsd[i] = XmlObject.Factory.parse(schemas[i],
                        (new XmlOptions()).setLoadLineNumbers().setLoadMessageDigest().setCompileDownloadUrls());
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to parse XSD schema", e);
        }

        SchemaTypeSystem schemaTypeSystem = null;
        try {
            schemaTypeSystem = XmlBeans.compileXsd(xsd, XmlBeans.getContextTypeLoader(), new XmlOptions());
        } catch (XmlException e) {
            for (Object error : e.getErrors()) {
                getLog().error("Line " + ((XmlError) error).getLine() + ": " + error.toString());
            }
            throw new MojoExecutionException("Failed to compile XSD schema", e);
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to compile XSD schema", e);
        }
        return schemaTypeSystem;
    }

    /**
     * Finds nested schema definitions and puts globally WSDL defined namespaces to schema level.
     *
     * @param wsdl
     * @param namespacesWsdl
     * @param schemaNsPrefix
     */
    private String[] getNestedSchemas(XmlObject wsdl, String[] namespacesWsdl, String schemaNsPrefix) {
        List<String> schemas = new ArrayList<String>();
        String openedStartTag = "<" + schemaNsPrefix + "schema";
        String endTag = "</" + schemaNsPrefix + "schema>";

        int cursor = 0;
        while (wsdl.xmlText().indexOf(openedStartTag, cursor) != -1) {
            int begin = wsdl.xmlText().indexOf(openedStartTag, cursor);
            int end = wsdl.xmlText().indexOf(endTag, begin) + endTag.length();
            int insertPointNamespacesWsdl = wsdl.xmlText().indexOf(" ", begin);

            StringBuffer buf = new StringBuffer();
            buf.append(wsdl.xmlText().substring(begin, insertPointNamespacesWsdl)).append(" ");

            for (String nsWsdl : namespacesWsdl) {
                String nsPrefix = nsWsdl.substring(0, nsWsdl.indexOf("="));
                if (!wsdl.xmlText().substring(begin, end).contains(nsPrefix)) {
                    buf.append(nsWsdl).append(" ");
                }
            }

            buf.append(wsdl.xmlText().substring(insertPointNamespacesWsdl, end));
            schemas.add(buf.toString());
            cursor = end;
        }

        return schemas.toArray(new String[] {});
    }

    /**
     * Finds schema tag and extracts the namespace prefix.
     * @param wsdl
     * @return
     */
    private String extractSchemaNamespacePrefix(XmlObject wsdl) {
        String schemaNsPrefix = "";
        if (wsdl.xmlText().contains(":schema")) {
            int cursor = wsdl.xmlText().indexOf(":schema");
            for (int i = cursor; i > cursor - 100; i--) {
                schemaNsPrefix = wsdl.xmlText().substring(i, cursor);
                if (schemaNsPrefix.startsWith("<")) {
                    return schemaNsPrefix.substring(1) + ":";
                }
            }
        }
        return schemaNsPrefix;
    }

    /**
     * Returns an array of all namespace declarations, found on wsdl-level.
     *
     * @param wsdl
     * @return
     */
    private String[] extractNamespacesOnWsdlLevel(XmlObject wsdl) {
        int cursor = wsdl.xmlText().indexOf(":") + ":definitions ".length();
        String nsWsdlOrig = wsdl.xmlText().substring(cursor, wsdl.xmlText().indexOf(">", cursor));
        int noNs = StringUtils.countOccurrencesOf(nsWsdlOrig, "xmlns:");
        String[] namespacesWsdl = new String[noNs];
        cursor = 0;
        for (int i = 0; i < noNs; i++) {
            int begin = nsWsdlOrig.indexOf("xmlns:", cursor);
            int end = nsWsdlOrig.indexOf("\"", begin + 20);
            namespacesWsdl[i] = nsWsdlOrig.substring(begin, end) + "\"";
            cursor = end;
        }
        return namespacesWsdl;
    }

    /**
     * Returns the value of an xml-attribute
     *
     * @param rootObject
     * @param pathToAttribute
     * @return
     * @throws MojoExecutionException
     */
    private String evaluateAsString(XmlObject rootObject, String pathToAttribute) throws MojoExecutionException {
        XmlObject[] xmlObject = rootObject.selectPath(pathToAttribute);

        if (xmlObject.length == 0) {
            throw new MojoExecutionException("Unable to find element attribute " + pathToAttribute);
        }

        int begin = xmlObject[0].xmlText().indexOf(">") + 1;
        int end = xmlObject[0].xmlText().lastIndexOf("</");
        return xmlObject[0].xmlText().substring(begin, end);
    }

    /**
     * Finds nested XML schema definition and compiles it to a schema type system instance
     * @param xsd
     * @return
     * @throws MojoExecutionException
     */
    private SchemaTypeSystem compileXsd(String xsd) throws MojoExecutionException, IOException {
        File xsdFile;
        try {
            xsdFile = new PathMatchingResourcePatternResolver().getResource(xsd).getFile();
        } catch (FileNotFoundException e) {
            xsdFile = new File(xsd);
        }

        if (!xsdFile.exists()) {
            throw new MojoExecutionException("Unable to read XSD - does not exist in " + xsdFile.getAbsolutePath());
        }
        if (!xsdFile.canRead()) {
            throw new MojoExecutionException("Unable to read XSD - could not open in read mode");
        }

        XmlObject xsdObject;
        try {
            xsdObject = XmlObject.Factory.parse(xsdFile,
                    (new XmlOptions()).setLoadLineNumbers().setLoadMessageDigest().setCompileDownloadUrls());
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to parse XSD schema", e);
        }
        XmlObject[] schemas = new XmlObject[] { xsdObject };
        SchemaTypeSystem schemaTypeSystem = null;
        try {
            schemaTypeSystem = XmlBeans.compileXsd(schemas, XmlBeans.getContextTypeLoader(), new XmlOptions());
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to compile XSD schema", e);
        }
        return schemaTypeSystem;
    }

    /**
     * Method provides test creator instance. Basically introduced for better mocking capabilities in unit tests but
     * also useful for subclasses to provide customized creator instance.
     * .
     * @return test creator.
     */
    public TestCaseCreator getTestCaseCreator() {
        return TestCaseCreator.build();
    }

    /**
     * Sets the interactiveMode.
     * @param interactiveMode the interactiveMode to set
     */
    public void setInteractiveMode(boolean interactiveMode) {
        this.interactiveMode = interactiveMode;
    }

    /**
     * Sets the prompter.
     * @param prompter the prompter to set
     */
    public void setPrompter(Prompter prompter) {
        this.prompter = prompter;
    }
}