donaldp 2002/07/30 05:31:17
Modified: . build.xml
src/java/org/apache/avalon/phoenix/components/application
ExportHelper.java Resources.properties
src/java/org/apache/avalon/phoenix/components/embeddor
DefaultEmbeddor.java
src/java/org/apache/avalon/phoenix/components/kernel
DefaultApplicationContext.java
src/java/org/apache/avalon/phoenix/components/manager
AbstractJMXManager.java MX4JSystemManager.java
Resources.properties SubContext.java
src/java/org/apache/avalon/phoenix/interfaces
ApplicationContext.java SystemManager.java
Added: src/java/org/apache/avalon/phoenix/components/manager
MBeanInfoBuilder.java Target.java
Log:
Rework the Management subsystem so that it is capable of exporting mbeans to
MBeanServer if they have a coresponding mxinfo descriptor beside either themselves or
their services.
See mailing list for further details.
Submitted by: Huw Roberts <[EMAIL PROTECTED]>
Revision Changes Path
1.143 +41 -48 jakarta-avalon-phoenix/build.xml
Index: build.xml
===================================================================
RCS file: /home/cvs/jakarta-avalon-phoenix/build.xml,v
retrieving revision 1.142
retrieving revision 1.143
diff -u -r1.142 -r1.143
--- build.xml 15 Jul 2002 15:54:19 -0000 1.142
+++ build.xml 30 Jul 2002 12:31:16 -0000 1.143
@@ -38,6 +38,7 @@
<property name="build.src" value="${build.dir}/src"/>
<property name="build.classes" value="${build.dir}/classes"/>
<property name="build.reports" value="${build.dir}/reports"/>
+ <property name="build.xdoclet" value="${build.dir}/xdoclet"/>
<!-- Set the properties for source directories -->
<property name="src.dir" value="src"/>
@@ -59,16 +60,12 @@
<property name="xalan.jar" value="${lib.dir}/xalan-2.3.1.jar"/>
<property name="framework.jar"
value="${lib.dir}/avalon-framework-20020713.jar"/>
<property name="logkit.jar" value="${lib.dir}/logkit-1.1a.jar"/>
- <property name="jmxri.jar" value="${lib.dir}/jmxri.jar"/>
- <property name="jmxtools.jar" value="${lib.dir}/jmxtools.jar"/>
<property name="tools.jar" value="${java.home}/../lib/tools.jar"/>
<path id="project.class.path">
<pathelement location="${xerces.jar}"/>
<pathelement location="${framework.jar}"/>
<pathelement location="${logkit.jar}"/>
- <pathelement location="${jmxri.jar}"/>
- <pathelement location="${jmxtools.jar}"/>
<pathelement location="${wrapper.jar}"/>
<pathelement path="${mx4j.jar}"/>
<pathelement path="${mx4j-tools.jar}"/>
@@ -105,7 +102,6 @@
<available property="servlet.present" classname="javax.servlet.Servlet">
<classpath refid="project.class.path"/>
</available>
- <available property="jmx-ri.present" file="${jmxri.jar}"/>
<available property="jmx.present"
classname="javax.management.MBeanException">
<classpath refid="project.class.path"/>
</available>
@@ -185,10 +181,12 @@
unless="jmx.present"/>
<exclude
name="org/apache/avalon/phoenix/components/manager/rmiadaptor/*.java"
unless="jmx.present"/>
+ <exclude name="org/apache/avalon/phoenix/components/manager/Target.java"
+ unless="jmx.present"/>
+ <exclude
name="org/apache/avalon/phoenix/components/manager/MBeanInfoBuilder.java"
+ unless="jmx.present"/>
<exclude
name="org/apache/avalon/phoenix/components/manager/MX4JSystemManager.java"
unless="mx4j.present"/>
- <exclude
name="org/apache/avalon/phoenix/components/manager/DefaultManager.java"
- unless="jmx-ri.present"/>
<exclude name="org/apache/avalon/phoenix/launcher/DaemonLauncher.java"
unless="wrapper.present"/>
<exclude
name="org/apache/avalon/phoenix/components/kernel/beanshell/*.java"
@@ -204,19 +202,29 @@
</target>
- <target name="rmic" depends="compile" if="jmx.present"
- description="runs rmic on JMX Adaptor">
+ <!-- Make .xinfo, .mxinfo and manifest automatically for blocks -->
+ <target name="phoenix-xdoclet" depends="compile">
- <rmic base="${build.classes}"
-
classname="org.apache.avalon.phoenix.components.manager.rmiadaptor.RMIAdaptorImpl"
- stubVersion="1.2">
- <classpath refid="project.class.path" />
- </rmic>
+ <mkdir dir="${build.xdoclet}"/>
- </target>
+ <taskdef name="phoenix-blocks"
+ classname="org.apache.avalon.phoenix.tools.xdoclet.PhoenixXDoclet"
+ classpathref="project.class.path"/>
+
+ <phoenix-blocks
+ destdir="${build.xdoclet}"
+ classpathref="project.class.path">
+ <fileset dir="${java.dir}">
+ <include name="**" />
+ </fileset>
+ <blockinfo/>
+ <mxinfo/>
+ </phoenix-blocks>
+
+ </target>
<!-- Creates all the .jar files -->
- <target name="jars" depends="rmic">
+ <target name="jars" depends="phoenix-xdoclet">
<mkdir dir="${build.lib}"/>
@@ -242,14 +250,21 @@
</zipfileset>
</jar>
- <jar jarfile="${build.lib}/phoenix-engine.jar"
- basedir="${build.classes}" >
-
- <include name="org/apache/avalon/phoenix/engine/**"/>
- <include name="org/apache/avalon/phoenix/frontends/**"/>
- <include name="org/apache/avalon/phoenix/components/**"/>
- <include name="org/apache/avalon/phoenix/interfaces/**"/>
- <exclude name="org/apache/avalon/phoenix/launcher/**"/>
+ <jar jarfile="${build.lib}/phoenix-engine.jar">
+ <fileset dir="${build.classes}" >
+ <include name="org/apache/avalon/phoenix/engine/**"/>
+ <include name="org/apache/avalon/phoenix/frontends/**"/>
+ <include name="org/apache/avalon/phoenix/components/**"/>
+ <include name="org/apache/avalon/phoenix/interfaces/**"/>
+ <exclude name="org/apache/avalon/phoenix/launcher/**"/>
+ </fileset>
+ <fileset dir="${build.xdoclet}" >
+ <include name="org/apache/avalon/phoenix/engine/**"/>
+ <include name="org/apache/avalon/phoenix/frontends/**"/>
+ <include name="org/apache/avalon/phoenix/components/**"/>
+ <include name="org/apache/avalon/phoenix/interfaces/**"/>
+ <exclude name="org/apache/avalon/phoenix/launcher/**"/>
+ </fileset>
</jar>
<jar jarfile="${build.lib}/phoenix-bsh-commands.jar">
@@ -260,6 +275,7 @@
</target>
+
<target name="checkstyle" if="do.checkstyle" description="Checkstyle">
<!-- this invocation of checkstyle requires that checkstyle be downloaded
and setup -->
@@ -298,29 +314,6 @@
</checkstyle>
</target>
- <target name="checkstyle-report"
- depends="checkstyle"
- if="do.checkstyle"
- description="Generate Checkstyle Report">
-
- <mkdir dir="${build.reports}/checkstyle"/>
- <property name="checkstyle.pathhack" location="."/>
- <style style="${tools.dir}/etc/checkstyle-frames.xsl"
in="${build.dir}/checkstyle-results.xml"
- out="${build.reports}/checkstyle/delete-me.html">
- <param name="pathhack" expression="${checkstyle.pathhack}"/>
- </style>
-
- </target>
-
- <!-- Create the lite build -->
- <target name="dist-lite-jmx" depends="jars" if="jmx-ri.present">
- <property name="bin.dist.lib" value="${bin.dist.dir}/lib"/>
- <mkdir dir="${bin.dist.lib}"/>
-
- <copy file="${jmxri.jar}" todir="${bin.dist.lib}"/>
- <copy file="${jmxtools.jar}" todir="${bin.dist.lib}"/>
- </target>
-
<!-- Copy BeanShell jars -->
<target name="dist-beanshell" description="Copies Beanshell jars"
if="beanshell.jars">
<copy file="${build.lib}/phoenix-bsh-commands.jar" todir="${bin.dist.lib}"/>
@@ -332,7 +325,7 @@
</target>
<!-- Create the lite build -->
- <target name="dist-lite" depends="dist-lite-jmx"
+ <target name="dist-lite" depends="jars"
description="generates the Phoenix distribution without the javadocs">
<property name="bin.dist.bin" value="${bin.dist.dir}/bin"/>
1.2 +26 -21
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/application/ExportHelper.java
Index: ExportHelper.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/application/ExportHelper.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ExportHelper.java 19 May 2002 02:31:04 -0000 1.1
+++ ExportHelper.java 30 Jul 2002 12:31:16 -0000 1.2
@@ -40,23 +40,37 @@
final String name = metaData.getName();
final ClassLoader classLoader = block.getClass().getClassLoader();
+ final Class[] serviceClasses = new Class[ services.length ];
+
for( int i = 0; i < services.length; i++ )
{
final ServiceDescriptor service = services[ i ];
try
{
- final Class clazz = classLoader.loadClass( service.getName() );
- context.exportObject( name, clazz, block );
+ serviceClasses[ i ] = classLoader.loadClass( service.getName() );
}
catch( final Exception e )
{
final String reason = e.toString();
final String message =
- REZ.getString( "export.error", name, service.getName(), reason
);
+ REZ.getString( "bad-mx-service.error", name, service.getName(),
reason );
getLogger().error( message );
throw new CascadingException( message, e );
}
}
+
+ try
+ {
+ context.exportObject( name, serviceClasses, block );
+ }
+ catch( final Exception e )
+ {
+ final String message =
+ REZ.getString( "export.error", name, e );
+ getLogger().error( message );
+ throw new CascadingException( message, e );
+ }
+
}
/**
@@ -67,25 +81,16 @@
final BlockMetaData metaData,
final Object block )
{
- final ServiceDescriptor[] services =
metaData.getBlockInfo().getManagementAccessPoints();
final String name = metaData.getName();
- final ClassLoader classLoader = block.getClass().getClassLoader();
-
- for( int i = 0; i < services.length; i++ )
+ try
{
- final ServiceDescriptor service = services[ i ];
- try
- {
- final Class clazz = classLoader.loadClass( service.getName() );
- context.unexportObject( name, clazz );
- }
- catch( final Exception e )
- {
- final String reason = e.toString();
- final String message =
- REZ.getString( "unexport.error", name, service.getName(),
reason );
- getLogger().error( message );
- }
+ context.unexportObject( name );
+ }
+ catch( final Exception e )
+ {
+ final String message =
+ REZ.getString( "unexport.error", name, e );
+ getLogger().error( message );
}
}
}
1.25 +3 -2
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/application/Resources.properties
Index: Resources.properties
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/application/Resources.properties,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -r1.24 -r1.25
--- Resources.properties 13 Jul 2002 19:36:57 -0000 1.24
+++ Resources.properties 30 Jul 2002 12:31:16 -0000 1.25
@@ -12,8 +12,9 @@
lifecycle-fail.error=Block named "{0}" failed to pass through the
{1,choice,0#Creation|1#Logger
initialization|2#Contextualization|3#Composing|4#Configuration|5#Parameterizing|6#Initialization|7#Starting|8#Stopping|9#Disposing|10#Destruction}
stage. (Reason: {2}).
-export.error=Block named "{0}" failed to have the "{1}" management service exposed
to management system. (Reason: {2})
-unexport.error=Block named "{0}" failed to have the "{1}" management service
removed from management system. (Reason: {2})
+bad-mx-service.error=Block named "{0}" failed to have the "{1}" management service
exposed to management system as class could not be loaded.
+export.error=Unable to export Block named "{0}" to management system.
+unexport.error=Unable to unexport Block named "{0}" from management system.
helper.isa-blocklistener.error=Warning: Using deprecated BlockListener interface
for listener named "{0}" with classname "{1}".
1.75 +4 -2
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/embeddor/DefaultEmbeddor.java
Index: DefaultEmbeddor.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/embeddor/DefaultEmbeddor.java,v
retrieving revision 1.74
retrieving revision 1.75
diff -u -r1.74 -r1.75
--- DefaultEmbeddor.java 26 Jul 2002 09:49:21 -0000 1.74
+++ DefaultEmbeddor.java 30 Jul 2002 12:31:16 -0000 1.75
@@ -417,6 +417,7 @@
* until setupComponents() is called.
*/
private synchronized void createComponents()
+ throws Exception
{
try
{
@@ -428,11 +429,12 @@
m_entries[ i ].setObject( object );
}
}
- catch( Exception e )
+ catch( final Exception e )
{
final String message =
REZ.getString( "embeddor.error.createComponents.failed" );
getLogger().fatalError( message, e );
+ throw new Exception( message, e );
}
}
@@ -636,7 +638,7 @@
final SystemManager systemManager =
(SystemManager)getServiceManager().lookup( SystemManager.ROLE );
- SystemManager componentManager = systemManager.getSubContext( null,
"component" );
+ final SystemManager componentManager = systemManager.getSubContext( null,
"component" );
componentManager.unregister( ManagementRegistration.EMBEDDOR.getName() );
1.25 +3 -3
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/kernel/DefaultApplicationContext.java
Index: DefaultApplicationContext.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/kernel/DefaultApplicationContext.java,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -r1.24 -r1.25
--- DefaultApplicationContext.java 26 Jul 2002 09:49:21 -0000 1.24
+++ DefaultApplicationContext.java 30 Jul 2002 12:31:16 -0000 1.25
@@ -136,11 +136,11 @@
* @param object the actual object to export
*/
public void exportObject( final String name,
- final Class service,
+ final Class[] services,
final Object object )
throws Exception
{
- m_blockManager.register( name, object, new Class[]{service} );
+ m_blockManager.register( name, object, services );
}
/**
@@ -149,7 +149,7 @@
* @param name the name of object to unexport
* @param service the interface of object with which to unexport
*/
- public void unexportObject( final String name, final Class service )
+ public void unexportObject( final String name )
throws Exception
{
m_blockManager.unregister( name );
1.3 +206 -67
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/AbstractJMXManager.java
Index: AbstractJMXManager.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/AbstractJMXManager.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- AbstractJMXManager.java 26 Jul 2002 09:49:21 -0000 1.2
+++ AbstractJMXManager.java 30 Jul 2002 12:31:16 -0000 1.3
@@ -7,15 +7,17 @@
*/
package org.apache.avalon.phoenix.components.manager;
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import java.util.Set;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
+import javax.management.modelmbean.ModelMBean;
+import javax.management.modelmbean.ModelMBeanInfo;
import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources;
-import org.apache.avalon.phoenix.components.kernel.DefaultKernel;
-import org.apache.avalon.phoenix.components.kernel.DefaultKernelMBean;
import org.apache.avalon.phoenix.interfaces.ManagerException;
-import org.apache.excalibur.baxter.JavaBeanMBean;
/**
* An abstract class via which JMX Managers can extend.
@@ -29,9 +31,8 @@
{
private static final Resources REZ =
ResourceManager.getPackageResources( AbstractJMXManager.class );
-
+ private MBeanInfoBuilder topicBuilder;
private MBeanServer m_mBeanServer;
-
private String m_domain = "Phoenix";
public void initialize()
@@ -41,6 +42,9 @@
final MBeanServer mBeanServer = createMBeanServer();
setMBeanServer( mBeanServer );
+
+ topicBuilder = new MBeanInfoBuilder();
+ setupLogger( topicBuilder );
}
public void start()
@@ -76,10 +80,9 @@
{
try
{
- final Object mBean = createMBean( object, interfaces );
- final ObjectName objectName = createObjectName( name );
- getMBeanServer().registerMBean( mBean, objectName );
- return mBean;
+ final Target target = createTarget( name, object, interfaces );
+ exportTarget( target );
+ return target;
}
catch( final Exception e )
{
@@ -90,63 +93,6 @@
}
/**
- * Create MBean for specified object (and interfaces if any).
- *
- * @param object the object
- * @param interfaces the interfaces to export
- * @return the MBean
- * @throws ManagerException if unable to create MBean
- */
- private Object createMBean( final Object object,
- final Class[] interfaces )
- throws ManagerException
- {
- if( null != interfaces )
- {
- return new JavaBeanMBean( object, interfaces );
- }
- else
- {
- return createMBean( object );
- }
- }
-
- /**
- * Create JMX name for object.
- *
- * @param name the name of object
- * @return the {@link ObjectName} representing object
- * @throws MalformedObjectNameException if malformed name
- */
- private ObjectName createObjectName( final String name )
- throws MalformedObjectNameException
- {
- return new ObjectName( getDomain() + ":" + name );
- }
-
- /**
- * Create a MBean for specified object.
- * The following policy is used top create the MBean...
- *
- * @param object the object to create MBean for
- * @return the MBean to be exported
- * @throws ManagerException if an error occurs
- */
- private Object createMBean( final Object object )
- throws ManagerException
- {
- //HACK: ugly Testing hack!!
- if( object instanceof DefaultKernel )
- {
- return new DefaultKernelMBean( (DefaultKernel)object );
- }
- else
- {
- return new JavaBeanMBean( object );
- }
- }
-
- /**
* Stop the exported object from being managed.
*
* @param name the name of object
@@ -210,4 +156,197 @@
*/
protected abstract MBeanServer createMBeanServer()
throws Exception;
+
+ /**
+ * Creates a target that can then be exported for management. A topic is created
+ * for each interface and for topics specified in the mxinfo file, if present
+ *
+ * @param name name of the target
+ * @param object managed object
+ * @param interfaces interfaces to be exported
+ * @return the management target
+ */
+ protected Target createTarget( final String name,
+ final Object object,
+ Class[] interfaces )
+ {
+ final Target target = new Target( name, object );
+ try
+ {
+ topicBuilder.build( target, object.getClass(), interfaces );
+ }
+ catch( final Exception e )
+ {
+ getLogger().debug( e.getMessage(), e );
+ }
+
+ return target;
+ }
+
+ /**
+ * Exports the target to the management repository. This is done by exporting
+ * each topic in the target.
+ *
+ * @param target the management target
+ */
+ protected void exportTarget( final Target target )
+ throws Exception
+ {
+ // loop through each topic and export it
+ final Set topicNames = target.getTopicNames();
+ final Iterator i = topicNames.iterator();
+ while( i.hasNext() )
+ {
+ final String topicName = (String)i.next();
+ final ModelMBeanInfo topic = target.getTopic( topicName );
+ final String targetName = target.getName();
+ final Object managedResource = target.getManagedResource();
+ Object targetObject = managedResource;
+ if( topic.getMBeanDescriptor().getFieldValue( "proxyClassName" ) !=
null )
+ {
+ targetObject = createManagementProxy( topic, managedResource );
+ }
+
+ // use a proxy adapter class to manage object
+ exportTopic( topic,
+ targetObject,
+ targetName );
+ }
+ }
+
+ /**
+ * Exports the topic to the management repository. The name of the topic in the
+ * management repository will be the target name + the topic name
+ *
+ * @param topic the descriptor for the topic
+ * @param target to be managed
+ * @param targetName the target's name
+ */
+ protected Object exportTopic( final ModelMBeanInfo topic,
+ final Object target,
+ final String targetName )
+ throws Exception
+ {
+ final Object mBean = createMBean( topic, target );
+ final ObjectName objectName = createObjectName( targetName + ",topic=" +
topic.getDescription() );
+ getMBeanServer().registerMBean( mBean, objectName );
+
+ // debugging stuff.
+ /*
+ ModelMBean modelMBean = (ModelMBean)mBean;
+ ModelMBeanInfo modelMBeanInfo = (ModelMBeanInfo)modelMBean.getMBeanInfo();
+ MBeanAttributeInfo[] attList = modelMBeanInfo.getAttributes();
+ for( int i = 0; i < attList.length; i++ )
+ {
+ ModelMBeanAttributeInfo mbai = (ModelMBeanAttributeInfo)attList[ i ];
+ Descriptor d = mbai.getDescriptor();
+ String[] fieldNames = d.getFieldNames();
+ for( int j = 0; j < fieldNames.length; j++ )
+ {
+ String fieldName = fieldNames[ j ];
+ System.out.println( "Field name = " + fieldName +
+ " / value = " + d.getFieldValue( fieldName ) +
+ "::" +mbai.getType() + " value " +
+ modelMBean.getAttribute( mbai.getName() ) + " for " +
mbai.getName() );
+ }
+ }
+ */
+
+ return mBean;
+ }
+
+ /**
+ * Create a MBean for specified object.
+ * The following policy is used top create the MBean...
+ *
+ * @param target the object to create MBean for
+ * @return the MBean to be exported
+ * @throws ManagerException if an error occurs
+ */
+ private Object createMBean( final ModelMBeanInfo topic,
+ final Object target )
+ throws ManagerException
+ {
+ final String className = topic.getClassName();
+ // Load the ModelMBean implementation class
+ Class clazz;
+ try
+ {
+ clazz = Class.forName( className );
+ }
+ catch( Exception e )
+ {
+ final String message =
+ REZ.getString( "jmxmanager.error.mbean.load.class",
+ className );
+ getLogger().error( message, e );
+ throw new ManagerException( message, e );
+ }
+
+ // Create a new ModelMBean instance
+ ModelMBean mbean = null;
+ try
+ {
+ mbean = (ModelMBean)clazz.newInstance();
+ mbean.setModelMBeanInfo( topic );
+ }
+ catch( final Exception e )
+ {
+ final String message =
+ REZ.getString( "jmxmanager.error.mbean.instantiate",
+ className );
+ getLogger().error( message, e );
+ throw new ManagerException( message, e );
+ }
+
+ // Set the managed resource (if any)
+ try
+ {
+ if( null != target )
+ {
+ mbean.setManagedResource( target, "ObjectReference" );
+ }
+ }
+ catch( Exception e )
+ {
+ final String message =
+ REZ.getString( "jmxmanager.error.mbean.set.resource",
+ className );
+ getLogger().error( message, e );
+ throw new ManagerException( message, e );
+ }
+
+ return mbean;
+ }
+
+ /**
+ * Create JMX name for object.
+ *
+ * @param name the name of object
+ * @return the {@link ObjectName} representing object
+ * @throws MalformedObjectNameException if malformed name
+ */
+ private ObjectName createObjectName( final String name )
+ throws MalformedObjectNameException
+ {
+ return new ObjectName( getDomain() + ":" + name );
+ }
+
+ /**
+ * Instantiates a proxy management object for the target object
+ *
+ * this should move out of bridge and into Registry, it isn't specifically for
jmx
+ */
+ private Object createManagementProxy( final ModelMBeanInfo topic,
+ final Object managedObject )
+ throws Exception
+ {
+ final String proxyClassname =
(String)topic.getMBeanDescriptor().getFieldValue( "proxyClassName" );
+ final ClassLoader classLoader = managedObject.getClass().getClassLoader();
+ final Class proxyClass = classLoader.loadClass( proxyClassname );
+ final Class[] argTypes = new Class[]{Object.class};
+ final Object[] argValues = new Object[]{managedObject};
+ final Constructor constructor = proxyClass.getConstructor( argTypes );
+ return constructor.newInstance( argValues );
+ }
}
1.13 +2 -1
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/MX4JSystemManager.java
Index: MX4JSystemManager.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/MX4JSystemManager.java,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -r1.12 -r1.13
--- MX4JSystemManager.java 26 Jul 2002 09:49:21 -0000 1.12
+++ MX4JSystemManager.java 30 Jul 2002 12:31:16 -0000 1.13
@@ -25,7 +25,8 @@
* @author <a href="mailto:[EMAIL PROTECTED]">Huw Roberts</a>
*/
public class MX4JSystemManager
- extends AbstractJMXManager implements Configurable
+ extends AbstractJMXManager
+ implements Configurable
{
private static final int DEFAULT_HTTPADAPTER_PORT =
Integer.getInteger( "phoenix.adapter.http", 8082 ).intValue();
1.5 +16 -0
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/Resources.properties
Index: Resources.properties
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/Resources.properties,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- Resources.properties 5 Mar 2002 12:39:22 -0000 1.4
+++ Resources.properties 30 Jul 2002 12:31:16 -0000 1.5
@@ -1,4 +1,18 @@
manager.error.interfaces.null=Interfaces parameter is null for object {0}.
+mxinfo.error.missing.method=Can't find method {0}.
+mxinfo.error.missing.property=Can't find property named: {0}.
+mxinfo.error.proxy=Failed to create proxy topic for {0}.
+jmxmanager.error.mbean.set.resource=Failed to set managed resource on MBean of
class {0}.
+jmxmanager.error.mbean.instantiate=Failed to instantiate MBean of class {0}.
+mxinfo.debug.building.proxy.topic=Building proxyTopic {0}.
+mxinfo.error.topic=Failed to create topic for {0}.
+mxinfo.debug.adding.topic=Adding topic {0}.
+subcontext.error.no.subcontext=Cannot request a named subcontext here.
+subcontext.error.type.null=type cannot be null or empty
+mxinfo.error.introspect=Can't introspect class {0}.
+mxinfo.error.target=Failed to create topics for target {0}.
+mxinfo.debug.found.mxinfo=mxinfo found for {0}.
+mxinfo.debug.building=Building target for {0}.
manager.error.unregister.noentry=No entry with name {0}.
manager.error.verify.notinterface=Can not export {0} for management as it is not an
interface.
manager.error.verify.notinstance=Can not export {0} for management as object does
not implement interface.
@@ -7,4 +21,6 @@
jmxmanager.error.export.fail=Unable to export {0} as mBean.
jmxmanager.error.unexport.fail=Unable to unexport {0} as mBean.
+jmxmanager.error.mbean.load.class=Failed to find MBean of class {0} in classpath.
+mxinfo.error.file=Failed to read mxinfo file {0}.
jmxmanager.error.mbeanserver.create=Failed to create MBean Server of class {0}.
1.4 +9 -5
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/SubContext.java
Index: SubContext.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/SubContext.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- SubContext.java 26 Jul 2002 09:49:21 -0000 1.3
+++ SubContext.java 30 Jul 2002 12:31:16 -0000 1.4
@@ -8,6 +8,8 @@
package org.apache.avalon.phoenix.components.manager;
import java.util.HashMap;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.phoenix.interfaces.ManagerException;
import org.apache.avalon.phoenix.interfaces.SystemManager;
@@ -21,14 +23,14 @@
class SubContext
implements SystemManager
{
+ private static final Resources REZ =
+ ResourceManager.getPackageResources( SubContext.class );
+
private static final String EMPTY_STRING = "";
private final HashMap m_subcontexts = new HashMap();
-
private final SystemManager m_parent;
-
private final String m_name;
-
private final String m_type;
/**
@@ -114,12 +116,14 @@
{
if( null == type || EMPTY_STRING.equals( type ) )
{
- final String message = "type cannot be null or empty";
+ final String message =
+ REZ.getString( "subcontext.error.no.subcontext" );
throw new ManagerException( message );
}
else if( null != name && this.m_type == null )
{
- final String message = "cannot request a named subcontext here";
+ final String message =
+ REZ.getString( "subcontext.error.no.subcontext" );
throw new ManagerException( message );
}
1.1
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/MBeanInfoBuilder.java
Index: MBeanInfoBuilder.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.avalon.phoenix.components.manager;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import javax.management.Descriptor;
import javax.management.MBeanParameterInfo;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanNotificationInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;
import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.phoenix.tools.configuration.ConfigurationBuilder;
import org.xml.sax.InputSource;
/**
* An MBeanInfoBuilder is responsible for building <code>ManagementTopic</code>
* objects from Configuration objects. The format for Configuration object
* is specified in the MxInfo specification. The information is loaded into
* the Target structure.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Huw Roberts</a>
* @version $Revision: 1.1 $ $Date: 2002/07/30 12:31:16 $
*/
public final class MBeanInfoBuilder extends AbstractLogEnabled
{
private static final Resources REZ =
ResourceManager.getPackageResources( MBeanInfoBuilder.class );
private static final String REQ_MODEL_MBEAN =
"javax.management.modelmbean.RequiredModelMBean";
public void build( final Target target,
final Class managedClass,
final Class[] interfaces )
throws ConfigurationException
{
final String notice =
REZ.getString( "mxinfo.debug.building", managedClass.getName() );
getLogger().debug( notice );
// if the managed class has an mxinfo file, build the target from it
// (this includes any proxies)
Configuration config = loadMxInfo( managedClass );
if( null != config )
{
final String message =
REZ.getString( "mxinfo.debug.found.mxinfo", managedClass.getName() );
getLogger().debug( message );
buildFromMxInfo( target, managedClass, config );
}
// for each interface, generate a topic from its mxinfo file
// or through introspection
for( int i = 0, j = interfaces.length; i < j; i++ )
{
try
{
config = loadMxInfo( interfaces[ i ] );
if( config == null )
{
buildFromIntrospection( target, interfaces[ i ] );
}
else
{
buildFromMxInfo( target, managedClass, config );
}
}
catch( final Exception e )
{
final String message =
REZ.getString( "mxinfo.error.target", target.getName() );
getLogger().error( message, e );
throw new ConfigurationException( message );
}
}
}
/**
* Create a <code>ModelMBeanInfoSupport</code> object for specified classname
from
* specified configuration data.
*/
private void buildFromMxInfo( final Target target,
final Class managedClass,
final Configuration config )
throws ConfigurationException
{
BeanInfo beanInfo;
try
{
beanInfo = Introspector.getBeanInfo( managedClass );
}
catch( final Exception e )
{
final String message =
REZ.getString( "mxinfo.error.introspect", managedClass.getName() );
throw new ConfigurationException( message, e );
}
// load each topic
final Configuration[] topicsConfig = config.getChildren( "topic" );
for( int i = 0; i < topicsConfig.length; i++ )
{
final ModelMBeanInfoSupport topic =
buildTopic( topicsConfig[ i ], beanInfo );
target.addTopic( topic );
}
// load each proxy
final Configuration[] proxysConfig = config.getChildren( "proxy" );
for( int i = 0; i < proxysConfig.length; i++ )
{
final ModelMBeanInfoSupport topic =
buildProxyTopic( proxysConfig[ i ], managedClass );
target.addTopic( topic );
}
}
/**
* Builds a topic based on introspection of the interface
*/
private void buildFromIntrospection( final Target target,
final Class interfaceClass )
throws ConfigurationException
{
try
{
final BeanInfo beanInfo =
Introspector.getBeanInfo( interfaceClass );
// do the methods
final MethodDescriptor[] methods = beanInfo.getMethodDescriptors();
final ArrayList operations = new ArrayList();
for( int j = 0; j < methods.length; j++ )
{
// skip getters and setters
final String name = methods[ j ].getName();
if( !(name.startsWith( "get" ) ||
name.startsWith( "set" ) ||
name.startsWith( "is" )) )
{
operations.add( buildOperationInfo( methods[ j ], null ) );
}
}
final ModelMBeanOperationInfo[] operationList =
(ModelMBeanOperationInfo[])
operations.toArray( new ModelMBeanOperationInfo[ 0 ] );
// do the attributes
final PropertyDescriptor[] propertys = beanInfo.getPropertyDescriptors();
final ModelMBeanAttributeInfo[] attributes =
new ModelMBeanAttributeInfo[ propertys.length ];
for( int j = 0; j < propertys.length; j++ )
{
attributes[ j ] = buildAttributeInfo( propertys[ j ], null );
}
final ModelMBeanConstructorInfo[] constructors =
new ModelMBeanConstructorInfo[ 0 ];
final ModelMBeanNotificationInfo[] notifications =
new ModelMBeanNotificationInfo[ 0 ];
final String shortName = getShortName( interfaceClass.getName() );
final ModelMBeanInfoSupport topic =
new ModelMBeanInfoSupport( REQ_MODEL_MBEAN,
shortName,
attributes,
constructors,
operationList,
notifications );
// add it to target
final String message = REZ.getString( "mxinfo.debug.adding.topic",
topic.getDescription() );
getLogger().debug( message );
target.addTopic( topic );
}
catch( final Exception e )
{
final String message =
REZ.getString( "mxinfo.error.topic", interfaceClass );
throw new ConfigurationException( message, e );
}
}
/**
* A utility method to build a <code>ModelMBeanInfoSupport</code>
* object from specified configuration and BeanInfo.
*
* @return the created ModelMBeanInfoSupport
* @throws ConfigurationException if an error occurs
*/
private ModelMBeanInfoSupport buildTopic( final Configuration config,
final BeanInfo beanInfo )
throws ConfigurationException
{
final ModelMBeanAttributeInfo[] attributes =
buildAttributeInfos( config, beanInfo );
final ModelMBeanOperationInfo[] operations =
buildOperationInfos( config, beanInfo );
final ModelMBeanConstructorInfo[] constructors =
new ModelMBeanConstructorInfo[ 0 ];
final ModelMBeanNotificationInfo[] notifications =
new ModelMBeanNotificationInfo[ 0 ];
final String name = config.getAttribute( "name" );
final ModelMBeanInfoSupport topic =
new ModelMBeanInfoSupport( REQ_MODEL_MBEAN,
name,
attributes,
constructors,
operations,
notifications );
return topic;
}
/**
* Build a topic for a proxy management class
*
* @param proxyTagConfig
* @param managedClass
* @return
*/
private ModelMBeanInfoSupport buildProxyTopic( final Configuration
proxyTagConfig,
final Class managedClass )
throws ConfigurationException
{
try
{
final String proxyName = proxyTagConfig.getAttribute( "name" );
final String message = REZ.getString(
"mxinfo.debug.building.proxy.topic", proxyName );
getLogger().debug( message );
final Class proxyClass = managedClass.getClassLoader().loadClass(
proxyName );
final Configuration classConfig = loadMxInfo( proxyClass );
final Configuration topicConfig = classConfig.getChild( "topic" );
final BeanInfo info = Introspector.getBeanInfo( proxyClass );
final ModelMBeanInfoSupport topic = buildTopic( topicConfig, info );
final Descriptor mBeanDescriptor = topic.getMBeanDescriptor();
mBeanDescriptor.setField( "proxyClassName", proxyName );
topic.setMBeanDescriptor( mBeanDescriptor );
return topic;
}
catch( final Exception e )
{
if( e instanceof ConfigurationException )
{
throw (ConfigurationException)e;
}
else
{
final String message = REZ.getString( "mxinfo.error.proxy",
managedClass.getName() );
throw new ConfigurationException( message );
}
}
}
/**
* Builds the management attributes from the configuration
*
* @param config topic's configuration element
* @param info managed class' BeanInfo from introspector
* @throws ConfigurationException
*/
private ModelMBeanAttributeInfo[] buildAttributeInfos( final Configuration
config,
final BeanInfo info )
throws ConfigurationException
{
final Configuration[] attributesConfig = config.getChildren( "attribute" );
final ModelMBeanAttributeInfo[] attributeList =
new ModelMBeanAttributeInfo[ attributesConfig.length ];
final PropertyDescriptor[] propertys = info.getPropertyDescriptors();
for( int i = 0; i < attributesConfig.length; i++ )
{
final Configuration attribute = attributesConfig[ i ];
final String name = attribute.getAttribute( "name" );
final PropertyDescriptor property =
getPropertyDescriptor( name, propertys );
attributeList[ i ] = buildAttributeInfo( property, attribute );
}
return attributeList;
}
/**
* Builds a management config
*
* @param property from BeanInfo
* @param config configuration element - can be null, in which case defaults are
used
*/
private ModelMBeanAttributeInfo buildAttributeInfo( final PropertyDescriptor
property,
final Configuration config )
{
final String name = property.getName();
final Method readMethod = property.getReadMethod();
final Method writeMethod = property.getWriteMethod();
final String type = property.getPropertyType().getName();
String description = property.getDisplayName();
boolean isReadable = (readMethod != null);
boolean isWriteable = (writeMethod != null);
if( config != null )
{
// use config info, or BeanInfo if config info is missing
description =
config.getAttribute( "description", description );
// defaults to true if there is a read method, otherwise defaults to
false
isReadable =
config.getAttributeAsBoolean( "isReadable", true ) && isReadable;
// defaults to true if there is a write method, otherwise defaults to
false
isWriteable =
config.getAttributeAsBoolean( "isWriteable", true ) && isWriteable;
}
final boolean isIs =
(readMethod != null) && readMethod.getName().startsWith( "is" );
final ModelMBeanAttributeInfo info =
new ModelMBeanAttributeInfo( name, type, description, isReadable,
isWriteable, isIs );
// additional info needed for modelMbean to work
final Descriptor descriptor = info.getDescriptor();
descriptor.setField( "currencyTimeLimit", new Integer( 1 ) );
if( isReadable )
{
descriptor.setField( "getMethod", readMethod.getName() );
}
if( isWriteable )
{
descriptor.setField( "setMethod", writeMethod.getName() );
}
info.setDescriptor( descriptor );
return info;
}
/**
* Returns the PropertyDescriptor with the specified name from the array
*/
private PropertyDescriptor getPropertyDescriptor( final String name,
final PropertyDescriptor[]
propertys )
throws ConfigurationException
{
for( int i = 0; i < propertys.length; i++ )
{
if( propertys[ i ].getName().equals( name ) )
{
return propertys[ i ];
}
}
final String message =
REZ.getString( "mxinfo.error.missing.property", name );
throw new ConfigurationException( message );
}
/**
* Builds the management operations
*
* @param config topic configuration element to build from
* @param info BeanInfo for managed class from introspector
* @throws ConfigurationException
*/
private ModelMBeanOperationInfo[] buildOperationInfos( final Configuration
config,
final BeanInfo info )
throws ConfigurationException
{
final Configuration[] operationsConfig =
config.getChildren( "operation" );
final ModelMBeanOperationInfo[] operations =
new ModelMBeanOperationInfo[ operationsConfig.length ];
final MethodDescriptor[] methodDescriptors = info.getMethodDescriptors();
for( int i = 0; i < operationsConfig.length; i++ )
{
final Configuration operation = operationsConfig[ i ];
final String name = operation.getAttribute( "name" );
final MethodDescriptor method =
getMethodDescriptor( name, methodDescriptors );
operations[ i ] = buildOperationInfo( method, operation );
}
return operations;
}
/**
* Builds an operation descriptor from a configuration node
*
* @param method method as returned from beanInfo
* @param config configuration element, can be null
* @throws ConfigurationException if the configiration has the wrong elements
* @return the operation descriptor based on the configuration
*/
private ModelMBeanOperationInfo buildOperationInfo( final MethodDescriptor
method,
final Configuration config )
throws ConfigurationException
{
ModelMBeanOperationInfo info;
if( config == null )
{
info = new ModelMBeanOperationInfo( method.getDisplayName(),
method.getMethod() );
}
else
{
final String name = method.getName();
final String type = method.getMethod().getReturnType().getName();
final String description =
config.getAttribute( "description",
method.getDisplayName() );
final int impact =
config.getAttributeAsInteger( "impact",
ModelMBeanOperationInfo.UNKNOWN );
final Configuration[] paramConfig =
config.getChildren( "param" );
final MBeanParameterInfo[] params =
new MBeanParameterInfo[ paramConfig.length ];
for( int i = 0; i < paramConfig.length; i++ )
{
params[ i ] = buildParameterInfo( paramConfig[ i ] );
}
info = new ModelMBeanOperationInfo( name, description, params, type,
impact );
}
// additional info needed for modelMbean to work
final Descriptor descriptor = info.getDescriptor();
descriptor.setField( "currencyTimeLimit", new Integer( 1 ) );
info.setDescriptor( descriptor );
return info;
}
/**
* Returns the MethodDescriptor with the specified name from the array
*/
private MethodDescriptor getMethodDescriptor( final String name,
final MethodDescriptor[] methods )
throws ConfigurationException
{
for( int i = 0; i < methods.length; i++ )
{
if( methods[ i ].getName().equals( name ) )
{
return methods[ i ];
}
}
final String message = REZ.getString( "mxinfo.error.missing.method", name );
throw new ConfigurationException( message );
}
/**
* Builds the param descriptor from the configuration data
*
* @throws ConfigurationException if configuration not structured corretly
* @return the descriptor
*/
private MBeanParameterInfo buildParameterInfo( Configuration paramConfig )
throws ConfigurationException
{
final String name = paramConfig.getAttribute( "name" );
final String description = paramConfig.getAttribute( "description" );
final String type = paramConfig.getAttribute( "type" );
return new MBeanParameterInfo( name, type, description );
}
/**
* Returns the configuration for the class or null if there is no mxinfo
* file for it.
*
* @param clazz the class to load the configuration for
* @throws ConfigurationException
* @return the configuration file, or null if none exists
*/
private Configuration loadMxInfo( final Class clazz )
throws ConfigurationException
{
final String mxinfoName =
"/" + clazz.getName().replace( '.', '/' ) + ".mxinfo";
try
{
InputStream stream = clazz.getResourceAsStream( mxinfoName );
if( null == stream )
{
return null;
}
final InputSource source = new InputSource( stream );
// build with validation against DTD
return ConfigurationBuilder.build( source, true );
}
catch( Exception e )
{
final String message =
REZ.getString( "mxinfo.error.file", mxinfoName );
getLogger().error( message, e );
throw new ConfigurationException( message );
}
}
/**
* Returns the class name without the package name
*/
private String getShortName( final String className )
{
return className.substring( className.lastIndexOf( '.' ) + 1 );
}
}
1.1
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/components/manager/Target.java
Index: Target.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.avalon.phoenix.components.manager;
import java.util.HashMap;
import java.util.Set;
import javax.management.modelmbean.ModelMBeanInfo;
/**
* It reprensents a managed object in the managegement space. It is a container for
* zero or more management topics and zero or more management lists.
*
* @author robertsh
* @version $$
*/
public class Target
{
private final String m_name;
private final HashMap m_topics;
private final Object m_managedResource;
/**
* Creates new Target
*
* @param name the name for the target
* @param managedResource the object that this managedResource represents in the
management hierarchy
*/
public Target( final String name,
final Object managedResource )
{
m_name = name;
m_managedResource = managedResource;
m_topics = new HashMap();
}
/**
* Returns the name of the Target
* @return the name
*/
public String getName()
{
return m_name;
}
/**
* Returns the object managed by the target
*
* @return the managed object
*/
public Object getManagedResource()
{
return m_managedResource;
}
/**
* Topics are a set of attributes and operations relevant to a particular
* aspect of an object. A Target must typically have at least one topic in
* order to be manageable.
*
* @param topic
*/
public void addTopic( final ModelMBeanInfo topic )
{
m_topics.put( topic.getDescription(), topic );
}
/**
* Removes a topic for this target
* @param name the name of the topic to remove
*/
public void removeTopic( final String name )
{
m_topics.remove( name );
}
/**
* Gets a topic for this Target
*
* @param name the name of the topic
* @return the topic of that name
*/
public ModelMBeanInfo getTopic( final String name )
{
return (ModelMBeanInfo)m_topics.get( name );
}
/**
* Returns the Set of topics for this Target
*
* @return the <CODE>Set</CODE> of topic names
*/
public Set getTopicNames()
{
return m_topics.keySet();
}
}
1.14 +3 -4
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/interfaces/ApplicationContext.java
Index: ApplicationContext.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/interfaces/ApplicationContext.java,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- ApplicationContext.java 14 Jul 2002 02:27:16 -0000 1.13
+++ ApplicationContext.java 30 Jul 2002 12:31:17 -0000 1.14
@@ -32,19 +32,18 @@
* and using the specified name.
*
* @param name the name of object to export
- * @param interfaceClass the interface of object with which to export
+ * @param interfaceClasses the interface of object with which to export
* @param object the actual object to export
*/
- void exportObject( String name, Class interfaceClass, Object object )
+ void exportObject( String name, Class[] interfaceClasses, Object object )
throws Exception;
/**
* Unexport specified object from management system.
*
* @param name the name of object to unexport
- * @param interfaceClass the interface of object with which to unexport
*/
- void unexportObject( String name, Class interfaceClass )
+ void unexportObject( String name )
throws Exception;
/**
1.9 +1 -0
jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/interfaces/SystemManager.java
Index: SystemManager.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/interfaces/SystemManager.java,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -r1.8 -r1.9
--- SystemManager.java 13 Jul 2002 10:15:13 -0000 1.8
+++ SystemManager.java 30 Jul 2002 12:31:17 -0000 1.9
@@ -63,6 +63,7 @@
* is created.
*
* @param name name of the object in the parent context that will own this one
+ * @param type of objects that will be managed in this context
* @throws ManagerException if context cannot be created or retrieved
* @return the subcontext with the specified name
*/
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>