You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@avalon.apache.org by do...@apache.org on 2002/06/04 10:46:22 UTC

cvs commit: jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/tools/verifier AssemblyVerifier.java

donaldp     2002/06/04 01:46:22

  Added:       src/java/org/apache/avalon/phoenix/tools/verifier
                        AssemblyVerifier.java
  Log:
  Add a basic verifier for generic assemblys
  
  Revision  Changes    Path
  1.1                  jakarta-avalon-phoenix/src/java/org/apache/avalon/phoenix/tools/verifier/AssemblyVerifier.java
  
  Index: AssemblyVerifier.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.phoenix.tools.verifier;
  
  import java.util.ArrayList;
  import java.util.Stack;
  import org.apache.avalon.excalibur.i18n.ResourceManager;
  import org.apache.avalon.excalibur.i18n.Resources;
  import org.apache.avalon.framework.logger.AbstractLogEnabled;
  import org.apache.avalon.framework.logger.Logger;
  import org.apache.excalibur.containerkit.metadata.ComponentMetaData;
  import org.apache.excalibur.containerkit.metadata.DependencyMetaData;
  import org.apache.excalibur.containerkit.metainfo.ComponentInfo;
  import org.apache.excalibur.containerkit.metainfo.DependencyDescriptor;
  import org.apache.excalibur.containerkit.metainfo.ServiceDescriptor;
  import org.apache.excalibur.containerkit.verifier.Verifier;
  import org.apache.excalibur.containerkit.verifier.VerifyException;
  
  /**
   * This Class verifies that Sars are valid. It performs a number
   * of checks to make sure that the Sar represents a valid
   * application and excluding runtime errors will start up validly.
   * Some of the checks it performs include;
   *
   * <ul>
   *   <li>Verify names of Sar, Blocks and BlockListeners contain only
   *       letters, digits or the '_' character.</li>
   *   <li>Verify that the names of the Blocks and BlockListeners are
   *       unique to Sar.</li>
   *   <li>Verify that the dependendencies specified in assembly.xml
   *       correspond to dependencies specified in ComponentInfo files.</li>
   *   <li>Verify that the inter-block dependendencies specified in
   *       assembly.xml are valid. This essentially means that if
   *       Block A requires Service S from Block B then Block B must
   *       provide Service S.</li>
   *   <li>Verify that there are no circular dependendencies between
   *       blocks.</li>
   *   <li>Verify that the Class objects for Blocks support the Block
   *       interface and any specified Services.</li>
   *   <li>Verify that the Class objects for BlockListeners support the
   *       BlockListener interface.</li>
   * </ul>
   *
   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
   * @version $Revision: 1.1 $ $Date: 2002/06/04 08:46:22 $
   */
  public class AssemblyVerifier
      extends AbstractLogEnabled
  {
      private static final Resources REZ =
          ResourceManager.getPackageResources( AssemblyVerifier.class );
      private final Verifier m_verifier = new Verifier();
  
      public void enableLogging( Logger logger )
      {
          super.enableLogging( logger );
          setupLogger( m_verifier );
      }
  
      /**
       * Validate and Verify the specified assembly (ie organization
       * of components). See the Class Javadocs for the rules and
       * regulations of assembly.
       *
       * @param components the Components that make up assembly
       * @param classLoader the ClassLoader used to load types. This is used
       *                    to verify that specified Class objects exist and
       *                    implement the correct interfaces.
       * @throws VerifyException if an error occurs
       */
      public void verifySar( final ComponentMetaData[] components,
                             final ClassLoader classLoader )
          throws VerifyException
      {
          String message = null;
  
          message = REZ.getString( "verify-valid-names" );
          getLogger().info( message );
          verifyValidNames( components );
  
          message = REZ.getString( "verify-unique-names" );
          getLogger().info( message );
          checkNamesUnique( components );
  
          message = REZ.getString( "verify-dependencies-mapping" );
          getLogger().info( message );
          verifyValidDependencies( components );
  
          message = REZ.getString( "verify-dependency-references" );
          getLogger().info( message );
          verifyDependencyReferences( components );
  
          message = REZ.getString( "verify-nocircular-dependencies" );
          getLogger().info( message );
          verifyNoCircularDependencies( components );
  
          message = REZ.getString( "verify-block-type" );
          getLogger().info( message );
          verifyTypes( components, classLoader );
      }
  
      /**
       * Verfiy that all Blocks have the needed dependencies specified correctly.
       *
       * @param blocks the ComponentMetaData objects for the blocks
       * @throws VerifyException if an error occurs
       */
      private void verifyValidDependencies( final ComponentMetaData[] blocks )
          throws VerifyException
      {
          for( int i = 0; i < blocks.length; i++ )
          {
              verifyDependenciesMap( blocks[ i ] );
          }
      }
  
      /**
       * Verfiy that there are no circular references between Components.
       *
       * @param components the ComponentMetaData objects for the components
       * @throws VerifyException if an error occurs
       */
      private void verifyNoCircularDependencies( final ComponentMetaData[] components )
          throws VerifyException
      {
          for( int i = 0; i < components.length; i++ )
          {
              final ComponentMetaData component = components[ i ];
  
              final Stack stack = new Stack();
              stack.push( component );
              verifyNoCircularDependencies( component, components, stack );
              stack.pop();
          }
      }
  
      /**
       * Verfiy that there are no circular references between Components.
       *
       * @param components the ComponentMetaData objects for the components
       * @throws VerifyException if an error occurs
       */
      private void verifyNoCircularDependencies( final ComponentMetaData component,
                                                 final ComponentMetaData[] components,
                                                 final Stack stack )
          throws VerifyException
      {
          final ComponentMetaData[] dependencies = getDependencies( component, components );
  
          for( int i = 0; i < dependencies.length; i++ )
          {
              final ComponentMetaData dependency = dependencies[ i ];
              if( stack.contains( dependency ) )
              {
                  final String trace = getDependencyTrace( dependency, stack );
                  final String message =
                      REZ.getString( "dependency-circular",
                                     component.getName(),
                                     trace );
                  throw new VerifyException( message );
              }
  
              stack.push( dependency );
              verifyNoCircularDependencies( dependency, components, stack );
              stack.pop();
          }
      }
  
      /**
       * Get a string defining path from top of stack till
       * it reaches specified component.
       *
       * @param component the component
       * @param stack the Stack
       * @return the path of dependency
       */
      private String getDependencyTrace( final ComponentMetaData component,
                                         final Stack stack )
      {
          final StringBuffer sb = new StringBuffer();
          sb.append( "[ " );
  
          final String name = component.getName();
          final int size = stack.size();
          final int top = size - 1;
          for( int i = top; i >= 0; i-- )
          {
              final ComponentMetaData other = (ComponentMetaData)stack.get( i );
              if( top != i )
              {
                  sb.append( ", " );
              }
              sb.append( other.getName() );
  
              if( other.getName().equals( name ) )
              {
                  break;
              }
          }
  
          sb.append( ", " );
          sb.append( name );
  
          sb.append( " ]" );
          return sb.toString();
      }
  
      /**
       * Get array of dependencies for specified Component from specified
       * Component array.
       *
       * @param component the component to get dependencies of
       * @param components the total set of components in application
       * @return the dependencies of component
       */
      private ComponentMetaData[] getDependencies( final ComponentMetaData component,
                                                   final ComponentMetaData[] components )
      {
          final ArrayList dependencies = new ArrayList();
          final DependencyMetaData[] deps = component.getDependencies();
  
          for( int i = 0; i < deps.length; i++ )
          {
              final String name = deps[ i ].getName();
              final ComponentMetaData other = getComponentMetaData( name, components );
              dependencies.add( other );
          }
  
          return (ComponentMetaData[])dependencies.toArray( new ComponentMetaData[ 0 ] );
      }
  
      /**
       * Verfiy that the inter-Component dependencies are valid.
       *
       * @param components the ComponentMetaData objects for the components
       * @throws VerifyException if an error occurs
       */
      private void verifyDependencyReferences( final ComponentMetaData[] components )
          throws VerifyException
      {
          for( int i = 0; i < components.length; i++ )
          {
              verifyDependencyReferences( components[ i ], components );
          }
      }
  
      /**
       * Verfiy that the inter-Component dependencies are valid for specified Component.
       *
       * @param component the ComponentMetaData object for the component
       * @param others the ComponentMetaData objects for the other components
       * @throws VerifyException if an error occurs
       */
      private void verifyDependencyReferences( final ComponentMetaData component,
                                               final ComponentMetaData[] others )
          throws VerifyException
      {
          final ComponentInfo info = component.getComponentInfo();
          final DependencyMetaData[] roles = component.getDependencies();
  
          for( int i = 0; i < roles.length; i++ )
          {
              final String providerName = roles[ i ].getName();
              final String roleName = roles[ i ].getRole();
              final ServiceDescriptor service =
                  info.getDependency( roleName ).getService();
  
              //Get the other block that is providing service
              final ComponentMetaData provider = getComponentMetaData( providerName, others );
              if( null == provider )
              {
                  final String message =
                      REZ.getString( "dependency-noblock",
                                     providerName,
                                     component.getName() );
                  throw new VerifyException( message );
              }
  
              //make sure that the block offers service
              //that user expects it to be providing
              final ServiceDescriptor[] services = provider.getComponentInfo().getServices();
              if( !hasMatchingService( service, services ) )
              {
                  final String message =
                      REZ.getString( "dependency-noservice",
                                     providerName,
                                     service,
                                     component.getName() );
                  throw new VerifyException( message );
              }
          }
      }
  
      /**
       * Get Block with specified name from specified Block array.
       *
       * @param name the name of block to get
       * @param components the array of Blocks to search
       * @return the Block if found, else null
       */
      private ComponentMetaData getComponentMetaData( final String name,
                                                      final ComponentMetaData[] components )
      {
          for( int i = 0; i < components.length; i++ )
          {
              if( components[ i ].getName().equals( name ) )
              {
                  return components[ i ];
              }
          }
  
          return null;
      }
  
      /**
       * Verfiy that all Components specify classes that implement the
       * advertised interfaces.
       *
       * @param components the ComponentMetaData objects for the components
       * @throws VerifyException if an error occurs
       */
      private void verifyTypes( final ComponentMetaData[] components,
                                     final ClassLoader classLoader )
          throws VerifyException
      {
          for( int i = 0; i < components.length; i++ )
          {
              verifyType( components[ i ], classLoader );
          }
      }
  
      /**
       * Verfiy that specified Block designate classes that implement the
       * advertised interfaces.
       *
       * @param block the ComponentMetaData object for the blocks
       * @throws VerifyException if an error occurs
       */
      private void verifyType( final ComponentMetaData block, final ClassLoader classLoader )
          throws VerifyException
      {
          final String name = block.getName();
          final String classname = block.getClassname();
          Class clazz = null;
          try
          {
              clazz = classLoader.loadClass( classname );
          }
          catch( final Exception e )
          {
              final String message =
                  REZ.getString( "bad-block-class",
                                                    name,
                                                    classname,
                                                    e.getMessage() );
              throw new VerifyException( message );
          }
  
          final Class[] interfaces =
              getServiceClasses( name,
                                 block.getComponentInfo().getServices(),
                                 classLoader );
  
          m_verifier.verifyComponent( name, clazz, interfaces );
      }
  
      /**
       * Verify that the names of the specified Components are valid.
       *
       * @param components the Components metadata
       * @throws VerifyException if an error occurs
       */
      private void verifyValidNames( final ComponentMetaData[] components )
          throws VerifyException
      {
          for( int i = 0; i < components.length; i++ )
          {
              final String name = components[ i ].getName();
              if( !isValidName( name ) )
              {
                  final String message =
                      REZ.getString( "invalid-block-name", name );
                  throw new VerifyException( message );
              }
          }
      }
  
      /**
       * Return true if specified name is valid.
       * Valid names consist of letters, digits or the '_' character.
       *
       * @param name the name to check
       * @return true if valid, false otherwise
       */
      private boolean isValidName( final String name )
      {
          final int size = name.length();
          for( int i = 0; i < size; i++ )
          {
              final char ch = name.charAt( i );
  
              if( !Character.isLetterOrDigit( ch ) && '-' != ch )
              {
                  return false;
              }
          }
  
          return true;
      }
  
      /**
       * Verify that the names of the specified blocks and listeners are unique.
       * It is not valid for the same name to be used in multiple Blocks and or
       * BlockListeners.
       *
       * @param components the Components
       * @throws VerifyException if an error occurs
       */
      private void checkNamesUnique( final ComponentMetaData[] components )
          throws VerifyException
      {
          for( int i = 0; i < components.length; i++ )
          {
              final String name = components[ i ].getName();
              verifyUniqueName( components, name, i );
          }
      }
  
      /**
       * Verfify that specified name is unique among the specified components.
       *
       * @param components the array of components to check
       * @param name the name of component
       * @param index the index of component in array (so we can skip it)
       * @throws VerifyException if names are not unique
       */
      private void verifyUniqueName( final ComponentMetaData[] components,
                                     final String name,
                                     final int index )
          throws VerifyException
      {
          for( int i = 0; i < components.length; i++ )
          {
              final String other = components[ i ].getName();
              if( index != i && other.equals( name ) )
              {
                  final String message =
                      REZ.getString( "duplicate-name", name );
                  throw new VerifyException( message );
              }
          }
      }
  
      /**
       * Retrieve a list of DependencyMetaData objects for ComponentMetaData
       * and verify that there is a 1 to 1 map with dependencies specified
       * in ComponentInfo.
       *
       * @param block the ComponentMetaData describing the block
       * @throws VerifyException if an error occurs
       */
      private void verifyDependenciesMap( final ComponentMetaData block )
          throws VerifyException
      {
          //Make sure all role entries specified in config file are valid
          final DependencyMetaData[] roles = block.getDependencies();
          for( int i = 0; i < roles.length; i++ )
          {
              final String roleName = roles[ i ].getRole();
              final DependencyDescriptor descriptor = block.getComponentInfo().getDependency( roleName );
  
              //If there is no dependency descriptor in ComponentInfo then
              //user has specified an uneeded dependency.
              if( null == descriptor )
              {
                  final String message =
                      REZ.getString( "unknown-dependency",
                                     roles[ i ].getName(),
                                     roleName,
                                     block.getName() );
                  throw new VerifyException( message );
              }
          }
  
          //Make sure all dependencies in ComponentInfo file are satisfied
          final DependencyDescriptor[] dependencies = block.getComponentInfo().getDependencies();
          for( int i = 0; i < dependencies.length; i++ )
          {
              final DependencyMetaData role = block.getDependency( dependencies[ i ].getRole() );
  
              //If there is no Role then the user has failed
              //to specify a needed dependency.
              if( null == role )
              {
                  final String message =
                      REZ.getString( "unspecified-dependency",
                                     dependencies[ i ].getRole(),
                                     block.getName() );
                  throw new VerifyException( message );
              }
          }
      }
  
      /**
       * Retrieve an array of Classes for all the services (+ the Block interface)
       * that a Block offers. This method also makes sure all services offered are
       * interfaces.
       *
       * @param name the name of block
       * @param services the services the Block offers
       * @param classLoader the classLoader
       * @return an array of Classes for all the services
       * @throws VerifyException if an error occurs
       */
      private Class[] getServiceClasses( final String name,
                                         final ServiceDescriptor[] services,
                                         final ClassLoader classLoader )
          throws VerifyException
      {
          final Class[] classes = new Class[ services.length ];
          for( int i = 0; i < services.length; i++ )
          {
              final String classname = services[ i ].getName();
              try
              {
                  classes[ i ] = classLoader.loadClass( classname );
              }
              catch( final Throwable t )
              {
                  final String message =
                      REZ.getString( "bad-service-class", name, classname, t.getMessage() );
                  throw new VerifyException( message, t );
              }
  
              if( !classes[ i ].isInterface() )
              {
                  final String message =
                      REZ.getString( "service-not-interface", name, classname );
                  throw new VerifyException( message );
              }
          }
  
          return classes;
      }
  
      /**
       * Return true if specified service matches any of the
       * candidate services.
       *
       * @param candidates an array of candidate services
       * @param service the service
       * @return true if candidate services contains a service that matches
       *         specified service, false otherwise
       */
      private boolean hasMatchingService( final ServiceDescriptor service,
                                          final ServiceDescriptor[] candidates )
      {
          for( int i = 0; i < candidates.length; i++ )
          {
              if( service.matches( candidates[ i ] ) )
              {
                  return true;
              }
          }
  
          return false;
      }
  }
  
  
  

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