mcconnell    2002/08/08 14:30:18

  Modified:    assembly/demo/src/java/org/apache/excalibur/playground
                        EmbeddedDemo.java
               assembly/src/etc kernel.xml
               assembly/src/java/org/apache/excalibur/merlin/assembly
                        ContainerManager.java
               assembly/src/java/org/apache/excalibur/merlin/container
                        Container.java DefaultContainer.java
                        StateEvent.java
               assembly/src/java/org/apache/excalibur/merlin/kernel
                        DefaultKernel.java Kernel.java
               assembly/src/java/org/apache/excalibur/merlin/model/builder
                        ContainerBuilder.java
  Log:
  General enhancements to sysnc/async management.
  
  Revision  Changes    Path
  1.8       +9 -23     
jakarta-avalon-excalibur/assembly/demo/src/java/org/apache/excalibur/playground/EmbeddedDemo.java
  
  Index: EmbeddedDemo.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/demo/src/java/org/apache/excalibur/playground/EmbeddedDemo.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- EmbeddedDemo.java 8 Aug 2002 10:02:34 -0000       1.7
  +++ EmbeddedDemo.java 8 Aug 2002 21:30:16 -0000       1.8
  @@ -73,6 +73,7 @@
           // Create the kernel with a root container named "work".
           //
   
  +        getLogger().info("creating kernel");
           m_kernel = new DefaultKernel( );
           DefaultContext context = new DefaultContext( m_context );
           context.put( DefaultKernel.PATH_KEY, "work" );
  @@ -84,6 +85,7 @@
           // Get the root container from the kernel we just created.
           //
      
  +        getLogger().debug("retriving root");
           Container root = m_kernel.getRootContainer();
   
           //
  @@ -92,18 +94,8 @@
           // add this to the root container to define another new sub-container.
           //
   
  -        ReferenceDescriptor containerInterface = 
  -          new ReferenceDescriptor( 
"org.apache.excalibur.merlin.container.Container" );
  -        ContainerDescriptor cd = (ContainerDescriptor) manager.getProfile( 
containerInterface );
  -        Container container;
  -        if( cd != null )
  -        {
  -            container = root.createSubContainer( "admin", cd, new 
ClasspathDescriptor() );
  -        }
  -        else
  -        {
  -            throw new IllegalStateException("Manager returned a null container 
profile.");
  -        }
  +        getLogger().debug("adding subcontainer: admin");
  +        Container container = root.createSubContainer( "admin" );
   
           //
           // Add a component to the container programatically.
  @@ -113,12 +105,6 @@
           // container.
           //
   
  -        //DependencyDescriptor descriptor = 
  -        //  new DependencyDescriptor( 
  -        //    "my-component",
  -        //    new ReferenceDescriptor( 
"org.apache.excalibur.playground.SimpleService" ) );
  -        //Profile profile = manager.getProfile( descriptor );
  -
           Profile profile = manager.getProfile( 
             new ReferenceDescriptor( "org.apache.excalibur.playground.SimpleService" 
)  );
   
  @@ -128,9 +114,9 @@
   
           if( profile != null )
           {
  -            getLogger().info( "dynamic profile: " + profile );
  +            getLogger().debug( "dynamic profile: " + profile );
               container.install( new Profile[]{ profile } );
  -            getLogger().info("installation sucessfull" );
  +            getLogger().debug("installation sucessfull" );
           }
           else
           {
  @@ -145,13 +131,13 @@
       public void start() throws Exception
       {
           getLogger().info("starting");
  -        m_kernel.startup();
  +        m_kernel.start();
       }
   
  -    public void stop()
  +    public void stop() throws Exception
       {
           getLogger().info("stopping");
  -        m_kernel.shutdown();
  +        m_kernel.stop();
       }
   
       public void dispose()
  
  
  
  1.27      +8 -5      jakarta-avalon-excalibur/assembly/src/etc/kernel.xml
  
  Index: kernel.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-avalon-excalibur/assembly/src/etc/kernel.xml,v
  retrieving revision 1.26
  retrieving revision 1.27
  diff -u -r1.26 -r1.27
  --- kernel.xml        8 Aug 2002 10:02:34 -0000       1.26
  +++ kernel.xml        8 Aug 2002 21:30:17 -0000       1.27
  @@ -21,7 +21,7 @@
      the corresponds to the name of the logging file.
      -->
   
  -   <logging priority="WARN" target="default">
  +   <logging priority="INFO" target="default">
         <target name="kernel">
           <file location="kernel.log" />
         </target>
  @@ -30,7 +30,7 @@
      <categories priority="INFO">
        <category priority="WARN"  name="logging" />
        <category priority="WARN"  name="loader" />
  -     <category priority="INFO"  name="export" />
  +     <category priority="WARN"  name="export" />
      </categories>
   
      <!--
  @@ -147,7 +147,7 @@
              enabled="true" 
              activation="true">
   
  -           <categories priority="DEBUG"/>
  +           <categories priority="INFO"/>
   
              <configuration>
                <message>This is a custom message.</message>
  @@ -156,10 +156,13 @@
            </component>
   
            <!--
  -         Demonstration of a component that contains an embedded kernel.
  +         Demonstration of a component that contains an embedded kernel that 
programatically
  +         adds a container or two, and a new component type.  To enable this demo - 
change the 
  +         enabled attribute on the component element to true.
            -->
            <component name="embedder" 
  -           class="org.apache.excalibur.playground.EmbeddedDemo" activation="true">
  +           class="org.apache.excalibur.playground.EmbeddedDemo" enabled="false" 
activation="true">
  +           <categories priority="INFO"/>
              <context>
                <import key="avalon:home" name="avalon:home"/>
                <import key="classloader" name="classloader"/>
  
  
  
  1.8       +14 -44    
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/assembly/ContainerManager.java
  
  Index: ContainerManager.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/assembly/ContainerManager.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- ContainerManager.java     8 Aug 2002 10:02:35 -0000       1.7
  +++ ContainerManager.java     8 Aug 2002 21:30:17 -0000       1.8
  @@ -240,16 +240,6 @@
       public void contextualize( Context context ) throws ContextException
       {
           m_context = context;
  -
  -        try
  -        {
  -            m_categories = (CategoriesDescriptor) context.get( 
CATEGORIES_DESCRIPTOR_KEY );
  -        }
  -        catch( ContextException e )
  -        {
  -            // optional
  -        }
  -
           super.contextualize( context );
       }
   
  @@ -262,24 +252,6 @@
           XMLContainerUtil creator = new XMLContainerUtil();
   
           //
  -        // take care of logging category declarations from the configuration
  -        // if not already supplied under the context
  -        //
  -
  -        if( m_categories == null )
  -        {
  -            Category loader = new Category( "loader", "WARN", null );
  -            Category assembly = new Category( "loader.assembly" );
  -            Category types = new Category( "loader.types" );
  -
  -            m_categories = new CategoriesDescriptor( 
  -              m_name, "INFO", null, new Category[]{ loader, assembly, types } );
  -        }
  -
  -        String path = getPath().replace('/','.').substring(1);
  -        getLoggingManager().addCategories( path, m_categories );
  -
  -        //
           // start internal initialization
           //
   
  @@ -343,8 +315,9 @@
       //======================================================================
   
      /**
  -    * Method invoked by the container's container to signal the activation of 
  -    * all components.
  +    * Method invoked by the managers container to signal the activation of 
  +    * all components.  This method may be invoked multiple times during a 
  +    * the life of the manager.
       * 
       * @exception Exception if a start related error occurs
       */
  @@ -368,10 +341,6 @@
                       getLocalLogger().info( profile + " [activating]"); 
                       resource.access();
                   }
  -                else
  -                {
  -                    getLocalLogger().info( profile + " [already running]"); 
  -                }
               }
               else
               {
  @@ -430,28 +399,30 @@
   
       public ContainerManager createContainerManager( 
                                    String name,
  -                                 ContainerDescriptor descriptor, 
  -                                 ClasspathDescriptor classpath, 
  -                                 Logger logger )
  +                                 ClasspathDescriptor classpath )
       {
           try
           {
               ContainerManager loader = new ContainerManager( this, name );
               DefaultContext context = new DefaultContext( m_context );
  -            context.put( ContainerManager.CATEGORIES_DESCRIPTOR_KEY, 
descriptor.getCategories() );
               context.put( TypeManager.CLASSPATH_DESCRIPTOR_KEY, classpath );
   
               context.makeReadOnly();
  -            loader.enableLogging( logger );
  +            if( this instanceof KernelManager )
  +            {
  +                loader.enableLogging( getLogger() );
  +            }
  +            else
  +            {
  +                loader.enableLogging( getLogger().getChildLogger( name ) );
  +            }
               loader.contextualize( context );
               loader.initialize();
               return loader;
           }
           catch( Throwable e )
           {
  -            final String error = "error creating manager for container descriptor: 
" 
  -              + descriptor.getName() 
  -              + " in manager: " + getPath();
  +            final String error = "error creating manager for: " + getPath() + "/" + 
name;
               throw new TypeRuntimeException( error, e );
           }
       }
  @@ -778,7 +749,6 @@
       {
           return (Resource) m_resources.get( profile );
       }
  -
   
      /**
       * Returns the set of profiles capable of supporting the supplied service.
  
  
  
  1.11      +34 -8     
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/container/Container.java
  
  Index: Container.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/container/Container.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- Container.java    8 Aug 2002 10:02:35 -0000       1.10
  +++ Container.java    8 Aug 2002 21:30:17 -0000       1.11
  @@ -29,10 +29,9 @@
    * @version $Revision$ $Date$
    * @see DefaultContainer
    */
  -public interface Container extends Controller
  +public interface Container
   {
   
  -
      /**
       * Static state enumeration value indicating that the state of a container is 
unknown.
       */
  @@ -52,17 +51,22 @@
       static final int STARTED = 2;
   
      /**
  +    * Static state enumeration value indicating that a container has been suspended.
  +    */
  +    static final int SUSPENDED = 3;
  +
  +   /**
       * Static state enumeration value indicating that a container has completed
       * the shutdown of all subsidiary containers and the internal components 
       * have been shutdown.
       */
  -    static final int STOPPED = 3;
  +    static final int STOPPED = 4;
   
      /**
       * Static state enumeration value indicating that a container and all
       * subsidiary containers have been disposed of.
       */
  -    static final int DISPOSED = 4;
  +    static final int DISPOSED = 5;
   
      /**
       * Return the container name.
  @@ -97,7 +101,29 @@
       * Add and assemble the supplied set of profiles.
       * @param profiles the profiles to assemble
       */
  -    public void install( Profile[] profiles ) throws Exception;
  +    void install( Profile[] profiles ) 
  +      throws Exception;
  +
  +   /**
  +    * Creation of a new empty container associated as a subsidiary of this container
  +    * using the same container profile as this container and an empty classpath.
  +    *
  +    * @param name the name to assign to the new container
  +    * @exception Exception is an error occurs
  +    */
  +    Container createSubContainer( String name ) 
  +      throws Exception;
  +
  +   /**
  +    * Creation of a new empty container associated as a subsidiary of this container
  +    * using the same container profile as this container.
  +    *
  +    * @param name the name to assign to the new container
  +    * @param classpath the container classpath
  +    * @exception Exception is an error occurs
  +    */
  +    Container createSubContainer( String name, ClasspathDescriptor classpath ) 
  +      throws Exception;
   
      /**
       * Creation of a new empty container associated as a subsidiary of this 
container.
  @@ -108,6 +134,6 @@
       * @exception Exception is an error occurs
       */
       public Container createSubContainer( 
  -       String name, ContainerDescriptor descriptor, ClasspathDescriptor classpath ) 
throws Exception;
  -
  +      String name, ContainerDescriptor descriptor, ClasspathDescriptor classpath ) 
  +      throws Exception;
   }
  
  
  
  1.27      +399 -138  
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/container/DefaultContainer.java
  
  Index: DefaultContainer.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/container/DefaultContainer.java,v
  retrieving revision 1.26
  retrieving revision 1.27
  diff -u -r1.26 -r1.27
  --- DefaultContainer.java     8 Aug 2002 10:02:35 -0000       1.26
  +++ DefaultContainer.java     8 Aug 2002 21:30:17 -0000       1.27
  @@ -38,6 +38,7 @@
   import org.apache.avalon.framework.activity.Disposable;
   import org.apache.avalon.framework.activity.Executable;
   import org.apache.avalon.framework.activity.Startable;
  +import org.apache.avalon.framework.activity.Suspendable;
   import org.apache.avalon.framework.CascadingRuntimeException;
   import org.apache.avalon.framework.configuration.Configuration;
   import org.apache.avalon.framework.configuration.DefaultConfiguration;
  @@ -87,7 +88,7 @@
    * @author <a href="mailto:[EMAIL PROTECTED]";>Stephen McConnell</a>
    * @version $Revision$ $Date$
    */
  -public class DefaultContainer extends AbstractLogEnabled implements Container, 
Contextualizable, Configurable, Initializable, Disposable, Runnable, StateListener
  +public class DefaultContainer extends AbstractLogEnabled implements Container, 
Contextualizable, Configurable, Initializable, Startable, Suspendable, Disposable, 
Runnable, StateListener
   {
       //=======================================================================
       // static
  @@ -118,6 +119,11 @@
       */
       public static final String CONFIGURATIONS_KEY = "configurations";
   
  +   /**
  +    * Context key used to locate the logging manager.
  +    */
  +    public static final String LOGGING_KEY = "logging";
  +
       private static final Resources REZ =
           ResourceManager.getPackageResources( DefaultContainer.class );
   
  @@ -185,6 +191,10 @@
   
       private Configuration[] m_configs;
   
  +    private DefaultLoggerManager m_logging;
  +
  +    private Throwable m_error;
  +
       //=======================================================================
       // Contextualizable
       //=======================================================================
  @@ -198,6 +208,8 @@
       *         a {@link ContainerManager} (REQUIRED}</li>
       *  <li>{@link #DESCRIPTOR_KEY} the container meta data 
       *         {@link ContainerDescriptor} (REQUIRED}</li>
  +    *  <li>{@link #LOGGING_KEY} the logging manager 
  +              {@link DefaultLoggerManager} (REQUIRED}</li>
       *  <li>{@link #CONFIGURATIONS_KEY} the set of subsidiary container 
       *         configurations {@link Configuration}[] (OPTIONAL}</li>
       *  <li>{@link #STATE_LISTENER_KEY} a container state listener 
  @@ -211,6 +223,7 @@
       {
           m_manager = (ContainerManager) context.get( MANAGER_KEY );
           m_descriptor = (ContainerDescriptor) context.get( DESCRIPTOR_KEY );
  +        m_logging = (DefaultLoggerManager) context.get( LOGGING_KEY );
   
           try
           {
  @@ -262,10 +275,43 @@
       }
   
       //=======================================================================
  -    // Runnable
  +    // Initializable
       //=======================================================================
   
      /**
  +    * Initalization of a <code>Container</code>.  The implementation launches
  +    * a new thread of execution within which the primary initalization actions
  +    * are undertaken during which all declared component {@link Profile} 
  +    * and subsidiary {@link ContainerDescriptor} instances are added to the 
  +    * container model.  Component profiles are assembled using the 
  +    * container {@link ContainerManager}, followed by the creation and 
  +    * initialization of the subsidiary containers.
  +    *
  +    * @exception Exception never throw but declared in order for specilized
  +    *   containers to manage local exceptions.  If a container initialization
  +    *   occurs it will raise a ContainerRuntimeException under the thread
  +    *   that is running this container.
  +    */
  +    public void initialize() throws Exception
  +    {
  +        synchronized( this )
  +        {
  +            getLogger().debug("initialization");
  +            m_thread = new Thread( (Runnable) this );
  +            m_thread.start();
  +            while(( m_state < INITIALIZED ) && ( m_error == null ))
  +            {
  +                sleep();
  +            }
  +            if( m_error != null )
  +            {
  +                throw new ContainerException("Initialization failure.", m_error );
  +            }
  +        }
  +        getLogger().debug("initialization complete");
  +    }
  +
  +   /**
       * Invoked by the parent to initiate the thread after which the thread will 
       * monitor action requests arrising from initialize, startup, shutdown and 
dispose
       * requests.
  @@ -288,10 +334,20 @@
                           {
                                case STARTED:
                                    if( m_state == INITIALIZED )
  -                                   handleStartup();
  +                                 {
  +                                     handleStartup();
  +                                 }
  +                                 else if( m_state == SUSPENDED )
  +                                 {
  +                                     handleResume();
  +                                 }
                                    break;
  -                             case STOPPED:
  +                             case SUSPENDED:
                                    if( m_state == STARTED )
  +                                   handleSuspend();
  +                                 break;
  +                             case STOPPED:
  +                                 if(( m_state == STARTED ) || ( m_state == 
SUSPENDED )) 
                                      handleShutdown();
                                    break;
                           }
  @@ -306,33 +362,10 @@
               final String error = "Unexpected error during container execution.";
               Throwable exception = new ContainerException( error, e );
               handleDispose( exception );
  +            m_error = exception;
           }
       }
   
  -    //=======================================================================
  -    // Initializable
  -    //=======================================================================
  -
  -   /**
  -    * Initalization of a <code>Container</code>.  The implementation launches
  -    * a new thread of execution within which the primary initalization actions
  -    * are undertaken during which all declared component {@link Profile} 
  -    * and subsidiary {@link ContainerDescriptor} instances are added to the 
  -    * container model.  Component profiles are assembled using the 
  -    * container {@link ContainerManager}, followed by the creation and 
  -    * initialization of the subsidiary containers.
  -    *
  -    * @exception Exception never throw but declared in order for specilized
  -    *   containers to manage local exceptions.  If a container initialization
  -    *   occurs it will raise a ContainerRuntimeException under the thread
  -    *   that is running this container.
  -    */
  -    public void initialize() throws Exception
  -    {
  -        m_thread = new Thread( (Runnable) this );
  -        m_thread.start();
  -    }
  -
      /**
       * Implementation of the initialization method.  Initialization is handled after
       * a thread is established so that we can assign the manager as the thread 
context 
  @@ -343,8 +376,6 @@
           if( m_state >= INITIALIZED ) 
             return;
   
  -        getLogger().debug( "initialization" );
  -
           // 
           // initiate component assembly
           //
  @@ -375,22 +406,9 @@
           {
               final Configuration conf = m_configs[i];
               final Container container = createContainer( conf );
  -            //m_containers.add( container );
           }
   
  -        getLogger().debug("commence wait" );
  -        while( !hasAchievedState( INITIALIZED ) )
  -        {
  -            try
  -            {
  -                sleep();
  -            }
  -            catch( Throwable e )
  -            {
  -            }
  -        }
           m_state = INITIALIZED;
  -        getLogger().debug("initialized" );
           fireStateChange( new StateEvent( this, INITIALIZED ) );
       }
   
  @@ -401,16 +419,95 @@
      /**
       * Request the startup of the container.
       */
  -    public void startup() throws Exception
  +    public void start() throws Exception
       {
  -        synchronized( m_action )
  +        synchronized( this )
           {
               m_action = new Integer( STARTED );
  +            getLogger().debug("startup initiated");
  +            while(( m_state < STARTED ) && ( m_error == null ))
  +            {
  +                sleep();
  +            }
  +            if( m_error != null )
  +            {
  +                throw new ContainerException("Startup failure.", m_error );
  +            }
           }
  +        getLogger().debug("startup complete");
  +    }
  +
  +    /**
  +     * Suspends the container.
  +     */
  +    public void suspend()
  +    {
  +        synchronized( this )
  +        {
  +            m_action = new Integer( SUSPENDED );
  +            getLogger().debug("suspension initiated");
  +            while(( m_state < SUSPENDED ) && ( m_error == null ))
  +            {
  +                sleep();
  +            }
  +            if( m_error != null )
  +            {
  +                throw new ContainerRuntimeException("Suspension failure.", m_error 
);
  +            }
  +        }
  +        getLogger().debug("suspension completed");
  +    }
  +
  +    /**
  +     * Resumes the container from a suspended state.
  +     */
  +    public void resume()
  +    {
  +        if( m_state != SUSPENDED )
  +          throw new IllegalStateException(
  +            "Container is not suspended.");
  +
  +        synchronized( this )
  +        {
  +            m_action = new Integer( STARTED );
  +            getLogger().debug("suspension initiated");
  +            while(( m_state == SUSPENDED ) && ( m_error == null ))
  +            {
  +                sleep();
  +            }
  +            if( m_error != null )
  +            {
  +                throw new ContainerRuntimeException("Resumption failure.", m_error 
);
  +            }
  +        }
  +        getLogger().debug("resumption completed");
  +    }
  +
  +
  +   /**
  +    * Request the shutdown of the container.
  +    */
  +    public void stop() throws Exception
  +    {
  +        synchronized( this )
  +        {
  +            m_action = new Integer( STOPPED );
  +            getLogger().debug("shutdown initiated");
  +            while(( m_state < STOPPED ) && ( m_error == null ))
  +            {
  +                sleep();
  +            }
  +            if( m_error != null )
  +            {
  +                throw new ContainerException("Shutdown failure.", m_error );
  +            }
  +        }
  +        getLogger().debug("shutdown completed");
       }
   
      /**
  -    * Implementation of the container startup procedure.
  +    * Implementation of the container startup procedure. This is invoked ONCE 
  +    * during the lifetime of the container.
       */
       private void handleStartup() throws Exception
       {
  @@ -429,33 +526,9 @@
             getLogger().debug("container startup");
   
           //
  -        // startup all of the components in this container 
  -        // before starting up any of the nested containers
  +        // list the profiles in the startup graph
           //
   
  -        start();
  -
  -        Iterator iterator = m_containers.iterator();
  -        while( iterator.hasNext() )
  -        {
  -            ((Container)iterator.next()).startup();
  -        }
  -
  -        while( !hasAchievedState( STARTED ) )
  -          sleep();
  -
  -        getLogger().debug("container startup complete");
  -        m_state = STARTED;
  -        fireStateChange( new StateEvent( this, STARTED ) );
  -    }
  -
  -   /**
  -    * Activate all local components.
  -    * 
  -    * @exception Exception if a start related error occurs
  -    */
  -    private void start() throws Exception
  -    {
           if( getLogger().isInfoEnabled() )
           {
               Profile[] profiles = m_manager.getStartupGraph( false );
  @@ -476,22 +549,40 @@
                   getLogger().info( "startup" );
               }
           }
  +
  +        //
  +        // request startup of any unstarted compoents
  +        //
  +
           m_manager.start();
  -    }
   
  -   /**
  -    * Request the shutdown of the container.
  -    */
  -    public void shutdown()
  -    {
  -        synchronized( m_action )
  +        //
  +        // statup all of the subsidiary containers
  +        //
  +
  +        synchronized( m_containers )
           {
  -            m_action = new Integer( STOPPED );
  +            Iterator iterator = m_containers.iterator();
  +            while( iterator.hasNext() )
  +            {
  +                Object next = iterator.next();
  +                if( next instanceof Startable )
  +                {
  +                    ((Startable)next).start();
  +                }
  +            }
  +
  +            while( !hasAchievedState( STARTED ) )
  +              sleep();
           }
  +
  +        getLogger().debug("container startup complete");
  +        m_state = STARTED;
  +        fireStateChange( new StateEvent( this, STARTED ) );
       }
   
      /**
  -    * Impleementation of the container shutdown process.
  +    * Implementation of the container shutdown process.
       */
       public void handleShutdown()
       {
  @@ -512,30 +603,35 @@
           // the components in this container 
           //
   
  -        Iterator iterator = m_containers.iterator();
  -        while( iterator.hasNext() )
  +        synchronized( m_containers )
           {
  -            ((Container)iterator.next()).shutdown();
  +            Iterator iterator = m_containers.iterator();
  +            while( iterator.hasNext() )
  +            {
  +                Object next = iterator.next();
  +                if( next instanceof Startable )
  +                {
  +                    try
  +                    {
  +                        ((Startable)next).stop();
  +                    }
  +                    catch( Throwable e )
  +                    {
  +                        final String warning = 
  +                          "Failed to stop sub-container: " 
  +                          + ((Container)next).getName();
  +                        getLogger().warn( warning, e );
  +                    }
  +                }
  +            }
  +            while( !hasAchievedState( STOPPED ) )
  +              sleep();
           }
   
  -        stop();
  -
  -        while( !hasAchievedState( STOPPED ) )
  -          sleep();
  -
  -        getLogger().debug("container shutdown complete");
  -        m_state = STOPPED;
  -        fireStateChange( new StateEvent( this, STOPPED ) );
  -    }
  +        //
  +        // list all of the local components to stop
  +        //
   
  -   /**
  -    * Method invoked by the container's container to signal the deactivation of 
  -    * all components.
  -    * 
  -    * @exception Exception if a stop related error occurs
  -    */
  -    private void stop()
  -    {
           if( getLogger().isInfoEnabled() )
           {
               Profile[] profiles = m_manager.getShutdownGraph();
  @@ -557,7 +653,103 @@
                   getLogger().info( "shutdown" );
               }
           }
  +
  +        //
  +        // stop the local components
  +        //
  +
           m_manager.stop();
  +
  +        getLogger().debug("container shutdown complete");
  +        m_state = STOPPED;
  +        fireStateChange( new StateEvent( this, STOPPED ) );
  +    }
  +
  +    private void handleSuspend()
  +    {
  +        if( m_state < INITIALIZED )
  +          throw new IllegalStateException(
  +            "Container has not been initialized.");
  +
  +        if( m_state == SUSPENDED )
  +          return;
  +
  +        if( m_state > STOPPED )
  +          throw new IllegalStateException(
  +            "Container has been stopped.");
  +
  +        if( getLogger().isDebugEnabled() )
  +          getLogger().debug("container suspension");
  +
  +        synchronized( m_containers )
  +        {
  +            Iterator iterator = m_containers.iterator();
  +
  +            while( iterator.hasNext() )
  +            {
  +                Object next = iterator.next();
  +                if( next instanceof Suspendable )
  +                {
  +                    ((Suspendable)next).suspend();
  +                }
  +            }
  +
  +            while( !hasAchievedState( SUSPENDED ) )
  +              sleep();
  +        }
  +
  +        getLogger().debug("container suspension complete");
  +        m_state = SUSPENDED;
  +        fireStateChange( new StateEvent( this, SUSPENDED ) );
  +    }
  +
  +    private void handleResume()
  +    {
  +        if( m_state != SUSPENDED )
  +          throw new IllegalStateException(
  +            "Container is not suspended.");
  +
  +        if( getLogger().isDebugEnabled() )
  +          getLogger().debug("container resuming");
  +
  +        //
  +        // reapply start to the manager
  +        //
  +
  +        try
  +        {
  +            m_manager.start();
  +        }
  +        catch( Throwable e )
  +        {
  +            final String warning = "Container type manager raised an error while 
resuming.";
  +            getLogger().warn( warning, e );
  +        }
  +
  +        //
  +        // resume subsidiary containers
  +        //
  +
  +        synchronized( m_containers )
  +        {
  +            Iterator iterator = m_containers.iterator();
  +
  +            while( iterator.hasNext() )
  +            {
  +                Object next = iterator.next();
  +                if( next instanceof Suspendable )
  +                {
  +                    ((Suspendable)next).resume();
  +                }
  +            }
  +
  +            while( !hasAchievedState( STARTED ) )
  +              sleep();
  +        }
  +
  +        getLogger().debug("container resumption complete");
  +        m_state = STARTED;
  +        fireStateChange( new StateEvent( this, STARTED ) );
       }
   
       //=======================================================================
  @@ -578,14 +770,20 @@
               case INITIALIZED:
                   getLogger().debug( 
                         "subsidiary container initialized: " + container.getName() );
  +                synchronized( m_containers )
  +                {
  +                    m_containers.add( container );
  +                }
                   fireStructuralChange( 
                     new ContainerEvent( this, container, ContainerEvent.ADD ) ); 
                   break;
               case DISPOSED:
  -                getLogger().debug(
  +                synchronized( m_containers )
  +                {
  +                    getLogger().debug(
                         "removing subsidiary container: " + container.getName() );
  -                //container.removeStateListener( this );
  -                m_containers.remove( container );
  +                    m_containers.remove( container );
  +                }
                   fireStructuralChange( 
                     new ContainerEvent( this, container, ContainerEvent.REMOVE ) );
           }
  @@ -675,16 +873,19 @@
           {
               list.add( local[i] );
           }
  -        Iterator iterator = m_containers.iterator();
  -        while( iterator.hasNext() )
  +        synchronized( m_containers )
           {
  -            Container container = (Container)iterator.next();
  -            Resource[] resources = container.getResources();
  -            for( int j=0; j<resources.length; j++ )
  -            {
  -                Resource resource = resources[j];
  -                if( !list.contains( resource ) )
  -                  list.add( resources[j] );
  +            Iterator iterator = m_containers.iterator();
  +            while( iterator.hasNext() )
  +            {
  +                Container container = (Container)iterator.next();
  +                Resource[] resources = container.getResources();
  +                for( int j=0; j<resources.length; j++ )
  +                {
  +                    Resource resource = resources[j];
  +                    if( !list.contains( resource ) )
  +                      list.add( resources[j] );
  +                }
               }
           }
           return (Resource[]) list.toArray( new Resource[0] );
  @@ -697,7 +898,10 @@
       */
       public Container[] getContainers()
       {
  -        return (Container[]) m_containers.toArray( new Container[0] );
  +        synchronized( m_containers )
  +        {
  +            return (Container[]) m_containers.toArray( new Container[0] );
  +        }
       }
   
      /**
  @@ -707,6 +911,10 @@
       */
       public void install( Profile[] profiles ) throws Exception
       {
  +        if( m_state != SUSPENDED )
  +          if( m_state != INITIALIZED )
  +            throw new IllegalStateException( "Container not in suspended or 
initialized state." );
  +
           for( int i=0; i<profiles.length; i++ )
           {
               m_manager.addProfile( profiles[i] );
  @@ -723,10 +931,18 @@
       */
       public void dispose()
       {
  -        synchronized( m_action )
  +        synchronized( this )
           {
  +            m_error = null;
               m_action = new Integer( DISPOSED );
  +            getLogger().debug("diposal initiated");
  +            while(( m_state < DISPOSED ) && ( m_error == null ))
  +            {
  +                sleep();
  +            }
           }
  +        getLogger().debug("disposal complete");
  +
       }
   
      /**
  @@ -764,20 +980,19 @@
           // dispose of all of the nested containers
           //
   
  -        getLogger().debug("dispose");
  -        Iterator iterator = m_containers.iterator();
  -        while( iterator.hasNext() )
  +        synchronized( m_containers )
           {
  -            Container container = (Container)iterator.next();
  -            if( container instanceof Disposable )
  +            Iterator iterator = m_containers.iterator();
  +            while( iterator.hasNext() )
               {
  -               ((Disposable)container).dispose();
  +                Container container = (Container)iterator.next();
  +                if( container instanceof Disposable )
  +                {
  +                    ((Disposable)container).dispose();
  +                }
               }
           }
   
  -        while( !hasAchievedState( DISPOSED ) )
  -            sleep();
  -
           m_manager.dispose();
   
           getLogger().debug("done");
  @@ -851,9 +1066,44 @@
       }
   
      /**
  +    * Creation of a new empty container associated as a subsidiary of this container
  +    * using the same container profile as this container and an empty classpath.
  +    *
  +    * @param name the name to assign to the new container
  +    * @exception Exception is an error occurs
  +    */
  +    public Container createSubContainer( 
  +                              String name ) 
  +      throws Exception
  +    {
  +        return createContainer( 
  +         name, m_descriptor, new ClasspathDescriptor(), new Configuration[0], new 
Configuration[0] );
  +    }
  +
  +
  +   /**
  +    * Creation of a new empty container associated as a subsidiary of this container
  +    * using the same container profile as this container.
  +    *
  +    * @param name the name to assign to the new container
  +    * @param classpath the container classpath
  +    * @exception Exception is an error occurs
  +    */
  +    public Container createSubContainer( 
  +                              String name,
  +                              ClasspathDescriptor classpath ) 
  +      throws Exception
  +    {
  +        return createContainer( 
  +         name, m_descriptor, classpath, new Configuration[0], new Configuration[0] 
);
  +    }
  +
  +   /**
       * Creation of a new empty container associated as a subsidiary of this 
container.
       *
  +    * @param name the name to assign to the new container
       * @param descriptor the container meta descriptor
  +    * @param classpath the container classpath
       * @exception Exception is an error occurs
       */
       public Container createSubContainer( 
  @@ -862,7 +1112,12 @@
                                 ClasspathDescriptor classpath ) 
         throws Exception
       {
  -        return createContainer( name, descriptor, classpath, new Configuration[0], 
new Configuration[0] );
  +        if( m_state != SUSPENDED ) 
  +          if( m_state != INITIALIZED )
  +            throw new IllegalStateException( "Container not in initialized or 
suspended state." );
  +
  +        return createContainer( 
  +          name, descriptor, classpath, new Configuration[0], new Configuration[0] );
       }
   
      /**
  @@ -883,12 +1138,19 @@
           try
           {
               //
  +            // create the logging categories for the container
  +            //
  +
  +            CategoriesDescriptor categories = descriptor.getCategories();
  +            String base = m_manager.getPath().replace('/','.').substring(1) + "." + 
name;
  +            m_logging.addCategories( base, categories );
  +
  +            //
               // create the manager
               //
   
               final ContainerManager manager = 
  -              m_manager.createContainerManager( 
  -                name, descriptor, classpath, getLogger().getChildLogger( name ) );
  +              m_manager.createContainerManager( name, classpath );
   
               //
               // populate the descriptor with the components it contains
  @@ -922,6 +1184,7 @@
               DefaultContext context = new DefaultContext();
               context.put( MANAGER_KEY, manager );
               context.put( DESCRIPTOR_KEY, descriptor );
  +            context.put( LOGGING_KEY, m_logging );
               context.put( CONFIGURATIONS_KEY, containers );
               context.put( STATE_LISTENER_KEY, this );
               context.makeReadOnly();
  @@ -930,11 +1193,6 @@
                 createContainerInstance( 
                   manager, descriptor.getType().getInfo().getImplementationKey() );
   
  -            synchronized( m_containers )
  -            {
  -                m_containers.add( container );
  -            }
  -
               final Logger logger = getLogger().getChildLogger( name );
               container.enableLogging( logger );
               container.contextualize( context );
  @@ -1047,13 +1305,16 @@
       */
       private boolean hasAchievedState( int state )
       {
  -        Iterator iterator = m_containers.iterator();
  -        while( iterator.hasNext() )
  +        synchronized( m_containers )
           {
  -            Container container = (Container)iterator.next();
  -            if( container.getState() < state )
  -              return false;
  +            Iterator iterator = m_containers.iterator();
  +            while( iterator.hasNext() )
  +            {
  +                Container container = (Container)iterator.next();
  +                if( container.getState() < state )
  +                  return false;
  +            }
  +            return true;
           }
  -        return true;
       }
   }
  
  
  
  1.3       +1 -1      
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/container/StateEvent.java
  
  Index: StateEvent.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/container/StateEvent.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- StateEvent.java   5 Aug 2002 12:39:37 -0000       1.2
  +++ StateEvent.java   8 Aug 2002 21:30:17 -0000       1.3
  @@ -40,7 +40,7 @@
       *
       * @param container the source container
       * @param state int value of (@link Container#INITIALIZED}, {@link 
Container#STARTED}, 
  -    *    {@link Container#STOPPED} or {@link Container#DISPOSED}
  +    *    {@link Container#SUSPENDED}, {@link Container#STOPPED} or {@link 
Container#DISPOSED}
       */
       public StateEvent( Container container, int state ) 
       {
  
  
  
  1.32      +58 -128   
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/kernel/DefaultKernel.java
  
  Index: DefaultKernel.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/kernel/DefaultKernel.java,v
  retrieving revision 1.31
  retrieving revision 1.32
  diff -u -r1.31 -r1.32
  --- DefaultKernel.java        8 Aug 2002 10:02:35 -0000       1.31
  +++ DefaultKernel.java        8 Aug 2002 21:30:17 -0000       1.32
  @@ -104,7 +104,7 @@
     
           File <strong>base</strong> = new File( <font 
color="darkred"/>&quot;.&quot;</font> );
           DefaultContext <strong>context</strong> = new DefaultContext();
  -        <strong>context</strong>.put( DefaultKernel.APPLICATION_DIR_KEY, 
<strong>base</strong> );
  +        <strong>context</strong>.put( DefaultKernel.DIR_KEY, <strong>base</strong> 
);
           <strong>context</strong>.makeReadOnly();
          
           <font color="gray">//
  @@ -145,7 +145,7 @@
    * @version $Revision$ $Date$
    */
   public class DefaultKernel 
  -  implements Kernel, Contextualizable, Configurable, Initializable, Startable, 
Disposable, StateListener
  +  implements Kernel, Contextualizable, Configurable, Initializable, Startable, 
Disposable
   {
       //=======================================================================
       // static
  @@ -209,7 +209,7 @@
       private Logger m_localLogger;
   
      /**
  -    * The kernel path.
  +    * The kernel relative path.
       */
       private String m_path;
   
  @@ -224,13 +224,21 @@
       */
       private static DefaultLoggerManager m_logging;
   
  +   /**
  +    * The kernel manager's base path.
  +    */
  +    private String m_base;
  +
  +    private ContainerManager m_parent;
   
       //=======================================================================
       // Contextualizable
       //=======================================================================
   
      /**
  -    * Invoked by the bootstrap process to supply the root directory.
  +    * Invoked by the bootstrap process to supply the root directory and 
  +    * optional a path identifying the name of the root container.
  +    *
       * @param context the context object containing the inital parameters
       * @exception ContextException if the supplied does not contain a 
       *   DIR_KEY value.
  @@ -282,46 +290,59 @@
               if( Thread.currentThread().getContextClassLoader() instanceof 
ContainerManager )
               {
                   m_root = false;
  +                m_parent = 
  +                 (ContainerManager)Thread.currentThread().getContextClassLoader();
  +                m_base = m_parent.getPath().replace('/','.').substring(1) + "." + 
name;
               }
               else
               {
                   m_root = true;
                   LoggingDescriptor logging = 
                     m_creator.createLoggingDescriptor( m_config.getChild("logging"), 
name );
  -                CategoriesDescriptor categories = 
  -                  m_creator.createCategoriesDescriptor( name, 
m_config.getChild("categories") );
                   m_logging = new DefaultLoggerManager( logging );
  +                m_base = name;
               }
   
  -            DefaultContext ctx = new DefaultContext( m_context );
  +            //
  +            // Setup the logging categories for the kernel
  +            //
  +
  +            CategoriesDescriptor categories = 
  +              m_creator.createCategoriesDescriptor( name, 
m_config.getChild("categories") );
  +            getLoggingManager().addCategories( m_base, categories );
  +
  +            //
  +            // Create the kernel type manager.
  +            // Create a context object container the logging manager and the 
  +            // categories to be applied relative to the manager's path, include
  +            // the extension directives and classpath descriptor.
  +            //
   
               ExtensionsDescriptor extensions = 
                 m_creator.createExtensionsDescriptor( m_config.getChild("extensions") 
);
               ClasspathDescriptor classpath = 
                 m_creator.createClasspathDescriptor( m_config.getChild("classpath") );
  -            LoggingDescriptor loggingDescriptor = 
  -              m_creator.createLoggingDescriptor( m_config.getChild("logging"), name 
);
  -            CategoriesDescriptor categories = 
  -              m_creator.createCategoriesDescriptor( name, 
m_config.getChild("categories") );
   
  -            m_logger = getLoggingManager().getLoggerForCategory( m_path );
  +            m_logger = getLoggingManager().getLoggerForCategory( m_base );
               m_localLogger = m_logger.getChildLogger("kernel");
   
               m_manager = new KernelManager( m_path );
               m_manager.enableLogging( m_logger );
  +            DefaultContext ctx = new DefaultContext( m_context );
               ctx.put( KernelManager.LOGGING_KEY, m_logging );
  -            ctx.put( ContainerManager.CATEGORIES_DESCRIPTOR_KEY, categories );
               ctx.put( ContainerManager.EXTENSIONS_DESCRIPTOR_KEY, extensions );
               ctx.put( ContainerManager.CLASSPATH_DESCRIPTOR_KEY, classpath );
               m_manager.contextualize( ctx );
               m_manager.initialize();
   
               Thread.currentThread().setContextClassLoader( m_manager );
  -
  -
               getLogger().info("kernel established: " + name );
   
  +
               m_container = createContainer( config );
  +            m_initialized = true;
  +
  +            getLogger().info("kernel initialization complete");
   
           }
           catch( Throwable e )
  @@ -329,11 +350,6 @@
               final String error = "kernel establishment failure";
               throw new KernelException( error, e );
           }
  -
  -
  -        while( !m_initialized )
  -            sleep();
  -
       }
   
      /**
  @@ -408,10 +424,10 @@
               final ClasspathDescriptor classpath = 
                 m_creator.createClasspathDescriptor( config.getChild("classpath") );
               final ContainerManager manager = 
  -              m_manager.createContainerManager( name, descriptor, classpath, 
m_logger );
  +              m_manager.createContainerManager( name, classpath );
   
               //
  -            // populate the descriptor with the components it contains
  +            // populate the container descriptor with the components it contains
               //
   
               final Configuration[] components = config.getChildren("component");
  @@ -443,8 +459,8 @@
               final DefaultContext context = new DefaultContext();
               context.put( DefaultContainer.MANAGER_KEY, manager );
               context.put( DefaultContainer.DESCRIPTOR_KEY, descriptor );
  +            context.put( DefaultContainer.LOGGING_KEY, m_logging );
               context.put( DefaultContainer.CONFIGURATIONS_KEY, 
config.getChildren("container") );
  -            context.put( DefaultContainer.STATE_LISTENER_KEY, this );
               context.makeReadOnly();
     
               final DefaultContainer container = createContainerInstance( m_manager, 
classname );
  @@ -487,21 +503,6 @@
       */
       public void start() throws Exception
       {
  -        startup();
  -    }
  -
  -    public void stop()
  -    {
  -        shutdown();
  -    }
  -
  -    //=======================================================================
  -    // Controller
  -    //=======================================================================
  -
  -
  -    public void startup() throws Exception
  -    {
           if( !m_initialized )
             throw new IllegalStateException("not initialized");
           if( m_stopped )
  @@ -514,7 +515,8 @@
   
           try
           {
  -            m_container.startup();
  +            m_container.start();
  +            m_started = true;
           }
           catch( Throwable e )
           {
  @@ -523,17 +525,12 @@
               throw new KernelException( error, e );
           }
   
  -        getLogger().info("startup initiated");
  -
  -        while( !m_started )
  -          sleep();
  -
           if( getLogger().isInfoEnabled() )
             getLogger().info("started");
       }
   
  -    public void shutdown()
  -    {    
  +    public void stop() throws Exception
  +    {
           if( !m_initialized )
             throw new IllegalStateException("not initialized");
           if( m_stopped )
  @@ -541,15 +538,16 @@
           if( !m_started )
             throw new IllegalStateException("not started");
   
  -        m_container.shutdown();
  -        if( getLogger().isInfoEnabled() )
  -          getLogger().info("shutdown intiated");
  -
  -        while( !m_stopped )
  -          sleep();
  -
  -        if( getLogger().isInfoEnabled() )
  -          getLogger().info("stopped");
  +        try
  +        {
  +            m_container.stop();
  +            m_stopped = true;
  +        }
  +        catch( Throwable e )
  +        {
  +            final String error = "Unepected error while stopping root container.";
  +            getLogger().warn( error, e );
  +        }
       }
   
       //=======================================================================
  @@ -565,57 +563,6 @@
           if( m_container != null )
           {
               m_container.dispose();
  -            while( !m_disposed )
  -              sleep();
  -        }
  -    }
  -
  -    //=======================================================================
  -    // StateListener
  -    // Listens to the state of the root container.
  -    //=======================================================================
  -
  -   /**
  -    * Method invoked when the root container state changes.
  -    */
  -    public void stateChanged( StateEvent event )
  -    {
  -        int state = event.getState();
  -        switch( state )
  -        {
  -            case Container.INITIALIZED:
  -                m_initialized = true;
  -                getLogger().info("initilialized");
  -                try
  -                {
  -                    verify();
  -                    getLogger().info( "verified" );
  -                    Logger export = getLogger().getChildLogger( "export" );
  -                    if( export.isInfoEnabled() )
  -                    {
  -                        getLogger().info( "listing exportable resources" );
  -                        Resource[] resources = getResources();
  -                        for( int i=0; i<resources.length; i++ )
  -                        {
  -                            export.info( resources[i].getPath() );
  -                        }
  -                    }
  -                }
  -                catch( Throwable e )
  -                {
  -                    getLogger().error("verification failure", e );
  -                    m_container.dispose();
  -                }
  -                break;
  -            case Container.STARTED:
  -                m_started = true;
  -                break;
  -            case Container.STOPPED:
  -                m_stopped = true;
  -                break;
  -            case Container.DISPOSED:
  -                m_disposed = true;
  -                break;
           }
       }
   
  @@ -630,7 +577,10 @@
   
       public Resource[] getResources()
       {
  -        return m_container.getResources();
  +        synchronized( m_container )
  +        {
  +            return m_container.getResources();
  +        }
       }
   
      /**
  @@ -664,24 +614,4 @@
           getLogger().debug("commencing assembly verification");
           verifier.verifyAssembly( profiles );
       }
  -
  -    //=======================================================================
  -    // internals
  -    //=======================================================================
  -
  -
  -   /**
  -    * Internel utility to sleep a bit.
  -    */
  -    private void sleep()
  -    {
  -        try
  -        {
  -            Thread.currentThread().sleep( 100 );
  -        }
  -        catch( Throwable wakeup )
  -        {
  -        }
  -    }
  -
   }
  
  
  
  1.12      +2 -2      
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/kernel/Kernel.java
  
  Index: Kernel.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/kernel/Kernel.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- Kernel.java       4 Aug 2002 06:43:20 -0000       1.11
  +++ Kernel.java       8 Aug 2002 21:30:17 -0000       1.12
  @@ -22,7 +22,7 @@
    * @author <a href="mailto:[EMAIL PROTECTED]";>Stephen McConnell</a>
    * @version $Revision$ $Date$
    */
  -public interface Kernel extends Controller
  +public interface Kernel
   {
   
      /**
  
  
  
  1.2       +2 -2      
jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/model/builder/ContainerBuilder.java
  
  Index: ContainerBuilder.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-avalon-excalibur/assembly/src/java/org/apache/excalibur/merlin/model/builder/ContainerBuilder.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ContainerBuilder.java     8 Aug 2002 10:00:50 -0000       1.1
  +++ ContainerBuilder.java     8 Aug 2002 21:30:18 -0000       1.2
  @@ -19,7 +19,7 @@
   import org.apache.excalibur.merlin.model.ContainerDescriptor;
   
   /**
  - * A ProfileBuilder is responsible for building {@link Profile}
  + * A ProfileBuilder is responsible for building {@link ContainerDescriptor}
    * objects from Configuration objects. 
    *
    * @author <a href="mailto:[EMAIL PROTECTED]";>Stephen McConnell</a>
  
  
  

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to