dion 02/05/19 15:50:00
Modified: src/java/org/apache/maven/j2ee WarClassLoader.java
WarValidator.java WarFile.java
ValidationBroadcaster.java ValidationListener.java
ValidationStatusListener.java
ValidationFormatter.java
Log:
no message
Revision Changes Path
1.2 +26 -2
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/WarClassLoader.java
Index: WarClassLoader.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/WarClassLoader.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- WarClassLoader.java 15 May 2002 15:02:01 -0000 1.1
+++ WarClassLoader.java 19 May 2002 22:49:59 -0000 1.2
@@ -69,19 +69,43 @@
* A {@link ClassLoader} that loads classes from a {@link WarFile}
*
* @author <a href="mailto:[EMAIL PROTECTED]">dIon Gillard</a>
- * @version $Id: WarClassLoader.java,v 1.1 2002/05/15 15:02:01 dion Exp $
+ * @version $Id: WarClassLoader.java,v 1.2 2002/05/19 22:49:59 dion Exp $
*/
public class WarClassLoader extends URLClassLoader
{
/** temp files created by classloader */
private List tempFiles = new ArrayList();
+ /** war file to be used for class loading */
+ WarFile war;
/** Creates a new instance of WarClassLoader
* @param war a {@link WarFile}
*/
- public WarClassLoader(WarFile war) throws MalformedURLException, IOException
+ public WarClassLoader(WarFile war) throws IOException, MalformedURLException
{
super (new URL[0]);
+ this.war = war;
+ addURLs();
+ }
+
+ /** Creates a new instance of WarClassLoader with the provided parent
+ * classloader as the one to delegate to if classes can't be found
+ * @param war a {@link WarFile}
+ * @param classloader a {@link ClassLoader} to delegate to.
+ */
+ public WarClassLoader(WarFile war, ClassLoader classloader) throws
+ IOException, MalformedURLException
+ {
+ super(new URL[0], classloader);
+ this.war = war;
+ addURLs();
+ }
+
+ /** Add WEB-INF/classes and WEB-INF/lib/*.jar as extra classpath URLs
+ * @param
+ */
+ private void addURLs() throws IOException, MalformedURLException
+ {
File warFile = new File(war.getName());
URL webInfClasses = new URL("jar:" + warFile.toURL() + "!/" +
"WEB-INF/classes/");
1.14 +135 -32
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/WarValidator.java
Index: WarValidator.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/WarValidator.java,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- WarValidator.java 17 May 2002 05:36:18 -0000 1.13
+++ WarValidator.java 19 May 2002 22:49:59 -0000 1.14
@@ -73,16 +73,30 @@
import java.util.List;
import java.util.Map;
+import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+
import org.apache.maven.executor.AbstractExecutor;
/**
* A task to validate a war file. The following is checked:
* <ol>
- * <li>The war file has a web.xml</li>
+ * <li>The war file exists</li>
+ * <li>The war file is readable</li>
+ * <li>The war file has a web.xml (warning)</li>
+ * <li>Servlets defined by a <code><servlet><code> tag are loadable
+ * from the war file and <strong>not</strong> the classpath</li>
+ * <li>JSPs defined by a <code><servlet><code> tag exist in the war
+ * </li>
+ * <li>Taglibs defined by a <code><taglib></code> have a <code>
+ * <taglib-location></code> that exists in the war</li>
* </ol>
* @author dIon Gillard
- * @version $Id: WarValidator.java,v 1.13 2002/05/17 05:36:18 dion Exp $
+ * @version $Id: WarValidator.java,v 1.14 2002/05/19 22:49:59 dion Exp $
+ * @task add validator for error-page/locations
+ * @task add validator for form-login-config
*/
public class WarValidator extends AbstractExecutor
{
@@ -96,14 +110,15 @@
/** whether or not the build process should fail if a validation error
occurs */
private boolean failOnError = true;
-
+
+
//--- Constructors ---------------------------------------------------------
/** Creates a new instance of WarValidator */
- public WarValidator()
+ public WarValidator()
{
addValidationListener(getStatus());
}
-
+
//--- Methods --------------------------------------------------------------
/** Setter for property broadcaster.
* @param broadcaster New value of property broadcaster.
@@ -112,7 +127,7 @@
{
this.broadcaster = broadcaster;
}
-
+
/** Provides access to the status listener that is automatically attached
* to the validation
* @return Value of property status.
@@ -121,16 +136,16 @@
{
return status;
}
-
+
/** Changes the status listener that is associated with this validator
* @param status New value of property status.
*/
- private void setStatus(ValidationStatusListener status)
+ private void setStatus(ValidationStatusListener status)
{
this.status = status;
}
-
- /**
+
+ /**
* Perform the validation.
*/
public void execute() throws Exception
@@ -140,13 +155,13 @@
throw new NullPointerException("war file name should not be null");
}
validate();
- if (getStatus().isError() && isFailOnError())
+ if (getStatus().isError() && isFailOnError())
{
throw new BuildException("Errors occurred during validation. "+
"Messages should have been provided");
}
}
-
+
/**
* validate the provided war file
*/
@@ -154,7 +169,7 @@
{
try
{
- getBroadcaster().fireStartedEvent( new ValidationEvent(this,
+ getBroadcaster().fireStartedEvent( new ValidationEvent(this,
getWarFileName(), "war validation started"));
validateFile();
if (!getStatus().isError())
@@ -164,11 +179,11 @@
}
finally
{
- getBroadcaster().fireEndedEvent( new ValidationEvent(this,
+ getBroadcaster().fireEndedEvent( new ValidationEvent(this,
getWarFileName(), "war validation ended"));
}
}
-
+
/** validate the war file can be read and exists
*/
private void validateFile()
@@ -177,25 +192,25 @@
if (!warFile.exists())
{
- getBroadcaster().fireErrorEvent(new ValidationEvent(this,
+ getBroadcaster().fireErrorEvent(new ValidationEvent(this,
getWarFileName(), "File does not exist"));
return;
}
if (!warFile.canRead())
{
- getBroadcaster().fireErrorEvent(new ValidationEvent(this,
+ getBroadcaster().fireErrorEvent(new ValidationEvent(this,
getWarFileName(), "File can't be read"));
return;
}
}
-
+
/** Validate the web.xml entry in the provided jar file.
* @param jarFile - a jar file with a known WEB-INF/web.xml
*/
private void validateWebXml()
{
WarFile war = null;
- try
+ try
{
war = new WarFile(getWarFileName());
if (war.getWebXmlEntry() == null)
@@ -205,26 +220,31 @@
return;
}
validateServlets(war);
+ validateJSPs(war);
+ validateTaglibs(war);
}
catch (IOException ioe)
{
- getBroadcaster().fireErrorEvent(new ValidationEvent(this,
- getWarFileName(), "Error opening war file for web.xml - " +
+ getBroadcaster().fireErrorEvent(new ValidationEvent(this,
+ getWarFileName(), "Error opening war file for web.xml - " +
"possibly missing manifest"));
}
}
- /**
+ /** Validate the servlets defined in the war file (as defined by a
+ * <code><servlet></code> tag in web.xml), making sure that their
+ * class defined can be loaded from the war and is not part of the
+ * external classpath
*/
private void validateServlets(WarFile war)
{
try
{
Map servlets = war.getServlets();
- if (servlets.size() != 0)
+ if (servlets.size() != 0)
{
- // create a URL classloader from temp files + WEB-INF/classes
- ClassLoader classLoader = new WarClassLoader(war);
+ ClassLoader classLoader = new WarClassLoader(war,
+ getClass().getClassLoader());
String className = null;
Map.Entry entry = null;
for (Iterator entries = servlets.entrySet().iterator();
@@ -232,6 +252,10 @@
{
entry = (Map.Entry) entries.next();
className = (String) entry.getValue();
+ getBroadcaster().fireInformationEvent( new
+ ValidationEvent(this, getWarFileName(),
+ "validating servlet name: " + entry.getKey() +
+ " class: " + className));
// check each servlet by loadClass
validateClass(className, classLoader);
}
@@ -240,7 +264,47 @@
catch (IOException ioe)
{
ioe.printStackTrace();
- getBroadcaster().fireErrorEvent(new ValidationEvent(this,
+ getBroadcaster().fireErrorEvent(new ValidationEvent(this,
+ getWarFileName(), "Error reading WEB-INF/web.xml"));
+ }
+ }
+
+ /** Validate the jsps defined in the war file (as defined by a
+ * <code><servlet></code> tag with a nested <code><jsp-file>
+ * </code> in web.xml), making sure that the resource specifed by
+ * <code><jsp-file></code> exists in the war file
+ */
+ private void validateJSPs(WarFile war)
+ {
+ try
+ {
+ Map jsps = war.getJSPs();
+ if (jsps.size() != 0)
+ {
+ Map.Entry entry = null;
+ for (Iterator entries = jsps.entrySet().iterator();
+ entries.hasNext();)
+ {
+ entry = (Map.Entry)entries.next();
+ String jspFile = (String)entry.getValue();
+ getBroadcaster().fireInformationEvent( new
+ ValidationEvent(this, getWarFileName(),
+ "validating servlet name: " + entry.getKey() +
+ " jsp file: " + jspFile));
+
+ if (!war.hasFile(jspFile))
+ {
+ getBroadcaster().fireErrorEvent( new ValidationEvent(
+ this, getWarFileName(), "JSP File: '" + jspFile +
+ "' not found"));
+ }
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ getBroadcaster().fireErrorEvent(new ValidationEvent(this,
getWarFileName(), "Error reading WEB-INF/web.xml"));
}
}
@@ -252,20 +316,20 @@
*/
private void validateClass(String className, ClassLoader loader)
{
- try
+ try
{
Class clazz = loader.loadClass(className);
if (clazz.getClassLoader() != loader)
{
// loaded from classpath - a no no.
- getBroadcaster().fireErrorEvent( new ValidationEvent(this,
+ getBroadcaster().fireErrorEvent( new ValidationEvent(this,
getWarFileName(), "class (" + className + ") loaded from " +
"system classpath rather than war file"));
}
}
catch (ClassNotFoundException e)
{
- getBroadcaster().fireErrorEvent( new ValidationEvent(this,
+ getBroadcaster().fireErrorEvent( new ValidationEvent(this,
getWarFileName(), "class (" + className + ") not found "));
}
catch (NoClassDefFoundError error)
@@ -276,6 +340,45 @@
}
}
+
+ /** Validate the taglibs defined in the war file (as defined by a
+ * <code><taglib></code> tag in web.xml), making sure that the
+ * resource specifed by <code><taglib-location></code> exists in the
+ * war file
+ */
+ private void validateTaglibs(WarFile war)
+ {
+ try
+ {
+ Map taglibs = war.getTaglibs();
+ if (taglibs.size() != 0)
+ {
+ Map.Entry entry = null;
+ for (Iterator entries = taglibs.entrySet().iterator();
+ entries.hasNext();)
+ {
+ entry = (Map.Entry)entries.next();
+ String uri = (String)entry.getKey();
+ String location = (String)entry.getValue();
+ getBroadcaster().fireInformationEvent( new
+ ValidationEvent(this, getWarFileName(),
+ "validating taglib uri: " + uri));
+ if (!war.hasFile(location))
+ {
+ getBroadcaster().fireErrorEvent( new ValidationEvent(
+ this, getWarFileName(), "Taglib location: '" +
+ location + "' not found"));
+ }
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ getBroadcaster().fireErrorEvent(new ValidationEvent(this,
+ getWarFileName(), "Error reading WEB-INF/web.xml"));
+ }
+ }
/**
* add a listener to the list to be notified
@@ -285,7 +388,7 @@
{
getBroadcaster().addValidationListener(listener);
}
-
+
/**
* remove a listener from the list to be notified
* @param listener a {@link ValidationListener}
@@ -294,7 +397,7 @@
{
getBroadcaster().removeValidationListener(listener);
}
-
+
/** Getter for property warFileName.
* @return Value of property warFileName.
*/
@@ -327,7 +430,7 @@
{
addValidationListener((ValidationListener) formatter);
}
-
+
/** provide a string representation of the validator
* @return "WarValidator(file)"
*/
1.2 +113 -18
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/WarFile.java
Index: WarFile.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/WarFile.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- WarFile.java 15 May 2002 15:02:01 -0000 1.1
+++ WarFile.java 19 May 2002 22:49:59 -0000 1.2
@@ -79,7 +79,7 @@
* Represents a J2EE War File
*
* @author <a href="mailto:[EMAIL PROTECTED]">dIon Gillard</a>
- * @version $Id: WarFile.java,v 1.1 2002/05/15 15:02:01 dion Exp $
+ * @version $Id: WarFile.java,v 1.2 2002/05/19 22:49:59 dion Exp $
*/
public class WarFile extends JarFile
{
@@ -162,30 +162,103 @@
Map servlets = new HashMap();
if (getWebXmlEntry() != null)
{
- try
+ Document webXml = getWebXml();
+ List servletNodes = webXml.selectNodes("//servlet");
+ Node node = null;
+ for (Iterator nodes = servletNodes.iterator(); nodes.hasNext();)
{
- SAXReader xmlReader = new SAXReader(false);
- InputStream webXmlStream = getInputStream(getWebXmlEntry());
- Document webXml = xmlReader.read(webXmlStream);
- List servletNodes = webXml.selectNodes("//servlet");
- Node node = null;
- for (Iterator nodes = servletNodes.iterator(); nodes.hasNext();)
+ node = (Node) nodes.next();
+ String servletName = node.selectSingleNode("./servlet-name")
+ .getText();
+ Node servletClass = node.selectSingleNode("./servlet-class");
+ if (servletClass != null)
{
- node = (Node) nodes.next();
- String servletName = node.selectSingleNode("./servlet-name")
- .getText();
- String servletClass =
- node.selectSingleNode("./servlet-class").getText();
- servlets.put(servletName, servletClass);
+ servlets.put(servletName, servletClass.getText());
}
}
- catch (DocumentException de)
+ }
+ return servlets;
+ }
+
+ /**
+ * Get a map of servlet name -> jsp file. The map has a size of zero
+ * if there are no jsp files defined or no web.xml in the war.
+ * @return a map of jsps defined using the <code><servlet></code> tag
+ * held in the war.
+ * @throws IOException if there are problems reading from the war
+ */
+ public Map getJSPs() throws IOException
+ {
+ Map jsps = new HashMap();
+ if (getWebXmlEntry() != null)
+ {
+ Document webXml = getWebXml();
+ List servletNodes = webXml.selectNodes("//servlet");
+ Node node = null;
+ for (Iterator nodes = servletNodes.iterator(); nodes.hasNext();)
{
- de.printStackTrace();
- throw new IOException(de.getMessage());
+ node = (Node) nodes.next();
+ String servletName = node.selectSingleNode("./servlet-name")
+ .getText();
+ Node jspFile = node.selectSingleNode("./jsp-file");
+ if (jspFile != null)
+ {
+ jsps.put(servletName, jspFile.getText());
+ }
}
}
- return servlets;
+
+ return jsps;
+ }
+
+ /** Get a map of taglib-uri -> taglib-location. The map has zero size
+ * if there are no taglibs defined or no web.xml in the war
+ */
+ public Map getTaglibs() throws IOException
+ {
+ Map taglibs = new HashMap();
+ if (getWebXmlEntry() != null)
+ {
+ Document webXml = getWebXml();
+ List taglibNodes = webXml.selectNodes("//taglib");
+ Node node = null;
+ for (Iterator nodes = taglibNodes.iterator(); nodes.hasNext();)
+ {
+ node = (Node) nodes.next();
+ String taglibUri = node.selectSingleNode("./taglib-uri")
+ .getText();
+ String taglibLocation = node.selectSingleNode(
+ "./taglib-location").getText();
+ taglibs.put(taglibUri, taglibLocation);
+ }
+ }
+ return taglibs;
+ }
+
+ /** Get the web.xml back as a dom4j Document, for easier processing
+ * @return a {@link Document} representing the web.xml
+ * @throws IOException if there are any issues reading the web.xml
+ * or producing the xml document
+ */
+ private Document getWebXml() throws IOException
+ {
+ if (getWebXmlEntry() == null)
+ {
+ throw new IOException("Attempted to get non-existent web.xml");
+ }
+ try
+ {
+ SAXReader xmlReader = new SAXReader(false);
+ xmlReader.setEntityResolver(new J2EEEntityResolver());
+ InputStream webXmlStream = getInputStream(getWebXmlEntry());
+ Document webXml = xmlReader.read(webXmlStream);
+ return webXml;
+ }
+ catch (DocumentException de)
+ {
+ de.printStackTrace();
+ throw new IOException(de.getMessage());
+ }
}
/** Provide a set of jar files as found in WEB-INF/lib
@@ -229,5 +302,27 @@
inStream.close();
return tempFile;
+ }
+
+ /** Tests whether a 'file' exists in the war. A file in this case is
+ * a jar entry prefixed with a '/'. e.g. the file /WEB-INF/web.xml is
+ * the same as the jar entry WEB-INF/web.xml
+ */
+ public boolean hasFile(String fileName)
+ {
+ if (fileName == null) {
+ throw new NullPointerException("fileName parameter can't be null");
+ }
+
+ String entryName = null;
+ if (fileName.startsWith("/"))
+ {
+ entryName = fileName.substring(1);
+ return getJarEntry(entryName) != null;
+ }
+ else
+ {
+ return false;
+ }
}
}
1.3 +16 -1
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationBroadcaster.java
Index: ValidationBroadcaster.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationBroadcaster.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- ValidationBroadcaster.java 12 May 2002 15:29:37 -0000 1.2
+++ ValidationBroadcaster.java 19 May 2002 22:49:59 -0000 1.3
@@ -59,7 +59,7 @@
/**
*
- * @version $Id: ValidationBroadcaster.java,v 1.2 2002/05/12 15:29:37 dion Exp $
+ * @version $Id: ValidationBroadcaster.java,v 1.3 2002/05/19 22:49:59 dion Exp $
* @author dion
*/
public class ValidationBroadcaster
@@ -129,6 +129,21 @@
}
}
+ /**
+ * fire a {@link ValidationListener#validationInformation(ValidationEvent)
+ * information} event.
+ * @param event the event to broadcast
+ */
+ public void fireInformationEvent(ValidationEvent event)
+ {
+ for (Iterator listeners = getListeners().iterator();
+ listeners.hasNext(); )
+ {
+ ((ValidationListener) listeners.next()).validationInformation(
+ event);
+ }
+ }
+
/** Getter for property listeners.
* @return Value of property listeners.
*/
1.4 +8 -1
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationListener.java
Index: ValidationListener.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationListener.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- ValidationListener.java 12 May 2002 15:24:26 -0000 1.3
+++ ValidationListener.java 19 May 2002 22:49:59 -0000 1.4
@@ -60,7 +60,7 @@
* An interface implemented by those who want to receive validation events
* from a J2EE Validator (WAR, EAR etc).
*
- * @version $Id: ValidationListener.java,v 1.3 2002/05/12 15:24:26 dion Exp $
+ * @version $Id: ValidationListener.java,v 1.4 2002/05/19 22:49:59 dion Exp $
* @author dion
*/
public interface ValidationListener extends EventListener
@@ -85,6 +85,13 @@
* @param event a {@link ValidationEvent}
*/
void validationWarning(ValidationEvent event);
+
+ /**
+ * Called when validation information is provided. This may be any
+ * information the validator feels is relevant.
+ * @param event a {@link ValidationEvent}
+ */
+ void validationInformation(ValidationEvent event);
/**
* Called when validation ends
1.2 +29 -1
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationStatusListener.java
Index: ValidationStatusListener.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationStatusListener.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ValidationStatusListener.java 15 May 2002 01:48:20 -0000 1.1
+++ ValidationStatusListener.java 19 May 2002 22:49:59 -0000 1.2
@@ -58,7 +58,7 @@
* A {@link ValidationListener} that tracks events and will provide a true/false
* response about the status of the validation
* @author <a href="mailto:[EMAIL PROTECTED]">dIon Gillard</a>
- * @version $Id: ValidationStatusListener.java,v 1.1 2002/05/15 01:48:20 dion Exp $
+ * @version $Id: ValidationStatusListener.java,v 1.2 2002/05/19 22:49:59 dion Exp $
*/
public class ValidationStatusListener implements ValidationListener
{
@@ -70,6 +70,8 @@
private boolean error = false;
/** Whether the validation has had 1 or more warnings */
private boolean warning = false;
+ /** Whether the validation has had 1 or more info messages */
+ private boolean information = false;
/** Creates a new instance of ValidationStatusListener */
public ValidationStatusListener()
@@ -96,6 +98,15 @@
setWarning(true);
}
+ /** Called when validation info messages are issued. Sets the
+ * <code>information</code> property to <code>true</true>.
+ * @param event a {@link ValidationEvent}
+ */
+ public void validationInformation(ValidationEvent event)
+ {
+ setInformation(true);
+ }
+
/**
* Called when validation starts. Sets the <code>started</code> property to
* <code>true</code>
@@ -179,4 +190,21 @@
{
this.warning = warning;
}
+
+ /** Gets whether an info message has been issued.
+ * @return Value of property information.
+ */
+ public boolean isInformation()
+ {
+ return information;
+ }
+
+ /** Sets whether an info message has been issued.
+ * @param information New value of property information.
+ */
+ public void setInformation(boolean information)
+ {
+ this.information = information;
+ }
+
}
1.5 +16 -1
jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationFormatter.java
Index: ValidationFormatter.java
===================================================================
RCS file:
/home/cvs/jakarta-turbine-maven/src/java/org/apache/maven/j2ee/ValidationFormatter.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- ValidationFormatter.java 15 May 2002 04:57:10 -0000 1.4
+++ ValidationFormatter.java 19 May 2002 22:49:59 -0000 1.5
@@ -61,7 +61,7 @@
/**
* Base class for formatters of validation events - handles plain and xml
* formats.
- * @version $Id: ValidationFormatter.java,v 1.4 2002/05/15 04:57:10 dion Exp $
+ * @version $Id: ValidationFormatter.java,v 1.5 2002/05/19 22:49:59 dion Exp $
* @author dion
*/
public class ValidationFormatter implements ValidationListener
@@ -189,6 +189,21 @@
else
{
printEventAsXML(event, "warning");
+ }
+ }
+
+ /** Called when validation information events are fired.
+ * @param event a {@link ValidationEvent}
+ */
+ public void validationInformation(ValidationEvent event)
+ {
+ if (isPlain())
+ {
+ printEvent("info", event);
+ }
+ else
+ {
+ printEventAsXML(event, "info");
}
}
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>