// Copyright (c) 2010 SuccessFactors, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the following
// disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// * Neither the name of the SuccessFactors, Inc. nor the names of
// its contributors may be used to endorse or promote products
// derived from this software without specific prior written
// permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
package org.owasp.jxt;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.owasp.jxt.compiler.Compiler;
import org.owasp.jxt.servlet.JxtServletBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* JxtC -- A command-line .jxt pre-compiler. Useful for pre-compiling
* all .jxt files in a web-app, or just running sanity checks on the
* files before their put up on a server.
*
* @author Jeffrey Ichnowski
* @version $Revision: 8 $
*/
public class JxtC {
static final Logger _log = LoggerFactory.getLogger(JxtEngine.class);
/**
* These are the command-line options that are supported. The
* enum is used to specify an option's command-line flag
* (e.g. "-d"), and the internal property that maps to
* (e.g. "outputDir"). Reflection is then used to set the
* property when it is encountered on the command line. The
* property name is also used as a message key to display a
* localized message when -help is used. The reason for an enum
* (instead of just using reflection directly), is that it gives
* us control over the order (e.g. for displaying "-help") and
* allows control over which properties are to be exposed to the
* command line, and how.
*/
enum Option {
HELP("-help", "showHelp"),
VERBOSE("-v", "verbose"),
OUTPUT_DIR("-d", "outputDir"),
// PACKAGE_NAME("-p", "packageName"), // not implemented yet
WEBROOT("-webroot", "webRoot", true),
COMPILER("-compiler", "compilerType"),
TEMPDIR("-tempdir", "tempDir"),
CLASS_PATH("-classpath", "classPath"),
CP("-cp", "classPath"),
COMPILER_SOURCE_VM("-source", "compilerSourceVM"),
COMPILER_TARGET_VM("-target", "compilerTargetVM"),
DEBUGGING_INFO("-g", "compilerDebugInfo");
private static final Map<String,Option> FLAGMAP = new HashMap<String,Option>();
static {
try {
Map<String,PropertyDescriptor> propMap = new HashMap<String,PropertyDescriptor>();
for (PropertyDescriptor prop : Introspector.getBeanInfo(JxtC.class)
.getPropertyDescriptors())
{
propMap.put(prop.getName(), prop);
}
for (Option opt : values()) {
FLAGMAP.put(opt.flag, opt);
opt._propertyDescriptor = propMap.get(opt.propertyName);
assert opt._propertyDescriptor != null : "no such property: "+opt.propertyName;
}
} catch (IntrospectionException e) {
_log.error("internal error", e);
throw (AssertionError)new AssertionError().initCause(e);
}
}
public final String flag;
public final String propertyName;
public final boolean required;
private PropertyDescriptor _propertyDescriptor;
Option(String flg, String propName) {
this(flg, propName, false);
}
Option(String flg, String propName, boolean req) {
this.flag = flg;
this.propertyName = propName;
this.required = req;
}
public static Option forFlag(String flag) {
return FLAGMAP.get(flag);
}
public String getFlagHelp() {
if (_propertyDescriptor.getPropertyType() == Boolean.TYPE) {
return flag;
} else {
// TODO: localize "<arg>"
return flag+" <arg>";
}
}
public Object get(JxtC main) {
try {
return _propertyDescriptor.getReadMethod().invoke(main);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw new Error(e);
}
}
public void set(JxtC main, Iterator<String> argIter) {
try {
Class<?> type = _propertyDescriptor.getPropertyType();
Method setter = _propertyDescriptor.getWriteMethod();
if (Boolean.TYPE == type) {
setter.invoke(main, true);
} else if (String.class == type) {
setter.invoke(main, argIter.next());
} else if (File.class == type) {
setter.invoke(main, new File(argIter.next()));
} else {
throw new AssertionError(
"Don't know how to handle property type: "+type);
}
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException)e.getCause();
} else {
throw new Error(e);
}
}
}
}
private boolean _verbose;
private boolean _showHelp;
private File _outputDir;
private String _packageName;
private List<String> _files = new ArrayList<String>();
private String _compilerType;
private File _tempDir;
private String _classPath;
private File _webRoot;
private String _compilerSourceVM = "1.5";
private String _compilerTargetVM = "1.5";
private boolean _compilerDebugInfo;
private File _webInf;
private File _webXmlSource;
private File _webXmlTarget;
private JxtEngine _engine;
public final JxtC parseCommandLine(String[] args) {
for (Iterator<String> argIter = Arrays.asList(args).iterator() ; argIter.hasNext() ;) {
String arg = argIter.next();
if (arg.startsWith("-")) {
Option opt = Option.forFlag(arg);
if (opt == null) {
throw new IllegalArgumentException(
Messages.format("jxtc.unknown-flag", arg));
}
opt.set(this, argIter);
} else {
_files.add(arg);
}
}
return this;
}
private void showHelp() {
System.out.println(Messages.get("jxtc.usage"));
int flagWidth = 0;
for (Option opt : Option.values()) {
flagWidth = Math.max(opt.getFlagHelp().length(), flagWidth);
}
String fmt = " %-"+flagWidth+"s %s\n";
for (Option opt : Option.values()) {
System.out.printf(
fmt,
opt.getFlagHelp(),
Messages.get("jxtc.option."+opt.propertyName));
}
}
private void showSetup() {
for (Option opt : Option.values()) {
System.out.print(opt.propertyName+" = ");
System.out.println(opt.get(this));
}
System.out.println("Files: "+_files);
}
private void findFiles(File dir, String path, List<String> files) {
for (String name : dir.list()) {
File file = new File(dir, name);
if (file.isDirectory()) {
findFiles(file, path + name + File.separator, files);
} else if (file.isFile() && name.endsWith(JxtEngine.FILE_EXTENSION)) {
files.add(path + name);
}
}
}
private Iterable<String> files() {
if (!_files.isEmpty()) {
// TODO: rebase absolute files paths to web-app root
return _files;
} else {
List<String> files = new ArrayList<String>();
findFiles(_webRoot, "", files);
return files;
}
}
/**
* Generates the web.xml using the original web.xml and adding in
* the new servlet mappings.
*
* @param servletMap a mapping from jxt file name to generated
* class.
*/
private void generateWebXML(Map<String,String> servletMap) {
try {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
final Node root = doc.appendChild(doc.createElement("fragment"));
for (Entry<String,String> entry : servletMap.entrySet()) {
Node servlet = root.appendChild(doc.createElement("servlet"));
servlet.appendChild(doc.createElement("servlet-name")).setTextContent(entry.getKey());
servlet.appendChild(doc.createElement("servlet-class")).setTextContent(entry.getValue());
}
for (Entry<String,String> entry : servletMap.entrySet()) {
Node mapping = root.appendChild(doc.createElement("servlet-mapping"));
mapping.appendChild(doc.createElement("servlet-name")).setTextContent(entry.getKey());
mapping.appendChild(doc.createElement("url-pattern")).setTextContent("/"+entry.getKey());
}
TransformerFactory factory = TransformerFactory.newInstance();
factory.setURIResolver(new URIResolver() {
public Source resolve(String href, String base) throws TransformerException {
return new DOMSource(root);
}
});
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
if (_verbose) {
System.out.println("Reading "+_webXmlSource);
}
// First transform the web.xml through an XSLT that
// removes the additions of a previous run.
factory.newTransformer(
new StreamSource(getClass().getResourceAsStream("jxtc-strip-additions.xsl")))
.transform(new StreamSource(_webXmlSource),
new StreamResult(buffer));
if (_verbose) {
System.out.println("Writing "+_webXmlTarget);
}
// Then pass the result of that transform into an XSLT
// that will add in the new mappings.
factory.newTransformer(
new StreamSource(getClass().getResourceAsStream("jxtc-webxml.xsl")))
.transform(new StreamSource(new ByteArrayInputStream(buffer.toByteArray())),
new StreamResult(_webXmlTarget));
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
}
/**
* Performs the actual work. The instance should already be set
* up by calling parseCommandLine or directly setting the
* appropriate properties.
*/
public final void run() {
// if (_verbose) {
// showSetup();
// }
if (_showHelp) {
showHelp();
return;
}
if (_webRoot == null) {
throw new IllegalArgumentException(
Messages.format("jxtc.missing-required-flag", Option.WEBROOT.flag));
}
File outDir = _outputDir;
if (outDir == null) {
outDir = new File(_webRoot, "classes");
}
_webInf = new File(_webRoot, "WEB-INF");
_webXmlSource = new File(_webInf, "web.xml");
_webXmlTarget = new File(_webInf, "web.xml");
Compiler compiler = Compiler.forName(_compilerType);
compiler.setSource(_compilerSourceVM);
compiler.setTarget(_compilerTargetVM);
compiler.setDebug(_compilerDebugInfo);
compiler.setClassPath(_classPath);
try {
_engine = JxtEngine.builder()
.webRoot(_webRoot)
.classPath(_classPath)
.tempDir(_tempDir)
.classDir(outDir)
.compiler(compiler)
.build();
} catch (JxtConfigException e) {
System.out.println("Configuration error: "+e);
return;
}
Map<String,String> servletMap = new LinkedHashMap<String,String>();
boolean errors = false;
for (String file : files()) {
if (_verbose) {
System.out.print("Compiling "+file+" ");
System.out.flush();
}
try {
JxtCompilation<JxtServletBase> result = _engine.compileServlet(file);
for (LocatedMessage warning : result.getWarnings()) {
System.out.println(warning);
}
if (_verbose) {
System.out.println("--> "+result.getTemplateClass().getName());
}
servletMap.put(file, result.getTemplateClass().getName());
} catch (CompileException e) {
if (!_verbose) {
System.out.print("Compiling "+file+" ");
}
System.out.println(" errors.");
System.out.println(e.getMessage());
errors = true;
}
}
if (errors) {
System.out.println("Errors encountered, web.xml mappings will not be updated.");
} else {
generateWebXML(servletMap);
}
}
public static void main(String[] args) {
try {
new JxtC().parseCommandLine(args).run();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
System.out.println(Messages.get("jxtc.short-usage"));
}
}
public final boolean isVerbose() {
return this._verbose;
}
public final void setVerbose(final boolean argVerbose) {
this._verbose = argVerbose;
}
public final boolean isShowHelp() {
return this._showHelp;
}
public final void setShowHelp(final boolean argShowHelp) {
this._showHelp = argShowHelp;
}
public final File getOutputDir() {
return this._outputDir;
}
public final void setOutputDir(final File argOutputDir) {
this._outputDir = argOutputDir;
}
public final String getPackageName() {
return this._packageName;
}
public final void setPackageName(final String argPackageName) {
this._packageName = argPackageName;
}
public final String getCompilerType() {
return this._compilerType;
}
public final void setCompilerType(final String argCompilerType) {
this._compilerType = argCompilerType;
}
public final File getTempDir() {
return this._tempDir;
}
public final void setTempDir(final File argTempDir) {
this._tempDir = argTempDir;
}
public final String getClassPath() {
return this._classPath;
}
public final void setClassPath(final String argClassPath) {
this._classPath = argClassPath;
}
public final File getWebRoot() {
return this._webRoot;
}
public final void setWebRoot(final File argWebRoot) {
this._webRoot = argWebRoot;
}
public final String getCompilerSourceVM() {
return this._compilerSourceVM;
}
public final void setCompilerSourceVM(final String argCompilerSourceVM) {
this._compilerSourceVM = argCompilerSourceVM;
}
public final String getCompilerTargetVM() {
return this._compilerTargetVM;
}
public final void setCompilerTargetVM(final String argCompilerTargetVM) {
this._compilerTargetVM = argCompilerTargetVM;
}
public final boolean isCompilerDebugInfo() {
return this._compilerDebugInfo;
}
public final void setCompilerDebugInfo(final boolean argCompilerDebugInfo) {
this._compilerDebugInfo = argCompilerDebugInfo;
}
} // JxtC
|