Package nexj.core.meta.xml

Source Code of nexj.core.meta.xml.XMLSOAMetadataLoader

// Copyright 2011 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.meta.xml;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.w3c.dom.Element;

import nexj.core.meta.MetadataException;
import nexj.core.meta.xml.XMLMetadataHelper.ContextFixup;
import nexj.core.scripting.Compiler;
import nexj.core.scripting.GlobalEnvironment;
import nexj.core.scripting.Machine;
import nexj.core.scripting.PCodeFunction;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Symbol;
import nexj.core.util.HashHolder;
import nexj.core.util.HashHolderList;
import nexj.core.util.HashTab;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.Named;
import nexj.core.util.ObjUtil;
import nexj.core.util.StringUtil;
import nexj.core.util.SysUtil;
import nexj.core.util.TextPosition;
import nexj.core.util.XMLUtil;

/**
* Loader for SOA Definitions and SOA Implementations.
*/
public class XMLSOAMetadataLoader
{
   // constants

   /**
    * The symbol for defining a new dynamic object system class.
    */
   public final static Symbol DEFINE_CLASS = Symbol.define("define-class");

   /**
    * The symbol for defining a class method of a dynamic object system class.
    */
   public final static Symbol CLASS_METHOD = Symbol.define("class-method");

   /**
    * The symbol for defining a class attribute of a dynamic object system class.
    */
   public final static Symbol CLASS_ATTRIBUTE = Symbol.define("class-attribute");

   /**
    * The symbol for defining an instance attribute of a dynamic object system class.
    */
   public final static Symbol ATTRIBUTE = Symbol.define("attribute");

   /**
    * The interfaces attribute symbol for specifying the interfaces of a service.
    */
   public final static Symbol INTERFACES = Symbol.define("interfaces");

   /**
    * The implementations attribute symbol for specifying the implementations of a service.
    */
   public final static Symbol IMPLEMENTATIONS = Symbol.define("implementations");

   /**
    * The :init symbol to specify attribute initializers.
    */
   public final static Symbol INIT = Symbol.define(":init");

   /**
    * Regular expression pattern for matching one or more whitespace characters.
    */
   protected final static Pattern SPACE_PATTERN = Pattern.compile("\\s+");

   // associations

   /**
    * The metadata loading helper.
    */
   protected XMLMetadataHelper m_helper;

   /**
    * The definitions loaded by this loader.
    */
   protected Lookup m_definitionMap = new HashTab(); // of type Definition[String]

   /**
    * The implementations loaded by this loader.
    */
   protected Lookup m_implementationMap = new HashTab(); // of type Implementation[String]

   /**
    * Fixups for resolving references by name.
    */
   protected List m_resolutionFixupList = new ArrayList();

   /**
    * Holds every independently-referenceable object loaded by the loader, indexed by global name.
    */
   protected Lookup m_globalMap = new HashTab(); // of type Object[String]

   // constructors

   /**
    * Creates a new loader for loading SOA metadata from an XML file.
    * @param helper The XML metadata loading helper.
    */
   public XMLSOAMetadataLoader(XMLMetadataHelper helper)
   {
      m_helper = helper;
   }

   // operations

   /**
    * Loads an SOA Definition from a DOM element.
    * @param element The DOM element containing the definition.
    * @param sName The definition name.
    */
   public void loadDefinition(Element element, String sName)
   {
      XMLMetadataHelper.verifyRootElement(element, "SOADefinition");

      final Definition definition = new Definition();

      loadPart(element, definition);
      definition.setVersion(XMLUtil.getStringAttr(element, "version", definition.getVersion()));

      if (m_definitionMap.put(definition.getNamePrefix(), definition) != null)
      {
         throw new MetadataException("err.meta.soa.definitionDup",
            new Object[] {definition.getName(), definition.getVersion(), definition.getNamePrefix()});
      }

      Element child = XMLUtil.findChildElement(element, "Types");

      if (child != null)
      {
         XMLUtil.forEachChildElement(child, "Type", new XMLUtil.ElementHandler()
         {
            public void handleElement(Element typeElement)
            {
               final ModelType type = new ModelType(definition);

               loadPart(typeElement, type);

               if (!definition.addType(type))
               {
                  throw new MetadataException("err.meta.soa.typeDup",
                     new Object[] {type.getName(), definition.getNamePrefix()});
               }

               m_globalMap.put(type.getGlobalName(), type);

               String sBases = StringUtil.trimToNull(XMLUtil.getStringAttr(typeElement, "bases"));

               if (sBases != null)
               {
                  final String[] sBaseArray = SPACE_PATTERN.split(sBases);

                  m_resolutionFixupList.add(new ContextFixup(m_helper)
                  {
                     public void fixup()
                     {
                        for (int i = 0; i < sBaseArray.length; i++)
                        {
                           String sBaseTypeName = definition.resolveTypeRef(sBaseArray[i]);
                           ModelType baseType = (ModelType)m_globalMap.get(sBaseTypeName);

                           if (baseType == null)
                           {
                              throw new MetadataException("err.meta.soa.unknownType",
                                 new Object[] {sBaseTypeName, type.getGlobalName(), definition.getNamePrefix()});
                           }

                           type.addBase(baseType);
                        }
                     }
                  });
               }

               Element attributesElement = XMLUtil.findChildElement(typeElement, "Attributes");

               if (attributesElement != null)
               {
                  XMLUtil.forEachChildElement(attributesElement, "Attribute", new XMLUtil.ElementHandler()
                  {
                     public void handleElement(Element attributeElement)
                     {
                        Attribute attribute = new Attribute();

                        loadPart(attributeElement, attribute);
                        attribute.setType(XMLUtil.getReqStringAttr(attributeElement, "type"))// TODO: Resolve later
                        attribute.setCollection(XMLUtil.getBooleanAttr(attributeElement, "collection", attribute.isCollection()));
                        attribute.setRequired(XMLUtil.getBooleanAttr(attributeElement, "required", attribute.isRequired()));

                        if (!type.addAttribute(attribute))
                        {
                           throw new MetadataException("err.meta.soa.attributeDup",
                              new Object[] {attribute.getName(), type.getName(), definition.getNamePrefix()});
                        }
                     }
                  });
               }
            }
         });
      }

      child = XMLUtil.findChildElement(element, "Interfaces");

      if (child != null)
      {
         XMLUtil.forEachChildElement(child, "Interface", new XMLUtil.ElementHandler()
         {
            public void handleElement(Element interfaceElement)
            {
               final Interface iface = new Interface(definition);

               loadPart(interfaceElement, iface);

               if (!definition.addInterface(iface))
               {
                  throw new MetadataException("err.meta.soa.interfaceDup",
                     new Object[] {iface.getName(), definition.getNamePrefix()});
               }

               m_globalMap.put(iface.getGlobalName(), iface);

               XMLUtil.forEachChildElement(interfaceElement, "Method", new XMLUtil.ElementHandler()
               {
                  public void handleElement(Element methodElement)
                  {
                     final Method method = new Method();

                     loadPart(methodElement, method);

                     Element argumentsElement = XMLUtil.findChildElement(methodElement, "Arguments");

                     if (argumentsElement != null)
                     {
                        XMLUtil.forEachChildElement(argumentsElement, "Argument", new XMLUtil.ElementHandler()
                        {
                           public void handleElement(Element argumentElement)
                           {
                              Argument argument = new Argument();

                              loadPart(argumentElement, argument);

                              if (!method.addArgument(argument))
                              {
                                 throw new MetadataException("err.meta.soa.argumentDup",
                                    new Object[] {argument.getName(), method.getName(), iface.getName(), definition.getNamePrefix()});
                              }

                              argument.setType(XMLUtil.getReqStringAttr(argumentElement, "type"))// TODO: Resolve later
                              argument.setCollection(XMLUtil.getBooleanAttr(argumentElement, "collection", argument.isCollection()));
                           }
                        });
                     }

                     if (!iface.addMethod(method))
                     {
                        throw new MetadataException("err.meta.soa.methodDup",
                           new Object[] {method.getName(), method.getArgString(), iface.getName(), definition.getNamePrefix()});
                     }

                     Element resultElement = XMLUtil.findChildElement(methodElement, "Result");

                     if (resultElement != null)
                     {
                        Result result = new Result();

                        method.setResult(result);
                        result.setType(XMLUtil.getReqStringAttr(resultElement, "type"))// TODO: Resolve later
                        result.setCollection(XMLUtil.getBooleanAttr(resultElement, "collection", result.isCollection()));
                        result.setDescription(XMLUtil.getStringAttr(resultElement, "description", result.getDescription()));
                     }

                     Element faultsElement = XMLUtil.findChildElement(methodElement, "Faults");

                     if (faultsElement != null)
                     {
                        XMLUtil.forEachChildElement(faultsElement, "Fault", new XMLUtil.ElementHandler()
                        {
                           public void handleElement(Element faultElement)
                           {
                              Fault fault = new Fault();

                              fault.m_sRef = XMLUtil.getReqStringAttr(faultElement, "type")// TODO: Resolve later
                              method.addFault(fault);
                           }
                        });
                     }
                  }
               });
            }
         });
      }

      child = XMLUtil.findChildElement(element, "Services");

      if (child != null)
      {
         XMLUtil.forEachChildElement(child, "Service", new XMLUtil.ElementHandler()
         {
            public void handleElement(Element serviceElement)
            {
               final Service service = new Service(definition);

               loadPart(serviceElement, service);

               if (!definition.addService(service))
               {
                  throw new MetadataException("err.meta.soa.serviceDup",
                     new Object[] {service.getName(), definition.getNamePrefix()});
               }

               m_globalMap.put(service.getGlobalName(), service);

               Element interfacesElement = XMLUtil.findChildElement(serviceElement, "Interfaces");
               final int nCounter[] = {0};

               XMLUtil.forEachChildElement(interfacesElement, "Interface", new XMLUtil.ElementHandler()
               {
                  public void handleElement(Element interfaceElement)
                  {
                     final String sInterfaceName = definition.resolveInterfaceRef(XMLUtil.getReqStringAttr(interfaceElement, "ref"));
                     final InterfaceRef ref = new InterfaceRef();

                     ref.setDefault(XMLUtil.getBooleanAttr(interfaceElement, "default", ref.isDefault()));
                     service.addInterfaceRef(ref);

                     if (ref.isDefault())
                     {
                        nCounter[0]++;
                     }

                     m_resolutionFixupList.add(new ContextFixup(m_helper)
                     {
                        public void fixup()
                        {
                           Interface iface = (Interface)m_globalMap.get(sInterfaceName);

                           if (iface == null)
                           {
                              throw new MetadataException("err.meta.soa.unknownInterface",
                                 new Object[] {sInterfaceName, service.getName(), definition.getNamePrefix()});
                           }

                           ref.setInterface(iface);
                        }
                     });
                  }
               });

               if (nCounter[0] == 0)
               {
                  throw new MetadataException("err.meta.soa.missingDefaultInterface",
                     new Object[] {service.getName(), definition.getNamePrefix()});
               }
               else if (nCounter[0] >= 2)
               {
                  throw new MetadataException("err.meta.soa.multipleDefaultInterface",
                     new Object[] {service.getName(), definition.getNamePrefix()});
               }
            }
         });
      }

      child = XMLUtil.findChildElement(element, "Bindings");

      XMLUtil.forEachChildElement(child, "Binding", new XMLUtil.ElementHandler()
      {
         public void handleElement(Element bindingElement)
         {
            String sBindingName = XMLUtil.getReqStringAttr(bindingElement, "protocol");

            if (!definition.addBinding(sBindingName))
            {
               throw new MetadataException("err.meta.soa.bindingDup",
                  new Object[] {sBindingName, definition.getNamePrefix()});
            }
         }
      });

      EmbeddedResourceURLStreamHandler handler = new EmbeddedResourceURLStreamHandler();
      String sPrefix = definition.getNamePrefix();

      loadXMLResources(element, "Channels", "Channel", sPrefix + ":channel:", ".channel", handler);
      loadXMLResources(element, "IntegrationInterfaces", "Interface", sPrefix + ":integration:interface:", ".interface", handler);
      loadTextResources(element, "Libraries", "Library", ".scm", handler);
      loadXMLResources(element, "Messages", "Message", sPrefix + ":message:", ".message", handler);
      loadXMLResources(element, "IntegrationServices", sPrefix + ":integration:service:", "Service", ".service", handler);
      loadXMLResources(element, "Transformations", "Transformation", sPrefix + ":transformation:", ".transformation", handler);
   }

   /**
    * Loads XML resources that are embedded in the SOA Definition. The resources are added to the listing
    * maintained by the XML helper.
    * @param element The parent element of the resource container.
    * @param sContainerElementName The name of the element that contains the resources to load.
    * @param sElementName The name of the element at the resource root.
    * @param sNSPrefix The namespace prefix for the resource.
    * @param sFileExtension The file extension for these resources.
    * @param handler The handler for resolving the URLs.
    */
   protected void loadXMLResources(Element element, String sContainerElementName, String sElementName,
      final String sNSPrefix, final String sFileExtension, final EmbeddedResourceURLStreamHandler handler)
   {
      Element child = XMLUtil.findChildElement(element, sContainerElementName);

      if (child == null)
      {
         return;
      }

      final XMLMetadataListing listing = m_helper.getListing();

      XMLUtil.forEachChildElement(child, sElementName, new XMLUtil.ElementHandler()
      {
         public void handleElement(Element libraryElement)
         {
            String sName = sNSPrefix + XMLUtil.getReqStringAttr(libraryElement, "name") + sFileExtension;

            libraryElement.removeAttribute("name");

            String sBody = XMLUtil.formatXML(libraryElement);
            URL url = handler.getURL(sName);

            handler.register(url, sBody);
            listing.addResource(sName, new XMLResource(sName, url, true));
         }
      });
   }

   /**
    * Loads text resources that are embedded in the SOA Definition. The resources are added to the listing
    * maintained by the XML helper.
    * @param element The parent element of the resource container.
    * @param sContainerElementName The name of the element that contains the resources to load.
    * @param sElementName The name of the element at the resource root.
    * @param sFileExtension The file extension for these resources.
    * @param handler The handler for resolving the URLs.
    */
   protected void loadTextResources(Element element, String sContainerElementName, String sElementName,
      final String sFileExtension, final EmbeddedResourceURLStreamHandler handler)
   {
      Element child = XMLUtil.findChildElement(element, sContainerElementName);

      if (child == null)
      {
         return;
      }

      final XMLMetadataListing listing = m_helper.getListing();

      XMLUtil.forEachChildElement(child, sElementName, new XMLUtil.ElementHandler()
      {
         public void handleElement(Element libraryElement)
         {
            String sName = XMLUtil.getReqStringAttr(libraryElement, "name") + sFileExtension;
            String sBody = XMLUtil.getElementValue(libraryElement);
            URL url = handler.getURL(sName);

            handler.register(url, sBody);
            listing.addResource(sName, new XMLResource(sName, url, true));
         }
      });
   }

   /**
    * Resolves references in the SOA metadata. Must be called before any implementations are loaded, but after
    * all definitions have been loaded.
    */
   public void resolveReferences()
   {
      m_helper.fixup(m_resolutionFixupList.iterator());
      m_resolutionFixupList.clear();
   }

   /**
    * Loads an SOA Implementation from a DOM element and store the implementations in this loader.
    * @param element The DOM element containing the implementation.
    * @param sName The implementation name.
    * @param env The environment for script parsing.
    */
   public void loadImplementation(Element element, String sName, final GlobalEnvironment env)
   {
      XMLMetadataHelper.verifyRootElement(element, "SOAImplementation");

      String sServiceName = XMLUtil.getReqStringAttr(element, "service");
      final Service service = (Service)m_globalMap.get(sServiceName);

      if (service == null)
      {
         throw new MetadataException("err.meta.soa.unknownService", new Object[] {sServiceName});
      }

      final Implementation impl = new Implementation(service);

      if (m_implementationMap.put(impl.getNamePrefix(), impl) != null)
      {
         throw new MetadataException("err.meta.soa.implementationDup",
            new Object[] {impl.getNamePrefix(), service.getName(), service.getDefinition().getNamePrefix()});
      }

      XMLUtil.forEachChildElement(element, "Interface", new XMLUtil.ElementHandler()
      {
         public void handleElement(Element interfaceElement)
         {
            String sInterfaceName = XMLUtil.getReqStringAttr(interfaceElement, "name");
            final Interface iface = service.getInterface(sInterfaceName);

            if (iface == null)
            {
               throw new MetadataException("err.meta.soa.implementation.undefinedInterface",
                  new Object[] {sInterfaceName, service.getName(), service.getDefinition().getNamePrefix()});
            }

            final InterfaceImplementation ifaceImpl = new InterfaceImplementation(impl, iface);

            if (!impl.addInterface(ifaceImpl))
            {
               throw new MetadataException("err.meta.soa.implementation.interfaceDup",
                  new Object[] {iface.getGlobalName(), service.getGlobalName()});
            }

            XMLUtil.forEachChildElement(interfaceElement, "Method", new XMLUtil.ElementHandler()
            {
               public void handleElement(Element methodElement)
               {
                  MethodImplementation method = new MethodImplementation();

                  method.setName(XMLUtil.getReqStringAttr(methodElement, "name"));
                  method.setArgs(XMLUtil.getStringAttr(methodElement, "args"));

                  if (!ifaceImpl.addMethod(method))
                  {
                     throw new MetadataException("err.meta.soa.implementation.methodDup",
                        new Object[] {method.getName(), method.getArgString(), iface.getName(), impl.getService().getGlobalName()});
                  }

                  if (iface.getMethod(method.getName(), method.getArgCount()) == null)
                  {
                     throw new MetadataException("err.meta.soa.implementation.unknownMethod",
                        new Object[] {method.getName(), method.getArgString(), iface.getName(), impl.getService().getGlobalName()});
                  }

                  Element scriptElement = XMLUtil.findChildElement(methodElement, "Script");

                  if (scriptElement != null)
                  {
                     method.setScript(m_helper.parse(XMLUtil.getElementValue(scriptElement), true, null, null, env));
                  }
               }
            });
         }
      });
   }

   /**
    * Loads common attributes from the element into the part.
    * @param element The DOM element to load the attributes.
    * @param part The part to initialize.
    */
   public void loadPart(Element element, SOAObject part)
   {
      part.setName(XMLUtil.getReqStringAttr(element, "name"));
      part.setDescription(XMLUtil.getStringAttr(element, "description", part.getDescription()));
   }

   // code generation operations

   /**
    * Initializes the definitions and implementations into the global environment.
    * @param machine The virtual machine.
    */
   public void initDefinitions(Machine machine)
   {
      Compiler compiler = new Compiler();
      Lookup posMap = new HashTab();

      for (Iterator defItr = m_definitionMap.valueIterator(); defItr.hasNext(); )
      {
         Definition def = (Definition)defItr.next();

         for (Iterator typeItr = def.getTypesIterator(); typeItr.hasNext(); )
         {
            ModelType type = (ModelType)typeItr.next();
            Object code = type.getCode();
            PCodeFunction fun;

            posMap.clear();
            posMap.put(code, new TextPosition(0, 0, "definition:" + type.getGlobalName()));
            fun = compiler.compile(code, posMap, machine, false);
            machine.invoke(fun, (Pair)null);
         }

         for (Iterator intItr = def.getInterfacesIterator(); intItr.hasNext(); )
         {
            Interface iface = (Interface)intItr.next();
            Object code = iface.getCode();
            PCodeFunction fun;

            posMap.clear();
            posMap.put(code, new TextPosition(0, 0, "definition:" + iface.getGlobalName()));
            fun = compiler.compile(code, posMap, machine, false);
            machine.invoke(fun, (Pair)null);
         }

         for (Iterator svcItr = def.getServicesIterator(); svcItr.hasNext(); )
         {
            Service service = (Service)svcItr.next();
            Object code = service.getCode();
            PCodeFunction fun;

            posMap.clear();
            posMap.put(code, new TextPosition(0, 0, "definition:" + service.getGlobalName()));
            fun = compiler.compile(code, posMap, machine, false);
            machine.invoke(fun, (Pair)null);
         }
      }

      for (Iterator implItr = m_implementationMap.valueIterator(); implItr.hasNext(); )
      {
         Implementation impl = (Implementation)implItr.next();
         Object code = impl.getCode();
         PCodeFunction fun;

         posMap.clear();
         posMap.put(code, new TextPosition(0, 0, "implementation:" + impl.getNamePrefix()));

         try
         {
            machine.getGlobalEnvironment().defineVariable(Symbol.SYS_CURRENT_LOGGER,
               Logger.getLogger(SysUtil.NAMESPACE + ".soa." +
                  impl.getService().getDefinition().getNamePrefix().replace('.', '_') + '.' + impl.getService().getName()));
            fun = compiler.compile(code, posMap, machine, false);
            machine.invoke(fun, (Pair)null);
         }
         finally
         {
            machine.getGlobalEnvironment().removeVariable(Symbol.SYS_CURRENT_LOGGER);
         }
      }

      m_definitionMap.clear();
      m_implementationMap.clear();
   }

   // inner classes

   /**
    * URL handler for processing "soadef:/" URLs, which are used to load metadata resources embedded within
    * an SOA definition.
    */
   protected static class EmbeddedResourceURLStreamHandler extends URLStreamHandler
   {
      // constants

      /**
       * The URL scheme handled by this handler.
       */
      protected final static String SCHEME = "soadef";

      // associations

      /**
       * Map of URLs to content data.
       */
      protected Lookup m_urlContentMap = new HashTab(); // of type (byte[])[URL]

      // operations

      /**
       * @see java.net.URLStreamHandler#openConnection(java.net.URL)
       */
      protected URLConnection openConnection(URL u) throws IOException
      {
         byte[] nContentArray = (byte[])m_urlContentMap.get(u);

         if (nContentArray == null)
         {
            throw new IOException("Resource not found: " + u);
         }

         return new MemoryURLConnection(u, nContentArray);
      }

      /**
       * Registers some string data as the content of the given URL.
       * @param url The URL.
       * @param sContent The content of the URL.
       */
      public void register(URL url, String sContent)
      {
         try
         {
            m_urlContentMap.put(url, sContent.getBytes("UTF-8"));
         }
         catch (UnsupportedEncodingException e)
         {
            ObjUtil.rethrow(e);
         }
      }

      /**
       * Gets a URL to the given resource.
       * @param sName The resource name.
       * @return A URL to the resource.
       */
      public URL getURL(String sName)
      {
         try
         {
            return new URL(null, SCHEME + ':' + sName, this);
         }
         catch (MalformedURLException ex)
         {
            throw ObjUtil.rethrow(ex);
         }
      }

      /**
       * Allows soadef URLs to be used as keys in a hash table.
       * @see java.net.URLStreamHandler#equals(java.net.URL, java.net.URL)
       */
      protected boolean equals(URL u1, URL u2)
      {
         if (!u1.getProtocol().equals(u2.getProtocol()))
         {
            return false;
         }

         return u1.getPath().equals(u2.getPath());
      }

      /**
       * Allows soadef URLs to be used as keys in a hash table.
       * @see java.net.URLStreamHandler#hashCode(java.net.URL)
       */
      protected int hashCode(URL u)
      {
         assert u.getProtocol().equals(SCHEME);

         return u.getPath().hashCode();
      }
   }

   /**
    * A URL connection to data that are held in memory.
    */
   protected static class MemoryURLConnection extends URLConnection
   {
      /**
       * The contents of this URL.
       */
      protected byte[] m_nContentArray;

      // constructors

      /**
       * Creates a new connection to data that are held in memory.
       * @param url The URL.
       * @param nContentArray The contents of this URL.
       */
      public MemoryURLConnection(URL url, byte[] nContentArray)
      {
         super(url);
         m_nContentArray = nContentArray;
      }

      // operations

      /**
       * @see java.net.URLConnection#connect()
       */
      public void connect() throws IOException
      {
         if (m_nContentArray == null)
         {
            throw new IOException("Resource not found: " + url);
         }
      }

      /**
       * @see java.net.URLConnection#getInputStream()
       */
      public InputStream getInputStream() throws IOException
      {
         return new ByteArrayInputStream(m_nContentArray);
      }
   }

   /**
    * The object trees loaded by this loader must be converted to Scheme code to load them into
    * the dynamic object system, so they are marked as CodeProvider.
    */
   protected interface CodeProvider
   {
      /**
       * Gets the dynamic object system representation of this object.
       * @return The Scheme code for this object.
       */
      public Object getCode();
   }

   /**
    * An object that is put in the global environment.
    */
   protected interface GlobalObject
   {
      /**
       * Gets the fully-qualified name of this object.
       * @return The fully-qualified name of this object.
       */
      public String getGlobalName();
   }

   /**
    * An intermediate form for holding the definitions of SOA metadata during metadata loading.
    */
   protected static abstract class SOAObject implements Named, CodeProvider
   {
      // attributes

      /**
       * The name, local to the context in which it is defined.
       */
      protected String m_sName;

      /**
       * Human-readable documentation about this object.
       */
      protected String m_sDescription = "";

      // operations

      /**
       * @see nexj.core.util.Named#getName()
       */
      public String getName()
      {
         return m_sName;
      }

      /**
       * Sets the object name.
       * @param sName The name.
       */
      public void setName(String sName)
      {
         m_sName = sName;
      }

      /**
       * Gets the documentation on this object.
       * @return The object description.
       */
      public String getDescription()
      {
         return m_sDescription;
      }

      /**
       * Sets the documentation on this object.
       * @param sDescription The object description.
       */
      public void setDescription(String sDescription)
      {
         m_sDescription = (sDescription == null) ? "" : sDescription.trim();
      }

      /**
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.CodeProvider#getCode()
       */
      public Object getCode()
      {
         return Symbol.define(m_sName);
      }
   }

   /**
    * An SOA definition.
    */
   protected static class Definition extends SOAObject
   {
      // attributes

      /**
       * The version of this definition.
       */
      protected String m_sVersion = "1.0";

      // associations

      /**
       * A list of the information model types.
       */
      protected Set m_typeSet = new HashHolder(); // of type ModelType[]

      /**
       * A list of the interfaces defined here.
       */
      protected Set m_interfaceSet = new HashHolder(4); // of type Interface[]

      /**
       * A list of the services.
       */
      protected Set m_serviceSet = new HashHolder(4); // of type Service[]

      /**
       * The bindings supported by this definition.
       */
      protected Set m_bindingSet = new HashSet(2); // of type Symbol[]

      // operations

      /**
       * Sets the version.
       * @param sVersion The version string.
       */
      public void setVersion(String sVersion)
      {
         m_sVersion = sVersion;
      }

      /**
       * Gets the version.
       * @return The version string.
       */
      public String getVersion()
      {
         return m_sVersion;
      }

      /**
       * Adds a binding.
       * @param sBinding The binding to add.
       * @return True if the binding was added; false if it is a duplicate.
       */
      public boolean addBinding(String sBinding)
      {
         return m_bindingSet.add(Symbol.define(sBinding));
      }

      /**
       * Gets the name prefix of this definition. Used as a prefix for the other metadata
       * in the definition.
       * @return The name prefix of this definition.
       */
      public String getNamePrefix()
      {
         return m_sName + ':' + m_sVersion;
      }

      /**
       * Adds an information model type to this definition.
       * @param type The type to add.
       * @return True if the type was added; false if it is a duplicate.
       */
      public boolean addType(ModelType type)
      {
         return m_typeSet.add(type);
      }

      /**
       * Gets an iterator over the types defined in this definition.
       * @return The ModelType iterator.
       */
      public Iterator getTypesIterator()
      {
         return m_typeSet.iterator();
      }

      /**
       * Adds an interface to this definition.
       * @param iface The interface to add.
       * @return True if the interface was added; false if it is a duplicate.
       */
      public boolean addInterface(Interface iface)
      {
         return m_interfaceSet.add(iface);
      }

      /**
       * Gets an iterator over the interfaces defined in this definition.
       * @return The Interface iterator.
       */
      public Iterator getInterfacesIterator()
      {
         return m_interfaceSet.iterator();
      }

      /**
       * Adds a service to this definition.
       * @param service The service to add.
       * @return True if the service was added; false if it is a duplicate.
       */
      public boolean addService(Service service)
      {
         return m_serviceSet.add(service);
      }

      /**
       * Gets an iterator over the services defined in this definition.
       * @return The Service iterator.
       */
      public Iterator getServicesIterator()
      {
         return m_serviceSet.iterator();
      }

      /**
       * Resolves a reference to an interface.
       * @param sRef The interface reference, relative to this definition.
       * @return The fully-qualified name of the interface.
       */
      public String resolveInterfaceRef(String sRef)
      {
         return (sRef.indexOf(':') < 0) ? getNamePrefix() + ":interface:" + sRef : sRef;
      }

      /**
       * Resolves a reference to a service.
       * @param sRef The service reference, relative to this definition.
       * @return The fully-qualified name of the service.
       */
      public String resolveTypeRef(String sRef)
      {
         return (sRef.indexOf(':') < 0) ? getNamePrefix() + ":type:" + sRef : sRef;
      }
   }

   /**
    * A service. Consists of one or more interfaces.
    */
   protected class Service extends SOAObject implements GlobalObject
   {
      // associations

      /**
       * The definition where this service is defined.
       */
      protected Definition m_definition;

      /**
       * A list of references to the interfaces of this service.
       * The default interface must be the first interface.
       */
      protected List m_interfaceList = new ArrayList(2); // of type InterfaceRef

      /**
       * The implementation of this service, if any.
       */
      protected Implementation m_implementation;

      // constructors

      /**
       * Creates a new service definition.
       * @param definition The definition where this service is defined.
       */
      public Service(Definition definition)
      {
         m_definition = definition;
      }

      // operations

      /**
       * Gets the definition where this service is defined.
       * @return The definition.
       */
      public Definition getDefinition()
      {
         return m_definition;
      }

      /**
       * Adds an interface to this service.
       * @param iface A reference to the interface.
       */
      public void addInterfaceRef(InterfaceRef iface)
      {
         if (iface.isDefault())
         {
            m_interfaceList.add(0, iface);
         }
         else
         {
            m_interfaceList.add(iface);
         }
      }

      /**
       * Gets the number of interfaces on this service.
       * @return The number of interfaces.
       */
      public int getInterfaceCount()
      {
         return m_interfaceList.size();
      }

      /**
       * Gets an iterator over this service's interfaces.
       * @return An iterator over the InterfaceRefs.
       */
      public Iterator getInterfaceIterator()
      {
         return m_interfaceList.iterator();
      }

      /**
       * Gets an interface by name. If the name is a fully-qualified reference, then look it up
       * in the global environment.
       * This only works after references have been resolved.
       * @param sRef The interface name, relative to this service.
       * @return The interface.
       */
      public Interface getInterface(String sRef)
      {
         String sQName = m_definition.resolveInterfaceRef(sRef);
         Object obj = m_globalMap.get(sQName);

         if (obj instanceof Interface)
         {
            Interface iface = (Interface)obj;

            // Reference is local but the found interface is on a different definition: not found
            if (sQName != sRef && iface.getDefinition() != m_definition)
            {
               return null;
            }

            return iface;
         }

         return null;
      }

      /**
       * Sets the implementation of this service.
       * @param implementation The implementation, if any.
       */
      public void setImplementation(Implementation implementation)
      {
         m_implementation = implementation;
      }

      /**
       * Generates the following code:
       *
       *    (define-class <definition QName>:service:<name> () "Description"
       *       (class-attribute interfaces :init '(<default interface symbol> <interface #2 symbol> ...))
       *       (class-attribute implementations :init '(<interface impl #1> <interface impl #2> ...))
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.SOAObject#getCode()
       */
      public Object getCode()
      {
         Pair interfaces = null;

         for (int i = m_interfaceList.size() - 1; i >= 0; i--)
         {
            interfaces = new Pair(
               Symbol.define(((InterfaceRef)m_interfaceList.get(i)).getInterface().getGlobalName()),
               interfaces
            );
         }

         Pair implementations = null;

         if (m_implementation != null)
         {
            for (Iterator itr = m_implementation.getInterfaceImplementationIterator(); itr.hasNext(); )
            {
               InterfaceImplementation ifaceImpl = (InterfaceImplementation)itr.next();

               implementations = new Pair(Symbol.define(ifaceImpl.getGlobalName()), implementations);
            }
         }

         return Pair.list(
            DEFINE_CLASS,
            Symbol.define(getGlobalName()),
            null,
            m_sDescription,
            Pair.list(
               CLASS_ATTRIBUTE,
               INTERFACES,
               INIT,
               Pair.quote(interfaces)
            ),
            Pair.list(
               CLASS_ATTRIBUTE,
               IMPLEMENTATIONS,
               INIT,
               Pair.quote(implementations)
            )
         );
      }

      /**
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.GlobalObject#getGlobalName()
       */
      public String getGlobalName()
      {
         StringBuilder buf = new StringBuilder();

         buf.append(m_definition.getNamePrefix());
         buf.append(":service:");
         buf.append(m_sName);

         return buf.toString();
      }

      /**
       * Ensures unique naming of a service within its SOA definition.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof Service))
         {
            return false;
         }

         return m_sName.equals(((Service)obj).m_sName);
      }

      /**
       * Ensures unique naming of a service within its SOA definition.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode();
      }
   }

   /**
    * The reference to an interface from a service.
    */
   protected static class InterfaceRef
   {
      // associations

      /**
       * The referenced interface.
       */
      protected Interface m_interface;

      // attributes

      /**
       * True if this is the service's default interface; false otherwise.
       */
      protected boolean m_bDefault;

      // operations

      /**
       * Sets the interface.
       * @param iface The referenced interface.
       */
      public void setInterface(Interface iface)
      {
         m_interface = iface;
      }

      /**
       * Gets the interface.
       * @return The referenced interface.
       */
      public Interface getInterface()
      {
         return m_interface;
      }

      /**
       * Sets whether the referenced interface is the default interface.
       * @param bDefault True if this is the service's default interface; false otherwise.
       */
      public void setDefault(boolean bDefault)
      {
         m_bDefault = bDefault;
      }

      /**
       * Gets whether the referenced interface is the default interface.
       * @return True if this is the service's default interface; false otherwise.
       */
      public boolean isDefault()
      {
         return m_bDefault;
      }
   }

   /**
    * An interface. Consists of one or more methods.
    */
   protected static class Interface extends SOAObject implements GlobalObject
   {
      // associations

      /**
       * The definition where this interface is defined.
       */
      protected Definition m_definition;

      /**
       * The methods defined on this interface.
       */
      protected Set m_methodSet = new HashHolder();

      // constructors

      /**
       * Creates a new interface.
       * @param definition The definition where this interface is defined.
       */
      public Interface(Definition definition)
      {
         m_definition = definition;
      }

      // operations

      /**
       * Gets the parent definition.
       * @return The definition where this interface is defined.
       */
      public Definition getDefinition()
      {
         return m_definition;
      }

      /**
       * Adds a method to this interface.
       * @param method The method to add.
       * @return True if the method was added; false if a method with the same signature has already
       * been added.
       */
      public boolean addMethod(Method method)
      {
         return m_methodSet.add(method);
      }

      /**
       * Given a method signature (name and argument count), finds the method.
       * @param sName The method name.
       * @param nArgCount The method argument count.
       * @return The method; null if not found.
       */
      public Method getMethod(String sName, int nArgCount)
      {
         for (Iterator itr = m_methodSet.iterator(); itr.hasNext(); )
         {
            Method method = (Method)itr.next();

            if (!method.getName().equals(sName))
            {
               continue;
            }

            if (method.getArgCount() != nArgCount)
            {
               continue;
            }

            return method;
         }

         return null;
      }

      /**
       * Generates the following code:
       *
       *    (define-class <definition QName>:interface:<name> () "Description"
       *       (class-method ...)
       *       ...
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.SOAObject#getCode()
       */
      public Object getCode()
      {
         Pair methods = null;

         for (Iterator itr = m_methodSet.iterator(); itr.hasNext(); )
         {
            methods = new Pair(((Method)itr.next()).getCode(), methods);
         }

         return new Pair(
            DEFINE_CLASS,
            new Pair(Symbol.define(getGlobalName()),
               new Pair(null,
                  new Pair(getDescription(), methods)
               )
            )
         );
      }

      /**
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.GlobalObject#getGlobalName()
       */
      public String getGlobalName()
      {
         StringBuilder buf = new StringBuilder(m_definition.getNamePrefix().length() + 11 + m_sName.length());

         buf.append(m_definition.getNamePrefix());
         buf.append(":interface:");
         buf.append(m_sName);

         return buf.toString();
      }

      /**
       * Ensures unique naming of an interface within its SOA definition.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof Interface))
         {
            return false;
         }

         return m_sName.equals(((Interface)obj).m_sName);
      }

      /**
       * Ensures unique naming of an interface within its SOA definition.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode();
      }
   }

   /**
    * A method from an interface. Declares zero or more arguments, an optional return value, and zero
    * or more faults.
    */
   protected static class Method extends SOAObject
   {
      // associations

      /**
       * The ordered set of arguments.
       */
      protected HashHolderList m_argumentList = new HashHolderList(); // of type Argument[]

      /**
       * The return value type & information. Null for no return value (void).
       */
      protected Result m_result;

      /**
       * The faults that this method can raise.
       */
      protected List m_faultList = new ArrayList(); // of type Fault[]

      // operations

      /**
       * Adds an argument to this method.
       * @param argument The argument to add.
       * @return True if the argument was added; false if there is already an argument with the same name.
       */
      public boolean addArgument(Argument argument)
      {
         return m_argumentList.add(argument);
      }

      /**
       * Gets a list of the arguments.
       * @return A string representation of the method arguments.
       */
      public String getArgString()
      {
         StringBuilder buf = new StringBuilder();

         buf.append('(');

         for (int i = 0; i < m_argumentList.size(); i++)
         {
            Argument arg = (Argument)m_argumentList.get(i);

            if (i > 0)
            {
               buf.append(' ');
            }

            buf.append(arg.getName());
         }

         buf.append(')');

         return buf.toString();
      }

      /**
       * Gets the number of arguments for this method.
       * @return The method argument count.
       */
      public int getArgCount()
      {
         return m_argumentList.size();
      }

      /**
       * Sets the return value type/etc. of this method.
       * @param result The method result.
       */
      public void setResult(Result result)
      {
         m_result = result;
      }

      /**
       * Gets the return value type/etc. of this method.
       * @return The method result.
       */
      public Result getResult()
      {
         return m_result;
      }

      /**
       * Adds a fault to this method.
       * @param fault The fault to add.
       */
      public void addFault(Fault fault)
      {
         m_faultList.add(fault);
      }

      /**
       * Generates the following code:
       *
       *    (class-method <name> (<args>) "Description"
       *       (throw (java.lang.UnsupportedOperationException'new <name>))
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.SOAObject#getCode()
       */
      public Object getCode()
      {
         Pair argList = null;

         for (int i = m_argumentList.size() - 1; i >= 0; i--)
         {
            argList = new Pair(((Argument)m_argumentList.get(i)).getCode(), argList);
         }

         return Pair.list(
            CLASS_METHOD,
            Symbol.define(m_sName),
            argList,
            getDescription(),
            Pair.list(
               Symbol.THROW,
               Pair.list(
                  UnsupportedOperationException.class,
                  Pair.quote(Symbol.NEW),
                  m_sName
               )
            )
         );
      }

      /**
       * Ensures two methods with equivalent signatures cannot be added to the same interface.
       * Signatures are equivalent if the method name matches and the number of arguments is
       * the same.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof Method))
         {
            return false;
         }

         Method other = (Method)obj;

         if (!m_sName.equals(other.m_sName))
         {
            return false;
         }

         if (m_argumentList.size() != other.m_argumentList.size())
         {
            return false;
         }

         return true;
      }

      /**
       * Ensures two methods with equivalent signatures cannot be added to the same interface.
       * Signatures are equivalent if the method name matches and the number of arguments is
       * the same.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode() ^ m_argumentList.size();
      }
   }

   /**
    * An argument to a method.
    */
   protected static class Argument extends SOAObject
   {
      // attributes

      /**
       * The type name.
       */
      protected String m_sType;

      /**
       * True if the argument is a collection; false otherwise.
       */
      protected boolean m_bCollection;

      // operations

      /**
       * Sets the type of data passed via this argument.
       * @param sType The argument data type.
       */
      public void setType(String sType)
      {
         m_sType = sType;
      }

      /**
       * Gets the type of data passed via this argument.
       * @return The argument data type.
       */
      public String getType()
      {
         return m_sType;
      }

      /**
       * Sets whether this argument holds a collection.
       * @param bCollection True if the argument is a collection; false otherwise.
       */
      public void setCollection(boolean bCollection)
      {
         m_bCollection = bCollection;
      }

      /**
       * Gets whether this argument holds a collection.
       * @return True if the argument is a collection; false otherwise.
       */
      public boolean isCollection()
      {
         return m_bCollection;
      }

      /**
       * Ensures unique naming of an argument for its method.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof Argument))
         {
            return false;
         }

         return m_sName.equals(((Argument)obj).m_sName);
      }

      /**
       * Ensures unique naming of an argument for its method.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode();
      }
   }

   /**
    * The result of a method.
    */
   protected static class Result
   {
      // attributes

      /**
       * The return value type.
       */
      public String m_sType;

      /**
       * The documentation for this result.
       */
      public String m_sDescription = "";

      /**
       * True if the result is a collection; false otherwise.
       */
      public boolean m_bCollection;

      // operations

      /**
       * Sets the return value type.
       * @param sType The type name.
       */
      public void setType(String sType)
      {
         m_sType = sType;
      }

      /**
       * Gets the return value type.
       * @return The type name.
       */
      public String getType()
      {
         return m_sType;
      }

      /**
       * Sets the documentation on this result.
       * @param sDescription The result description.
       */
      public void setDescription(String sDescription)
      {
         m_sDescription = (sDescription == null) ? "" : sDescription.trim();
      }

      /**
       * Gets the documentation on this result.
       * @return The result description.
       */
      public String getDescription()
      {
         return m_sDescription;
      }

      /**
       * Sets whether the result returns a collection.
       * @param bCollection True if the result is a collection; false otherwise.
       */
      public void setCollection(boolean bCollection)
      {
         m_bCollection = bCollection;
      }

      /**
       * Gets whether the result returns a collection.
       * @return True if the result is a collection; false otherwise.
       */
      public boolean isCollection()
      {
         return m_bCollection;
      }
   }

   /**
    * A fault. Can be thrown by a method.
    */
   protected static class Fault
   {
      /**
       * The fault type.
       */
      public String m_sRef;
   }

   /**
    * An information model type.
    */
   protected static class ModelType extends SOAObject implements GlobalObject
   {
      /**
       * The definition where this information model type is defined.
       */
      public Definition m_definition;

      /**
       * The attributes on this type.
       */
      protected Set m_attributeSet = new HashHolder();

      /**
       * The list of base types.
       */
      protected List m_baseList = new ArrayList(2);

      // constructors

      /**
       * Creates a new information model type.
       * @param definition The definition where this type is defined.
       */
      public ModelType(Definition definition)
      {
         m_definition = definition;
      }

      // operations

      /**
       * Adds a base to this type.
       * @param base The base type to add.
       */
      public void addBase(ModelType base)
      {
         m_baseList.add(base);
      }

      /**
       * Adds an attribute to this type.
       * @param attribute The attribute to add.
       * @return True if the attribute was added; false if an attribute of the same name already exists.
       */
      public boolean addAttribute(Attribute attribute)
      {
         return m_attributeSet.add(attribute);
      }

      /**
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.GlobalObject#getGlobalName()
       */
      public String getGlobalName()
      {
         StringBuilder buf = new StringBuilder(m_definition.getNamePrefix().length() + 6 + m_sName.length());

         buf.append(m_definition.getNamePrefix());
         buf.append(":type:");
         buf.append(m_sName);

         return buf.toString();
      }

      /**
       * Generates the following code:
       *
       *    (define-class <definition QName>:type:<name> (<bases>) "Description"
       *       (attribute ...)
       *       ...
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.SOAObject#getCode()
       */
      public Object getCode()
      {
         Pair attributes = null;

         for (Iterator itr = m_attributeSet.iterator(); itr.hasNext(); )
         {
            attributes = new Pair(((Attribute)itr.next()).getCode(), attributes);
         }

         Pair bases = null;

         for (int i = m_baseList.size() - 1; i >= 0; i--)
         {
            bases = new Pair(Symbol.define(((ModelType)m_baseList.get(i)).getGlobalName()), bases);
         }

         return new Pair(
            DEFINE_CLASS,
            new Pair(Symbol.define(getGlobalName()),
               new Pair(bases,
                  new Pair(getDescription(), attributes)
               )
            )
         );
      }

      /**
       * Ensures unique naming of a type within its SOA definition.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof ModelType))
         {
            return false;
         }

         return m_sName.equals(((ModelType)obj).m_sName);
      }

      /**
       * Ensures unique naming of a type within its SOA definition.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode();
      }
   }

   /**
    * An attribute of an information model type.
    */
   protected static class Attribute extends SOAObject
   {
      // attributes

      /**
       * The type of data stored in this attribute.
       */
      protected String m_sType;

      /**
       * True if the attribute must be non-null at RPC boundaries.
       */
      protected boolean m_bRequired;

      /**
       * True if the attribute holds a collection; false otherwise.
       */
      protected boolean m_bCollection;

      // operations

      /**
       * Gets the type of data stored in this attribute.
       * @return The attribute data type.
       */
      public String getType()
      {
         return m_sType;
      }

      /**
       * Sets the type of data stored in this attribute.
       * @param sType The attribute data type.
       */
      public void setType(String sType)
      {
         m_sType = sType;
      }

      /**
       * Gets whether the attribute must be non-null at RPC boundaries.
       * @return True if this attribute must be non-null; false otherwise.
       */
      public boolean isRequired()
      {
         return m_bRequired;
      }

      /**
       * Sets whether this attribute must be non-null at RPC boundaries.
       * @param bRequired True if this attribute must be non-null; false otherwise.
       */
      public void setRequired(boolean bRequired)
      {
         m_bRequired = bRequired;
      }

      /**
       * Gets whether the attribute holds a collection.
       * @return True if the attribute is a collection; false otherwise.
       */
      public boolean isCollection()
      {
         return m_bCollection;
      }

      /**
       * Sets whether the attribute holds a collection.
       * @param bCollection True if the attribute is a collection; false otherwise.
       */
      public void setCollection(boolean bCollection)
      {
         m_bCollection = bCollection;
      }

      /**
       * Generates the following code:
       *
       *    (attribute <attribute name> "Description")
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.SOAObject#getCode()
       */
      public Object getCode()
      {
         return Pair.list(ATTRIBUTE, Symbol.define(m_sName), getDescription());
      }

      /**
       * Ensures unique naming of an attribute within its type.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof Attribute))
         {
            return false;
         }

         return m_sName.equals(((Attribute)obj).m_sName);
      }

      /**
       * Ensures unique naming of an attribute within its type.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode();
      }
   }

   /**
    * An SOA implementation. This is an implementation of a service.
    */
   protected static class Implementation implements CodeProvider
   {
      // associations

      /**
       * The service that is implemented.
       */
      protected Service m_service;

      /**
       * The interface implementations provided by this implementation.
       */
      protected Set m_interfaceSet; // of type ImplementationInterface[]

      // constructors

      public Implementation(Service service)
      {
         m_service = service;
         m_interfaceSet = new HashHolder(service.getInterfaceCount());
         service.setImplementation(this);
      }

      // operations

      /**
       * Gets the service that is implemented.
       * @return The implemented service.
       */
      public Service getService()
      {
         return m_service;
      }

      /**
       * Adds the implementation of an interface.
       * @param iface An interface implementation.
       * @return True if the implementation was added; false if there is already an implementation of the same
       * interface.
       */
      public boolean addInterface(InterfaceImplementation iface)
      {
         return m_interfaceSet.add(iface);
      }

      /**
       * Gets an iterator over the interface implementations.
       * @return An InterfaceImplementation iterator.
       */
      public Iterator getInterfaceImplementationIterator()
      {
         return m_interfaceSet.iterator();
      }

      /**
       * Generates the following code:
       *
       *    (begin
       *       (define-class <definition QName>:implementation:<service name>:<interface name>
       *          ...
       *       )
       *       ...
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.CodeProvider#getCode()
       */
      public Object getCode()
      {
         Pair code = null;

         for (Iterator itr = m_interfaceSet.iterator(); itr.hasNext(); )
         {
            InterfaceImplementation iface = (InterfaceImplementation)itr.next();

            code = new Pair(iface.getCode(), code);
         }

         return new Pair(Symbol.BEGIN, code);
      }

      /**
       * Gets the name prefix of this implementation. Used as a prefix for the interface
       * implementations.
       * @return The name prefix of this implementation.
       */
      public String getNamePrefix()
      {
         StringBuilder buf = new StringBuilder();

         buf.append(m_service.getDefinition().getNamePrefix());
         buf.append(":implementation:");
         buf.append(m_service.getName());

         return buf.toString();
      }
   }

   /**
    * The implementation of an interface (within the context of a service implementation).
    */
   protected static class InterfaceImplementation implements CodeProvider, GlobalObject
   {
      // associations

      /**
       * The service implementation.
       */
      protected Implementation m_implementation;

      /**
       * The interface that is implemented.
       */
      protected Interface m_interface;

      /**
       * The implemented methods.
       */
      protected Set m_methodSet = new HashHolder(); // of type ImplementationMethod[]

      // constructors

      /**
       * Creates a new implementation of an interface.
       * @param implementation The implementation parent.
       * @param iface The implemented interface.
       */
      public InterfaceImplementation(Implementation implementation, Interface iface)
      {
         m_implementation = implementation;
         m_interface = iface;
      }

      // operations

      /**
       * Adds the implementation of a method to this interface.
       * @param method A method implementation.
       * @return True if the method was added; false if there is already an implementation for the same method.
       */
      public boolean addMethod(MethodImplementation method)
      {
         return m_methodSet.add(method);
      }

      /**
       * Generates the following code:
       *
       *    (define-class <implementation QName>:<interface name> (<interface>) ""
       *       (class-method ...)
       *       (class-method ...)
       *       ...
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.CodeProvider#getCode()
       */
      public Object getCode()
      {
         Pair methods = null;

         for (Iterator itr = m_methodSet.iterator(); itr.hasNext(); )
         {
            MethodImplementation method = (MethodImplementation)itr.next();

            methods = new Pair(method.getCode(), methods);
         }

         return new Pair(DEFINE_CLASS,
            new Pair(Symbol.define(getGlobalName()),
               new Pair(Pair.list(
                     Symbol.define(m_interface.getGlobalName())
                  ),
                  new Pair("", methods)
               )
            )
         );
      }

      /**
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.GlobalObject#getGlobalName()
       */
      public String getGlobalName()
      {
         return m_implementation.getNamePrefix() + ':' + m_interface.getName();
      }

      /**
       * Ensures only one implementation of an interface may be added to an SOA implementation.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (obj instanceof InterfaceImplementation)
         {
            return m_interface == ((InterfaceImplementation)obj).m_interface;
         }

         return false;
      }

      /**
       * Ensures only one implementation of an interface may be added to an SOA implementation.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_interface.hashCode();
      }
   }

   /**
    * The implementation of a method.
    */
   protected static class MethodImplementation implements CodeProvider, Named
   {
      // attributes

      /**
       * The method name.
       */
      protected String m_sName;

      // associations

      /**
       * The method argument list.
       */
      protected Pair m_args;

      /**
       * The method script.
       */
      protected Object m_script;

      // operations

      /**
       * Sets the method name.
       * @param sName The method name.
       */
      public void setName(String sName)
      {
         m_sName = sName;
      }

      /**
       * @see nexj.core.util.Named#getName()
       */
      public String getName()
      {
         return m_sName;
      }

      /**
       * Sets the method script.
       * @param script The method script.
       */
      public void setScript(Object script)
      {
         m_script = script;
      }

      /**
       * Sets the argument list.
       * @param sArgs A space-separated list of argument names.
       */
      public void setArgs(String sArgs)
      {
         sArgs = StringUtil.trimToNull(sArgs);
         m_args = (sArgs == null) ? null : Pair.attributeList(sArgs, null);
      }

      /**
       * Gets a list of the arguments.
       * @return A string representation of the method arguments.
       */
      public String getArgString()
      {
         return m_args.toString();
      }

      /**
       * Gets the number of arguments for this method.
       * @return The method argument count.
       */
      public int getArgCount()
      {
         return Pair.length(m_args);
      }

      /**
       * Generates the following code:
       *
       *    (class-method <name> (<args>) ""
       *       <script>
       *    )
       *
       * @see nexj.core.meta.xml.XMLSOAMetadataLoader.SOAObject#getCode()
       */
      public Object getCode()
      {
         return new Pair(CLASS_METHOD,
            new Pair(Symbol.define(m_sName),
               new Pair(m_args,
                  new Pair("", m_script)
               )
            )
         );
      }

      /**
       * Ensures that two methods with the same signature can't be added to the same interface.
       * @see java.lang.Object#equals(java.lang.Object)
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof MethodImplementation))
         {
            return false;
         }

         MethodImplementation other = (MethodImplementation)obj;

         if (!m_sName.equals(other.m_sName))
         {
            return false;
         }

         if (Pair.length(m_args) != Pair.length(other.m_args))
         {
            return false;
         }

         return true;
      }

      /**
       * Ensures that two methods with the same signature can't be added to the same interface.
       * @see java.lang.Object#hashCode()
       */
      public int hashCode()
      {
         return m_sName.hashCode() ^ Pair.length(m_args);
      }
   }
}
TOP

Related Classes of nexj.core.meta.xml.XMLSOAMetadataLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.