0001: // THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
0002: // CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
0003: // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
0004: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
0005: // OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
0006: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0007: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
0008: // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0009: // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0010: // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
0011: // EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
0012: // POSSIBILITY OF SUCH DAMAGE.
0013: //
0014: // Copyright 2000-2005 © Softaris Pty.Ltd. All Rights Reserved.
0015: package com.metaboss.sdlctools.applications.systemtester;
0016:
0017: import java.io.File;
0018: import java.io.FileFilter;
0019: import java.io.FileOutputStream;
0020: import java.io.FileReader;
0021: import java.io.IOException;
0022: import java.io.PrintWriter;
0023: import java.io.StringReader;
0024: import java.io.StringWriter;
0025: import java.lang.reflect.InvocationHandler;
0026: import java.lang.reflect.InvocationTargetException;
0027: import java.lang.reflect.Method;
0028: import java.lang.reflect.Proxy;
0029: import java.net.URL;
0030: import java.net.URLClassLoader;
0031: import java.text.SimpleDateFormat;
0032: import java.util.ArrayList;
0033: import java.util.Arrays;
0034: import java.util.Calendar;
0035: import java.util.Comparator;
0036: import java.util.Date;
0037: import java.util.HashMap;
0038: import java.util.HashSet;
0039: import java.util.Iterator;
0040: import java.util.List;
0041: import java.util.Map;
0042: import java.util.Set;
0043: import java.util.StringTokenizer;
0044:
0045: import javax.xml.parsers.DocumentBuilder;
0046: import javax.xml.parsers.DocumentBuilderFactory;
0047: import javax.xml.transform.OutputKeys;
0048: import javax.xml.transform.Transformer;
0049: import javax.xml.transform.TransformerConfigurationException;
0050: import javax.xml.transform.TransformerFactory;
0051: import javax.xml.transform.dom.DOMSource;
0052: import javax.xml.transform.stream.StreamResult;
0053: import javax.xml.transform.stream.StreamSource;
0054:
0055: import org.apache.commons.logging.Log;
0056: import org.apache.commons.logging.LogFactory;
0057: import org.jaxen.XPath;
0058: import org.jaxen.dom.DOMXPath;
0059: import org.w3c.dom.Attr;
0060: import org.w3c.dom.Comment;
0061: import org.w3c.dom.Document;
0062: import org.w3c.dom.Element;
0063: import org.w3c.dom.NamedNodeMap;
0064: import org.w3c.dom.Node;
0065: import org.w3c.dom.NodeList;
0066: import org.xml.sax.ErrorHandler;
0067: import org.xml.sax.InputSource;
0068: import org.xml.sax.SAXException;
0069: import org.xml.sax.SAXParseException;
0070:
0071: import com.metaboss.util.DOMUtils;
0072: import com.metaboss.util.DirectoryUtils;
0073: import com.metaboss.util.ReflectionUtils;
0074: import com.metaboss.util.StringUtils;
0075:
0076: /** This class runs a single test scenario (note that scenarion may in fact consist of many test steps.
0077: * It should be called from command line and needs following arguments
0078: * <UL>
0079: * <LI>-scenarioPath=<test scenario directory path>. If it is not specified - the current working directory is used.
0080: * All xml files residing in this directory (apart from possible specimen xml file described below) will be read, sorted in alphabetical order
0081: * and executed in sequence.</LI>
0082: * <LI>-scenarioName=<a name of this scenario>. If it is not specified - "UnnamedScenario" name is used.
0083: * This is the name which is used to form a core of the log file name and also for the console output.
0084: * For example scenarioName might be 'MyRegressionTest' or 'AcceptanceScenarios' etc. The actual log file
0085: * name is formed by concatenating scenarioName, scenarioRunName and timestamp.</LI>
0086: * <LI>-scenarioRunName=<a name of the run of this scenario>. If it is not specified the empty string is used.
0087: * This is the name which is used to form a suffix of the log file name and also for the console output.
0088: * For example scenarioRunName might be 'JBossClient' or 'OracleInprocess' etc. The actual log file
0089: * name is formed by concatenating scenarioName, scenarioRunName and timestamp.</LI>
0090: * <LI>-clientPath=<system clients configuration directory path>. This directory should contain subdirectories with
0091: * names <EnterpriseName>.<SystemName>.<ServicemoduleName>. When runner parses scenarios it uses
0092: * namespace information from the test input documents to understand which client is needed, loads
0093: * contents of the matching directory in the specified path and executes servicemodule's xmlstrings adapter.
0094: * This basically allows for each client to be configurred and versioned separately (i.e. it may have different
0095: * versions of libraries etc...) If path is not given, path from under current directory is assumed. If
0096: * directory is not found - attempt is made to load adapter from the system classpath</LI>
0097: * <LI>-loggingPath=<test logs directory path>. While runner is running - it outputs the log file (one per run).
0098: * The name of this file is formed from the scenarioName (see argument description above) and
0099: * date and time of the test. This ensures recognisable name of the log file and uniqueness of the names
0100: * even if same test is run more than once in quick succession.</LI>
0101: * <LI>-specimenFile=<path and name of the scenario log specimen file>. This parameter
0102: * triggers the test scenario validation against specified specimen file. This validation takes place
0103: * at the end of the scenario run. The specimen file is simply a previous good log file (i.e. result of good scenario run)
0104: * with all volotile data removed from it. Comparison process will ensure that all data in specimen file
0105: * is present in the log file being validated, <u>but not visa versa</u>. If this file is not specified
0106: * validation of the test data against the specimen is not performed.</LI>
0107: * <LI>-logTestPerformance=<true or flse>. Default is true.
0108: * This argument controls output of the performance statistics records into the scenario log. Note that
0109: * by its nature performance data is volotile and there is usually a lot of it in the log. Therefore
0110: * the presence of the performance data in the log makes it a bad candidate for a specimen.
0111: * Unless you want to do lots of manual editing (i.e. deleting of performance data from the log file)
0112: * it is a good idea to switch off performance records output when capturing the log to be used as a specimen.</LI>
0113: * </UL>
0114: */
0115: public class ScenarioRunner {
0116: private class SpecialFileFilter implements FileFilter {
0117: private Set mExcludeFilesPaths = new HashSet();
0118:
0119: public SpecialFileFilter(File[] pExcludeFiles) {
0120: if (pExcludeFiles != null) {
0121: for (int i = 0; i < pExcludeFiles.length; i++) {
0122: mExcludeFilesPaths.add(pExcludeFiles[i]
0123: .getAbsolutePath());
0124: }
0125: }
0126: }
0127:
0128: // Filter out not xml files and possible excluded files
0129: public boolean accept(File pPathname) {
0130: return pPathname.isFile() == true
0131: && pPathname.isHidden() == false
0132: && pPathname.canRead() == true
0133: && pPathname.getName().toLowerCase().endsWith(
0134: ".xml") == true
0135: && mExcludeFilesPaths.contains(pPathname
0136: .getAbsolutePath()) == false;
0137: }
0138: };
0139:
0140: private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
0141: private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
0142: private static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
0143: private static final String W3C_XSLT_SCHEMA = "http://www.w3.org/1999/XSL/Transform";
0144: private static final String METABOSS_SCENARIO_RUNNER_SCHEMA = "http://www.metaboss.com/XMLSchemas/MetaBoss/SdlcTools/SystemTester/1.0";
0145: private static final String W3C_XMLNS_SCHEMA = "http://www.w3.org/2000/xmlns/";
0146:
0147: private static final Log sLogger = LogFactory
0148: .getLog(ScenarioRunner.class);
0149: private static final SimpleDateFormat sLogPathSuffixFormatter = new SimpleDateFormat(
0150: "yyyy-MM-dd HH.mm.ss");
0151: private static final SimpleDateFormat sPerformanceDataTimestampFormatter = new SimpleDateFormat(
0152: "yyyy-MM-dd HH:mm:ss.SSS");
0153: private static ScenarioRunner sScenarioRunnerInstance = null;
0154: private String[] mIncludePath = null;
0155: private String[] mScenarioPath = null;
0156: private String mScenarioName = null;
0157: private String mScenarioRunName = null;
0158: private String mClientPath = null;
0159: private String mLoggingPath = null;
0160: private String mSpecimenFilePath = null;
0161: private File mScenarioLogFile = null;
0162: private boolean mScenarioLogNeedsSaving = false;
0163: private TemplateContext mScenarioContext = null;
0164: private boolean mLogTestPerformance = true;
0165: private DocumentBuilder mScenarioDocumentBuilder = null;
0166: private DocumentBuilder mAnyDocumentBuilder = null;
0167: private DocumentBuilder mTransformationResultDocumentBuilder = null;
0168: private TransformerFactory mTransformerFactory = null;
0169: private Transformer mTestLogDocumentTransformer = null;
0170:
0171: // Scenario variables. Get refreshed on every scenario run
0172: private String mScenarioLoggingPath;
0173: private String mScenarioClientPathPrefix;
0174: private String mScenarioRootClassLoaderDirectoryPath;
0175: private Date mTestLogStartTime;
0176: private Map mAdapterRunners = new HashMap(); // Adapters indexed by namespace
0177:
0178: // This class holds the current running context for the template invocation
0179: // Template will be invoked with one of the elments referred by this class depending on scope
0180: private class TemplateContext {
0181: public Document mTestLogDocument = null;
0182: public Element mTestScenarioLogElement = null;
0183: public Element mTopMostTestCaseLogElement = null; // The very first top level test case (used to insert comments before)
0184: public Element mTestCaseLogElement = null; // Current test case
0185: public List mTestCaseLogElementStack = new ArrayList(); // Stack of the previous ones, excluding the current
0186: public Set mUsedIds = new HashSet();
0187: }
0188:
0189: public ScenarioRunner() throws Exception {
0190: // Simple error handler - just outputs everything onto the log
0191: ErrorHandler lErrorHandler = new ErrorHandler() {
0192: public void error(SAXParseException pException)
0193: throws SAXException {
0194: sLogger.error("XML Parser had error", pException);
0195: throw new SAXException(
0196: "Handler has intercepted parsing error.",
0197: pException);
0198: }
0199:
0200: public void fatalError(SAXParseException pException)
0201: throws SAXException {
0202: sLogger.fatal("XML Parser had fatal error", pException);
0203: throw new SAXException(
0204: "Handler has intercepted fatal parsing error.",
0205: pException);
0206: }
0207:
0208: public void warning(SAXParseException pException) {
0209: sLogger.warn("XML Parser had warning", pException);
0210: }
0211: };
0212: // Create simple validating document builder, which will read test documents.
0213: {
0214: DocumentBuilderFactory dbf = DocumentBuilderFactory
0215: .newInstance();
0216: dbf.setNamespaceAware(true);
0217: dbf.setValidating(true);
0218: dbf.setIgnoringElementContentWhitespace(true);
0219: dbf.setIgnoringComments(true);
0220: dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
0221: dbf.setAttribute(JAXP_SCHEMA_SOURCE, ScenarioRunner.class
0222: .getResourceAsStream("SystemTesterSchema.xsd"));
0223: mScenarioDocumentBuilder = dbf.newDocumentBuilder();
0224: mScenarioDocumentBuilder.setErrorHandler(lErrorHandler);
0225: }
0226: // Create simple validating any document builder, which will be used on inputs / outputs (any namespace)
0227: {
0228: DocumentBuilderFactory dbf = DocumentBuilderFactory
0229: .newInstance();
0230: dbf.setNamespaceAware(true);
0231: dbf.setValidating(false);
0232: dbf.setIgnoringElementContentWhitespace(true);
0233: dbf.setIgnoringComments(true);
0234: mAnyDocumentBuilder = dbf.newDocumentBuilder();
0235: mAnyDocumentBuilder.setErrorHandler(lErrorHandler);
0236: }
0237: // Create transformation result document builder - dissolves CDATA
0238: {
0239: DocumentBuilderFactory dbf = DocumentBuilderFactory
0240: .newInstance();
0241: dbf.setCoalescing(true);
0242: dbf.setNamespaceAware(true);
0243: dbf.setValidating(false);
0244: dbf.setIgnoringElementContentWhitespace(true);
0245: dbf.setIgnoringComments(true);
0246: mTransformationResultDocumentBuilder = dbf
0247: .newDocumentBuilder();
0248: mTransformationResultDocumentBuilder
0249: .setErrorHandler(lErrorHandler);
0250: }
0251:
0252: // Also create transformer factory
0253: mTransformerFactory = TransformerFactory.newInstance();
0254: }
0255:
0256: public synchronized void setScenarioPath(String pScenarioPath) {
0257: mScenarioPath = pScenarioPath.split(";");
0258: }
0259:
0260: public synchronized void setIncludePath(String pIncludePath) {
0261: mIncludePath = pIncludePath.split(";");
0262: }
0263:
0264: public synchronized void setScenarioName(String pScenarioName) {
0265: mScenarioName = pScenarioName;
0266: }
0267:
0268: public synchronized void setScenarioRunName(String pScenarioRunName) {
0269: mScenarioRunName = pScenarioRunName;
0270: }
0271:
0272: public synchronized void setClientPath(String pClientPath) {
0273: mClientPath = new File(pClientPath).getAbsolutePath();
0274: }
0275:
0276: public synchronized void setLoggingPath(String pLoggingPath) {
0277: mLoggingPath = new File(pLoggingPath).getAbsolutePath();
0278: }
0279:
0280: public synchronized void setSpecimenFile(String pSpecimenFile) {
0281: mSpecimenFilePath = new File(pSpecimenFile).getAbsolutePath();
0282: }
0283:
0284: public synchronized void setLogTestPerformance(boolean pNeedToLog) {
0285: mLogTestPerformance = pNeedToLog;
0286: }
0287:
0288: // This helper class is responsible for processing subscription events
0289: private class SubscriptionHandler implements InvocationHandler {
0290: private Object mSubscriberInstance;
0291: private TemplateContext mTemplateContext;
0292: private Element mTargetSubscriptionLogElement;
0293:
0294: // Constructor
0295: public SubscriptionHandler(Class pSubscriberInterface,
0296: TemplateContext pTemplateContext,
0297: Element pTargetSubscriptionLogElement)
0298: throws TransformerConfigurationException {
0299: mTemplateContext = pTemplateContext;
0300: mTargetSubscriptionLogElement = pTargetSubscriptionLogElement;
0301: mSubscriberInstance = Proxy.newProxyInstance(
0302: pSubscriberInterface.getClassLoader(),
0303: new Class[] { pSubscriberInterface }, this );
0304: }
0305:
0306: // Getter for the subscriber instance
0307: public Object getSubscriberInstance() {
0308: return mSubscriberInstance;
0309: }
0310:
0311: // Handle any subscriber method
0312: public Object invoke(Object proxy, Method method, Object[] args)
0313: throws Throwable {
0314: String lMethodName = method.getName();
0315: sLogger.debug("Method " + lMethodName
0316: + " has been invoked on event subscriber object");
0317: Date lInvocationTime = new Date();
0318: // Filter special methods
0319: if (lMethodName.equals("onEvent") && args != null
0320: && args.length == 1) {
0321: String lEventDocumentString = (String) args[0];
0322: Document lEventDocument = mAnyDocumentBuilder
0323: .parse(new InputSource(new StringReader(
0324: lEventDocumentString)));
0325: Element lEventElement = lEventDocument
0326: .getDocumentElement();
0327: Element lEventLogElement = (Element) mTargetSubscriptionLogElement
0328: .appendChild(mTemplateContext.mTestLogDocument
0329: .createElement("SubscriptionEventLog"));
0330: // Append performance information if necessary
0331: if (mLogTestPerformance) {
0332: Element lArrivalElement = (Element) lEventLogElement
0333: .appendChild(mTemplateContext.mTestLogDocument
0334: .createElement("Arrival"));
0335: populateTimestampElement(lArrivalElement,
0336: lInvocationTime);
0337: }
0338: lEventLogElement
0339: .appendChild(mTemplateContext.mTestLogDocument
0340: .importNode(lEventElement, true));
0341: // Log the test output
0342: if (sLogger.isDebugEnabled()) {
0343: StringWriter lIndentedResultFileContents = new StringWriter();
0344: // Initialise transformer to use in this class
0345: Transformer lTransformer = mTransformerFactory
0346: .newTransformer();
0347: lTransformer.setOutputProperty(OutputKeys.METHOD,
0348: "xml");
0349: lTransformer.setOutputProperty(
0350: OutputKeys.STANDALONE, "yes");
0351: lTransformer.setOutputProperty(OutputKeys.INDENT,
0352: "yes");
0353: lTransformer.transform(
0354: new DOMSource(lEventDocument),
0355: new StreamResult(
0356: lIndentedResultFileContents));
0357: sLogger.debug("Received following event:\r\n"
0358: + lIndentedResultFileContents);
0359: }
0360: } else if (lMethodName.equals("subscriptionInitiated")
0361: && args != null && args.length == 1) {
0362: String lEventDocumentString = (String) args[0];
0363: Document lEventDocument = mAnyDocumentBuilder
0364: .parse(new InputSource(new StringReader(
0365: lEventDocumentString)));
0366: Element lEventElement = lEventDocument
0367: .getDocumentElement();
0368: Element lEventLogElement = (Element) mTargetSubscriptionLogElement
0369: .appendChild(mTemplateContext.mTestLogDocument
0370: .createElement("SubscriptionInitiatedLog"));
0371: // Append performance information if necessary
0372: if (mLogTestPerformance) {
0373: Element lArrivalElement = (Element) lEventLogElement
0374: .appendChild(mTemplateContext.mTestLogDocument
0375: .createElement("Arrival"));
0376: populateTimestampElement(lArrivalElement,
0377: lInvocationTime);
0378: }
0379: lEventLogElement
0380: .appendChild(mTemplateContext.mTestLogDocument
0381: .importNode(lEventElement, true));
0382: // Log the test output
0383: if (sLogger.isDebugEnabled()) {
0384: StringWriter lIndentedResultFileContents = new StringWriter();
0385: // Initialise transformer to use in this class
0386: Transformer lTransformer = mTransformerFactory
0387: .newTransformer();
0388: lTransformer.setOutputProperty(OutputKeys.METHOD,
0389: "xml");
0390: lTransformer.setOutputProperty(
0391: OutputKeys.STANDALONE, "yes");
0392: lTransformer.setOutputProperty(OutputKeys.INDENT,
0393: "yes");
0394: lTransformer.transform(
0395: new DOMSource(lEventDocument),
0396: new StreamResult(
0397: lIndentedResultFileContents));
0398: sLogger
0399: .debug("Received 'subscriptionInitiated' event with following data:\r\n"
0400: + lIndentedResultFileContents);
0401: }
0402: } else if (lMethodName.equals("subscriptionInterrupted")
0403: && args != null && args.length == 1) {
0404: Element lEventLogElement = (Element) mTargetSubscriptionLogElement
0405: .appendChild(mTemplateContext.mTestLogDocument
0406: .createElement("SubscriptionInterruptedLog"));
0407: // Append performance information if necessary
0408: if (mLogTestPerformance) {
0409: Element lArrivalElement = (Element) lEventLogElement
0410: .appendChild(mTemplateContext.mTestLogDocument
0411: .createElement("Arrival"));
0412: populateTimestampElement(lArrivalElement,
0413: lInvocationTime);
0414: }
0415: if (args[0] != null) {
0416: appendExceptionElement(lEventLogElement,
0417: (Throwable) args[0]);
0418: if (sLogger.isDebugEnabled())
0419: sLogger
0420: .debug(
0421: "Received 'subscriptionInterrupted' event with causing exception information.\r\n",
0422: (Throwable) args[0]);
0423: } else {
0424: if (sLogger.isDebugEnabled())
0425: sLogger
0426: .debug("Received 'subscriptionInterrupted' event without exception information.");
0427: }
0428: } else if (lMethodName.equals("subscriptionRestored")
0429: && (args == null || args.length == 0)) {
0430: Element lEventLogElement = (Element) mTargetSubscriptionLogElement
0431: .appendChild(mTemplateContext.mTestLogDocument
0432: .createElement("SubscriptionRestoredLog"));
0433: // Append performance information if necessary
0434: if (mLogTestPerformance) {
0435: Element lArrivalElement = (Element) lEventLogElement
0436: .appendChild(mTemplateContext.mTestLogDocument
0437: .createElement("Arrival"));
0438: populateTimestampElement(lArrivalElement,
0439: lInvocationTime);
0440: }
0441: if (sLogger.isDebugEnabled())
0442: sLogger
0443: .debug("Received 'subscriptionRestored' event.");
0444: } else if (lMethodName.equals("subscriptionTerminated")
0445: && (args == null || args.length == 0)) {
0446: Element lEventLogElement = (Element) mTargetSubscriptionLogElement
0447: .appendChild(mTemplateContext.mTestLogDocument
0448: .createElement("SubscriptionTerminatedLog"));
0449: // Append performance information if necessary
0450: if (mLogTestPerformance) {
0451: Element lArrivalElement = (Element) lEventLogElement
0452: .appendChild(mTemplateContext.mTestLogDocument
0453: .createElement("Arrival"));
0454: populateTimestampElement(lArrivalElement,
0455: lInvocationTime);
0456: }
0457: if (sLogger.isDebugEnabled())
0458: sLogger
0459: .debug("Received 'subscriptionTerminated' event.");
0460: } else
0461: // Filter some basic java.lang.Object methods
0462: if (lMethodName.equals("hashCode")
0463: && (args == null || args.length == 0))
0464: return new Integer(hashCode());
0465: else if (lMethodName.equals("toString")
0466: && (args == null || args.length == 0))
0467: return new String(toString());
0468: else if (lMethodName.equals("equals") && args != null
0469: && args.length == 1)
0470: return new Boolean(equals(args[0]));
0471: return null;
0472: }
0473: }
0474:
0475: // This helper class caches the instance of the underlying service adapter and provides
0476: // ability to execute services or subscribe for events
0477: private class AdapterRunner {
0478: private ClassLoader mContextClassloader;
0479: private Object mAdapterInstance;
0480: private Method mExecuteMethod;
0481: private Class mSubscriberInterface;
0482: private Method mSubscribeMethod;
0483: private Method mUnsubscribeMethod;
0484:
0485: // Creates the adapter runner for the namespace
0486: public AdapterRunner(String pNamespace) throws IOException,
0487: ClassNotFoundException, NoSuchMethodException,
0488: InstantiationException, IllegalAccessException {
0489: // Disassemble the namespace. Load class and obtain reflect method
0490: StringTokenizer lTokenizer = new StringTokenizer(
0491: pNamespace, "/", false);
0492: if (lTokenizer.countTokens() != 5)
0493: throw new IllegalArgumentException(
0494: "Namespace '"
0495: + pNamespace
0496: + "' is not valid. Expected format <EnterpriseName>/Systems/<SystemName>/Services/<ServicemoduleName>");
0497: String lEnterpriseName = lTokenizer.nextToken();
0498: if (!lTokenizer.nextToken().equals("Systems"))
0499: throw new IllegalArgumentException(
0500: "Namespace '"
0501: + pNamespace
0502: + "' is not valid. Expected format <EnterpriseName>/Systems/<SystemName>/Services/<ServicemoduleName>");
0503: String lSystemName = lTokenizer.nextToken();
0504: if (!lTokenizer.nextToken().equals("Services"))
0505: throw new IllegalArgumentException(
0506: "Namespace '"
0507: + pNamespace
0508: + "' is not valid. Expected format <EnterpriseName>/Systems/<SystemName>/Services/<ServicemoduleName>");
0509: String lServicemoduleName = lTokenizer.nextToken();
0510: String lAdapterClassName = "com."
0511: + lEnterpriseName.toLowerCase() + "."
0512: + lSystemName.toLowerCase()
0513: + ".adapters.generic.xmlstrings.services."
0514: + lServicemoduleName.toLowerCase()
0515: + ".ADServicemodule";
0516: List lURLs = new ArrayList();
0517: File lAdapterDirectory = new File(mScenarioClientPathPrefix
0518: + lEnterpriseName + "." + lSystemName + "."
0519: + lServicemoduleName).getAbsoluteFile();
0520: if (lAdapterDirectory.exists()) {
0521: lURLs.add(lAdapterDirectory.toURL());
0522: // Add jars
0523: File[] lFiles = lAdapterDirectory
0524: .listFiles(new FileFilter() {
0525: public boolean accept(File pPathname) {
0526: return pPathname.isFile()
0527: && pPathname.getName()
0528: .toLowerCase()
0529: .endsWith(".jar");
0530: }
0531: });
0532: // Load all files one by one
0533: for (int i = 0; i < lFiles.length; i++)
0534: lURLs.add(lFiles[i].toURL());
0535: }
0536: lURLs.add(new File(mScenarioRootClassLoaderDirectoryPath)
0537: .toURL());
0538: mContextClassloader = new URLClassLoader((URL[]) lURLs
0539: .toArray(new URL[lURLs.size()]));
0540:
0541: // Instantiate adapter class
0542: Class lAdapterClass = mContextClassloader
0543: .loadClass(lAdapterClassName);
0544: mAdapterInstance = lAdapterClass.newInstance();
0545:
0546: // Find the method used to execute operations
0547: mExecuteMethod = lAdapterClass.getMethod(
0548: "executeOperation", new Class[] { String.class });
0549:
0550: // Find subscriber interface
0551: mSubscriberInterface = ReflectionUtils.getInnerClass(
0552: lAdapterClass, "Subscriber");
0553: mSubscribeMethod = lAdapterClass.getMethod("subscribe",
0554: new Class[] { mSubscriberInterface, String.class });
0555: mUnsubscribeMethod = lAdapterClass.getMethod("unsubscribe",
0556: new Class[] { mSubscriberInterface });
0557: }
0558:
0559: public String executeOperation(String pInputDocument)
0560: throws InvocationTargetException,
0561: IllegalAccessException {
0562: Thread lExecutionThread = Thread.currentThread();
0563: ClassLoader lOriginalContextClassloader = lExecutionThread
0564: .getContextClassLoader();
0565: try {
0566: lExecutionThread
0567: .setContextClassLoader(mContextClassloader);
0568: return (String) mExecuteMethod.invoke(mAdapterInstance,
0569: new Object[] { pInputDocument });
0570: } finally {
0571: lExecutionThread
0572: .setContextClassLoader(lOriginalContextClassloader);
0573: }
0574: }
0575:
0576: public String subscribe(String pInputDocument,
0577: TemplateContext pTemplateContext,
0578: Element pTargetSubscriptionLogElement)
0579: throws InvocationTargetException,
0580: IllegalAccessException,
0581: TransformerConfigurationException {
0582: // Create subscriber which implements required interface
0583: SubscriptionHandler lHandler = new SubscriptionHandler(
0584: mSubscriberInterface, pTemplateContext,
0585: pTargetSubscriptionLogElement);
0586: Thread lExecutionThread = Thread.currentThread();
0587: ClassLoader lOriginalContextClassloader = lExecutionThread
0588: .getContextClassLoader();
0589: try {
0590: lExecutionThread
0591: .setContextClassLoader(mContextClassloader);
0592: return (String) mSubscribeMethod.invoke(
0593: mAdapterInstance, new Object[] {
0594: lHandler.getSubscriberInstance(),
0595: pInputDocument });
0596: } finally {
0597: lExecutionThread
0598: .setContextClassLoader(lOriginalContextClassloader);
0599: }
0600: }
0601: }
0602:
0603: /** Runs scenario and returns true if test was successfull and false otherwise */
0604: public synchronized boolean runScenario() throws Exception {
0605: // If scenario name is not given - use Unnamed
0606: if (mScenarioName == null || mScenarioName.length() == 0)
0607: mScenarioName = "UnnamedScenario";
0608: // Do a bit of a trick to get absolute path to even cwd
0609: mScenarioClientPathPrefix = (mClientPath != null && mClientPath
0610: .length() > 0) ? (mClientPath + File.separator) : "";
0611: // Initialise default classloader - will load all jars fom the path trick below just to cater for the case if we are using curr working dir
0612: mScenarioRootClassLoaderDirectoryPath = new File(
0613: mScenarioClientPathPrefix + "jnk.txt")
0614: .getAbsoluteFile().getParent();
0615:
0616: mAdapterRunners.clear();
0617: // We will make sure that directory exists, but will not clean it up
0618: String lScenarioRunName = mScenarioName
0619: + ((mScenarioRunName != null) ? ("_" + mScenarioRunName)
0620: : "");
0621: mScenarioLogFile = new File(
0622: ((mLoggingPath != null && mLoggingPath.length() > 0) ? (mLoggingPath + File.separator)
0623: : "")
0624: + lScenarioRunName
0625: + "_"
0626: + sLogPathSuffixFormatter.format(new Date())
0627: + ".xml");
0628: DirectoryUtils.ensureThereIsDirectory(mScenarioLogFile
0629: .getAbsoluteFile().getParent());
0630:
0631: // Obtain all xml files in all directoris on scenario path (may be excluding some files, such as specimen for example)
0632: File[] lExcludedFiles = mSpecimenFilePath != null ? (new File[] { new File(
0633: mSpecimenFilePath) })
0634: : new File[0];
0635: // Special case - if scenario path is not given than the scenario is in the current directory
0636: if (mScenarioPath == null || mScenarioPath.length == 0)
0637: mScenarioPath = new String[] { new File("jnk.txt")
0638: .getAbsoluteFile().getParentFile()
0639: .getAbsolutePath() };
0640: ArrayList lTestInputFilesList = new ArrayList();
0641: for (int i = 0; i < mScenarioPath.length; i++) {
0642: File lScenarioDirectory = new File(mScenarioPath[i])
0643: .getAbsoluteFile();
0644: sLogger.info("Scanning " + lScenarioDirectory
0645: + " directory for the test cases to execute.");
0646: File[] lTestInputFiles = lScenarioDirectory
0647: .listFiles(new SpecialFileFilter(lExcludedFiles));
0648: if (lTestInputFiles == null || lTestInputFiles.length == 0) {
0649: sLogger.info("Found no test cases to execute in "
0650: + lScenarioDirectory + " directory");
0651: continue;
0652: }
0653: sLogger.info("Found "
0654: + Integer.toString(lTestInputFiles.length)
0655: + " test cases to execute in " + lScenarioDirectory
0656: + " directory");
0657: // Sort files by name and than add to the common list
0658: // This way files will be sorted within the directory
0659: Arrays.sort(lTestInputFiles, new Comparator() {
0660: public int compare(Object o1, Object o2) {
0661: return ((File) o1).getName().compareTo(
0662: ((File) o2).getName());
0663: }
0664: });
0665: lTestInputFilesList.addAll(Arrays.asList(lTestInputFiles));
0666: }
0667: if (lTestInputFilesList.size() == 0) {
0668: sLogger.info("Found no test cases to execute.");
0669: return false;
0670: }
0671: sLogger.info("Commenced scenario " + lScenarioRunName);
0672: sLogger.info("Scenario log will be saved in "
0673: + mScenarioLogFile.getAbsolutePath() + " file.");
0674: if (lTestInputFilesList.size() == 1)
0675: sLogger.info("Found one test case to execute.");
0676: else
0677: sLogger.info("Found " + lTestInputFilesList.size()
0678: + " test cases to execute.");
0679:
0680: // Initialise test log start time close to the beginnig of tests
0681: mTestLogStartTime = new Date();
0682:
0683: mTestLogDocumentTransformer = mTransformerFactory
0684: .newTransformer();
0685: mTestLogDocumentTransformer.setOutputProperty(
0686: OutputKeys.METHOD, "xml");
0687: mTestLogDocumentTransformer.setOutputProperty(
0688: OutputKeys.STANDALONE, "yes");
0689: mTestLogDocumentTransformer.setOutputProperty(
0690: OutputKeys.INDENT, "yes");
0691:
0692: mScenarioContext = new TemplateContext();
0693: mScenarioContext.mTestLogDocument = mScenarioDocumentBuilder
0694: .newDocument();
0695: // For a start - create scenario element in default namsepace
0696: // Once document is fully built - we will move it into the namespace before saving
0697: mScenarioContext.mTestScenarioLogElement = (Element) mScenarioContext.mTestLogDocument
0698: .appendChild(mScenarioContext.mTestLogDocument
0699: .createElement("TestScenarioLog"));
0700: mScenarioContext.mTestScenarioLogElement.setAttribute("name",
0701: mScenarioName);
0702: mScenarioContext.mTestScenarioLogElement.setAttribute("status",
0703: "InProgress");
0704: boolean lHadTestCasesFailure = false; // Register failures so the final outcome can be established and reported
0705: // From now on we should attempt to save the scenario log regardless of the way in which application has been terminated
0706: mScenarioLogNeedsSaving = true;
0707: try {
0708: try {
0709: try {
0710: // Save file at the beginning for debugging purposes
0711:
0712: // Keep last formed TestStepLogElement for possible transformation
0713: for (int i = 0; i < lTestInputFilesList.size(); i++) {
0714: File lTestCaseDocumentFile = (File) lTestInputFilesList
0715: .get(i);
0716: sLogger
0717: .info(" Commenced test case defined in "
0718: + lTestCaseDocumentFile
0719: .getAbsolutePath());
0720: // Load test definition into dom document
0721: Document lInputDocument = parseDocumentWithoutNamespace(new InputSource(
0722: new FileReader(lTestCaseDocumentFile)));
0723:
0724: // Dump document it into debug string
0725: {
0726: Transformer lTransformer = mTransformerFactory
0727: .newTransformer();
0728: lTransformer.setOutputProperty(
0729: OutputKeys.METHOD, "xml");
0730: lTransformer.setOutputProperty(
0731: OutputKeys.STANDALONE, "yes");
0732: lTransformer.setOutputProperty(
0733: OutputKeys.INDENT, "no");
0734: StringWriter lRawStringWriter = new StringWriter();
0735: lTransformer.transform(new DOMSource(
0736: lInputDocument), new StreamResult(
0737: lRawStringWriter));
0738: sLogger.debug("Test case definition:\r\n"
0739: + lRawStringWriter.toString());
0740: }
0741: // Get the top element of the tree, ensure that this is a TestCasePlan element
0742: Element lTestCasePlanElement = lInputDocument
0743: .getDocumentElement();
0744: if (!lTestCasePlanElement.getTagName().equals(
0745: "TestCasePlan"))
0746: throw new Exception(
0747: "File "
0748: + lTestCaseDocumentFile
0749: .getAbsolutePath()
0750: + " must have TestCasePlan element as its root element");
0751: // See if we need to set the name up
0752: if (!lTestCasePlanElement.hasAttribute("name")) {
0753: // Get rid of .xml and set the file name as the name of the test
0754: String lTestCaseName = lTestCaseDocumentFile
0755: .getName()
0756: .substring(
0757: 0,
0758: lTestCaseDocumentFile
0759: .getName().length() - 4);
0760: lTestCasePlanElement.setAttribute("name",
0761: lTestCaseName);
0762: }
0763: Element lTestCaseLogElement = doTheTestCase(
0764: lTestCasePlanElement, mScenarioContext);
0765: sLogger.info(" Completed "
0766: + lTestCaseLogElement
0767: .getAttribute("name")
0768: + " test case defined in "
0769: + lTestCaseDocumentFile
0770: .getAbsolutePath()
0771: + " Status: "
0772: + lTestCaseLogElement
0773: .getAttribute("status"));
0774: if (!lHadTestCasesFailure) {
0775: String lTestCaseStatus = lTestCaseLogElement
0776: .getAttribute("status");
0777: if ((!lTestCaseStatus.equals("Success"))
0778: && (!lTestCaseStatus
0779: .equals("PrerequisiteFailure")))
0780: lHadTestCasesFailure = true;
0781: }
0782: }
0783: // If we have had test step failure - register it.
0784: if (lHadTestCasesFailure) {
0785: mScenarioContext.mTestScenarioLogElement
0786: .setAttribute("status",
0787: "ChildCaseFailure");
0788: Comment lComment = mScenarioContext.mTestLogDocument
0789: .createComment("One or more of the child test cases have failed.");
0790: mScenarioContext.mTestScenarioLogElement
0791: .insertBefore(
0792: lComment,
0793: mScenarioContext.mTopMostTestCaseLogElement);
0794: } else {
0795: mScenarioContext.mTestScenarioLogElement
0796: .setAttribute("status", "Success");
0797: Comment lComment = mScenarioContext.mTestLogDocument
0798: .createComment("All child test cases have succeeded.");
0799: mScenarioContext.mTestScenarioLogElement
0800: .insertBefore(
0801: lComment,
0802: mScenarioContext.mTopMostTestCaseLogElement);
0803: }
0804: } finally {
0805: // Create performance record if necessary
0806: if (mLogTestPerformance)
0807: appendPerformanceElement(
0808: mScenarioContext.mTestScenarioLogElement,
0809: mTestLogStartTime, new Date());
0810: }
0811: } catch (Throwable t) {
0812: if (t instanceof InvocationTargetException)
0813: t = ((InvocationTargetException) t)
0814: .getTargetException();
0815: sLogger
0816: .debug("Test scenario has resulted in exception: "
0817: + t.getMessage());
0818: appendExceptionElement(
0819: mScenarioContext.mTestScenarioLogElement, t);
0820: // By default if scenario has completed with exception - it is deemed to be a failure
0821: mScenarioContext.mTestScenarioLogElement.setAttribute(
0822: "status", "ExceptionFailure");
0823: Comment lComment = mScenarioContext.mTestLogDocument
0824: .createComment("Java exception occurred during test scenario execution.");
0825: mScenarioContext.mTestScenarioLogElement.insertBefore(
0826: lComment,
0827: mScenarioContext.mTopMostTestCaseLogElement);
0828: lHadTestCasesFailure = true; // Somethig wrong - indicate failure
0829: } finally {
0830: // Output the test cases completion message
0831: sLogger
0832: .info("Completed all test cases. Completion Status : "
0833: + mScenarioContext.mTestScenarioLogElement
0834: .getAttribute("status"));
0835: // Set namespace before saving the document. This will ensure that document is valid and readable
0836: // Setting namespace before than might have made templating more difficult
0837: mScenarioContext.mTestScenarioLogElement
0838: .setAttributeNS(W3C_XMLNS_SCHEMA, "xmlns",
0839: METABOSS_SCENARIO_RUNNER_SCHEMA);
0840:
0841: // If there is a specimen file - we need to compare them and
0842: // override the answer if cpecimen and this log are equal
0843: if (mSpecimenFilePath != null) {
0844: try {
0845: sLogger
0846: .info("Commencing test scenario validation against specimen. Specimen file: "
0847: + mSpecimenFilePath);
0848: File lSpecimenFile = new File(mSpecimenFilePath);
0849: // Load test definition into dom document
0850: Document lSpecimenDocument = parseDocumentWithoutNamespace(new InputSource(
0851: new FileReader(lSpecimenFile)));
0852: Document lSampleDocument = mScenarioContext.mTestLogDocument;
0853: // Suppress failure if comparison against specimen is successfull
0854: String[] lErrorMessages = SpecimenComparator
0855: .compareAgainstSpecimen(
0856: lSpecimenDocument,
0857: lSampleDocument, false, true);
0858: if (lErrorMessages != null) {
0859: mScenarioContext.mTestScenarioLogElement
0860: .setAttribute("status",
0861: "SpecimenMismatch");
0862: Comment lComment = mScenarioContext.mTestLogDocument
0863: .createComment("Test scenario validation has failed. Detected "
0864: + lErrorMessages.length
0865: + " mismatches with the specimen. Specimen file: "
0866: + mSpecimenFilePath
0867: + ". See the end of this file for mismatch details.");
0868: mScenarioContext.mTestScenarioLogElement
0869: .insertBefore(
0870: lComment,
0871: mScenarioContext.mTopMostTestCaseLogElement);
0872: lHadTestCasesFailure = true;
0873: // There are error messages - print them out and also add them as comments at the end of the log
0874: for (int i = 0; i < lErrorMessages.length; i++) {
0875: sLogger.info(" " + lErrorMessages[i]);
0876: Comment lMismatchComment = mScenarioContext.mTestLogDocument
0877: .createComment("Specimen mismatch: "
0878: + lErrorMessages[i]);
0879: mScenarioContext.mTestScenarioLogElement
0880: .appendChild(lMismatchComment);
0881: }
0882: sLogger
0883: .info("Test scenario validation has failed. Detected "
0884: + lErrorMessages.length
0885: + " mismatches with the specimen.");
0886: } else {
0887: sLogger
0888: .info("Test scenario validation has succeeded. No mismatches with the specimen have been detected.");
0889: // Supress failures because comparison with the specimen has been successfull
0890: mScenarioContext.mTestScenarioLogElement
0891: .setAttribute("status", "Success");
0892: Comment lComment = mScenarioContext.mTestLogDocument
0893: .createComment("Test scenario validation has succeeded. No mismatches with the specimen have been detected. Specimen file: "
0894: + mSpecimenFilePath + ".");
0895: mScenarioContext.mTestScenarioLogElement
0896: .insertBefore(
0897: lComment,
0898: mScenarioContext.mTopMostTestCaseLogElement);
0899: lHadTestCasesFailure = false;
0900: }
0901: } catch (Throwable t) {
0902: if (t instanceof InvocationTargetException)
0903: t = ((InvocationTargetException) t)
0904: .getTargetException();
0905: sLogger
0906: .debug("Test scenario validation has resulted in exception: "
0907: + t.getMessage());
0908: appendExceptionElement(
0909: mScenarioContext.mTestScenarioLogElement,
0910: t);
0911: // By default if scenario has completed with exception - it is deemed to be a failure
0912: mScenarioContext.mTestScenarioLogElement
0913: .setAttribute("status",
0914: "ExceptionFailure");
0915: Comment lComment = mScenarioContext.mTestLogDocument
0916: .createComment("Java exception occurred during validation of the test scenario against the specimen. Specimen file: "
0917: + mSpecimenFilePath + ".");
0918: mScenarioContext.mTestScenarioLogElement
0919: .insertBefore(
0920: lComment,
0921: mScenarioContext.mTopMostTestCaseLogElement);
0922: lHadTestCasesFailure = true; // Somethig wrong - indicate failure
0923: }
0924: } else {
0925: sLogger
0926: .info("Specimen file is not specified. Test scenario validation against specimen is omitted.");
0927: Comment lComment = mScenarioContext.mTestLogDocument
0928: .createComment("Specimen file was not specified. Test scenario validation against specimen was omitted.");
0929: mScenarioContext.mTestScenarioLogElement
0930: .insertBefore(
0931: lComment,
0932: mScenarioContext.mTopMostTestCaseLogElement);
0933: }
0934: }
0935: } finally {
0936: saveScenarioLogIfNecessary();
0937: // Log information about this scenario
0938: sLogger.info("Completed "
0939: + lScenarioRunName
0940: + " Test scenario run. Completion Status : "
0941: + mScenarioContext.mTestScenarioLogElement
0942: .getAttribute("status"));
0943: }
0944: return !lHadTestCasesFailure; // Return actual result
0945: }
0946:
0947: // Save scenario file if necessary
0948: public void saveScenarioLogIfNecessary() throws Exception {
0949: if (mScenarioLogNeedsSaving == true) {
0950: synchronized (mScenarioLogFile) {
0951: if (mScenarioLogNeedsSaving == true) {
0952: mTestLogDocumentTransformer.transform(
0953: new DOMSource(
0954: mScenarioContext.mTestLogDocument),
0955: new StreamResult(new FileOutputStream(
0956: mScenarioLogFile)));
0957: sLogger.info("Test log is saved into the "
0958: + mScenarioLogFile.getAbsolutePath());
0959: mScenarioLogNeedsSaving = false;
0960: }
0961: }
0962: }
0963: }
0964:
0965: // Helper. Populates timestamp element with values. Deals with any Start, Stop, Arrival
0966: private void populateTimestampElement(Element pTimestampElement,
0967: Date pTimestamp) {
0968: Document pOwnerDocument = pTimestampElement.getOwnerDocument();
0969: Element lTimestampElement = (Element) pTimestampElement
0970: .appendChild(pOwnerDocument.createElement("Timestamp"));
0971: lTimestampElement.appendChild(pOwnerDocument
0972: .createTextNode(sPerformanceDataTimestampFormatter
0973: .format(pTimestamp)));
0974: Element lJavatimestampElement = (Element) pTimestampElement
0975: .appendChild(pOwnerDocument
0976: .createElement("JavaTimestamp"));
0977: lJavatimestampElement.appendChild(pOwnerDocument
0978: .createTextNode(Long.toString(pTimestamp.getTime())));
0979: Element lLogtimestampElement = (Element) pTimestampElement
0980: .appendChild(pOwnerDocument
0981: .createElement("LogTimestamp"));
0982: lLogtimestampElement.appendChild(pOwnerDocument
0983: .createTextNode(Long.toString(pTimestamp.getTime()
0984: - mTestLogStartTime.getTime())));
0985: }
0986:
0987: // Helper. Appends performance element to the specified element
0988: private void appendPerformanceElement(Element pParentElement,
0989: Date pStartTimestamp, Date pEndTimestamp) {
0990: Document pOwnerDocument = pParentElement.getOwnerDocument();
0991: Element lPerformanceElement = (Element) pParentElement
0992: .appendChild(pOwnerDocument
0993: .createElement("PerformanceDocument"));
0994: // Do start element
0995: {
0996: Element lStartElement = (Element) lPerformanceElement
0997: .appendChild(pOwnerDocument.createElement("Start"));
0998: populateTimestampElement(lStartElement, pStartTimestamp);
0999: }
1000:
1001: // Do end element
1002: {
1003: Element lEndElement = (Element) lPerformanceElement
1004: .appendChild(pOwnerDocument.createElement("End"));
1005: populateTimestampElement(lEndElement, pEndTimestamp);
1006: }
1007: // Do duration element
1008: {
1009: Element lDurationElement = (Element) lPerformanceElement
1010: .appendChild(pOwnerDocument
1011: .createElement("Duration"));
1012: lDurationElement.appendChild(pOwnerDocument
1013: .createTextNode(Long.toString(pEndTimestamp
1014: .getTime()
1015: - pStartTimestamp.getTime())));
1016: }
1017: }
1018:
1019: // Helper. Appends information about exception element to the specified element
1020: private void appendExceptionElement(Element pTestStepLogElement,
1021: Throwable pException) {
1022: Document lOwnerDocument = pTestStepLogElement
1023: .getOwnerDocument();
1024: Element lExceptionElement = (Element) pTestStepLogElement
1025: .appendChild(lOwnerDocument
1026: .createElement("ExceptionDocument"));
1027: // Do java class element
1028: {
1029: Element lJavaClassElement = (Element) lExceptionElement
1030: .appendChild(lOwnerDocument
1031: .createElement("JavaClassName"));
1032: lJavaClassElement.appendChild(lOwnerDocument
1033: .createTextNode(pException.getClass().getName()));
1034: }
1035:
1036: // Do message element (do CDATA ourselves)
1037: {
1038: Element lMessageTextElement = (Element) lExceptionElement
1039: .appendChild(lOwnerDocument
1040: .createElement("ExceptionMessage"));
1041: String lExceptionMessage = pException.getMessage();
1042: if (lExceptionMessage != null)
1043: lMessageTextElement.appendChild(lOwnerDocument
1044: .createTextNode(lExceptionMessage));
1045: }
1046:
1047: // Do string element
1048: {
1049: Element lStringTextElement = (Element) lExceptionElement
1050: .appendChild(lOwnerDocument
1051: .createElement("ExceptionString"));
1052: lStringTextElement.appendChild(lOwnerDocument
1053: .createTextNode(pException.toString()));
1054: }
1055: // Do stack element
1056: {
1057: StringWriter lStringWriter = new StringWriter();
1058: pException.printStackTrace(new PrintWriter(lStringWriter));
1059: Element lStackTraceElement = (Element) lExceptionElement
1060: .appendChild(lOwnerDocument
1061: .createElement("StackTrace"));
1062: lStackTraceElement.appendChild(lOwnerDocument
1063: .createTextNode(lStringWriter.toString()));
1064: }
1065: }
1066:
1067: // Helper. Runs single test step and returns TestCaseLog element
1068: private Element doTheTestCase(Element pTestCasePlanElement,
1069: TemplateContext pTemplateContext) throws Exception {
1070: // Ensure that test case has a name
1071: if (!pTestCasePlanElement.hasAttribute("name"))
1072: pTestCasePlanElement
1073: .setAttribute("name", "UnnamedTestCase");
1074: if (!pTestCasePlanElement.hasAttribute("numberOfRuns")) {
1075: return doTheTestCase_SingleRun(pTestCasePlanElement,
1076: pTemplateContext, null);
1077: } else {
1078: String lLogIndent = StringUtils
1079: .getIndent(
1080: 2,
1081: pTemplateContext.mTestCaseLogElementStack
1082: .size()
1083: + (pTemplateContext.mTestCaseLogElement != null ? 3
1084: : 2));
1085: Element lTestCaseLogElement = null;
1086: try {
1087: // Log information about this test case
1088: sLogger.info(lLogIndent + "Commenced "
1089: + pTestCasePlanElement.getAttribute("name")
1090: + " TestCase Series");
1091:
1092: int lNumberOfRuns = Integer
1093: .parseInt(pTestCasePlanElement
1094: .getAttribute("numberOfRuns"));
1095:
1096: lTestCaseLogElement = pTemplateContext.mTestLogDocument
1097: .createElement("TestCaseLog");
1098: // Put the previous log on the stack and create our own log element
1099: if (pTemplateContext.mTestCaseLogElement != null) {
1100: // This is a sub test case
1101: Element lParentTestCaseElement = pTemplateContext.mTestCaseLogElement;
1102: pTemplateContext.mTestCaseLogElementStack.add(0,
1103: lParentTestCaseElement);
1104: pTemplateContext.mTestCaseLogElement = (Element) lParentTestCaseElement
1105: .appendChild(lTestCaseLogElement);
1106: } else {
1107: pTemplateContext.mTestCaseLogElement = (Element) pTemplateContext.mTestScenarioLogElement
1108: .appendChild(lTestCaseLogElement);
1109: // Remember if this is first every test case
1110: if (pTemplateContext.mTopMostTestCaseLogElement == null)
1111: pTemplateContext.mTopMostTestCaseLogElement = pTemplateContext.mTestCaseLogElement;
1112: }
1113: copyBaseElements(pTestCasePlanElement,
1114: pTemplateContext.mTestCaseLogElement,
1115: pTemplateContext);
1116: pTemplateContext.mTestCaseLogElement.setAttribute(
1117: "status", "InProgress");
1118: // Go in the loop through all runs and call this method recursively
1119: {
1120: boolean lHadChildTestCasesFailure = false;
1121: for (int lRunNumber = 1; lRunNumber <= lNumberOfRuns; lRunNumber++) {
1122: Element lChildTestCaseLogElement = doTheTestCase_SingleRun(
1123: pTestCasePlanElement, pTemplateContext,
1124: new Integer(lRunNumber));
1125: String lChldTestCaseStatus = lChildTestCaseLogElement
1126: .getAttribute("status");
1127: // Update status to the child case failed if necessary
1128: if (!lHadChildTestCasesFailure) {
1129: if ((!lChldTestCaseStatus.equals("Success"))
1130: && (!lChldTestCaseStatus
1131: .equals("PrerequisiteFailure"))) {
1132: lHadChildTestCasesFailure = true;
1133: pTemplateContext.mTestCaseLogElement
1134: .setAttribute("status",
1135: "ChildCaseFailure");
1136: }
1137: }
1138: // Get out of runs if child case status is anything but success
1139: if (!lChldTestCaseStatus.equals("Success")) {
1140: break;
1141: }
1142: }
1143: }
1144: // Set successful status if the test case survived so far without failure
1145: String lCurrentTestCaseStatus = pTemplateContext.mTestCaseLogElement
1146: .getAttribute("status");
1147: if (lCurrentTestCaseStatus.equals("InProgress"))
1148: pTemplateContext.mTestCaseLogElement.setAttribute(
1149: "status", "Success");
1150: return pTemplateContext.mTestCaseLogElement;
1151: } finally {
1152: // Log information about this test case
1153: sLogger.info(lLogIndent
1154: + "Completed "
1155: + pTestCasePlanElement.getAttribute("name")
1156: + " TestCase Series. Status: "
1157: + pTemplateContext.mTestCaseLogElement
1158: .getAttribute("status"));
1159: // Restore the previous element on a stack if the current element is ours
1160: if (lTestCaseLogElement != null
1161: && pTemplateContext.mTestCaseLogElement == lTestCaseLogElement) {
1162: if (pTemplateContext.mTestCaseLogElementStack
1163: .size() > 0)
1164: pTemplateContext.mTestCaseLogElement = (Element) pTemplateContext.mTestCaseLogElementStack
1165: .remove(0);
1166: else
1167: pTemplateContext.mTestCaseLogElement = null;
1168: }
1169: }
1170: }
1171: }
1172:
1173: // Helper. Runs single test step and returns TestCaseLog element
1174: private Element doTheTestCase_SingleRun(
1175: Element pTestCasePlanElement,
1176: TemplateContext pTemplateContext, Integer pRunNumber)
1177: throws Exception {
1178: // Obtain desired indent
1179: String lLogIndent = StringUtils
1180: .getIndent(
1181: 2,
1182: pTemplateContext.mTestCaseLogElementStack
1183: .size()
1184: + (pTemplateContext.mTestCaseLogElement != null ? 3
1185: : 2));
1186: Element lTestCaseLogElement = null;
1187: try {
1188: // Log information about this test case
1189: sLogger.info(lLogIndent + "Commenced "
1190: + pTestCasePlanElement.getAttribute("name")
1191: + " TestCase");
1192:
1193: lTestCaseLogElement = pTemplateContext.mTestLogDocument
1194: .createElement("TestCaseLog");
1195: // Put the previous log on the stack and create our own log element
1196: if (pTemplateContext.mTestCaseLogElement != null) {
1197: // This is a sub test case
1198: Element lParentTestCaseElement = pTemplateContext.mTestCaseLogElement;
1199: pTemplateContext.mTestCaseLogElementStack.add(0,
1200: lParentTestCaseElement);
1201: pTemplateContext.mTestCaseLogElement = (Element) lParentTestCaseElement
1202: .appendChild(lTestCaseLogElement);
1203: } else {
1204: pTemplateContext.mTestCaseLogElement = (Element) pTemplateContext.mTestScenarioLogElement
1205: .appendChild(lTestCaseLogElement);
1206: // Remember if this is first every test case
1207: if (pTemplateContext.mTopMostTestCaseLogElement == null)
1208: pTemplateContext.mTopMostTestCaseLogElement = pTemplateContext.mTestCaseLogElement;
1209: }
1210: copyBaseElements(pTestCasePlanElement,
1211: pTemplateContext.mTestCaseLogElement,
1212: pTemplateContext);
1213: if (pRunNumber != null)
1214: pTemplateContext.mTestCaseLogElement.setAttribute(
1215: "runNumber", pRunNumber.toString());
1216: pTemplateContext.mTestCaseLogElement.setAttribute("status",
1217: "InProgress");
1218: // First work on sleep before
1219: if (pTestCasePlanElement.hasAttribute("sleepBefore")) {
1220: String lSecondsToSleep = pTestCasePlanElement
1221: .getAttribute("sleepBefore");
1222: if (sLogger.isDebugEnabled())
1223: sLogger
1224: .info("Will sleep for "
1225: + lSecondsToSleep
1226: + " seconds as defined in the sleepBefore attribute of the TestCase...");
1227: Thread
1228: .sleep((long) (Integer
1229: .parseInt(lSecondsToSleep) * 1000));
1230: pTemplateContext.mTestCaseLogElement.setAttribute(
1231: "sleepBefore", lSecondsToSleep);
1232: }
1233: Date lTestCaseStart = new Date();
1234: try {
1235: Element lTestCaseSubElement = DOMUtils
1236: .getFirstChildElement(pTestCasePlanElement);
1237: // See if this element is PrerequisiteDocument
1238: if (lTestCaseSubElement.getTagName().equals(
1239: "PrerequisiteDocument")) {
1240: Element lTargetPrerequisiteDocumentElement = (Element) pTemplateContext.mTestCaseLogElement
1241: .appendChild(pTemplateContext.mTestLogDocument
1242: .createElement("PrerequisiteDocument"));
1243: copyChildElementsWithoutNamespacesWithTemplates(
1244: lTargetPrerequisiteDocumentElement,
1245: lTestCaseSubElement, pTemplateContext);
1246: // Now - analyse prerequisite element and determine whether test step outcome has to be overturned
1247: // Presence of any ErrorMessages or AbstainMessages will indicate that test should not be run
1248: NodeList lErrorMessagesList = lTargetPrerequisiteDocumentElement
1249: .getElementsByTagName("ErrorMessage");
1250: if (lErrorMessagesList.getLength() > 0) {
1251: // Prerequisite document contains some errors, no point to continue
1252: pTemplateContext.mTestCaseLogElement
1253: .setAttribute("status",
1254: "PrerequisiteFailure");
1255: return pTemplateContext.mTestCaseLogElement;
1256: }
1257: NodeList lAbstainMessagesList = lTargetPrerequisiteDocumentElement
1258: .getElementsByTagName("AbstainMessage");
1259: if (lAbstainMessagesList.getLength() > 0) {
1260: // Prerequisite document contains some abstain reasons, no point to continue
1261: pTemplateContext.mTestCaseLogElement
1262: .setAttribute("status",
1263: "PrerequisiteFailure");
1264: return pTemplateContext.mTestCaseLogElement;
1265: }
1266: lTestCaseSubElement = DOMUtils
1267: .getNextSiblingElement(lTestCaseSubElement);
1268: }
1269: // See if this element is a parameters document
1270: if (lTestCaseSubElement.getTagName().equals(
1271: "ParametersDocument")) {
1272: // Parameters document should just be copied into the log (with templates), which will make it available for other templates
1273: Element lTargetParametersDocumentElement = (Element) pTemplateContext.mTestCaseLogElement
1274: .appendChild(pTemplateContext.mTestLogDocument
1275: .createElement("ParametersDocument"));
1276: copyBaseElements(lTargetParametersDocumentElement,
1277: lTestCaseSubElement, pTemplateContext);
1278: copyChildElementsWithoutNamespacesWithTemplates(
1279: lTargetParametersDocumentElement,
1280: lTestCaseSubElement, pTemplateContext);
1281: lTestCaseSubElement = DOMUtils
1282: .getNextSiblingElement(lTestCaseSubElement);
1283: }
1284: // Go in the loop through all of child test plans, operation plans and templates and call this method recursively
1285: boolean lHadChildTestCasesFailure = false;
1286: boolean lHadSomethingToDo = false;
1287: for (; lTestCaseSubElement != null
1288: && (lTestCaseSubElement.getTagName().equals(
1289: "TestCasePlan")
1290: || lTestCaseSubElement.getTagName()
1291: .equals("OperationPlan")
1292: || lTestCaseSubElement.getTagName()
1293: .equals("Template") || lTestCaseSubElement
1294: .getTagName()
1295: .equals("SubscriptionPlan")); lTestCaseSubElement = DOMUtils
1296: .getNextSiblingElement(lTestCaseSubElement)) {
1297: lHadSomethingToDo = true;
1298: // See if this element is OperationPlan
1299: if (lTestCaseSubElement.getTagName().equals(
1300: "OperationPlan")) {
1301: // Process operation
1302: processOperationPlan(
1303: pTemplateContext.mTestCaseLogElement,
1304: lTestCaseSubElement, pTemplateContext);
1305: } else
1306: // See if this element is SubscriptionPlan
1307: if (lTestCaseSubElement.getTagName().equals(
1308: "SubscriptionPlan")) {
1309: // Process operation
1310: processSubscriptionPlan(
1311: pTemplateContext.mTestCaseLogElement,
1312: lTestCaseSubElement, pTemplateContext);
1313: } else
1314: // See if we have a series of child test cases
1315: if (lTestCaseSubElement.getTagName().equals(
1316: "TestCasePlan")) {
1317: Element lChildTestCaseLogElement = doTheTestCase(
1318: lTestCaseSubElement, pTemplateContext);
1319: if (!lHadChildTestCasesFailure) {
1320: String lChldTestCaseStatus = lChildTestCaseLogElement
1321: .getAttribute("status");
1322: if ((!lChldTestCaseStatus.equals("Success"))
1323: && (!lChldTestCaseStatus
1324: .equals("PrerequisiteFailure"))) {
1325: lHadChildTestCasesFailure = true;
1326: pTemplateContext.mTestCaseLogElement
1327: .setAttribute("status",
1328: "ChildCaseFailure");
1329: }
1330: }
1331: } else
1332: // See if we have to process template (there could only be one of these)
1333: if (lTestCaseSubElement.getTagName().equals(
1334: "Template")) {
1335: // Process template or template text. Default scope for the template is current test case
1336: Document lTempDocument = mScenarioDocumentBuilder
1337: .newDocument();
1338: Element lTestSubElementsList = lTempDocument
1339: .createElement("InternalTestStepList");
1340: processTemplate(lTestSubElementsList,
1341: lTestCaseSubElement, pTemplateContext);
1342: // Analyse what have we got
1343: Element lGeneratedTestCaseSubElement = DOMUtils
1344: .getFirstChildElement(lTestSubElementsList);
1345: if (lGeneratedTestCaseSubElement == null)
1346: throw new Exception(
1347: "No elements have been produced as the result of running a template. Expecting <TestCasePlan> or <OperationPlan>");
1348: // Go in the loop through all of child test plans and call this method recursively
1349: boolean lHadSomethingToDoAfterTemplate = false;
1350: for (; lGeneratedTestCaseSubElement != null
1351: && (lGeneratedTestCaseSubElement
1352: .getTagName().equals(
1353: "TestCasePlan") || lGeneratedTestCaseSubElement
1354: .getTagName().equals(
1355: "OperationPlan")); lGeneratedTestCaseSubElement = DOMUtils
1356: .getNextSiblingElement(lGeneratedTestCaseSubElement)) {
1357: lHadSomethingToDoAfterTemplate = true;
1358: // See if this element is OperationPlan
1359: if (lGeneratedTestCaseSubElement
1360: .getTagName().equals(
1361: "OperationPlan")) {
1362: // Process operation
1363: processOperationPlan(
1364: pTemplateContext.mTestCaseLogElement,
1365: lGeneratedTestCaseSubElement,
1366: pTemplateContext);
1367: } else
1368: // See if this element is SubscriptionPlan
1369: if (lGeneratedTestCaseSubElement
1370: .getTagName().equals(
1371: "SubscriptionPlan")) {
1372: // Process operation
1373: processSubscriptionPlan(
1374: pTemplateContext.mTestCaseLogElement,
1375: lGeneratedTestCaseSubElement,
1376: pTemplateContext);
1377: } else
1378: // See if we have a series of child test cases
1379: if (lGeneratedTestCaseSubElement
1380: .getTagName()
1381: .equals("TestCasePlan")) {
1382: Element lChildTestCaseLogElement = doTheTestCase(
1383: lGeneratedTestCaseSubElement,
1384: pTemplateContext);
1385: if (!lHadChildTestCasesFailure) {
1386: String lChldTestCaseStatus = lChildTestCaseLogElement
1387: .getAttribute("status");
1388: if ((!lChldTestCaseStatus
1389: .equals("Success"))
1390: && (!lChldTestCaseStatus
1391: .equals("PrerequisiteFailure"))) {
1392: lHadChildTestCasesFailure = true;
1393: pTemplateContext.mTestCaseLogElement
1394: .setAttribute("status",
1395: "ChildCaseFailure");
1396: }
1397: }
1398: }
1399: }
1400: if (lHadSomethingToDoAfterTemplate == false)
1401: throw new Exception(
1402: "Unexpected element type resulted from running a template. Expecting <TestCasePlan> or <OperationPlan>, got <"
1403: + lGeneratedTestCaseSubElement
1404: .getTagName() + ">");
1405: }
1406: }
1407: if (lHadSomethingToDo == false)
1408: throw new Exception(
1409: "Unexpected element type. Expecting <TestCasePlan> or <OperationPlan> or <Template>, got <"
1410: + lTestCaseSubElement.getTagName()
1411: + ">");
1412: // Set successful status if the test case survived so far without failure
1413: String lCurrentTestCaseStatus = pTemplateContext.mTestCaseLogElement
1414: .getAttribute("status");
1415: if (lCurrentTestCaseStatus.equals("InProgress"))
1416: pTemplateContext.mTestCaseLogElement.setAttribute(
1417: "status", "Success");
1418: } catch (Throwable t) {
1419: if (t instanceof InvocationTargetException)
1420: t = ((InvocationTargetException) t)
1421: .getTargetException();
1422: sLogger.debug("Test case has resulted in exception "
1423: + t.getClass().getName()
1424: + " Exception Message: " + t.getMessage());
1425: appendExceptionElement(
1426: pTemplateContext.mTestCaseLogElement, t);
1427: // By default if service has completed with exception - it is deemed to be a failure
1428: pTemplateContext.mTestCaseLogElement.setAttribute(
1429: "status", "ExceptionFailure");
1430: } finally {
1431: // See if there is an acceptance document, which may overrun the result
1432: Element lTestCaseSubElement = DOMUtils
1433: .getFirstChildElement(pTestCasePlanElement);
1434: while (lTestCaseSubElement != null
1435: && lTestCaseSubElement.getTagName().equals(
1436: "AcceptanceDocument") == false)
1437: lTestCaseSubElement = DOMUtils
1438: .getNextSiblingElement(lTestCaseSubElement);
1439: if (lTestCaseSubElement != null) {
1440: Element lTargetAcceptanceDocumentElement = (Element) pTemplateContext.mTestCaseLogElement
1441: .appendChild(pTemplateContext.mTestLogDocument
1442: .createElement("AcceptanceDocument"));
1443: copyChildElementsWithoutNamespacesWithTemplates(
1444: lTargetAcceptanceDocumentElement,
1445: lTestCaseSubElement, pTemplateContext);
1446: // Now - analyse acceptance element and determine whether test step outcome has to be overturned
1447:
1448: // This check is done first - so errors are always reported
1449: // Presence of any ErrorMessages will indicate that result status has to be forced to acceptance failure
1450:
1451: // This check is done second
1452: // Presence of any AcceptanceMessages will indicate that result status has to be forced acceptance success
1453:
1454: NodeList lErrorMessagesList = lTargetAcceptanceDocumentElement
1455: .getElementsByTagName("ErrorMessage");
1456: NodeList lAcceptanceMessagesList = lTargetAcceptanceDocumentElement
1457: .getElementsByTagName("AcceptanceMessage");
1458: if (lErrorMessagesList.getLength() > 0)
1459: pTemplateContext.mTestCaseLogElement
1460: .setAttribute("status",
1461: "AcceptanceFailure");
1462: else if (lAcceptanceMessagesList.getLength() > 0)
1463: pTemplateContext.mTestCaseLogElement
1464: .setAttribute("status", "Success");
1465: // If none of these messages present - result should be left alone
1466: // which will leave it as Exception failure for the exceptions and
1467: // success for all other cases
1468: }
1469: // If Acceptance document is not present - result should be left alone
1470: // which will leave it as TestStepFailure for the test step failures and success for all other cases
1471: // Create performance record for the test case if logging of the test performance is on
1472: if (mLogTestPerformance)
1473: appendPerformanceElement(
1474: pTemplateContext.mTestCaseLogElement,
1475: lTestCaseStart, new Date());
1476: // Now work on sleep after
1477: if (pTestCasePlanElement.hasAttribute("sleepAfter")) {
1478: String lSecondsToSleep = pTestCasePlanElement
1479: .getAttribute("sleepAfter");
1480: if (sLogger.isDebugEnabled())
1481: sLogger
1482: .info("Will sleep for "
1483: + lSecondsToSleep
1484: + " seconds as defined in the sleepAfter attribute of the TestCase...");
1485: Thread.sleep((long) (Integer
1486: .parseInt(lSecondsToSleep) * 1000));
1487: pTemplateContext.mTestCaseLogElement.setAttribute(
1488: "sleepAfter", lSecondsToSleep);
1489: }
1490: // Log information about this test case
1491: sLogger.info(lLogIndent
1492: + "Completed "
1493: + pTestCasePlanElement.getAttribute("name")
1494: + " TestCase. Status: "
1495: + pTemplateContext.mTestCaseLogElement
1496: .getAttribute("status"));
1497: }
1498: return pTemplateContext.mTestCaseLogElement;
1499: } finally {
1500: // Restore the previous element on a stack if the current element is ours
1501: if (lTestCaseLogElement != null
1502: && pTemplateContext.mTestCaseLogElement == lTestCaseLogElement) {
1503: if (pTemplateContext.mTestCaseLogElementStack.size() > 0)
1504: pTemplateContext.mTestCaseLogElement = (Element) pTemplateContext.mTestCaseLogElementStack
1505: .remove(0);
1506: else
1507: pTemplateContext.mTestCaseLogElement = null;
1508: }
1509: }
1510: }
1511:
1512: /** Operation invocation functionality, processes given Operation element and populates results back into the target element */
1513: public void processOperationPlan(Element pTargetElement,
1514: Element pOperationPlanElement,
1515: TemplateContext pTemplateContext) throws Exception {
1516: Element lTargetOperationLogElement = (Element) pTemplateContext.mTestCaseLogElement
1517: .appendChild(pTemplateContext.mTestLogDocument
1518: .createElement("OperationLog"));
1519: copyBaseElements(pOperationPlanElement,
1520: lTargetOperationLogElement, pTemplateContext);
1521:
1522: // Process InputDocument
1523: Element lSourceInputDocumentElement = DOMUtils
1524: .getFirstChildElement(pOperationPlanElement);
1525: if (lSourceInputDocumentElement == null
1526: || lSourceInputDocumentElement.getTagName().equals(
1527: "InputDocument") == false)
1528: throw new Exception(
1529: "OperationPlan element must contain InputDocument element, which is used to specify an input to the operation.");
1530: // Default scope is for templates inside InputDocument is TestCase
1531: Element lTargetInputDocumentElement = (Element) lTargetOperationLogElement
1532: .appendChild(pTemplateContext.mTestLogDocument
1533: .createElement("InputDocument"));
1534: copyChildElementsWithoutNamespacesWithTemplates(
1535: lTargetInputDocumentElement,
1536: lSourceInputDocumentElement, pTemplateContext);
1537: Element lInputElement = DOMUtils
1538: .getFirstChildElement(lTargetInputDocumentElement);
1539: if (lInputElement == null)
1540: throw new Exception(
1541: "The InputDocument is empty and can not be interpreted correctly.");
1542: // Now prepare actual input document and run it
1543: Document lTestInputDocument = mScenarioDocumentBuilder
1544: .newDocument();
1545: lTestInputDocument.appendChild(lTestInputDocument.importNode(
1546: lInputElement, true));
1547: DOMSource lDOMSource = new DOMSource(lTestInputDocument);
1548:
1549: Transformer lTransformer = mTransformerFactory.newTransformer();
1550: lTransformer.setOutputProperty(OutputKeys.METHOD, "xml");
1551: lTransformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
1552: // Transform with indent for the file and display if debug is on
1553: if (sLogger.isDebugEnabled()) {
1554: StringWriter lIndentedContents = new StringWriter();
1555: lTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
1556: lTransformer.transform(lDOMSource, new StreamResult(
1557: lIndentedContents));
1558: // Display message
1559: sLogger
1560: .debug("Running operation with following inputs:\r\n"
1561: + lIndentedContents.toString());
1562: }
1563: lTransformer.setOutputProperty(OutputKeys.INDENT, "no");
1564: StringWriter lRawContents = new StringWriter();
1565: lTransformer.transform(lDOMSource, new StreamResult(
1566: lRawContents));
1567:
1568: // Find the runner for the test
1569: String lInputNamespace = lInputElement.getNamespaceURI();
1570: if (lInputNamespace == null)
1571: throw new Exception(
1572: "Namespace attribute is missing on operation input element.");
1573: AdapterRunner lRunner = (AdapterRunner) mAdapterRunners
1574: .get(lInputNamespace);
1575: if (lRunner == null)
1576: mAdapterRunners.put(lInputNamespace,
1577: lRunner = new AdapterRunner(lInputNamespace));
1578: Date lOperationStart = new Date();
1579: String lResultFileContents = lRunner
1580: .executeOperation(lRawContents.toString());
1581: Date lOperationEnd = new Date();
1582: Document lTestOutputDocument = mAnyDocumentBuilder
1583: .parse(new InputSource(new StringReader(
1584: lResultFileContents)));
1585: Element lOperationResultElement = lTestOutputDocument
1586: .getDocumentElement();
1587: Element lOutputDocumentElement = (Element) lTargetOperationLogElement
1588: .appendChild(pTemplateContext.mTestLogDocument
1589: .createElement("OutputDocument"));
1590: lOutputDocumentElement
1591: .appendChild(pTemplateContext.mTestLogDocument
1592: .importNode(lOperationResultElement, true));
1593: // Log the test output
1594: if (sLogger.isDebugEnabled()) {
1595: StringWriter lIndentedResultFileContents = new StringWriter();
1596: lTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
1597: lTransformer.transform(new DOMSource(lTestOutputDocument),
1598: new StreamResult(lIndentedResultFileContents));
1599: sLogger
1600: .debug("Operation has returned following output:\r\n"
1601: + lIndentedResultFileContents);
1602: }
1603: // We append performance element only in case when there was no exception
1604: // This will ensure the relevance of the performance document on this level
1605: if (mLogTestPerformance)
1606: appendPerformanceElement(lTargetOperationLogElement,
1607: lOperationStart, lOperationEnd);
1608: // Test if operation contains a failure indicator
1609: if (!isOperationSuccessful(lOperationResultElement)) {
1610: // Change the status of the test case
1611: pTemplateContext.mTestCaseLogElement.setAttribute("status",
1612: "OperationFailure");
1613: }
1614: }
1615:
1616: /** Subscription invocation functionality, processes given SubscriptionPlan element and populates results back into the target element */
1617: public void processSubscriptionPlan(Element pTargetElement,
1618: Element pSubscriptionPlanElement,
1619: TemplateContext pTemplateContext) throws Exception {
1620: // Process SubscriptionPlan element
1621: Element lTargetSubscriptionLogElement = (Element) pTemplateContext.mTestCaseLogElement
1622: .appendChild(pTemplateContext.mTestLogDocument
1623: .createElement("SubscriptionLog"));
1624: copyBaseElements(pSubscriptionPlanElement,
1625: lTargetSubscriptionLogElement, pTemplateContext);
1626:
1627: // Process SubscriptionOperationPlan element
1628: Element lSubscriptionOperationPlanElement = DOMUtils
1629: .getFirstChildElement(pSubscriptionPlanElement);
1630: if (lSubscriptionOperationPlanElement == null
1631: || lSubscriptionOperationPlanElement.getTagName()
1632: .equals("SubscriptionOperationPlan") == false)
1633: throw new Exception(
1634: "SubscriptionPlan element must contain SubscriptionOperationPlan element, which is used to specify details of the subscription operation.");
1635: Element lTargetSubscriptionOperationLogElement = (Element) lTargetSubscriptionLogElement
1636: .appendChild(pTemplateContext.mTestLogDocument
1637: .createElement("SubscriptionOperationLog"));
1638: copyBaseElements(lSubscriptionOperationPlanElement,
1639: lTargetSubscriptionOperationLogElement,
1640: pTemplateContext);
1641:
1642: // Process InputDocument element
1643: Element lSourceInputDocumentElement = DOMUtils
1644: .getFirstChildElement(lSubscriptionOperationPlanElement);
1645: if (lSourceInputDocumentElement == null
1646: || lSourceInputDocumentElement.getTagName().equals(
1647: "InputDocument") == false)
1648: throw new Exception(
1649: "SubscriptionOperationPlan element must contain InputDocument element, which is used to specify an input to the operation.");
1650:
1651: // Default scope is for templates inside InputDocument is TestCase
1652: Element lTargetInputDocumentElement = (Element) lTargetSubscriptionOperationLogElement
1653: .appendChild(pTemplateContext.mTestLogDocument
1654: .createElement("InputDocument"));
1655: copyChildElementsWithoutNamespacesWithTemplates(
1656: lTargetInputDocumentElement,
1657: lSourceInputDocumentElement, pTemplateContext);
1658: Element lInputElement = DOMUtils
1659: .getFirstChildElement(lTargetInputDocumentElement);
1660: if (lInputElement == null)
1661: throw new Exception(
1662: "The InputDocument is empty and can not be interpreted correctly.");
1663:
1664: // Now prepare actual input document and run it
1665: Document lTestInputDocument = mScenarioDocumentBuilder
1666: .newDocument();
1667: lTestInputDocument.appendChild(lTestInputDocument.importNode(
1668: lInputElement, true));
1669: DOMSource lDOMSource = new DOMSource(lTestInputDocument);
1670:
1671: Transformer lTransformer = mTransformerFactory.newTransformer();
1672: lTransformer.setOutputProperty(OutputKeys.METHOD, "xml");
1673: lTransformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
1674: // Transform with indent for the file and display if debug is on
1675: if (sLogger.isDebugEnabled()) {
1676: StringWriter lIndentedContents = new StringWriter();
1677: lTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
1678: lTransformer.transform(lDOMSource, new StreamResult(
1679: lIndentedContents));
1680: // Display message
1681: sLogger
1682: .debug("Running subscription operation with following inputs:\r\n"
1683: + lIndentedContents.toString());
1684: }
1685: lTransformer.setOutputProperty(OutputKeys.INDENT, "no");
1686: StringWriter lRawContents = new StringWriter();
1687: lTransformer.transform(lDOMSource, new StreamResult(
1688: lRawContents));
1689:
1690: // Find the runner for the test
1691: String lInputNamespace = lInputElement.getNamespaceURI();
1692: if (lInputNamespace == null)
1693: throw new Exception(
1694: "Namespace attribute is missing on subscription operation input element.");
1695:
1696: AdapterRunner lRunner = (AdapterRunner) mAdapterRunners
1697: .get(lInputNamespace);
1698: if (lRunner == null)
1699: mAdapterRunners.put(lInputNamespace,
1700: lRunner = new AdapterRunner(lInputNamespace));
1701: Date lOperationStart = new Date();
1702: String lResultFileContents = lRunner.subscribe(lRawContents
1703: .toString(), pTemplateContext,
1704: lTargetSubscriptionLogElement);
1705: Date lOperationEnd = new Date();
1706: Document lTestOutputDocument = mAnyDocumentBuilder
1707: .parse(new InputSource(new StringReader(
1708: lResultFileContents)));
1709: Element lOperationResultElement = lTestOutputDocument
1710: .getDocumentElement();
1711: Element lOutputDocumentElement = (Element) lTargetSubscriptionOperationLogElement
1712: .appendChild(pTemplateContext.mTestLogDocument
1713: .createElement("OutputDocument"));
1714: lOutputDocumentElement
1715: .appendChild(pTemplateContext.mTestLogDocument
1716: .importNode(lOperationResultElement, true));
1717: // Log the test output
1718: if (sLogger.isDebugEnabled()) {
1719: StringWriter lIndentedResultFileContents = new StringWriter();
1720: lTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
1721: lTransformer.transform(new DOMSource(lTestOutputDocument),
1722: new StreamResult(lIndentedResultFileContents));
1723: sLogger
1724: .debug("Operation has returned following output:\r\n"
1725: + lIndentedResultFileContents);
1726: }
1727: // We append performance element only in case when there was no exception
1728: // This will ensure the relevance of the performance document on this level
1729: if (mLogTestPerformance)
1730: appendPerformanceElement(
1731: lTargetSubscriptionOperationLogElement,
1732: lOperationStart, lOperationEnd);
1733: // Test if operation contains a failure indicator
1734: if (!isOperationSuccessful(lOperationResultElement)) {
1735: // Change the status of the test case
1736: pTemplateContext.mTestCaseLogElement.setAttribute("status",
1737: "SubscriptionOperationFailure");
1738: }
1739: }
1740:
1741: /** Special functionality, processes given ValueOf element and populates results into the target element */
1742: public void processValueOf(Element pTargetElement,
1743: Element pValueOfElement, TemplateContext pTemplateContext)
1744: throws Exception {
1745: if (pValueOfElement.hasAttribute("select")) {
1746: // Output the result of the XPath select
1747: XPath path = new DOMXPath(pValueOfElement
1748: .getAttribute("select"));
1749: // Copy namespace specifications
1750: NamedNodeMap lAttributeNodes = pValueOfElement
1751: .getAttributes();
1752: int lAttributesCount = lAttributeNodes.getLength();
1753: for (int i = 0; i < lAttributesCount; i++) {
1754: Node lSourceAttributeNode = lAttributeNodes.item(i);
1755: String lNamespaceURI = lSourceAttributeNode
1756: .getNamespaceURI();
1757: if (lNamespaceURI != null
1758: && lNamespaceURI.equals(W3C_XMLNS_SCHEMA)) {
1759: String lAttributeName = lSourceAttributeNode
1760: .getNodeName();
1761: if (lAttributeName != null
1762: && lAttributeName.startsWith("xmlns:"))
1763: path.addNamespace(lAttributeName.substring(6),
1764: lSourceAttributeNode.getNodeValue());
1765: }
1766: }
1767: List lResults = path
1768: .selectNodes(pTemplateContext.mTestScenarioLogElement);
1769: for (Iterator lResultsIter = lResults.iterator(); lResultsIter
1770: .hasNext();) {
1771: Object lNextResult = lResultsIter.next();
1772: if (lNextResult instanceof String)
1773: pTargetElement.appendChild(pTargetElement
1774: .getOwnerDocument().createTextNode(
1775: (String) lNextResult));
1776: else if (lNextResult instanceof Element)
1777: copyChildElementsWithoutNamespacesWithTemplates(
1778: pTargetElement, (Element) lNextResult,
1779: pTemplateContext);
1780: else
1781: throw new Exception(
1782: "Unexpected type returned from the XPath search :"
1783: + lNextResult.getClass().getName());
1784: }
1785: } else if (pValueOfElement.hasAttribute("property")) {
1786: // Output the result of the XPath select
1787: String lPropertyText = System.getProperty(pValueOfElement
1788: .getAttribute("property"));
1789: if (lPropertyText == null)
1790: throw new Exception("Property '"
1791: + pValueOfElement.getAttribute("property")
1792: + "' is undefined.");
1793: pTargetElement.appendChild(pTargetElement
1794: .getOwnerDocument().createTextNode(lPropertyText));
1795: } else
1796: throw new Exception(
1797: "Unable to interpret the ValueOf element. It does not contain any supported instruction.");
1798: }
1799:
1800: /** Special functionality, processes given Timestamp element and populates results into the target element */
1801: public void processValueOfTimestamp(Element pTargetElement,
1802: Element pTimestampElement, TemplateContext pTemplateContext)
1803: throws Exception {
1804: // Initialise timestamp and do some math
1805: Calendar lTimestamp = Calendar.getInstance();
1806: if (pTimestampElement.hasAttribute("addYear"))
1807: lTimestamp.add(Calendar.YEAR,
1808: Integer.parseInt(pTimestampElement
1809: .getAttribute("addYear")));
1810: if (pTimestampElement.hasAttribute("addMonth"))
1811: lTimestamp.add(Calendar.MONTH, Integer
1812: .parseInt(pTimestampElement
1813: .getAttribute("addMonth")));
1814: if (pTimestampElement.hasAttribute("addDay"))
1815: lTimestamp
1816: .add(Calendar.DATE, Integer
1817: .parseInt(pTimestampElement
1818: .getAttribute("addDay")));
1819: if (pTimestampElement.hasAttribute("addHour"))
1820: lTimestamp.add(Calendar.HOUR_OF_DAY,
1821: Integer.parseInt(pTimestampElement
1822: .getAttribute("addHour")));
1823: if (pTimestampElement.hasAttribute("addMinute"))
1824: lTimestamp.add(Calendar.MINUTE, Integer
1825: .parseInt(pTimestampElement
1826: .getAttribute("addMinute")));
1827: if (pTimestampElement.hasAttribute("addSecond"))
1828: lTimestamp.add(Calendar.SECOND, Integer
1829: .parseInt(pTimestampElement
1830: .getAttribute("addSecond")));
1831: // Render the output
1832: SimpleDateFormat lSimpleDateFormat = new SimpleDateFormat(
1833: pTimestampElement.getAttribute("format"));
1834: String lTimestampExpresion = lSimpleDateFormat
1835: .format(lTimestamp.getTime());
1836: pTargetElement.appendChild(pTargetElement.getOwnerDocument()
1837: .createTextNode(lTimestampExpresion));
1838: }
1839:
1840: /** Include processor functionality, processes given Include element and populates results back into the target element */
1841: public void processInclude(Element pTargetElement,
1842: Element pTemplateElement, TemplateContext pTemplateContext)
1843: throws Exception {
1844: if (mIncludePath == null)
1845: throw new Exception(
1846: "-includePath parameter must be specified for the <Include> instruction to work correctly");
1847: // Get the file name
1848: String lFileName = pTemplateElement.getAttribute("filename");
1849: Document lInputDocument = null;
1850: for (int i = 0; i < mIncludePath.length; i++) {
1851: String lAttemptedFileName = mIncludePath[i]
1852: + File.separator + lFileName;
1853: File lIncludeFile = new File(lAttemptedFileName);
1854: if (lIncludeFile.exists() && lIncludeFile.isFile()) {
1855: // Load the documentt
1856: lInputDocument = mAnyDocumentBuilder
1857: .parse(new InputSource(new FileReader(
1858: lIncludeFile)));
1859: break;
1860: }
1861: }
1862: if (lInputDocument == null)
1863: throw new Exception("Unable to find include file '"
1864: + lFileName + "' anywhere on the include path.");
1865: // Import the included document and make it the child of the include element
1866: // Than call the copy routine recursively
1867: Node lImportedNode = pTemplateElement.getOwnerDocument()
1868: .importNode(lInputDocument.getDocumentElement(), true);
1869: pTemplateElement.appendChild(lImportedNode);
1870: copyChildElementsWithoutNamespacesWithTemplates(pTargetElement,
1871: pTemplateElement, pTemplateContext);
1872: }
1873:
1874: /** Template processor functionality, processes given Template element and populates results back into the target element */
1875: public void processTemplate(Element pTargetElement,
1876: Element pTemplateElement, TemplateContext pTemplateContext)
1877: throws Exception {
1878: // Find out the document to use as a source document for the template
1879: Element lSourceElement = null;
1880: {
1881: String lScope = pTemplateElement.getAttribute("scope");
1882: if (lScope.equals("/")) {
1883: // Whole scenario
1884: lSourceElement = pTemplateContext.mTestScenarioLogElement;
1885: } else if (lScope.length() == 0 || lScope.equals(".")) {
1886: // Current test case (this is also the default)
1887: lSourceElement = pTemplateContext.mTestCaseLogElement;
1888: } else {
1889: // Series of ../ - each one referring to one higher level
1890: int lLevels = 0;
1891: while (lScope.startsWith("../")) {
1892: lLevels++;
1893: lScope = lScope.substring(3);
1894: }
1895: if (lScope.length() > 0)
1896: throw new Exception(
1897: "Invalid format of scope attribute: "
1898: + pTemplateElement
1899: .getAttribute("scope"));
1900: if (lLevels > pTemplateContext.mTestCaseLogElementStack
1901: .size())
1902: throw new Exception(
1903: "Scope refers to not existant TestCase. Actual number of levels is "
1904: + pTemplateContext.mTestCaseLogElementStack
1905: .size()
1906: + " specified scope is "
1907: + pTemplateElement
1908: .getAttribute("scope"));
1909: lSourceElement = (Element) pTemplateContext.mTestCaseLogElementStack
1910: .get(lLevels - 1);
1911: }
1912: }
1913:
1914: String lTemplateType = pTemplateElement.getAttribute("type");
1915: if (lTemplateType.equals("XSLText"))
1916: processTemplateXSLText(pTargetElement, pTemplateElement,
1917: pTemplateContext, lSourceElement);
1918: else if (lTemplateType.equals("XSLDocument"))
1919: processTemplateXSLDocument(pTargetElement,
1920: pTemplateElement, pTemplateContext, lSourceElement);
1921: else
1922: throw new Exception(
1923: "Unrecognised value of the type attribute in the Template element: "
1924: + lTemplateType);
1925: }
1926:
1927: /** Template processor functionality, processes given Template element and populates results back into the target element */
1928: public void processTemplateXSLText(Element pTargetElement,
1929: Element pTemplateElement, TemplateContext pTemplateContext,
1930: Element pSourceElement) throws Exception {
1931: // Copy all Text and CData nodels into one
1932: StringBuffer lTemplateTextBuffer = new StringBuffer();
1933: NodeList lChildNodes = pTemplateElement.getChildNodes();
1934: int lChildrenCount = lChildNodes.getLength();
1935: for (int i = 0; i < lChildrenCount; i++) {
1936: Node lChildNode = lChildNodes.item(i);
1937: if (lChildNode.getNodeType() == Element.TEXT_NODE
1938: || lChildNode.getNodeType() == Element.CDATA_SECTION_NODE)
1939: lTemplateTextBuffer.append(lChildNode.getNodeValue());
1940: else
1941: throw new Exception(
1942: "The Template element with type XSLText must only contain text or CDATA child nodes.");
1943: }
1944: String lTemplateText = lTemplateTextBuffer.toString().trim();
1945: if (!lTemplateText.startsWith("<xsl:stylesheet"))
1946: throw new Exception(
1947: "XSLText Template element must have a child stylesheet definition text. Found:"
1948: + lTemplateText);
1949: // Build a reasonable document and parse it
1950: StringBuffer lTemplateDocumentText = new StringBuffer();
1951: lTemplateDocumentText
1952: .append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
1953: lTemplateDocumentText.append("<Template xmlns=\"");
1954: lTemplateDocumentText.append(METABOSS_SCENARIO_RUNNER_SCHEMA);
1955: lTemplateDocumentText.append("\" xmlns:xsl=\"");
1956: lTemplateDocumentText.append(W3C_XSLT_SCHEMA);
1957: lTemplateDocumentText.append("\">");
1958: lTemplateDocumentText.append(lTemplateText);
1959: lTemplateDocumentText.append("</Template>");
1960: Document lTemplateDocument = mScenarioDocumentBuilder
1961: .parse(new InputSource(new StringReader(
1962: lTemplateDocumentText.toString())));
1963: processTemplateXSLDocument(pTargetElement, lTemplateDocument
1964: .getDocumentElement(), pTemplateContext, pSourceElement);
1965: }
1966:
1967: /** Template processor functionality, processes given Template element and populates results back into the target element */
1968: public void processTemplateXSLDocument(Element pTargetElement,
1969: Element pTemplateElement, TemplateContext pTemplateContext,
1970: Element pSourceElement) throws Exception {
1971: // Prepared stylesheet document for use as a template
1972: NodeList lStylesheetElementsList = pTemplateElement
1973: .getElementsByTagNameNS(W3C_XSLT_SCHEMA, "stylesheet");
1974: if (lStylesheetElementsList.getLength() == 0)
1975: throw new Exception(
1976: "Template element does not have a child stylesheet element.");
1977: Element lStylesheetElement = (Element) lStylesheetElementsList
1978: .item(0);
1979:
1980: if (!lStylesheetElement.getTagName().equals("xsl:stylesheet"))
1981: throw new Exception(
1982: "XSL Template element must have a child stylesheet element. Found: "
1983: + lStylesheetElement.getTagName());
1984: // Ensure that the namespaces are setup in this element
1985: lStylesheetElement.setAttributeNS(W3C_XMLNS_SCHEMA, "xmlns",
1986: METABOSS_SCENARIO_RUNNER_SCHEMA);
1987: lStylesheetElement.setAttribute("xmlns:xsl", W3C_XSLT_SCHEMA);
1988:
1989: // Try to transform template to string
1990: StringWriter lRawTemplateContents = new StringWriter();
1991: // mTransformerFactory.setAttribute("debug",Boolean.TRUE);
1992: Transformer lTemplateTransformer = mTransformerFactory
1993: .newTransformer();
1994: lTemplateTransformer
1995: .setOutputProperty(OutputKeys.METHOD, "xml");
1996: lTemplateTransformer.setOutputProperty(OutputKeys.STANDALONE,
1997: "yes");
1998: lTemplateTransformer.setOutputProperty(OutputKeys.INDENT, "no");
1999: lTemplateTransformer.transform(
2000: new DOMSource(lStylesheetElement), new StreamResult(
2001: lRawTemplateContents));
2002: // Perform transformation to string buffer and than invoke parser
2003: // this should convert contents of CDATA to what they should be
2004: Transformer lTransformer = mTransformerFactory
2005: .newTransformer(new StreamSource(new StringReader(
2006: lRawTemplateContents.toString())));
2007: lTransformer.setOutputProperty(OutputKeys.METHOD, "xml");
2008: lTransformer.setOutputProperty(OutputKeys.STANDALONE, "no");
2009: lTransformer.setOutputProperty(OutputKeys.INDENT, "no");
2010: lTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
2011: "yes");
2012: StringWriter lRawContents = new StringWriter();
2013: lTransformer.transform(new DOMSource(pSourceElement),
2014: new StreamResult(lRawContents));
2015: Document lResultDocument = mTransformationResultDocumentBuilder
2016: .parse(new InputSource(new StringReader(
2017: "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><ArtificalRoot>"
2018: + lRawContents.toString()
2019: + "</ArtificalRoot>")));
2020:
2021: // Copy result elements back
2022: // for (Node lChildNode = lResultDocument.getDocumentElement().getFirstChild(); lChildNode != null; lChildNode = lChildNode.getNextSibling())
2023: // {
2024: // if (lChildNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE)
2025: // pTargetElement.appendChild(pTargetElement.getOwnerDocument().importNode(lChildNode, true));
2026: // }
2027: for (Node lChildNode = lResultDocument.getDocumentElement()
2028: .getFirstChild(); lChildNode != null; lChildNode = lChildNode
2029: .getNextSibling())
2030: importElementWithoutNamespace(pTargetElement, lChildNode);
2031: }
2032:
2033: public static void main(String[] args) {
2034: try {
2035: // Configure logger as early as possible
2036: org.apache.log4j.PropertyConfigurator.configure(System
2037: .getProperties());
2038: sScenarioRunnerInstance = new ScenarioRunner();
2039: if (args != null) {
2040: for (int i = 0; i < args.length; i++) {
2041: String lArg = args[i];
2042: if (lArg.startsWith("-scenarioPath="))
2043: sScenarioRunnerInstance.setScenarioPath(lArg
2044: .substring(14));
2045: else if (lArg.startsWith("-includePath="))
2046: sScenarioRunnerInstance.setIncludePath(lArg
2047: .substring(13));
2048: else if (lArg.startsWith("-scenarioName="))
2049: sScenarioRunnerInstance.setScenarioName(lArg
2050: .substring(14));
2051: else if (lArg.startsWith("-scenarioRunName="))
2052: sScenarioRunnerInstance.setScenarioRunName(lArg
2053: .substring(17));
2054: else if (lArg.startsWith("-clientPath="))
2055: sScenarioRunnerInstance.setClientPath(lArg
2056: .substring(12));
2057: else if (lArg.startsWith("-loggingPath="))
2058: sScenarioRunnerInstance.setLoggingPath(lArg
2059: .substring(13));
2060: else if (lArg.startsWith("-logTestPerformance="))
2061: sScenarioRunnerInstance
2062: .setLogTestPerformance(Boolean.valueOf(
2063: lArg.substring(19))
2064: .booleanValue());
2065: else if (lArg.startsWith("-specimenFile="))
2066: sScenarioRunnerInstance.setSpecimenFile(lArg
2067: .substring(14));
2068: else
2069: throw new IllegalArgumentException(
2070: "Unrecognised argument: " + lArg);
2071: }
2072: }
2073: // Insert shutdown hook which will save the document if it has not been saved before
2074: // this should help to save files when test has locked up and has to be aborted
2075: Runtime.getRuntime().addShutdownHook(
2076: new LogFileGuardingThread());
2077:
2078: System.exit(sScenarioRunnerInstance.runScenario() ? 0 : 1);
2079: } catch (Throwable t) {
2080: t.printStackTrace();
2081: System.exit(2);
2082: }
2083: }
2084:
2085: // Helper. Copies entire element from source to target and looses default system tester namespace
2086: private static Node importElementWithoutNamespace(
2087: Node lTargetParentNode, Node lSourceNode) {
2088: Document lTargetDocument = lTargetParentNode.getOwnerDocument();
2089:
2090: if (lSourceNode.getNodeType() == Node.ELEMENT_NODE) {
2091: String lSourceElementNamespaceURI = lSourceNode
2092: .getNamespaceURI();
2093: Element lTargetElement = null;
2094: if (lSourceElementNamespaceURI == null
2095: || lSourceElementNamespaceURI
2096: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA))
2097: lTargetElement = (Element) lTargetParentNode
2098: .appendChild(lTargetDocument
2099: .createElement(((Element) lSourceNode)
2100: .getTagName()));
2101: else
2102: lTargetElement = (Element) lTargetParentNode
2103: .appendChild(lTargetDocument.createElementNS(
2104: lSourceElementNamespaceURI,
2105: ((Element) lSourceNode).getTagName()));
2106: NamedNodeMap lAttributeNodes = lSourceNode.getAttributes();
2107: int lAttributesCount = lAttributeNodes.getLength();
2108: for (int i = 0; i < lAttributesCount; i++) {
2109: Node lSourceAttributeNode = lAttributeNodes.item(i);
2110: String lSourceAttributeNodeNamespaceURI = lSourceAttributeNode
2111: .getNamespaceURI();
2112: if (lSourceAttributeNodeNamespaceURI == null
2113: || lSourceAttributeNodeNamespaceURI
2114: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA))
2115: lTargetElement.setAttribute(lSourceAttributeNode
2116: .getLocalName(), lSourceAttributeNode
2117: .getNodeValue());
2118: else
2119: lTargetElement
2120: .setAttributeNodeNS((Attr) lTargetDocument
2121: .importNode(lSourceAttributeNode,
2122: true));
2123: }
2124: NodeList lSourceChildNodes = lSourceNode.getChildNodes();
2125: int lChildrenCount = lSourceChildNodes.getLength();
2126: for (int i = 0; i < lChildrenCount; i++)
2127: importElementWithoutNamespace(lTargetElement,
2128: lSourceChildNodes.item(i));
2129: return lTargetElement;
2130: } else
2131: return lTargetParentNode.appendChild(lTargetDocument
2132: .importNode(lSourceNode, true));
2133: }
2134:
2135: // Helper. Copies entire element from source to target and looses default system tester namespace
2136: private Element copyElementWithoutNamespaceWithTemplates(
2137: Element pTargetParentElement, Element pSourceElement,
2138: TemplateContext pTemplateContext) throws Exception {
2139: Document lTargetDocument = pTargetParentElement
2140: .getOwnerDocument();
2141:
2142: String lSourceElementNamespaceURI = pSourceElement
2143: .getNamespaceURI();
2144: Element lTargetElement = null;
2145: if (lSourceElementNamespaceURI == null
2146: || lSourceElementNamespaceURI
2147: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA))
2148: lTargetElement = (Element) pTargetParentElement
2149: .appendChild(lTargetDocument
2150: .createElement(pSourceElement.getTagName()));
2151: else
2152: lTargetElement = (Element) pTargetParentElement
2153: .appendChild(lTargetDocument.createElementNS(
2154: lSourceElementNamespaceURI, pSourceElement
2155: .getTagName()));
2156: NamedNodeMap lAttributeNodes = pSourceElement.getAttributes();
2157: int lAttributesCount = lAttributeNodes.getLength();
2158: for (int i = 0; i < lAttributesCount; i++) {
2159: Node lSourceAttributeNode = lAttributeNodes.item(i);
2160: String lSourceAttributeNodeNamespaceURI = lSourceAttributeNode
2161: .getNamespaceURI();
2162: if (lSourceAttributeNodeNamespaceURI == null
2163: || lSourceAttributeNodeNamespaceURI
2164: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA))
2165: lTargetElement.setAttribute(lSourceAttributeNode
2166: .getNodeName(), lSourceAttributeNode
2167: .getNodeValue());
2168: else
2169: lTargetElement
2170: .setAttributeNodeNS((Attr) lTargetDocument
2171: .importNode(lSourceAttributeNode, true));
2172: }
2173: // Now copy child elements
2174: copyChildElementsWithoutNamespacesWithTemplates(lTargetElement,
2175: pSourceElement, pTemplateContext);
2176:
2177: return lTargetElement;
2178: }
2179:
2180: // Helper. Returns document, where system tester elements are present without namespace
2181: // Our problem is that the test plan documents are done in default system tester namespace
2182: // but some times it is not default in the parsed documents and may even copy itself onto the log document
2183: // , which may stuff up the templates and transformation. So what we do is immediately
2184: // drop the default namespace. It also allows to use any elements inside the
2185: // ParametersDocuments etc.
2186: private Document parseDocumentWithoutNamespace(
2187: InputSource pDocumentInputSource) throws SAXException,
2188: IOException {
2189: // Load test definition into dom document
2190: Document lNamespacedDocument = mScenarioDocumentBuilder
2191: .parse(pDocumentInputSource);
2192: // Create clean document, convert root node and call import elements routine
2193: Document lCleanDocument = mAnyDocumentBuilder.newDocument();
2194: Element lSourceRootElement = lNamespacedDocument
2195: .getDocumentElement();
2196: String lSourceRootElementNamespaceURI = lSourceRootElement
2197: .getNamespaceURI();
2198: Element lTargetRootElement = null;
2199: if (lSourceRootElementNamespaceURI == null
2200: || lSourceRootElementNamespaceURI
2201: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA))
2202: lTargetRootElement = (Element) lCleanDocument
2203: .appendChild(lCleanDocument
2204: .createElement(lSourceRootElement
2205: .getTagName()));
2206: else
2207: lTargetRootElement = (Element) lCleanDocument
2208: .appendChild(lCleanDocument.createElementNS(
2209: lSourceRootElementNamespaceURI,
2210: lSourceRootElement.getTagName()));
2211: NamedNodeMap lAttributeNodes = lSourceRootElement
2212: .getAttributes();
2213: int lAttributesCount = lAttributeNodes.getLength();
2214: for (int i = 0; i < lAttributesCount; i++) {
2215: Node lSourceAttributeNode = lAttributeNodes.item(i);
2216: String lSourceAttributeNodeNamespaceURI = lSourceAttributeNode
2217: .getNamespaceURI();
2218: if (lSourceAttributeNodeNamespaceURI == null
2219: || lSourceAttributeNodeNamespaceURI
2220: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA))
2221: lTargetRootElement.setAttribute(lSourceAttributeNode
2222: .getLocalName(), lSourceAttributeNode
2223: .getNodeValue());
2224: else
2225: lTargetRootElement
2226: .setAttributeNodeNS((Attr) lCleanDocument
2227: .importNode(lSourceAttributeNode, true));
2228: }
2229: NodeList lSourceChildNodes = lSourceRootElement.getChildNodes();
2230: int lChildrenCount = lSourceChildNodes.getLength();
2231: for (int i = 0; i < lChildrenCount; i++)
2232: importElementWithoutNamespace(lTargetRootElement,
2233: lSourceChildNodes.item(i));
2234: // That's all
2235: return lCleanDocument;
2236: }
2237:
2238: // Helper. Copies all elements from source to target with optional template processing
2239: private void copyChildElementsWithoutNamespacesWithTemplates(
2240: Element pTargetLogElement, Element pSourceParentElement,
2241: TemplateContext pTemplateContext) throws Exception {
2242: Document lTargetDocument = pTargetLogElement.getOwnerDocument();
2243:
2244: // Iterate through all nodes - process elements in a special way and copy all other ones
2245: NodeList lSourceChildNodes = pSourceParentElement
2246: .getChildNodes();
2247: int lChildrenCount = lSourceChildNodes.getLength();
2248: for (int i = 0; i < lChildrenCount; i++) {
2249: Node lChildNode = lSourceChildNodes.item(i);
2250: int lNodeType = lChildNode.getNodeType();
2251: if (lNodeType == Node.ELEMENT_NODE) {
2252: Element lChildElement = (Element) lChildNode;
2253: // See if we need to do special processing
2254: String lChildElementNamespaceURI = lChildElement
2255: .getNamespaceURI();
2256: if (lChildElementNamespaceURI == null
2257: || lChildElementNamespaceURI
2258: .equals(METABOSS_SCENARIO_RUNNER_SCHEMA)) {
2259: // All special elements are inside default namespace or explicit MetaBoss namespace
2260: if (lChildElement.getTagName().equals("Template"))
2261: processTemplate(pTargetLogElement,
2262: lChildElement, pTemplateContext);
2263: else if (lChildElement.getTagName().equals(
2264: "Include"))
2265: processInclude(pTargetLogElement,
2266: lChildElement, pTemplateContext);
2267: else if (lChildElement.getTagName().equals(
2268: "ValueOf"))
2269: processValueOf(pTargetLogElement,
2270: lChildElement, pTemplateContext);
2271: else if (lChildElement.getTagName().equals(
2272: "ValueOfTimestamp"))
2273: processValueOfTimestamp(pTargetLogElement,
2274: lChildElement, pTemplateContext);
2275: else
2276: copyElementWithoutNamespaceWithTemplates(
2277: pTargetLogElement, lChildElement,
2278: pTemplateContext);
2279: } else
2280: copyElementWithoutNamespaceWithTemplates(
2281: pTargetLogElement, lChildElement,
2282: pTemplateContext);
2283: } else
2284: pTargetLogElement.appendChild(lTargetDocument
2285: .importNode(lChildNode, true));
2286: }
2287: }
2288:
2289: // Copyes base element attributes from source to target
2290: private void copyBaseElements(Element pSourceElement,
2291: Element pTargetElement, TemplateContext pTemplateContext)
2292: throws Exception {
2293: if (pSourceElement.hasAttribute("name"))
2294: pTargetElement.setAttribute("name", pSourceElement
2295: .getAttribute("name"));
2296: if (pSourceElement.hasAttribute("id")) {
2297: pTargetElement.setAttribute("id", pSourceElement
2298: .getAttribute("id"));
2299: // Verify that id is unique
2300: if (!pTemplateContext.mUsedIds.add(pSourceElement
2301: .getAttribute("id")))
2302: throw new Exception(
2303: "The value of the element id attribute must be unique throughout whole test scenario. Id value '"
2304: + pSourceElement.getAttribute("id")
2305: + "' is used at least twice in the scenario.");
2306: }
2307: if (pSourceElement.hasAttribute("description"))
2308: pTargetElement.setAttribute("description", pSourceElement
2309: .getAttribute("description"));
2310: }
2311:
2312: // Helper. Will look for occurrence of element <IsSuccessful>false</IsSuccessful> at any depth in any namespace
2313: // in the given element. Will return false if it is found. This allows to find out whether operation has failed or not
2314: private boolean isOperationSuccessful(
2315: Element pOperationResultElementToTest) throws Exception {
2316: // The result element from the structure must have <IsSuccessfull> element as an immediate child
2317: for (Element lChildElement = DOMUtils
2318: .getFirstChildElement(pOperationResultElementToTest); lChildElement != null; lChildElement = DOMUtils
2319: .getNextSiblingElement(lChildElement)) {
2320: if (lChildElement.getTagName().equals("IsSuccessful")) {
2321: NodeList lChildNodes = lChildElement.getChildNodes();
2322: if (lChildNodes.getLength() == 1) {
2323: Node lFirstChild = lChildNodes.item(0);
2324: if (lFirstChild != null
2325: && lFirstChild.getNodeType() == Node.TEXT_NODE) {
2326: String lNodeValue = lFirstChild.getNodeValue();
2327: if (lNodeValue != null) {
2328: if (lNodeValue.equals("false"))
2329: return false; // Operation appears to have failed
2330: else if (lNodeValue.equals("true"))
2331: return true; // Operation appears to have succeeded
2332: }
2333: }
2334: }
2335: throw new Exception(
2336: "Malformed operation result. The result element contains invalid <IsSuccessful> element. It is expected to only contain 'true' or 'false' string.");
2337: }
2338: }
2339: throw new Exception(
2340: "Malformed operation result. The result element does not contain <IsSuccessful> elements, which is used to analyse the outcome of the operation.");
2341: }
2342:
2343: private static class LogFileGuardingThread extends Thread {
2344: public void run() {
2345: try {
2346: if (sScenarioRunnerInstance != null)
2347: sScenarioRunnerInstance
2348: .saveScenarioLogIfNecessary();
2349: } catch (Throwable t) {
2350: sLogger
2351: .error("Unable to save the scenario log file",
2352: t);
2353: }
2354: }
2355: }
2356: }
|