mcconnell 2002/12/27 08:44:02 Modified: merlin/src/java/org/apache/avalon/merlin/kernel DefaultKernel.java Log: Rewrite of the block loading approach to leverage notion of Block as an extended appliance. Revision Changes Path 1.16 +425 -101 avalon-sandbox/merlin/src/java/org/apache/avalon/merlin/kernel/DefaultKernel.java Index: DefaultKernel.java =================================================================== RCS file: /home/cvs/avalon-sandbox/merlin/src/java/org/apache/avalon/merlin/kernel/DefaultKernel.java,v retrieving revision 1.15 retrieving revision 1.16 diff -u -r1.15 -r1.16 --- DefaultKernel.java 21 Dec 2002 05:18:22 -0000 1.15 +++ DefaultKernel.java 27 Dec 2002 16:44:02 -0000 1.16 @@ -55,6 +55,7 @@ import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; +import java.net.MalformedURLException; import java.util.List; import java.util.Iterator; import java.util.Map; @@ -67,8 +68,20 @@ import java.util.zip.ZipEntry; import java.net.JarURLConnection; +import org.apache.avalon.assembly.logging.LoggingManager; +import org.apache.avalon.assembly.logging.LoggingDescriptor; +import org.apache.avalon.assembly.logging.DefaultLoggingManager; +import org.apache.avalon.assembly.engine.EngineClassLoader; +import org.apache.avalon.assembly.engine.Engine; +import org.apache.avalon.assembly.engine.model.LibraryDescriptor; +import org.apache.avalon.assembly.engine.model.ClasspathDescriptor; +import org.apache.avalon.assembly.util.ExceptionHelper; +import org.apache.avalon.assembly.appliance.Appliance; +import org.apache.avalon.assembly.appliance.ApplianceContext; +import org.apache.avalon.assembly.appliance.DefaultApplianceContext; import org.apache.avalon.framework.CascadingException; import org.apache.avalon.framework.logger.Logger; +import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.activity.Startable; import org.apache.avalon.framework.activity.Disposable; @@ -82,46 +95,44 @@ import org.apache.avalon.framework.context.Contextualizable; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.service.DefaultServiceManager; -import org.apache.avalon.meta.model.LoggingDirective; import org.apache.avalon.merlin.block.Block; import org.apache.avalon.merlin.block.DefaultBlock; +import org.apache.avalon.merlin.block.BlockException; import org.apache.avalon.merlin.container.builder.XMLContainerCreator; -import org.apache.avalon.assembly.logging.LoggingManager; -import org.apache.avalon.assembly.logging.LoggingDescriptor; -import org.apache.avalon.assembly.logging.DefaultLoggingManager; -import org.apache.avalon.assembly.engine.EngineClassLoader; -import org.apache.avalon.assembly.engine.Engine; -import org.apache.avalon.assembly.engine.model.LibraryDescriptor; -import org.apache.avalon.assembly.engine.model.ClasspathDescriptor; -import org.apache.avalon.assembly.util.ExceptionHelper; -import org.apache.avalon.assembly.appliance.Appliance; -import org.apache.avalon.merlin.block.Block; -import org.apache.avalon.merlin.block.DefaultBlock; -import org.apache.avalon.merlin.block.BlockLoader; +import org.apache.avalon.merlin.container.ContainerDescriptor; +import org.apache.avalon.merlin.container.ContainerLoader; import org.apache.avalon.merlin.container.Container; +import org.apache.avalon.merlin.container.ContainerException; +import org.apache.avalon.merlin.container.DefaultContainer; import org.apache.avalon.merlin.service.DefaultRegistry; import org.apache.avalon.merlin.service.Registry; +import org.apache.avalon.meta.model.LoggingDirective; +import org.apache.avalon.meta.model.Profile; +import org.apache.avalon.meta.info.Type; /** * Default kernel implementation. The implementation provides support for * the following sequence of activties: * * <ul> + * <li>loading kernel.xml configuration + * <li>loading blocks.xml configuration + * <li>establishment of the logging system * <li>loading internal jar-file dependencies - * <li>establishment of a logging system - * <li>creation of a root container - * <li>startup of the container - * <li>management of interupt conditions or request for kernel disposal - * <li>error reporting + * <li>establishment of a root component management engine + * <li>block abstraction that encapsulates a heirachical container model + * <li>block configuration repository + * <li>block assembly, deployment and decommissioning + * <li>management of interupt conditions and kernel disposal + * <li>error management * </ul> * - * @see DefaultKernel - * * @author <a href="mailto:avalon-dev@jakarta.apache.org">Avalon Development Team</a> * @version $Revision$ $Date$ + * @see DefaultBlock */ -public class DefaultKernel extends BlockLoader implements Kernel, Contextualizable, Configurable, Initializable, Startable, Disposable +public class DefaultKernel extends ContainerLoader implements Kernel, Contextualizable, Initializable, Startable, Disposable { //============================================================== // static @@ -133,16 +144,23 @@ */ private static LoggingManager LOGGING; + protected static final String BLOCK_XML_ENTRY = "BLOCK-INF/block.xml"; + //============================================================== // state //============================================================== /** - * The supplied configuration. + * The kernel configuration. */ private Configuration m_config; /** + * The block configuration. + */ + private Configuration m_configuration; + + /** * The application home directory. */ private File m_home; @@ -184,20 +202,21 @@ //============================================================== /** - * <p>Application of a runtime context to this appliance. + * <p>Application of a runtime context to the kernel. * Context entries that may be supplied to an appliance manager are * detailed in the following table.</p> - * <table> - * <tr> - * <td><strong>key</strong></td><td><strong>type</strong></td><td><strong>description</strong></td> + * <table BORDER="1" CELLSPACING="0" CELLPADDING="3" WIDTH="90%" > + * <tr BGCOLOR="#EEEEFF"> + * <td><b>key</b></td><td><b>type</b></td><td><b>description</b></td> * </tr> * <tr> - * <td>urn:assembly:home</td> + * <td><code>urn:assembly:home</code></td> * <td>[EMAIL PROTECTED] java.io.File}</td> * <td>The kernel home directory.</td> * </tr> * </table> * @param context the runtime context + * @exception ContextException if an error occurs during the contextualization phase */ public void contextualize( Context context ) throws ContextException { @@ -205,30 +224,16 @@ } //============================================================== - // Configurable - //============================================================== - - /** - * Supply of the static configuration for the kernel. - * @param config the static configuration - */ - public void configure( Configuration config ) throws ConfigurationException - { - if( config == null ) - { - throw new NullPointerException( "config" ); - } - m_config = config; - } - - //============================================================== // Initializable //============================================================== /** - * Initialization of the kernel. + * Initialization of the kernel. The implementation hanldes the + * establishment of the logging system, bootstraps the assembly + * engine, loads all blocks declared in the kernel configuration, + * initiates block assembly and list available services. * - * @exception if an initialization error occurs. + * @exception Exception if an initialization error occurs. */ public void initialize() throws Exception { @@ -236,9 +241,39 @@ { throw new IllegalStateException("context"); } - if( m_config == null ) + + // + // load the kernel configuration + // + + File kernelFile = new File( m_home, "kernel.xml" ); + try { - throw new IllegalStateException("configuration"); + m_config = getConfiguration( kernelFile ); + } + catch( Throwable e ) + { + final String error = + "Unable to load kernel configuration from file: " + + kernelFile; + throw new CascadingException( error, e ); + } + + // + // load the blocks configuration + // + + File blocksFile = new File( m_home, "blocks.xml" ); + try + { + m_configuration = getConfiguration( blocksFile ); + } + catch( Throwable e ) + { + final String error = + "Unable to load blocks configuration from file: " + + kernelFile; + throw new CascadingException( error, e ); } // @@ -247,14 +282,13 @@ try { - final String root = "/"; if( LOGGING == null ) { - LOGGING = bootstrapLoggingManager( root ); + LOGGING = bootstrapLoggingManager( Container.PATH_SEPERATOR ); Configuration categoriesConfig = m_config.getChild( "categories" ); LoggingDirective categories = - CREATOR.createLoggingDirective( root, categoriesConfig ); - LOGGING.addCategories( root, categories ); + CREATOR.createLoggingDirective( Container.PATH_SEPERATOR, categoriesConfig ); + LOGGING.addCategories( Container.PATH_SEPERATOR, categories ); } final String sys = "/sys"; m_local = LOGGING.getLoggerForCategory( sys ); @@ -267,7 +301,7 @@ throw new CascadingException( error, e ); } - String domain = m_config.getChild( "system" ).getChild( "host" ).getValue( "localhost" ); + String domain = m_config.getChild( "system" ).getAttribute( "host", "localhost" ); m_registry = new DefaultRegistry( domain ); // @@ -276,7 +310,7 @@ if( getLogger().isInfoEnabled() ) { - getLogger().info( "initialization from: " + m_home ); + getLogger().info( "initialization from: " + m_home + " on " + domain ); } // @@ -314,14 +348,12 @@ Block[] blocks; try { - ClasspathDescriptor path = - CREATOR.createClasspathDescriptor( m_config.getChild( "blocks" ) ); - URL[] urls = ClasspathDescriptor.expand( m_home, path ); Context system = getSystemContext(); - blocks = loadBlocks( m_engine, m_registry, m_home, urls, system ); + blocks = loadPhysicalBlocks( m_configuration, system ); for( int i=0; i<blocks.length; i++ ) { - m_blocks.add( blocks[i] ); + Block block = blocks[i]; + m_blocks.add( block ); } } catch( Throwable e ) @@ -343,21 +375,18 @@ { getLogger().info( "commencing structural assembly phase" ); } - + for( int i=0; i<blocks.length; i++ ) { Block block = blocks[i]; - Appliance appliance = block.getAppliance(); try { - appliance.assemble(); - Container container = (Container)appliance.access(); - m_containers.put( block, container ); + block.assemble(); } catch( Throwable e ) { final String error = - "Unable to deploy root container: " + appliance; + "Unable to deploy block: " + block + " due to a structural assembly failure."; String log = ExceptionHelper.packException( error, e ); if( getLogger().isErrorEnabled() ) { @@ -366,22 +395,7 @@ throw new KernelException( error, e ); } } - - // - // Initiate component assembly on all blocks. - // - - if( getLogger().isInfoEnabled() ) - { - getLogger().info( "commencing composite assembly phase" ); - } - for( int i=0; i<blocks.length; i++ ) - { - Block block = blocks[i]; - Container container = (Container) m_containers.get( block ); - container.assemble(); - } - + // // list the registered services // @@ -407,7 +421,7 @@ /** * Start the kernel. * - * @exception if an startup error occurs. + * @exception Exception if an startup error occurs. */ public void start() throws Exception { @@ -423,18 +437,14 @@ while( iterator.hasNext() ) { Block block = (Block) iterator.next(); - Container container = (Container) m_containers.get( block ); - if( container != null ) - { - container.startup(); - } + block.startup(); } } /** * Stop the kernel. * - * @exception if an shutdown error occurs. + * @exception Exception if an shutdown error occurs. */ public void stop() throws Exception { @@ -450,11 +460,7 @@ while( iterator.hasNext() ) { Block block = (Block) iterator.next(); - Container container = (Container) m_containers.get( block ); - if( container != null ) - { - container.shutdown(); - } + block.shutdown(); } } @@ -479,13 +485,7 @@ while( iterator.hasNext() ) { Block block = (Block) iterator.next(); - Container container = (Container) m_containers.get( block ); - if( container != null ) - { - m_containers.remove( block ); - block.getAppliance().release( container ); - block.getAppliance().terminate(); - } + block.terminate(); } if( getLogger() != null ) @@ -501,6 +501,247 @@ // internals //============================================================== + /** + * Load all of the blocks declared in the block.xml configuration. + * The configuration contains an arbitary number of "block" elements + * each containing the name and path of a jar file to be loaded together + * with client supplied configuration data. + * + * @param conf the configuration loaded from the blocks.xml file + * @param system the system context + * @return the set of blocks + */ + private Block[] loadPhysicalBlocks( Configuration conf, Context system ) + throws Exception + { + Configuration[] configs = conf.getChildren("block"); + getLogger().info("block count = " + configs.length ); + + ArrayList blocks = new ArrayList(); + for( int i=0; i<configs.length; i++ ) + { + Configuration config = configs[i]; + try + { + Block block = loadPhysicalBlock( config, system ); + blocks.add( block ); + } + catch( Throwable e ) + { + final String error = + "Error during block deployment for block: " + i; + throw new BlockException( error, e ); + } + } + return (Block[]) blocks.toArray( new Block[0] ); + } + + /** + * Load a single block defintion. The defintion is a configuration + * element named "block" that contains a name and a jar file location. + * The block name is defined by the block name attribute and the location + * of the jar file if defined by the value of the "path" attribute. The + * implementation loads a block defintion for the supplied jar file + * and uses this to establish a subsidiary classloader and appliance + * to handle the implicit implementation container. The block returned + * from this method is the implicit appliance handling the root container. + * + * @param config the block declaration + * @param system the system context + * @return the block + * @exception Exception if a block loading error occurs + */ + private Block loadPhysicalBlock( Configuration config, Context system ) + throws Exception + { + + // + // get the basic information for the block declaration + // in the block.xml file + // + + String name = config.getAttribute( "name" ); + String path = config.getAttribute("path"); + Logger logger = getLogger().getChildLogger( name ); + logger.debug( "[" + path + "] as [" + name + "]"); + + // + // load the jar file referenced by the block declaration and get its + // manifest + // + + URL url = new File( m_home, path ).getCanonicalFile().toURL(); + JarFile jar = getJarFile( url ); + Manifest manifest = jar.getManifest(); + if( !DefaultBlock.isBlock( manifest ) ) + { + final String warning = + "Manifest does not declare a block within the resource: " + url; + throw new IllegalArgumentException( warning ); + } + + String packageName = DefaultBlock.getPackageName( manifest ); + if( packageName == null ) + { + final String error = "Missing package declaration in block: " + url; + throw new IllegalArgumentException( error ); + } + + // + // get the blocks packaged configuration and use that to establish + // the engine to supplied to the block + // + + Configuration base = getBlockConfiguration( jar ); + Configuration engineConfig = base.getChild( "engine" ); + EngineClassLoader engine = createChildEngine( m_engine, m_home, engineConfig, url, logger ); + + // + // create an appliance context and add to it a registry derived from the + // parent registry + // + + String partition = name + Container.PATH_SEPERATOR; + Configuration containment = base.getChild( "implementation" ); + ContainerDescriptor descriptor = createContainerDescriptor( name, engine, containment ); + Registry registry = m_registry.createChild( name ); + List list = createChildContainers( engine, registry, partition, containment, logger ); + + // + // create the appliance context for the container + // + + Map map = new Hashtable(); + map.put("urn:assembly:engine.classloader", engine ); + map.put("urn:merlin:container.containers", list ); + map.put("urn:merlin:container.descriptor", descriptor ); + map.put("urn:merlin:container.registry", registry ); + + DefaultApplianceContext context = new DefaultApplianceContext( descriptor ); + context.setName( name ); + context.setDeploymentContext( map ); + context.setApplianceClassname( DefaultBlock.class.getName() ); + + return (Block) engine.createAppliance( context, false ); + + } + + /** + * Create a descriptor of a container populated with the defintintion + * of the components it is managing. + * + * @param name the container name + * @param engine the classloader to use + * @param config the confuration describing the containment scenario + * @return the container descriptor + */ + private ContainerDescriptor createContainerDescriptor( + String name, EngineClassLoader engine, Configuration config ) + throws Exception + { + String classname = config.getAttribute( "class", DefaultContainer.class.getName() ); + Type type = engine.getRepository().getTypeManager().getType( classname ); + ContainerDescriptor descriptor = CREATOR.createContainerDescriptor( type, config, name ); + Configuration[] children = config.getChildren( "appliance" ); + for( int i=0; i<children.length; i++ ) + { + Profile profile = createProfile( name, engine, children[i] ); + engine.getRepository().getProfileManager().addProfile( profile ); + descriptor.addComponent( profile ); + } + return descriptor; + } + + /** + * Creation of a set of child container relative to a set of parent parameters. + * + * @param engine the parent classloader + * @param registry the parent registry + * @param partition the partition to be applied to the child containers + * @param config a confiuration containing a set of subsidiary container elements + * @param logger the logger from which child loggers shall be created + * @return a list of appliance instances each representing a container container + */ + private List createChildContainers( + EngineClassLoader engine, Registry registry, String partition, + Configuration config, Logger logger ) + throws Exception + { + List list = new ArrayList(); + Configuration[] children = config.getChildren( "container" ); + + for( int i=0; i<children.length; i++ ) + { + Configuration child = children[i]; + String name = child.getAttribute( "name" ); + Logger log = logger.getChildLogger( name ); + EngineClassLoader loader = createChildEngine( + engine, m_home, child.getChild("engine"), log ); + Registry reg = registry.createChild( name ); + + Appliance appliance = createContainerAppliance( + loader, reg, partition, name, child, log ); + list.add( appliance ); + } + + return list; + } + + /** + * Create a single containment appliance. + * + * @param engine the containers classloader + * @param registry the compoent registry to apply to the container + * @param partition the partition to assigne to the container + * @param name the appliance name + * @param config the configuration of the container + * @param logger the logging channel to apply to classloaders created for child containers + * @return the containment appliance + * @exception if an error occurs during creation of the containment appliance + */ + private Appliance createContainerAppliance( + EngineClassLoader engine, Registry registry, String partition, String name, + Configuration config, Logger logger ) + throws Exception + { + String subPartition = partition + name + Container.PATH_SEPERATOR; + List list = createChildContainers( engine, registry, subPartition, config, logger ); + ContainerDescriptor descriptor = createContainerDescriptor( name, engine, config ); + + // + // create the appliance context for the container + // + + Map map = new Hashtable(); + map.put("urn:assembly:engine.classloader", engine ); + map.put("urn:merlin:container.containers", list ); + map.put("urn:merlin:container.descriptor", descriptor ); + map.put("urn:merlin:container.registry", registry ); + map.put("urn:avalon:partition.name", partition ); + + DefaultApplianceContext context = new DefaultApplianceContext( descriptor ); + context.setName( name ); + context.setDeploymentContext( map ); + context.setPartitionName( partition ); + + // + // create the containement appliance + // + + try + { + context.makeReadOnly(); + return engine.createAppliance( context, false ); + } + catch( Throwable e ) + { + final String error = + "Unable to create containment appliance: " + name; + throw new ContainerException( error, e ); + } + + } + private LoggingManager bootstrapLoggingManager( String root ) throws Exception { if( m_config == null ) @@ -620,5 +861,88 @@ m_system = context; } return m_system; + } + + private Configuration getConfiguration( final File file ) throws ConfigurationException + { + try + { + DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); + InputStream is = new FileInputStream( file ); + if( is == null ) + { + throw new ConfigurationException( + "Could not load the configuration resource \"" + file + "\"" ); + } + return builder.build( is ); + } + catch( Throwable e ) + { + final String error = "Unable to create configuration from file: " + file; + throw new ConfigurationException( error, e ); + } + } + + private JarFile getJarFile( URL url ) throws MalformedURLException + { + URL xurl; + + String protocol = url.getProtocol(); + if( protocol.equals("jar") ) + { + xurl = url; + } + else + { + xurl = new URL( "jar:" + url.toString() + "!/" ); + } + + try + { + JarURLConnection connection = (JarURLConnection) xurl.openConnection(); + return connection.getJarFile(); + } + catch( Throwable ioe ) + { + final String error = + "Unexpected Exception while reading jar file from url: " + xurl + " cause: " + ioe; + throw new RuntimeException( error ); + } + } + + private Configuration getBlockConfiguration( JarFile jar ) throws Exception + { + if( jar == null ) + { + throw new NullPointerException( "jar" ); + } + + ZipEntry entry = jar.getEntry( BLOCK_XML_ENTRY ); + if( entry == null ) + { + if( getLogger().isDebugEnabled() ) + { + final String msg = "No block configuration - applying defaults."; + getLogger().debug( msg ); + } + return new DefaultConfiguration( "block", jar.getName() ); + } + + try + { + DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); + InputStream is = jar.getInputStream( entry ); + if( is == null ) + { + throw new RuntimeException( + "Could not load the configuration resource \"" + jar.getName() + "\"" ); + } + return builder.build( is ); + } + catch( Throwable e ) + { + final String error = "Unable to create configuration from jar file: " + jar.getName(); + throw new BlockException( error, e ); + } } }
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>