001    /*
002     * Copyright 1999-2002,2004 The Apache Software Foundation.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.apache.commons.latka;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileWriter;
022    import java.io.IOException;
023    import java.io.StringReader;
024    import java.io.StringWriter;
025    import java.net.URL;
026    import java.text.SimpleDateFormat;
027    import java.util.Date;
028    import java.util.Iterator;
029    import java.util.Properties;
030    
031    import javax.xml.transform.Source;
032    import javax.xml.transform.Transformer;
033    import javax.xml.transform.TransformerException;
034    import javax.xml.transform.TransformerFactory;
035    import javax.xml.transform.stream.StreamResult;
036    import javax.xml.transform.stream.StreamSource;
037    
038    import org.apache.commons.jelly.Jelly;
039    import org.apache.commons.jelly.JellyContext;
040    import org.apache.commons.jelly.XMLOutput;
041    
042    import org.apache.commons.latka.event.LatkaEventInfo;
043    import org.apache.commons.latka.event.LatkaEventListener;
044    import org.apache.commons.latka.jelly.JellyUtils;
045    
046    import org.apache.log4j.Category;
047    
048    /**
049     * This is the primary class for executing Latka functional
050     * tests.  The main(String aargs[]) class provides a convenient
051     * command-line interface for executing single tests.  See the
052     * Latka documentation for details on command-line usage.
053     * There is also a webapp-based Latka interface.
054     *
055     * @see Suite
056     * @see LatkaProperties
057     *
058     * @author Morgan Delagrange
059     * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
060     *
061     * @version $Revision: 155424 $
062     */
063    public class Latka {
064    
065        /** whether xml parsing is validating or not */
066        protected boolean _isValidating = true;
067        /** XSL stylesheet url to use when generating XML */
068        protected URL _reportStylesheetUrl = null;
069    
070        /** log4j category for logged output */
071        protected static final Category _log = 
072                                 Category.getInstance(Latka.class.getName());
073    
074        /** Latka configuration variables */
075        protected Properties _props = LatkaProperties.getProperties();
076    
077        /** Message displayed to user when incorrect args provided */
078        private static final String LATKA_USAGE = 
079                "\n######################\n"
080            +   "Latka usage:\n"
081            +   "  java org.apache.commons.latka.Latka \"XML_suite_URL\" "
082            +   "[\"propfile:file_name\"] [\"prop:prop_name=prop_value\"]\n\n"
083            +   "The quotes around properties are REQUIRED.\n"
084            +   "######################\n";
085        
086        /**
087         * Execute a single Latka test suite.  The Latka event listner
088         * will receive all the events generated during the test.
089         * The Latka packages contain an implmentation of LatkaEventInfo
090         * which can generate an XML report according to a standard
091         * DTD (see documentation).
092         * 
093         * @param suite    test suite to execute
094         * @param listener target for test events
095         * @exception LatkaException
096         *                   when any internal error occurs
097         * @see XMLReporter XML-based implementation of LatkaEventListener
098         */
099        public void runTests(Suite suite, LatkaEventListener listener)
100                    throws LatkaException {
101        
102            try {
103    
104                Jelly jelly = new Jelly();
105                URL url = suite.getURL();
106                jelly.setUrl(url);
107                jelly.setValidateXML(_isValidating);
108                jelly.setDefaultNamespaceURI("jelly:org.apache.commons.latka.jelly.LatkaTagLibrary");
109    
110                JellyContext context = buildJellyContext();
111                JellyUtils.getInstance().setLatkaEventListener(context,listener);
112    
113                String exturl = url.toExternalForm();
114                int lastSlash = exturl.lastIndexOf("/");
115                String extBase = exturl.substring(0,lastSlash+1);
116                URL baseurl = new URL(extBase);
117                context.setCurrentURL(baseurl);
118    
119                // add listener
120                jelly.compileScript().run(context,XMLOutput.createDummyXMLOutput());
121    
122            } catch (Exception e) {
123                throw new LatkaException(e);
124            }
125        }
126    
127        protected JellyContext buildJellyContext() {
128            JellyContext context = new JellyContext();
129            Iterator keys = _props.keySet().iterator();
130            while (keys.hasNext()) {
131                String key = (String) keys.next();
132                String value = _props.getProperty(key);
133                context.setVariable(key,value);
134            }
135    
136            return context;
137        }
138    
139        /**
140         * Set whether or not Latka will validate the XML in
141         * each test.
142         *
143         * @param isValidating whether or not to validate XML
144         */
145        public void setValidating(boolean isValidating) {
146            _isValidating = isValidating;
147        }
148    
149        /**
150         * Set the URL to be used when transforming XML generated by the XMLReporter
151         * @param url a valid URL referencing a stylesheet 
152         */
153        public void setReportStylesheet(URL url) {
154            _reportStylesheetUrl = url;
155        }
156    
157        /**
158         * Use this method to log XML generated by the
159         * XMLReporter class.
160         *
161         * @param xml XML to be logged
162         * @throws IOException if the XML could not be written to the filesystem
163         */
164        protected void logXML(String xml) throws IOException {
165    
166            String logProp = _props.getProperty("latka.writeLog");
167            if ((logProp != null) && (logProp.equals("false"))) {
168                return;
169            }
170    
171            File dir = new File("logs");
172            // try to create logs directory. if it fails, then check to
173            // make sure it is there (may have existed before) and is writeable
174            if (!dir.mkdirs()) {
175                if (!dir.exists ()) {
176                    throw new IOException("Unable to create logs directory");
177                } else if (!dir.canWrite ()) {
178                    throw new IOException("Unable to write to logs directory");
179                }
180            }
181            SimpleDateFormat formatter = new SimpleDateFormat ("yyyyMMdd'-'HHmm");
182            String dateString = formatter.format(new Date());
183            File file = new File(dir, dateString + ".xml");
184            FileWriter fileWriter = new FileWriter(file);
185            fileWriter.write(xml);
186            fileWriter.close();
187        }
188    
189        /**
190         * Transform the XML generated by the XMLReporter using
191         * the default stylesheet.
192         *
193         * @param xml XML generated by XMLReporter
194         * @return transformed report
195         * @throws LatkaException if the XML could not be transformed
196         */
197        public String transformXML(String xml) throws LatkaException {
198        
199            try {
200                StringWriter output = new StringWriter();
201    
202                Source xslSource = null;
203    
204                if (_reportStylesheetUrl == null) {
205                ClassLoader loader = Thread.currentThread().getContextClassLoader();
206                if (loader == null) {
207                    // there may not be a context class loader
208                    loader = getClass().getClassLoader();
209                    }
210    
211                xslSource = new StreamSource(
212                                loader.getResourceAsStream(
213                                "org.apache.commons.latka.report.xsl")
214                            );
215                } else {
216                    xslSource = new StreamSource(_reportStylesheetUrl.toString());
217                }
218    
219    
220                Transformer transformer = 
221                TransformerFactory.newInstance().newTransformer(xslSource);
222                StreamSource xmlSource = new StreamSource(new StringReader(xml));
223                StreamResult result = new StreamResult(output);
224                transformer.transform(xmlSource, result);
225                return output.toString();
226            } catch (TransformerException e) {
227                throw new LatkaException(e);
228            }
229        }
230    
231        /**
232         * Processes the command line arguments, executes a single test.
233         * and processes the resulting report.  Command line usage is
234         * described in the LATKA_USAGE constant.
235         *
236         * @param args arguments passed into the main(String[]) method
237         *
238         * @throws LatkaException if any problems are encountered during
239         * the execution of the tests
240         */
241        protected void runCommandLine(String args[]) throws LatkaException {
242    
243            if (args.length < 1) {
244                System.out.println(LATKA_USAGE);
245            }
246    
247            String urlString = args[0];
248    
249            if (args.length > 1) {
250    
251                for (int i = 1; i < args.length; ++i) {
252                    String arg = args[i];
253                    if (arg.startsWith("prop:")) {
254                        String propName = arg.substring(5, arg.indexOf("="));
255                        String propValue = arg.substring(arg.indexOf("=") + 1);
256                        _props.setProperty(propName, propValue);
257                    } else if (arg.startsWith("propfile:")) { 
258                        try {
259                            _props.load(new FileInputStream(arg.substring(9)));
260                        } catch (IOException e) {
261                            System.out.println("Could not load user prop file, uri="
262                                                + arg.substring(9));
263                        }                                            
264                    } else {
265                        throw new IllegalArgumentException(LATKA_USAGE);
266                    }
267                } // for
268            } // if
269    
270            String xml = null;
271            XMLReporter listener = new XMLReporter();
272    
273            // overkill, all we need is success/failure
274            LatkaEventInfo info = new DefaultLatkaEventInfo(listener); 
275    
276            try {
277    
278                URL url = new URL(urlString);
279                Suite suite = new Suite(url);
280    
281    
282                runTests(suite, info);
283    
284                xml = listener.getDocumentAsString();
285                logXML(xml);
286            } catch (IOException e) {
287                throw new LatkaException(e);
288            }
289    
290            System.out.println(transformXML(xml));
291    
292            if (info.didSuiteSucceed() == false) {
293                // throw an exception, so the process will
294                // return as a failure
295                throw new LatkaException("SUITE FAILED");
296            }
297        }
298    
299        /**
300         * Execute a single test suite via the command line
301         * interface.  See the Latka documentation for detailed
302         * usage instructions.  The Java process will return
303         * a 0 if all tests succeed and a 1 if any fail
304         * or there is an unrecoverable error in the test
305         * execution.
306         *
307         * @param args arguments containing the test suite location
308         *        and any properties or property file references
309         */
310        public static void main (String args[]) {
311    
312            Latka latka = new Latka();
313            try {
314                latka.runCommandLine(args);
315            } catch (LatkaException e) {
316                e.printStackTrace();
317                System.exit(1);
318            }
319        
320        }
321    }