You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@avalon.apache.org by le...@apache.org on 2002/06/23 15:17:29 UTC

cvs commit: jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer Composer.java ComposerImpl.java Handler.java MC.java MicroContainer.java MicroContainerComponentManager.java MicroContainerProxy.java Supplier.java SupplierImpl.java

leosutic    2002/06/23 06:17:29

  Added:       src/proposal/microcontainer build.xml readme.txt
               src/proposal/microcontainer/src/org/apache/avalon/microcontainer
                        Composer.java ComposerImpl.java Handler.java
                        MC.java MicroContainer.java
                        MicroContainerComponentManager.java
                        MicroContainerProxy.java Supplier.java
                        SupplierImpl.java
  Log:
  First attempt.
  
  Revision  Changes    Path
  1.1                  jakarta-avalon/src/proposal/microcontainer/build.xml
  
  Index: build.xml
  ===================================================================
  <?xml version="1.0"?>
  <project name="Avalon MicroContainer" default="main" basedir=".">
  
      <!--
        Give user a chance to override without editing this file
        (and without typing -D each time he compiles it)
      -->
      <property file="ant.properties"/>
      <property file="${user.home}/.ant.properties"/>
  
      <property name="debug" value="on"/>
      <property name="optimize" value="off"/>
      <property name="deprecation" value="off"/>
      <property name="compress.jars" value="true"/>
  
      <!-- Set the properties for intermediate directory -->
      <property name="build.dir" value="build"/>
      <property name="build.lib" value="${build.dir}/lib"/>
      <property name="build.classes" value="${build.dir}/classes"/>
  
      <!-- Set the properties for source directories -->
      <property name="src.dir" value="src"/>
  
      <path id="project.class.path">
          <pathelement location="${framework.jar}"/>
          <pathelement path="${java.class.path}" />
          <pathelement path="${build.classes}" />
      </path>
  
      <!-- Main target -->
      <target name="main" depends="test" description="Builds the MicroContainer sample"/>
  
      <!-- Prepares the build directory -->
      <target name="prepare">
          <tstamp/>
          <mkdir dir="${build.dir}"/>
      </target>
  
      <!-- Compiles the source code -->
      <target name="compile" depends="prepare" description="compiles the source code">
  
          <mkdir dir="${build.classes}"/>
  
          <javac srcdir="${src.dir}"
              destdir="${build.classes}"
              debug="${debug}"
              optimize="${optimize}"
              deprecation="${deprecation}"
              target="1.3">
              <classpath refid="project.class.path" />
          </javac>
      </target>
  
      <target name="test" depends="compile">
          <java classname="org.apache.avalon.microcontainer.MC"
              fork="true">
              <classpath refid="project.class.path" />
          </java>
      </target>
  </project>
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/readme.txt
  
  Index: readme.txt
  ===================================================================
  Leo Sutic <le...@inspireinfrastructure.com>:
  
  
                         AVALON MICROCONTAINER
                       - --------------------- -
  
  PURPOSE
    
  One of the barriers of entry to Avalon is the fact that you are buying
  into an entire architecture. Instead of just getting a set of classes
  that you can use as you were used to, you get a set of Avalon Components
  that must be put in a container and managed. Often this involves XML config
  files and other niceties that, while providing amazing flexibility, makes
  it difficult to lure unsuspecting developers into Avalon. We lose the "first
  component is for free" / "I can see that you're special, so I got something
  special for you" opportunity.
  
  Stefano mentioned that several potential new users of Avalon had tried to
  start a component by doing:
  
     ComponentInterface comp = new ComponentImpl ();
  
  and then nothing more, because that was what they were familiar with.
  
  The purpose of Avalon Microcontainer is to make something that is as
  close as possible to the above possible. Specifically:
  
   + Components should be able to be used with the Microcontainer without any 
     XML and any config files.
  
   + All configuration is handled programmatically.
  
   + As far as possible, usage of components should follow standard Java 
     idioms.
  
  
  BASIC IDEA
  
  Define a container that holds *one* component. Yep, that's right, one single 
  component for each container. Solve composition by linking these together.
  Solve usage of components by letting the container expose the component's
  interface. This may seem difficult to understand, but see the case study below
  to see how I reached the currect way of doing it.
  
  
  CASE STUDY
  
  The case I'm looking at has two components, the Supplier and the Composer.
  
  Their interfaces are defined in Supplier.java and Composer.java, respectively,
  and their implementations are in SupplierImpl.java and ComposerImpl.java.
  
  Let me list some issues:
  
   + ComposerImpl accesses the Supplier via a ComponentManager interface. It
     uses lookup("supplier") to get the Supplier.
  
   + ComposerImpl may require more than one Supplier during each transaction.
  
   + SupplierImpl may or may not be threadsafe.
  
   + The user may want to access Supplier directly.
  
   + The user definitely wants to access Supplier directly.
  
  So we have a component, ComposerImpl, that needs a Supplier. Had this been
  straight Java, we would expect to do something like this:
  
    ...
  
    SupplierImpl supplier = new SupplierImpl();
    ComposerImpl composer = new ComposerImpl(supplier);
  
    supplier.someMethod();
    composer.someMethod();
  
    ...
  
  So let's introduce an ObjectFactory class for construction:
  
    ...
  
    Supplier supplier = (Supplier) ObjectFactory.newInstance( 
                                       SupplierImpl.class 
                                   );
  
    // Pass the supplier in with role "supplier"
    Composer composer = (Composer) ObjectFactory.newInstance( 
                                       ComposerImpl.class, 
                                       new Role[]{ new Role("supplier", supplier) }
                                   );
  
    supplier.someMethod();
    composer.someMethod();
  
    ...
  
  But now we have a problem. Even though we do give ComposerImpl a Supplier
  with the correct role name, we have forgotten that ComposerImpl may do
  *several* lookups on "supplier", and that SupplierImpl may not be
  reusable. As it looks now, ComposerImpl will get the same instance of
  SupplierImpl all the time, and that may not work.
  
  So let's introduce a ObjectFactoryFactory. Give it a component implementation
  class and it will produce a factory that you can use to create new components.
  Then we pass *that* one to ComposerImpl (hidden behind a ComponentManager
  interface) and we're done, right?
  
    ...
  
    ObjectFactory supplierFactory = ObjectFactoryFactory.newInstance( 
                                        SupplierImpl.class 
                                    );
  
    // Pass the supplier in with role "supplier"
    Composer composer = ObjectFactoryFactory.newInstance( 
                            ComposerImpl.class, 
                            new Role[]{ new Role("supplier", supplierFactory) }
                        );
  
    
    supplier.someMethod();  // !!!!!!!!!!! This won't work anymore!
  
    composer.someMethod();  // !!!!!!!!!!! This won't work anymore!
  
    ...
  
  Guess not.
  
  The problem is that sometimes, we want to use the returned object as a factory
  (when it is used with Composable components) and sometimes we just want the
  "raw" component. The ideal would be something that exposes the interface of the
  component, but also exposed a factory interface. We would use the factory 
  interface of a component when giving it to another component that is a 
  composer, but use the ordinary interface otherwise.
  
  Solution: Dynamic proxies.
  
  When you do:
  
    Supplier supplier = (Supplier) ObjectFactory.newInstance( 
                                       SupplierImpl.class 
                                   );
  
  we should give you back an object that implements all of SupplierImpl's
  interfaces, but also an ObjectFactory interface that we can use to create
  new instances of SupplierImpl.
  
  This ObjectFactory interface will be used when combining the supplier with
  the composer, and the user who wants to use the returned SupplierImpl via the
  Supplier interface can do so as well.
  
  The end result of this is in the code, and below I will do a short 
  walkthrough.
  
  
  WALKTHROUGH
  
  Component Usage
  
  Below is the current demonstration class. It sets up a Supplier and a
  Composer using that Supplier, and then invokes methods on the Composer.
  
      public class MC 
      {
          public static void main (String[] args) throws Exception 
          {
              Supplier supplier = SupplierImpl.getInstance ();
              Composer comp = ComposerImpl.getInstance (supplier);
  
              comp.method();
  
              MicroContainer.release (comp);
              MicroContainer.release (supplier);
          }    
      }
  
  From top to bottom:
  
   + You notice that the Supplier is retrieved via a static method in the 
     implementation class, and that the same goes for the Composer.
     These are just convenience methods, and I'll show them in just a
     moment.
  
   + The returned Composer instance is used just as you would expect to 
     use it.
  
   + Note that you are required to release components. This can be done
     either by casting, or you can use a static method in MicroContainer
     that will do the job for you.
  
  Now, let's look at the SupplierImpl.getInstance() method:
  
      public static Supplier getInstance() 
      {
          return (Supplier) new MicroContainer( SupplierImpl.class )
                                    .sharedInstance( true )
                                    .create();
      }
  
  This command creates a new MicroContainer for SupplierImpl. It also
  specifies that SupplierImpl can be shared among several clients - 
  this is, for example, when Composer does multiple lookups on it. In terms
  of the usage described in the case study above, the "factory" aspect of
  the returned object will return the same singleton instance all the time.
  
  Here is the corresponding instance for ComposerImpl:
  
      public static Composer getInstance( Supplier supplier ) 
      {
          return (Composer) new MicroContainer( ComposerImpl.class )
                                    .addComponent( Supplier.ROLE, supplier )
                                    .sharedInstance( true )
                                    .create();
      }
  
  Similar, but note the addComponent() call - this sets up supplier as the
  component for the Supplier role.
  
  Also note that the getInstance method is type-safe: You must pass it a 
  supplier. If MicroContainer had used generic ObjectFactories this would
  not have been possible.
  
  
  Impact on Component Developers
  
  Developers can, if they feel like it, add a getInstance method for use
  with MicroContainer. However, it is not needed. 
  
  
  Impact on Component Users
  
  MicroContainer has no pooling of components, and provides no pluggability
  in itself. That is, when creating a container, you must specify the 
  implementation class.
  
  These two tradeoffs were made since MicroContainer would otherwise grow
  and become something like Fortress or ECM - a bit more than originally 
  envisioned.
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/Composer.java
  
  Index: Composer.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import org.apache.avalon.framework.component.Component;
  
  interface Composer extends Component {
      
      public static final String ROLE = Composer.class.getName();
      
      public void method ();
  }
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/ComposerImpl.java
  
  Index: ComposerImpl.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.component.Composable;
  
  public class ComposerImpl implements Composer, Composable, Disposable {
      
      private ComponentManager manager;
      
      public void compose( ComponentManager manager ) 
      {
          System.out.println( "In " + ComposerImpl.class.getName() + ".compose()" );
          this.manager = manager;
      }
      
      public void dispose() 
      {
          System.out.println( "In " + ComposerImpl.class.getName() + ".dispose()" );
      }
      
      public void method() 
      {
          System.out.println( "In " + ComposerImpl.class.getName() + ".method()" );
          Supplier supplier = null;
          try 
          {
              supplier = (Supplier) manager.lookup( Supplier.ROLE );
              supplier.supplierMethod ();
          }
          catch (ComponentException ce)
          {
              // Ignore for clarity's sake.
          }
          finally
          {
              manager.release(supplier);
          }
      }
      
      /**
       * Helper method to compose MicroContainers.
       */
      public static Composer getInstance( Supplier supplier ) 
      {
          return (Composer) new MicroContainer( ComposerImpl.class )
                                    .addComponent( Supplier.ROLE, supplier )
                                    .sharedInstance( true )
                                    .create();
      }
  }
  
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/Handler.java
  
  Index: Handler.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.Method;
  import java.lang.reflect.Proxy;
  import java.util.Map;
  
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.component.Composable;
  
  /**
   * InvocationHandler for MicroContainerProxies.
   */
  class Handler implements InvocationHandler {
      
      private Object instance;
      
      private final ComponentManager manager;
      
      private final Class componentClass;
      
      private final boolean shared;
      
      public Handler (Class componentClass, Map components, boolean shared) {
          this.componentClass = componentClass;
          this.manager = new MicroContainerComponentManager( components );
          this.shared = shared;
          
          this.instance = newInstance ();
      }
      
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          if (method.getName ().equals( "MicroContainerProxy__release" ))
          {
              release();
              return null;
          } 
          else if (method.getName().equals( "MicroContainerProxy__releaseInstance" ))
          {
              if ( !shared )
              {
                  release(args[0]);
              }
              return null;
          } 
          else if (method.getName().equals ( "MicroContainerProxy__newInstance" ))
          {
              if ( shared ) 
              {
                  return instance;
              } 
              else 
              {
                  return newInstance();
              }
          }
          return method.invoke(instance, args);
      } 
      
      public Object newInstance () {
          try 
          {
              Object inst = componentClass.newInstance();
              if (inst instanceof Composable)
              {
                  ((Composable) inst).compose( manager );
              }
              return inst;
          } 
          catch( Exception e ) 
          {
              throw new RuntimeException( e.toString () );
          }
      }
      
      public void release(Object o) {
          if (o instanceof Disposable)
          {
              ((Disposable) o).dispose ();
          }
      }  
      
      public void release() {
          if (instance instanceof Disposable)
          {
              ((Disposable) instance).dispose ();
          }
      }
  }
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MC.java
  
  Index: MC.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  /**
   * Demo.
   */
  public class MC {
      
      public static void main (String[] args) throws Exception 
      {
          Supplier supplier = SupplierImpl.getInstance ();
          Composer comp = ComposerImpl.getInstance (supplier);
          comp.method();
          
          MicroContainer.release (comp);
          MicroContainer.release (supplier);
      }    
  }
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MicroContainer.java
  
  Index: MicroContainer.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import java.lang.reflect.Proxy;
  import java.util.HashMap;
  import java.util.Map;
  
  /**
   * Factory class for MicroContainers. Usage follows the named parameter idiom:
   * <code>
   * Component myComponent = (Component) new MicroContainer( ComponentImpl.class )
   *                                             .sharedInstance( true )
   *                                             .components( componentMap )
   *                                             .create();
   * </code>
   */
  public class MicroContainer {
     
      private boolean sharedInstance = true;
      private Map components = new HashMap ();
      private final Class implementation;
      
      /**
       * Begin creation of a new MicroContainerProxy for the given component class.
       * Note that you are not interested in this class, but the object you get when
       * calling <code>create()</code>.
       */
      public MicroContainer( Class implementation )
      {
          this.implementation = implementation;
      }
      
      /**
       * Specifies whether the container should serve up the same instance to
       * all composers requesting an instance. If the component is thread safe,
       * you can set this to true. The default is <code>true</code>.
       */
      public MicroContainer sharedInstance( boolean shared )
      {
          this.sharedInstance = shared;
          return this;
      }
      
      /**
       * Populates the ComponentManager for the component class.
       * Each entry in the <code>components</code> map is assumed to be a
       * mapping from String to MicroContainerProxies, obtained via this class.
       */
      public MicroContainer components( Map components )
      {
          this.components = components;
          return this;
      }    
      
      public MicroContainer addComponent( String role, Object component )
      {
          components.put( role, component );
          return this;
      }
      
      /**
       * Creates a new MicroContainer proxy. The returned object implements all interfaces
       * of the implementation class.
       */
      public MicroContainerProxy create() throws RuntimeException 
      {
          try 
          {
              Class[] implementationInterfaces = implementation.getInterfaces ();
              Class[] proxyInterfaces = new Class[implementationInterfaces.length + 1];
              for (int i = 0; i < implementationInterfaces.length; i++) 
              {
                  proxyInterfaces[i + 1] = implementationInterfaces[i];
              }
              
              proxyInterfaces[0] = MicroContainerProxy.class;
              return (MicroContainerProxy) Proxy.newProxyInstance(
                  Thread.currentThread().getContextClassLoader(), 
                  proxyInterfaces, 
                  new Handler (implementation, components, sharedInstance)
                  );
          } 
          catch (Exception e) 
          {
              throw new RuntimeException( e.toString() );
          }
      }
      
      /**
       * Disposes of a MicroContainer proxy.
       */
      public static void release(Object o) 
      {
          if (o instanceof MicroContainerProxy) 
          {
              ((MicroContainerProxy) o).MicroContainerProxy__release();
          }
      }
  }
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MicroContainerComponentManager.java
  
  Index: MicroContainerComponentManager.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  
  import org.apache.avalon.framework.component.Component;
  import org.apache.avalon.framework.component.ComponentManager;
  
  /**
   * An implementation of the ComponentManager interface for
   * MicroContainers.
   */
  public class MicroContainerComponentManager implements ComponentManager {
      
      private final HashMap handlers = new HashMap ();
      private final HashMap used = new HashMap ();
      
      public MicroContainerComponentManager( Map components ) {
          Iterator iter = components.keySet().iterator();
          while (iter.hasNext ()) 
          {
              String role = (String) iter.next ();
              Object component = components.get (role);
              
              // Verify that component is a MicroContainerProxy...
              
              handlers.put (role, component);
          }
      }
      
      public boolean hasComponent( String role ) {
          return handlers.containsKey( role );
      }
      
      public Component lookup( String role ) {
          MicroContainerProxy handler = (MicroContainerProxy) handlers.get( role );
          
          Component component = (Component) handler.MicroContainerProxy__newInstance();
          
          used.put( component, handler );
          
          return component;
      }
      
      public void release( Component instance ) {
          MicroContainerProxy handler = (MicroContainerProxy) used.remove( instance );
          
          handler.MicroContainerProxy__releaseInstance( instance );
      }    
  }
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/MicroContainerProxy.java
  
  Index: MicroContainerProxy.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  
  /**
   * The "factory" interface for proxies. The method names have been
   * chose so as to not conflict with any known interface.
   */
  interface MicroContainerProxy {
      /**
       * Create a new instance of the managed component.
       */
      public Object MicroContainerProxy__newInstance();
      
      /**
       * Release the active instance of the component.
       */
      public void MicroContainerProxy__release();
      
      /**
       * Release an instance created via MicroContainerProxy__newInstance().
       */
      public void MicroContainerProxy__releaseInstance(Object instance);
  }
  
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/Supplier.java
  
  Index: Supplier.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import org.apache.avalon.framework.component.Component;
  
  interface Supplier extends Component {
      
      public static final String ROLE = Supplier.class.getName();
      
      public void supplierMethod ();
  }
  
  
  
  1.1                  jakarta-avalon/src/proposal/microcontainer/src/org/apache/avalon/microcontainer/SupplierImpl.java
  
  Index: SupplierImpl.java
  ===================================================================
  package org.apache.avalon.microcontainer;
  
  import java.util.HashMap;
  import java.util.Map;
  
  import org.apache.avalon.framework.activity.Disposable;
  
  public class SupplierImpl implements Supplier, Disposable {
      
      public void supplierMethod() 
      {
          System.out.println( "In " + SupplierImpl.class.getName() + ".supplierMethod()" );
      }
      
      public void dispose() 
      {
          System.out.println( "In " + SupplierImpl.class.getName() + ".dispose()" );
      }
      
      public static Supplier getInstance() 
      {
          return (Supplier) new MicroContainer( SupplierImpl.class ).sharedInstance( true ).create();
      }
  }
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>