Package nexj.core.meta.xml

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

// Copyright 2010 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

import org.w3c.dom.Element;

import nexj.core.meta.ClassAspect;
import nexj.core.meta.Component;
import nexj.core.meta.ContextMetadata;
import nexj.core.meta.ExternalLibrary;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Metadata;
import nexj.core.meta.MetadataCompoundValidationException;
import nexj.core.meta.MetadataException;
import nexj.core.meta.MetadataLookupException;
import nexj.core.meta.MetadataMarker;
import nexj.core.meta.MetadataObject;
import nexj.core.meta.MetadataValidationException;
import nexj.core.meta.NamedMetadataObject;
import nexj.core.meta.Primitive;
import nexj.core.meta.PrimitivePrivilege;
import nexj.core.meta.Privilege;
import nexj.core.meta.PrivilegeSet;
import nexj.core.meta.SystemResources;
import nexj.core.meta.integration.Channel;
import nexj.core.meta.integration.ChannelType;
import nexj.core.meta.integration.Format;
import nexj.core.meta.integration.Message;
import nexj.core.meta.integration.Transformation;
import nexj.core.meta.integration.service.Interface;
import nexj.core.meta.integration.service.Service;
import nexj.core.meta.persistence.DataSource;
import nexj.core.meta.persistence.DataSourceType;
import nexj.core.meta.testing.unit.UnitTest;
import nexj.core.meta.testing.unit.XMLUnitTestMetadataLoader;
import nexj.core.meta.ui.ClassMeta;
import nexj.core.meta.upgrade.Upgrade;
import nexj.core.meta.upgrade.XMLUpgradeMetadataLoader;
import nexj.core.meta.workflow.FlowMacro;
import nexj.core.meta.workflow.Workflow;
import nexj.core.meta.xml.XMLMetadataHelper.ResourceHandler;
import nexj.core.scripting.GlobalEnvironment;
import nexj.core.scripting.Symbol;
import nexj.core.util.Binary;
import nexj.core.util.ExceptionHolder;
import nexj.core.util.HashHolder;
import nexj.core.util.HashTab;
import nexj.core.util.HashTab2D;
import nexj.core.util.Holder;
import nexj.core.util.IOUtil;
import nexj.core.util.LinkedHashTab;
import nexj.core.util.LocaleUtil;
import nexj.core.util.Lookup;
import nexj.core.util.Lookup2D;
import nexj.core.util.Named;
import nexj.core.util.ObjUtil;
import nexj.core.util.SoftHashTab;
import nexj.core.util.StringTable;
import nexj.core.util.StringUtil;
import nexj.core.util.SysUtil;
import nexj.core.util.URLUtil;
import nexj.core.util.Undefined;
import nexj.core.util.XMLUtil;

/**
* The root object for all XML metadata definitions.
*/
public class XMLMetadata extends NamedMetadataObject implements Metadata
{
   // constants

   /**
    * The default SSL key store password.
    */
   private final static String DEFAULT_KEY_STORE_PASSWORD = "keypass";

   /**
    * The default HTTP context root.
    */
   private final static String DEFAULT_HTTP_CONTEXT_ROOT = "/" + SysUtil.NAMESPACE;

   /**
    * The default anonymous HTTP context root.
    */
   private final static String DEFAULT_HTTP_ANONYMOUS_CONTEXT_ROOT = "/" + SysUtil.NAMESPACE + "/anon";

   /**
    * The default forms-based-authentication HTTP context root.
    */
   private final static String DEFAULT_HTTP_FORM_AUTH_CONTEXT_ROOT = "/" + SysUtil.NAMESPACE + "/form";
  
   /**
    * The default push redirector HTTP context root.
    */
   private final static String DEFAULT_HTTP_PUSH_REDIRECTOR_CONTEXT_ROOT = "/" + SysUtil.NAMESPACE + "/pushRedirect";

   /**
    * The path to metadata upgrade classes.
    */
   public final static String METADATA_UPGRADE_PATH = "upgrade/";

   // attributes

   /**
    * The collection path flag.
    */
   protected boolean m_bCollectionPath = true;

   /**
    * The test recording flag.
    */
   protected boolean m_bTestInterceptor;

   /**
    * True if anonymous access to the flat page client is enabled.
    */
   protected boolean m_bAnonymousFlatPageEnabled;

   /**
    * The port offset to be added to any port used in call to bind().
    */
   protected int m_nPortOffset;

   /**
    * The repository root URL.
    */
   protected URL m_rootURL;

   /**
    * The repository base URL.
    */
   protected URL m_baseURL;

   /**
    * The configuration URL.
    */
   protected URL m_configURL;

   /**
    * The properties for overriding various values.
    */
   protected Properties m_properties;

   /**
    * The repository revision.
    */
   protected String m_sRevision;

   /**
    * The metadata namespace.
    */
   protected String m_sNamespace;

   /**
    * The metadata version.
    */
   protected String m_sVersion;

   /**
    * The earliest core version with compatible metadata.
    * When "+" is used at the end the metadata can use a later version of the framework.
    */
   protected String m_sCoreVersion;

   /**
    * The metadata checksum.
    */
   protected String m_sChecksum;

   /**
    * The base metadata namespace.
    */
   protected String m_sBaseNamespace;

   /**
    * The base metadata version.
    */
   protected String m_sBaseVersion;

   /**
    * The base metadata checksum.
    */
   protected String m_sBaseChecksum;

   /**
    * The default decryption/encryption password.
    */
   protected String m_sDataPassword;

   /**
    * The highest-security encryption scheme that was used to encrypt
    * connection and server metadata.
    */
   protected String m_sEncryptionScheme;

   /**
    * The primitive privilege count.
    */
   protected int m_nPrimitivePrivilegeCount;

   /**
    * The authentication protocol.
    */
   protected int m_nAuthProtocol;

   /**
    * The authentication service name.
    */
   protected String m_sAuthService;

   /**
    * The authentication domain.
    */
   protected String m_sAuthDomain;

   /**
    * The HTTP context root: the path at which the NexJ applications
    * will be made available.
    */
   protected String m_sHTTPContextRoot = DEFAULT_HTTP_CONTEXT_ROOT;

   /**
    * The anonymous HTTP context root: the path at which the NexJ applications
    * will be made available anonymously.
    */
   protected String m_sHTTPAnonymousContextRoot = DEFAULT_HTTP_ANONYMOUS_CONTEXT_ROOT;

   /**
    * The form-based-authentication HTTP context root: the path at which the
    * NexJ applications will be made available through form-based sign on.
    */
   protected String m_sHTTPFormContextRoot = DEFAULT_HTTP_FORM_AUTH_CONTEXT_ROOT;
  
   /**
    * The push redirector HTTP context root: the path at which the
    * NexJ push redirector  will be made available.
    */
   protected String m_sHTTPPushRedirectorContextRoot = DEFAULT_HTTP_PUSH_REDIRECTOR_CONTEXT_ROOT;

   /**
    * The path from the root of the WAR to the login page for form-based sign on.
    */
   protected String m_sHTTPFormLoginPage = "/login.htm";

   /**
    * The path from the root of the WAR to the login error page for form-based sign on.
    */
   protected String m_sHTTPFormErrorPage = "/login-error.htm";

   /**
    * The HTTP root: the full URI path to the NexJ web applications, as specified
    * in the "httpURL" property in the .server metadata file.
    */
   protected String m_sHTTPRoot;

   /**
    * The distribution flag.
    */
   protected boolean m_bDistributed;

   /**
    * The dynamic receiver flag.
    */
   protected boolean m_bDynamicReceiver;

   /**
    * The session timeout in minutes (0 is unlimited).
    */
   protected int m_nSessionTimeout = 20;

   /**
    * The connection integrity and confidentiality flag.
    */
   protected boolean m_bSecureTransport = true;

   /**
    * The session replication flag.
    */
   protected boolean m_bReplicatedSession;

   /**
    * The session persistence flag.
    */
   protected boolean m_bPersistentSession;

   /**
    * The test environment flag.
    */
   protected boolean m_bTestEnvironment;

   /**
    * Environment only loading.
    */
   protected boolean m_bEnvironmentOnly;

   /**
    * True if generic RPC calls are allowed; false to deny access to generic RPC.
    */
   protected boolean m_bGenericRPCAllowed;

   /**
    * True if anonymous access to the HTTP/text, HTTP/soap, and HTTP/xml RPC protocols is enabled.
    */
   protected boolean m_bAnonymousRPCEnabled;

   /**
    * The SSL key store password.
    */
   protected String m_sKeyStorePassword = DEFAULT_KEY_STORE_PASSWORD;

   /**
    * Encrypted passwords flag; a value of true indicates that the passwords
    * in the metadata are encrypted.
    */
   protected boolean m_bEncrypted;

   // associations

   /**
    * The global scripting environment.
    */
   protected GlobalEnvironment m_globalEnv = new GlobalEnvironment();

   /**
    * The listing of available resources.
    */
   protected XMLMetadataListing m_listing;

   /**
    * The generic RPC privilege.
    */
   protected PrimitivePrivilege m_genericRPCPrivilege;

   /**
    * The trusted client certificate for HTTP requests.
    */
   protected Certificate m_trustedCertificate;

   /**
    * The authentication properties.
    */
   protected Properties m_authProperties = new Properties();

   /**
    * The user principal under which anonymous HTTP requests will be processed.
    */
   protected Principal m_anonymousUser;

   /**
    * The privilege map - maps privilege name to privilege object.
    */
   protected Lookup m_privilegeMap = new HashTab(1024); // of type Privilege[String]

   /**
    * Maps class name to class definition.
    */
   protected Lookup m_metaclassMap = new HashTab(512); // of type Metaclass

   /**
    * The class aspect map: ClassAspect[String].
    */
   protected Lookup m_classAspectMap = new HashTab(8);

   /**
    * The class aspect list, sorted by name.
    */
   protected List m_classAspectList = new ArrayList(8);

   /**
    * The flow macro map: FlowMacro[String].
    */
   protected Lookup m_flowMacroMap = new HashTab(8);

   /**
    * The workflow map: Workflow[String][Integer].
    */
   protected Lookup2D m_workflowMap = new HashTab2D();

   /**
    * The current workflow map: Workflow[String].
    */
   protected Lookup m_currentWorkflowMap = new HashTab();

   /**
    * The channel type map: ChannelType[String].
    */
   protected Lookup m_channelTypeMap = new HashTab();

   /**
    * The data source type element map: DataSourceType[String].
    */
   protected Lookup m_channelTypeElementMap = new HashTab(8);

   /**
    * The channel map: Channel[String].
    */
   protected Lookup m_channelMap = new HashTab();

   /**
    * The message format map: Format[String].
    */
   protected Lookup m_formatMap = new HashTab();

   /**
    * The message map: Message[String].
    */
   protected Lookup m_messageMap = new HashTab(8);

   /**
    * The transformation map: Transformation[String].
    */
   protected Lookup m_transformationMap = new HashTab(8);

   /**
    * Maps a unit test name to resource path: String[String].
    */
   protected Lookup m_unitTestResMap = new HashTab(8);

   /**
    * Maps a unit test name to unit test metadata: UnitTest[String].
    */
   protected Lookup m_unitTestMap = new SoftHashTab(8);
  
   /**
    * The service interface map: Interface[String].
    */
   protected Lookup m_interfaceMap = new HashTab(8);

   /**
    * The service map: Service[String][Integer].
    */
   protected Lookup2D m_serviceMap = new HashTab2D();

   /**
    * The current service map: Service[String].
    */
   protected Lookup m_currentServiceMap = new HashTab();

   /**
    * Maps component name to component definition.
    */
   protected Lookup m_componentMap = new HashTab(256); // of type Component

   /**
    * The data source type map: DataSourceType[String].
    */
   protected Lookup m_dataSourceTypeMap = new HashTab(8);

   /**
    * The data source type element map: DataSourceType[String].
    */
   protected Lookup m_dataSourceTypeElementMap = new HashTab(8);

   /**
    * The data source map: DataSource[String].
    */
   protected Lookup m_dataSourceMap = new HashTab(8);

   /**
    * Maps a dump name to dump metadata: String[String].
    */
   protected Lookup m_dumpResMap = new HashTab(8);

   /**
    * Maps engine name to definition.
    */
   protected Lookup m_persistenceEngineMap = new HashTab(8); // of type PersistenceEngine

   /**
    * Maps schema name to definition.
    */
   protected Lookup m_schemaMap = new HashTab(4); // of type Schema[String]

   /**
    * The public symbol set.
    */
   protected Holder m_publicSymbolSet = new HashHolder(64); // of type Symbol

   /**
    * The session symbol set. These are session local variables.
    */
   protected Holder m_sessionSymbolSet = new HashHolder(32); // of type Symbol

   /**
    * The client symbol set.
    */
   protected Holder m_clientSymbolSet = new HashHolder(32); // of type Symbol

   /**
    * The locale name to object map: Locale[String].
    */
   protected Lookup m_localeMap = new HashTab();

   /**
    * The external library map: ExternalLibrary[String].
    */
   protected Lookup m_externalLibraryMap = new LinkedHashTab();

   /**
    * Maps locale name to a set of resource paths.
    */
   protected Lookup m_stringMap = new HashTab(64)
   {
      // constants

      /**
       * Serialization version.
       */
      private final static long serialVersionUID = -8557119405683556309L;

      // operations

      public Object put(Object key, Object value)
      {
         m_stringMetaMap.put(key, Undefined.VALUE);

         return super.put(key, value);
      }
   };

   /**
    * Maps an upgrade name to a resource path: String[String].
    */
   protected Lookup m_upgradeResMap = new LinkedHashTab(1);

   /**
    * Maps a locale name to a string table name.
    * Loaded lazily.
    */
   protected Lookup m_stringMetaMap = new HashTab(64);

   /**
    * Shared xml metadata helper for validation in single-threaded mode only.
    */
   protected XMLMetadataHelper m_validationHelper;

   // constructors

   /**
    * Creates the XMLMetadata container.
    * @param sName The metadata object name.
    * @param rootURL The repository root URL.
    * @param baseURL The repository base URL.
    * @param properties The metadata properties for overriding various values.
    * @param helper The metadata helper used for validation. Must be null when
    * creating a metadata object, which can be shared between multiple threads.
    */
   public XMLMetadata(String sName, URL rootURL, URL baseURL, Properties properties, XMLMetadataHelper helper)
   {
      super(sName);
      m_rootURL = rootURL;
      m_baseURL = baseURL;
      m_properties = properties;
      m_validationHelper = helper;
   }

   // operations

   /**
    * For framework internal use only.
    * @return A new metadata helper for on-demand loading.
    */
   public XMLMetadataHelper getHelper()
   {
      if (m_validationHelper != null)
      {
         return m_validationHelper;
      }

      return new XMLMetadataHelper(m_rootURL, m_baseURL, m_properties, m_listing);
   }

   /**
    * Sets the resource listing.
    * @param listing The resource listing.
    */
   public void setListing(XMLMetadataListing listing)
   {
      verifyNotReadOnly();
      m_listing = listing;
   }

   /**
    * @return The resource listing.
    */
   public XMLMetadataListing getListing()
   {
      return m_listing;
   }

   /**
    * Sets the configuration URL.
    * @param configURL The configuration URL to set.
    */
   public void setConfigurationURL(URL configURL)
   {
      verifyNotReadOnly();
      m_configURL = configURL;
   }

   /**
    * @return The configuration URL.
    */
   public URL getConfigurationURL()
   {
      return m_configURL;
   }

   /**
    * Sets the port offset to be added to base port by internal calls to bind().
    * @param nOffset The offset to be added to base port used in bind().
    */
   public void setPortOffset(int nOffset)
   {
      verifyNotReadOnly();
      m_nPortOffset = nOffset;
   }

   /**
    * @see nexj.core.meta.Metadata#getPortOffset()
    */
   public int getPortOffset()
   {
      return m_nPortOffset;
   }

   /**
    * Sets the repository revision.
    * @param sRevision The repository revision to set.
    */
   public void setRevision(String sRevision)
   {
      verifyNotReadOnly();
      m_sRevision = sRevision;
   }

   /**
    * @return The repository revision.
    */
   public String getRevision()
   {
      return m_sRevision;
   }

   /**
    * Sets the metadata namespace.
    * @param sNamespace The metadata namespace to set.
    */
   public void setNamespace(String sNamespace)
   {
      verifyNotReadOnly();
      m_sNamespace = sNamespace;
   }

   /**
    * @return The metadata namespace.
    */
   public String getNamespace()
   {
      return m_sNamespace;
   }

   /**
    * Sets the earliest core version with compatible metadata.
    * @param sVersion The framework compatibility version to set. With optional '+' suffix.
    */
   public void setCoreVersion(String sCoreVersion)
   {
      verifyNotReadOnly();
      m_sCoreVersion = sCoreVersion;
      m_bCollectionPath = (sCoreVersion == null || StringUtil.compareVersionRanges(sCoreVersion, "7.1.57.0") >= 0);

      m_bDynamicReceiver = (sCoreVersion == null
         || (sCoreVersion.startsWith("7.1.") && StringUtil.compareVersionRanges(sCoreVersion, "7.1.65.0") >= 0)
         || (sCoreVersion.startsWith("7.0.") && StringUtil.compareVersionRanges(sCoreVersion, "7.0.160.0") >= 0)
         || (sCoreVersion.startsWith("6.2.") && StringUtil.compareVersionRanges(sCoreVersion, "6.2.1.100") >= 0));
   }

   /**
    * @return The earliest core version with compatible metadata.
    */
   public String getCoreVersion()
   {
      return m_sCoreVersion;
   }

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

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

   /**
    * Sets the metadata checksum.
    * @param sChecksum The metadata checksum to set.
    */
   public void setChecksum(String sChecksum)
   {
      verifyNotReadOnly();
      m_sChecksum = sChecksum;
   }

   /**
    * @return The metadata checksum.
    */
   public String getChecksum()
   {
      return m_sChecksum;
   }

   /**
    * Sets the base metadata namespace.
    * @param sBaseNamespace The base metadata namespace to set.
    */
   public void setBaseNamespace(String sBaseNamespace)
   {
      verifyNotReadOnly();
      m_sBaseNamespace = sBaseNamespace;
   }

   /**
    * @return The base metadata namespace.
    */
   public String getBaseNamespace()
   {
      return m_sBaseNamespace;
   }

   /**
    * Sets the base metadata version.
    * @param sBaseVersion The base metadata version to set.
    */
   public void setBaseVersion(String sBaseVersion)
   {
      verifyNotReadOnly();

      if (sBaseVersion == null &&
         m_sNamespace != null)
      {
         sBaseVersion = "";
      }

      m_sBaseVersion = sBaseVersion;
   }

   /**
    * @return The base metadata version.
    */
   public String getBaseVersion()
   {
      return m_sBaseVersion;
   }

   /**
    * Sets the base metadata checksum.
    * @param sBaseChecksum The base metadata checksum to set.
    */
   public void setBaseChecksum(String sBaseChecksum)
   {
      verifyNotReadOnly();

      if (sBaseChecksum == null &&
         m_sNamespace != null)
      {
         sBaseChecksum = "";
      }

      m_sBaseChecksum = sBaseChecksum;
   }

   /**
    * @return The base metadata checksum.
    */
   public String getBaseChecksum()
   {
      return m_sBaseChecksum;
   }

   /**
    * Sets the collection path flag.
    * @param bCollectionPathEnabled The collection path flag to set.
    */
   public void setCollectionPathEnabled(boolean bCollectionPathEnabled)
   {
      verifyNotReadOnly();
      m_bCollectionPath = bCollectionPathEnabled;
   }

   /**
    * @return The collection path flag.
    * @see nexj.core.meta.Metadata#isCollectionPathEnabled()
    */
   public boolean isCollectionPathEnabled()
   {
      return m_bCollectionPath;
   }

   /**
    * @see nexj.core.meta.Metadata#isDynamicReceiverEnabled()
    */
   public boolean isDynamicReceiverEnabled()
   {
      return m_bDynamicReceiver;
   }

   /**
    * Sets the dynamic receiver flag.
    * @param bDynamicReceiverEnabled The dynamic receiver flag to set.
    */
   public void setDynamicReceiverEnabled(boolean bDynamicReceiverEnabled)
   {
      verifyNotReadOnly();
      m_bDynamicReceiver = bDynamicReceiverEnabled;
   }

   /**
    * Sets the authentication protocol.
    * @param nAuthProtocol The authentication protocol to set.
    */
   public void setAuthenticationProtocol(int nAuthProtocol)
   {
      verifyNotReadOnly();
      m_nAuthProtocol = nAuthProtocol;
   }

   /**
    * @return The authentication protocol.
    */
   public int getAuthenticationProtocol()
   {
      return m_nAuthProtocol;
   }

   /**
    * Sets the authentication service name.
    * @param sAuthService The authentication service name to set.
    */
   public void setAuthenticationService(String sAuthService)
   {
      verifyNotReadOnly();

      if ("".equals(sAuthService))
      {
         sAuthService = null;
      }

      if (sAuthService == null && m_nAuthProtocol == AUTH_PROTOCOL_SPNEGO)
      {
         throw new MetadataException("err.meta.missingAuthService");
      }

      m_sAuthService = sAuthService;
   }

   /**
    * @return The authentication service name.
    */
   public String getAuthenticationService()
   {
      return m_sAuthService;
   }

   /**
    * @see nexj.core.meta.Metadata#getAuthenticationProperties()
    */
   public Properties getAuthenticationProperties()
   {
      return m_authProperties;
   }

   /**
    * Sets the authentication domain.
    * @param sAuthDomain The authentication domain to set.
    */
   public void setAuthenticationDomain(String sAuthDomain)
   {
      verifyNotReadOnly();

      if ("".equals(sAuthDomain))
      {
         sAuthDomain = null;
      }

      m_sAuthDomain = sAuthDomain;
   }

   /**
    * @return The authentication domain.
    */
   public String getAuthenticationDomain()
   {
      return m_sAuthDomain;
   }

   /**
    * @param bAllowGenericRPC True if generic RPC calls are allowed;
    * false to deny access to generic RPC.
    */
   public void setGenericRPCAllowed(boolean bAllowGenericRPC)
   {
      verifyNotReadOnly();
      m_bGenericRPCAllowed = bAllowGenericRPC;
   }

   /**
    * @see nexj.core.meta.Metadata#isGenericRPCAllowed()
    */
   public boolean isGenericRPCAllowed()
   {
      return m_bGenericRPCAllowed;
   }

   /**
    * Sets the generic RPC privilege.
    * @param genericRPCPrivilege The generic RPC privilege to set.
    */
   public void setGenericRPCPrivilege(PrimitivePrivilege genericRPCPrivilege)
   {
      verifyNotReadOnly();
      m_genericRPCPrivilege = genericRPCPrivilege;
   }

   /**
    * @return The generic RPC privilege.
    */
   public PrimitivePrivilege getGenericRPCPrivilege()
   {
      return m_genericRPCPrivilege;
   }

   /**
    * Sets the user principal under which anonymous HTTP requests will be processed.
    * @param anonymousUser The anonymous user principal to set.
    */
   public void setAnonymousUser(Principal anonymousUser)
   {
      verifyNotReadOnly();
      m_anonymousUser = anonymousUser;
   }

   /**
    * @see nexj.core.meta.Metadata#getAnonymousUser()
    */
   public Principal getAnonymousUser()
   {
      return m_anonymousUser;
   }

   /**
    * @param bAnonymousRPCEnabled True if anonymous access to the HTTP/text, HTTP/soap, and HTTP/xml RPC protocols is enabled.
    */
   public void setAnonymousRPCEnabled(boolean bAnonymousRPCEnabled)
   {
      verifyNotReadOnly();
      m_bAnonymousRPCEnabled = bAnonymousRPCEnabled;
   }

   /**
    * @see nexj.core.meta.Metadata#isAnonymousRPCEnabled()
    */
   public boolean isAnonymousRPCEnabled()
   {
      return m_bAnonymousRPCEnabled;
   }

   /**
    * @param bAnonymousFlatPageEnabled True if anonymous access to the flat page client is enabled.
    */
   public void setAnonymousFlatPageEnabled(boolean bAnonymousFlatPageEnabled)
   {
      verifyNotReadOnly();
      m_bAnonymousFlatPageEnabled = bAnonymousFlatPageEnabled;
   }

   /**
    * @see nexj.core.meta.Metadata#isAnonymousFlatPageEnabled()
    */
   public boolean isAnonymousFlatPageEnabled()
   {
      return m_bAnonymousFlatPageEnabled;
   }

   /**
    * Sets the client certificate to trust for HTTP requests.
    *
    * @param certificate The trusted certificate for HTTP requests.
    */
   public void setTrustedCertificate(Certificate certificate)
   {
      verifyNotReadOnly();
      m_trustedCertificate = certificate;
   }

   /**
    * @see nexj.core.meta.Metadata#getTrustedCertificate()
    */
   public Certificate getTrustedCertificate()
   {
      return m_trustedCertificate;
   }

   /**
    * Sets the HTTP context root (the URI path at which the NexJ web applications
    * will be made available).
    *
    * @param sContextRoot The HTTP context root to set.
    */
   public void setHTTPContextRoot(String sContextRoot)
   {
      verifyNotReadOnly();
      m_sHTTPContextRoot = (sContextRoot != null) ? sContextRoot : DEFAULT_HTTP_CONTEXT_ROOT;
   }

   /**
    * @see nexj.core.meta.Metadata#getHTTPContextRoot()
    */
   public String getHTTPContextRoot()
   {
      return m_sHTTPContextRoot;
   }

   /**
    * Sets the anonymous HTTP context root (the path at which the NexJ web applications
    * will be made available anonymously).
    *
    * @param sContextRoot The anonymous HTTP context root to set.
    */
   public void setHTTPAnonymousContextRoot(String sContextRoot)
   {
      verifyNotReadOnly();
      m_sHTTPAnonymousContextRoot = (sContextRoot != null) ? sContextRoot : DEFAULT_HTTP_ANONYMOUS_CONTEXT_ROOT;
   }

   /**
    * @see nexj.core.meta.Metadata#getHTTPAnonymousContextRoot()
    */
   public String getHTTPAnonymousContextRoot()
   {
      return m_sHTTPAnonymousContextRoot;
   }

   /**
    * Sets the form-based-authentication HTTP context root (the path at which the NexJ
    * web applications will be made available through form-based sign on).
    *
    * @param sContextRoot The form-based-authentication HTTP context root to set.
    */
   public void setHTTPFormContextRoot(String sContextRoot)
   {
      verifyNotReadOnly();
      m_sHTTPFormContextRoot = (sContextRoot != null) ? sContextRoot : DEFAULT_HTTP_FORM_AUTH_CONTEXT_ROOT;
   }

   /**
    * @see nexj.core.meta.Metadata#getHTTPFormContextRoot()
    */
   public String getHTTPFormContextRoot()
   {
      return m_sHTTPFormContextRoot;
   }
  
   /**
    * Sets the push redirector HTTP context root (the path at which the NexJ
    * push redirector web application will be made available).
    *
    * @param sContextRoot The push redirector HTTP context root to set.
    */
   public void setHTTPPushRedirectorContextRoot(String sContextRoot)
   {
      verifyNotReadOnly();
      m_sHTTPPushRedirectorContextRoot = (sContextRoot != null) ? sContextRoot : DEFAULT_HTTP_PUSH_REDIRECTOR_CONTEXT_ROOT;
   }

   /**
    * @see nexj.core.meta.Metadata#getHTTPFormContextRoot()
    */
   public String getHTTPPushRedirectorContextRoot()
   {
      return m_sHTTPPushRedirectorContextRoot;
   }

   /**
    * Sets the login page for form-based sign on.
    *
    * @param sPath The path from the root of the WAR.
    */
   public void setHTTPFormLoginPage(String sPath)
   {
      verifyNotReadOnly();
      m_sHTTPFormLoginPage = sPath;
   }

   /**
    * @see nexj.core.meta.Metadata#getHTTPFormLoginPage()
    */
   public String getHTTPFormLoginPage()
   {
      return m_sHTTPFormLoginPage;
   }

   /**
    * Sets the login error page for form-based sign on.
    *
    * @param sPath The path from the root of the WAR.
    */
   public void setHTTPFormErrorPage(String sPath)
   {
      verifyNotReadOnly();
      m_sHTTPFormErrorPage = sPath;
   }

   /**
    * @see nexj.core.meta.Metadata#getHTTPFormErrorPage()
    */
   public String getHTTPFormErrorPage()
   {
      return m_sHTTPFormErrorPage;
   }

   /**
    * Sets the HTTP root (the full URI path to the NexJ web applications, as specified
    * in the "httpURL" property in the .server metadata file)
    *
    * @param sHTTPRoot The HTTP root to set.
    */
   public void setHTTPRoot(String sHTTPRoot)
   {
      m_sHTTPRoot = (sHTTPRoot != null) ? sHTTPRoot : "";
   }

   /**
    * Gets the HTTP root (the full URI path to the NexJ web applications, as specified
    * in the "httpURL" property in the .server metadata file)
    *
    * @return The HTTP root ("httpURL" property from the server metadata).
    */
   public String getHTTPRoot()
   {
      return m_sHTTPRoot;
   }

   /**
    * Sets the session timeout.
    * @param nTimeout The session timeout in minutes (0 is unlimited).
    */
   public void setSessionTimeout(int nTimeout)
   {
      m_nSessionTimeout = nTimeout;
   }

   /**
    * @return The session timeout in minutes (0 is unlimited).
    */
   public int getSessionTimeout()
   {
      return m_nSessionTimeout;
   }

   /**
    * Sets the distribution flag.
    * @param bDistributed The distribution flag to set.
    */
   public void setDistributed(boolean bDistributed)
   {
      verifyNotReadOnly();
      m_bDistributed = bDistributed;
   }

   /**
    * @return The distribution flag.
    */
   public boolean isDistributed()
   {
      return m_bDistributed;
   }

   /**
    * Sets the connection integrity and confidentiality flag.
    * @param bSecure The connection integrity and confidentiality flag to set.
    */
   public void setSecureTransport(boolean bSecure)
   {
      verifyNotReadOnly();
      m_bSecureTransport = bSecure;
   }

   /**
    * @return The connection integrity and confidentiality flag.
    */
   public boolean isSecureTransport()
   {
      return m_bSecureTransport;
   }

   /**
    * Sets the test recording flag.
    * @param bTestInterceptor The test recording flag to set.
    */
   public void setTestInterceptor(boolean bTestInterceptor)
   {
      verifyNotReadOnly();
      m_bTestInterceptor = bTestInterceptor;
   }

   /**
    * @return The test recording flag.
    */
   public boolean isTestInterceptor()
   {
      return m_bTestInterceptor;
   }
   /**
    * Sets the test environment flag.
    * @param bTestEnvironment The test environment flag to set.
    */
   public void setTestEnvironment(boolean bTestEnvironment)
   {
      verifyNotReadOnly();
      m_bTestEnvironment = bTestEnvironment;
   }

   /**
    * @return True if it is a test environment.
    */
   public boolean isTestEnvironment()
   {
      return m_bTestEnvironment;
   }

   /**
    * Sets the session replication flag.
    * @param bReplicatedSession The session replication flag to set.
    */
   public void setReplicatedSession(boolean bReplicatedSession)
   {
      verifyNotReadOnly();
      m_bReplicatedSession = bReplicatedSession;
   }

   /**
    * @return The session replication flag.
    */
   public boolean isReplicatedSession()
   {
      return m_bReplicatedSession;
   }

   /**
    * Sets the session persistence flag.
    * @param bPersistentSession The session persistence flag to set.
    */
   public void setPersistentSession(boolean bPersistentSession)
   {
      verifyNotReadOnly();
      m_bPersistentSession = bPersistentSession;
   }

   /**
    * @return The session persistence flag.
    */
   public boolean isPersistentSession()
   {
      return m_bPersistentSession;
   }

   /**
    * @see nexj.core.meta.Metadata#getGlobalEnvironment()
    */
   public GlobalEnvironment getGlobalEnvironment()
   {
      return m_globalEnv;
   }

   /**
    * Sets the SSL key store password.
    *
    * @param sKeyPass The key store password.
    */
   public void setKeyStorePassword(String sKeyPass)
   {
      verifyNotReadOnly();
      m_sKeyStorePassword = (sKeyPass != null) ? sKeyPass : DEFAULT_KEY_STORE_PASSWORD;
   }

   /**
    * Gets the SSL key store password.
    *
    * @return The key store password.
    */
   public String getKeyStorePassword()
   {
      return m_sKeyStorePassword;
   }

   /**
    * Adds a new data source type to the metadata.
    * @param dataSourceType The data source type to add.
    * @throws MetadataException if a data source type
    * with the same name already exists.
    */
   public void addDataSourceType(DataSourceType dataSourceType)
   {
      verifyNotReadOnly();

      Object oldDataSourceType = m_dataSourceTypeMap.put(dataSourceType.getName(), dataSourceType);

      if (oldDataSourceType != null)
      {
         m_dataSourceTypeMap.put(dataSourceType.getName(), oldDataSourceType);

         throw new MetadataException("err.meta.dataSourceTypeDup", new Object[]
         {
            dataSourceType.getName(),
            getName()
         });
      }

      dataSourceType.setMetadata(this);
   }

   /**
    * Gets a data source type by name.
    * @param sName The data source type name.
    * @return The data source type object.
    * @throws MetadataLookupException if the data source type does not exist.
    */
   public DataSourceType getDataSourceType(String sName)
   {
      DataSourceType dataSourceType = (DataSourceType) m_dataSourceTypeMap.get(sName);

      if (dataSourceType != null)
      {
         return dataSourceType;
      }

      throw new MetadataLookupException("err.meta.dataSourceTypeLookup", sName, this);
   }

   /**
    * @return The data source type count.
    */
   public int getDataSourceTypeCount()
   {
      return m_dataSourceTypeMap.size();
   }

   /**
    * @return An iterator for the contained data source type objects.
    */
   public Iterator getDataSourceTypeIterator()
   {
      return m_dataSourceTypeMap.valueIterator();
   }

   /**
    * Adds a new data source type XML element mapping to the metadata.
    * @param sElement The XML element name.
    * @param dataSourceType The data source type object.
    * @throws MetadataException if a duplicate entry is encountered.
    */
   public void addDataSourceTypeElement(String sElement, DataSourceType dataSourceType)
   {
      verifyNotReadOnly();

      m_dataSourceTypeElementMap.put(dataSourceType, sElement);

      if (sElement.equals("DataSource"))
      {
         return;
      }

      Object oldDataSourceType = m_dataSourceTypeElementMap.put(sElement, dataSourceType);

      if (oldDataSourceType != null)
      {
         m_dataSourceTypeElementMap.put(sElement, oldDataSourceType);

         throw new MetadataException("err.meta.dataSourceTypeElementDup", new Object[]
         {
            sElement,
            dataSourceType.getName(),
            getName()
         });
      }
   }

   /**
    * Gets a data source type by element name.
    * @param element The data source type element.
    * @return The data source type object.
    * @throws MetadataLookupException if the data source type does not exist.
    */
   public DataSourceType getDataSourceTypeByElement(Element element)
   {
      if (element.getNodeName().equals("DataSource"))
      {
         return getDataSourceType(XMLUtil.getReqStringAttr(element, "type"));
      }

      DataSourceType dataSourceType = (DataSourceType)m_dataSourceTypeElementMap.get(element.getNodeName());

      if (dataSourceType != null)
      {
         return dataSourceType;
      }

      throw new MetadataLookupException("err.meta.dataSourceTypeElementLookup", element.getNodeName(), this);
   }

   /**
    * Gets a data source type element name.
    * @param type The data source type.
    */
   public String getDataSourceTypeElement(DataSourceType type)
   {
      return (String)m_dataSourceTypeElementMap.get(type);
   }

   /**
    * Adds a new data source to the metadata.
    * @param dataSource The data source to add.
    * @throws MetadataException if a data source
    * with the same name already exists.
    */
   public void addDataSource(DataSource dataSource)
   {
      verifyNotReadOnly();

      Object oldDataSource = m_dataSourceMap.put(dataSource.getName(), dataSource);

      if (oldDataSource != null)
      {
         m_dataSourceMap.put(dataSource.getName(), oldDataSource);

         throw new MetadataException("err.meta.dataSourceDup", new Object[]
         {
            dataSource.getName(),
            getName()
         });
      }
   }

   /**
    * Gets a data source by name.
    * @param sName The data source name.
    * @return The data source object.
    * @throws MetadataLookupException if the data source does not exist.
    */
   public DataSource getDataSource(String sName)
   {
      DataSource dataSource = (DataSource)m_dataSourceMap.get(sName);

      if (dataSource != null)
      {
         return dataSource;
      }

      throw new MetadataLookupException("err.meta.dataSourceLookup", sName, this);
   }

   /**
    * @return The data source count.
    */
   public int getDataSourceCount()
   {
      return m_dataSourceMap.size();
   }

   /**
    * @return An iterator for the contained data source objects.
    */
   public Iterator getDataSourceIterator()
   {
      return m_dataSourceMap.valueIterator();
   }

   /**
    * @see nexj.core.meta.Metadata#getDataPassword()
    */
   public String getDataPassword()
   {
      return m_sDataPassword;
   }

   /**
    * @see nexj.core.meta.Metadata#setDataPassword(java.lang.String)
    */
   public void setDataPassword(String sPassword)
   {
      verifyNotReadOnly();

      m_sDataPassword = sPassword;
   }

   /**
    * @see nexj.core.meta.Metadata#getEncryptionScheme()
    */
   public String getEncryptionScheme()
   {
      return m_sEncryptionScheme;
   }

   /**
    * @see nexj.core.meta.Metadata#setEncryptionScheme(java.lang.String)
    */
   public void setEncryptionScheme(String sScheme)
   {
      verifyNotReadOnly();

      m_sEncryptionScheme = sScheme;
   }

   /**
    * @see nexj.core.meta.Metadata#isEncrypted()
    */
   public boolean isEncrypted()
   {
      return m_bEncrypted;
   }

   /**
    * Sets the flag indicating whether or not the passwords in this
    * metadata object are encrypted.
    *
    * @param bEncrypted True to indicate the passwords are encrypted;
    * false for decrypted passwords.
    */
   public void setEncrypted(boolean bEncrypted)
   {
      verifyNotReadOnly();
      m_bEncrypted = bEncrypted;
   }

   /**
    * @return True if is in the environment loading mode.
    */
   public boolean isEnvironmentOnly()
   {
      return m_bEnvironmentOnly;
   }

   /**
    * @param environmentOnly True if is in the environment loading mode.
    */
   public void setEnvironmentOnly(boolean environmentOnly)
   {
      m_bEnvironmentOnly = environmentOnly;
   }

   /**
    * Adds a new privilege to the metadata.
    * @param privilege The privilege to add.
    * @throws MetadataException if a privilege
    * with the same name already exists.
    */
   public void addPrivilege(Privilege privilege)
   {
      verifyNotReadOnly();

      Object oldPrivilege = m_privilegeMap.put(privilege.getName(), privilege);

      if (oldPrivilege != null)
      {
         m_privilegeMap.put(privilege.getName(), oldPrivilege);

         MetadataException e = new MetadataException("err.meta.privilegeDup", new Object[]{privilege.getName(), getName()});

         throw e;
      }

      if (privilege instanceof PrimitivePrivilege)
      {
         ((PrimitivePrivilege)privilege).setOrdinal(m_nPrimitivePrivilegeCount++);
      }
   }

   /**
    * Gets a privilege by name.
    * @param sName The privilege name.
    * @return The privilege object.
    * @throws MetadataLookupException if the privilege does not exist.
    */
   public Privilege getPrivilege(String sName)
   {
      Privilege privilege = (Privilege)m_privilegeMap.get(sName);

      if (privilege != null)
      {
         return privilege;
      }

      throw new MetadataLookupException("err.meta.privilegeLookup", sName, this);
   }

   /**
    * @see nexj.core.meta.Metadata#findPrivilege(java.lang.String)
    */
   public Privilege findPrivilege(String sName)
   {
      if (sName == null)
      {
         return null;
      }

      return (Privilege)m_privilegeMap.get(sName);
   }

   /**
    * @return The privilege count.
    */
   public int getPrivilegeCount()
   {
      return m_privilegeMap.size();
   }

   /**
    * @return An iterator for the contained privilege objects.
    */
   public Iterator getPrivilegeIterator()
   {
      return m_privilegeMap.valueIterator();
   }

   /**
    * @see nexj.core.meta.Metadata#getPrimitivePrivilege(java.lang.String)
    */
   public PrimitivePrivilege getPrimitivePrivilege(String sName)
   {
      Privilege privilege = (Privilege) m_privilegeMap.get(sName);

      if (privilege != null && privilege.isPrimitive())
      {
         return (PrimitivePrivilege)privilege;
      }

      throw new MetadataLookupException("err.meta.privilegeLookup", sName, this);
   }

   /**
    * @see nexj.core.meta.Metadata#getPrimitivePrivilegeCount()
    */
   public int getPrimitivePrivilegeCount()
   {
      return m_nPrimitivePrivilegeCount;
   }

   /**
    * @see nexj.core.meta.Metadata#createPrivilegeSet()
    */
   public PrivilegeSet createPrivilegeSet()
   {
      return new PrivilegeSet(m_nPrimitivePrivilegeCount);
   }

   /**
    * Adds a new public symbol to the metadata.
    * @param publicSymbol The public symbol to add.
    */
   public void addPublicSymbol(Symbol publicSymbol)
   {
      verifyNotReadOnly();
      m_publicSymbolSet.add(publicSymbol);
   }

   /**
    * Checks is a symbol is public.
    * @param symbol The symbol to check.
    * @return True is the symbol is public.
    */
   public boolean isPublicSymbol(Symbol symbol)
   {
      return m_publicSymbolSet.contains(symbol);
   }

   /**
    * @see nexj.core.meta.Metadata#addClientSymbol(nexj.core.scripting.Symbol)
    */
   public void addClientSymbol(Symbol symbol)
   {
      verifyNotReadOnly();
      m_clientSymbolSet.add(symbol);
   }

   /**
    * @see nexj.core.meta.Metadata#removeClientSymbol(nexj.core.scripting.Symbol)
    */
   public void removeClientSymbol(Symbol symbol)
   {
      verifyNotReadOnly();
      m_clientSymbolSet.remove(symbol);
   }

   /**
    * @see nexj.core.meta.Metadata#isClientSymbol(nexj.core.scripting.Symbol)
    */
   public boolean isClientSymbol(Symbol symbol)
   {
      return m_clientSymbolSet.contains(symbol);
   }

   /**
    * @see nexj.core.meta.Metadata#getClientSymbolIterator()
    */
   public Iterator getClientSymbolIterator()
   {
      return m_clientSymbolSet.iterator();
   }

   /**
    * @see nexj.core.meta.Metadata#addSessionSymbol(nexj.core.scripting.Symbol)
    */
   public void addSessionSymbol(Symbol symbol)
   {
      m_sessionSymbolSet.add(symbol);
   }

   /**
    * @see nexj.core.meta.Metadata#getSessionSymbolIterator()
    */
   public Iterator getSessionSymbolIterator()
   {
      return m_sessionSymbolSet.iterator();
   }

   /**
    * Adds a new class to the metadata.
    * @param metaclass The class to add.
    * @throws MetadataException if a class
    * with the same name already exists.
    */
   public void addMetaclass(Metaclass metaclass)
   {
      verifyNotReadOnly();

      if (m_classAspectMap.contains(metaclass.getName()))
      {
         throw new MetadataException("err.meta.metaclassDup", new Object[]{metaclass.getName(), getName()});
      }

      Object oldMetaclass = m_metaclassMap.put(metaclass.getName(), metaclass);

      if (oldMetaclass != null)
      {
         m_metaclassMap.put(metaclass.getName(), oldMetaclass);

         throw new MetadataException("err.meta.metaclassDup", new Object[]{metaclass.getName(), getName()});
      }

      metaclass.setMetadata(this);
   }

   /**
    * @see nexj.core.meta.Metadata#defineMetaclass(java.lang.String, MetadataObject)
    */
   public Metaclass defineMetaclass(String sName, MetadataObject referrer)
   {
      Metaclass metaclass = findMetaclass(sName);

      if (metaclass == null)
      {
         metaclass = new Metaclass(sName);
         metaclass.setForward(true);
         addMetaclass(metaclass);
         m_globalEnv.defineVariable(sName, metaclass);
      }

      metaclass.addReferrer(referrer);

      return metaclass;
   }

   /**
    * @see nexj.core.meta.Metadata#findMetaclass(java.lang.String)
    */
   public Metaclass findMetaclass(String sName)
   {
      return (Metaclass)m_metaclassMap.get(sName);
   }

   /**
    * Gets a class by name.
    * @param sName The class name.
    * @return The class object.
    * @throws MetadataLookupException if the class does not exist.
    */
   public Metaclass getMetaclass(String sName)
   {
      Metaclass metaclass = (Metaclass)m_metaclassMap.get(sName);

      if (metaclass != null)
      {
         return metaclass;
      }

      throw new MetadataLookupException("err.meta.metaclassLookup", sName, this);
   }

   /**
    * @return The class count.
    */
   public int getMetaclassCount()
   {
      return m_metaclassMap.size();
   }

   /**
    * @return An iterator for the contained class objects.
    */
   public Iterator getMetaclassIterator()
   {
      return m_metaclassMap.valueIterator();
   }

   /**
    * Adds a new class aspect to the metadata.
    * @param classAspect The class aspect to add.
    * @throws MetadataException if a class aspect
    * with the same name already exists.
    */
   public void addClassAspect(ClassAspect classAspect)
   {
      verifyNotReadOnly();

      if (m_metaclassMap.contains(classAspect.getName()))
      {
         throw new MetadataException("err.meta.classAspectDup", new Object[]{classAspect.getName(), getName()});
      }

      Object oldClassAspect = m_classAspectMap.put(classAspect.getName(), classAspect);

      if (oldClassAspect != null)
      {
         m_classAspectMap.put(classAspect.getName(), oldClassAspect);

         throw new MetadataException("err.meta.classAspectDup", new Object[]{classAspect.getName(), getName()});
      }

      classAspect.setMetadata(this);

      int i;

      for (i = m_classAspectList.size(); i > 0; --i)
      {
         if (Named.COMPARATOR.compare(m_classAspectList.get(i - 1), classAspect) <= 0)
         {
            break;
         }
      }

      m_classAspectList.add(i, classAspect);
   }

   /**
    * @see nexj.core.meta.Metadata#defineClassAspect(java.lang.String, MetadataObject)
    */
   public ClassAspect defineClassAspect(String sName, MetadataObject referrer)
   {
      ClassAspect aspect = findClassAspect(sName);

      if (aspect == null)
      {
         aspect = new ClassAspect(sName);
         aspect.setForward(true);
         addClassAspect(aspect);
      }

      aspect.addReferrer(referrer);

      return aspect;
   }

   /**
    * Gets a class aspect by name.
    * @param sName The class aspect name.
    * @return The class aspect object.
    * @throws MetadataLookupException if the class aspect does not exist.
    */
   public ClassAspect getClassAspect(String sName)
   {
      ClassAspect classAspect = (ClassAspect)m_classAspectMap.get(sName);

      if (classAspect != null)
      {
         return classAspect;
      }

      throw new MetadataLookupException("err.meta.classAspectLookup", sName, this);
   }

   /**
    * Finds a class aspect by name.
    * @param sName The class aspect name.
    * @return The class aspect object, or null if not found.
    */
   public ClassAspect findClassAspect(String sName)
   {
      return (ClassAspect)m_classAspectMap.get(sName);
   }

   /**
    * @return The class aspect count.
    */
   public int getClassAspectCount()
   {
      return m_classAspectMap.size();
   }

   /**
    * @return An iterator for the contained class aspect objects.
    */
   public Iterator getClassAspectIterator()
   {
      return m_classAspectList.iterator();
   }

   /**
    * Adds a new flow macro to the metadata.
    * @param flowMacro The flow macro to add.
    * @throws MetadataException if a flow macro
    * with the same name already exists.
    */
   public void addFlowMacro(FlowMacro flowMacro)
   {
      verifyNotReadOnly();

      Object oldFlowMacro = m_flowMacroMap.put(flowMacro.getName(), flowMacro);

      if (oldFlowMacro != null)
      {
         m_flowMacroMap.put(flowMacro.getName(), oldFlowMacro);

         throw new MetadataException("err.meta.flowMacroDup", new Object[]
         {
            flowMacro.getName(),
            getName()
         });
      }

      flowMacro.setMetadata(this);
   }

   /**
    * Gets a flow macro by name.
    * @param sName The flow macro name.
    * @return The flow macro object.
    * @throws MetadataLookupException if the flow macro does not exist.
    */
   public FlowMacro getFlowMacro(String sName)
   {
      FlowMacro flowMacro = (FlowMacro) m_flowMacroMap.get(sName);

      if (flowMacro != null)
      {
         return flowMacro;
      }

      throw new MetadataLookupException("err.meta.flowMacroLookup", sName, this);
   }

   /**
    * Finds a flow macro by name.
    * @param sName The flow macro name.
    * @return The flow macro object, or null if not found.
    */
   public FlowMacro findFlowMacro(String sName)
   {
      return (FlowMacro) m_flowMacroMap.get(sName);
   }

   /**
    * @return The flow macro count.
    */
   public int getFlowMacroCount()
   {
      return m_flowMacroMap.size();
   }

   /**
    * @return An iterator for the contained flow macro objects.
    */
   public Iterator getFlowMacroIterator()
   {
      return m_flowMacroMap.valueIterator();
   }

   /**
    * Adds a new workflow to the metadata.
    * @param workflow The workflow to add.
    * @throws MetadataException if a workflow
    * with the same name already exists.
    */
   public void addWorkflow(Workflow workflow)
   {
      verifyNotReadOnly();

      Workflow oldWorkflow = (Workflow)m_workflowMap.put(workflow.getName(), Primitive.createInteger(workflow.getVersion()), workflow);

      if (oldWorkflow != null)
      {
         m_workflowMap.put(workflow.getName(), Primitive.createInteger(workflow.getVersion()), oldWorkflow);
         throw new MetadataException("err.meta.workflowDup",
            new Object[]{workflow.getFullName(), getName()});
      }

      oldWorkflow = (Workflow)m_currentWorkflowMap.put(workflow.getName(), workflow);

      if (oldWorkflow != null && workflow.getVersion() < oldWorkflow.getVersion())
      {
         m_currentWorkflowMap.put(workflow.getName(), oldWorkflow);
      }
   }

   /**
    * @see nexj.core.meta.Metadata#findWorkflow(java.lang.String, int)
    */
   public Workflow findWorkflow(String sName, int nVersion)
   {
      return (Workflow)m_workflowMap.get(sName, Primitive.createInteger(nVersion));
   }

   /**
    * Gets a workflow by name.
    * @param sName The workflow name.
    * @return The workflow object.
    * @throws MetadataLookupException if the workflow does not exist.
    */
   public Workflow getWorkflow(String sName, int nVersion)
   {
      Workflow workflow = (Workflow)m_workflowMap.get(sName, Primitive.createInteger(nVersion));

      if (workflow != null)
      {
         return workflow;
      }

      throw new MetadataLookupException("err.meta.workflowLookup", sName + "." + nVersion, this);
   }

   /**
    * @see nexj.core.meta.Metadata#getWorkflow(java.lang.String)
    */
   public Workflow getWorkflow(String sName)
   {
      Workflow workflow = (Workflow)m_currentWorkflowMap.get(sName);

      if (workflow != null)
      {
         return workflow;
      }

      throw new MetadataLookupException("err.meta.workflowLookup", sName, this);
   }

   /**
    * @return The workflow count.
    */
   public int getWorkflowCount()
   {
      return m_workflowMap.size();
   }

   /**
    * @return An iterator for the contained workflow objects.
    */
   public Iterator getWorkflowIterator()
   {
      return m_workflowMap.valueIterator();
   }

   /**
    * Adds a new channel type to the metadata.
    * @param channelType The channel type to add.
    * @throws MetadataException if a channel type
    * with the same name already exists.
    */
   public void addChannelType(ChannelType channelType)
   {
      verifyNotReadOnly();

      Object oldChannelType = m_channelTypeMap.put(channelType.getName(), channelType);

      if (oldChannelType != null)
      {
         m_channelTypeMap.put(channelType.getName(), oldChannelType);

         throw new MetadataException("err.meta.channelTypeDup", new Object[]
         {
            channelType.getName(),
            getName()
         });
      }

      channelType.setMetadata(this);
   }

   /**
    * Gets a channel type by name.
    * @param sName The channel type name.
    * @return The channel type object.
    * @throws MetadataLookupException if the channel type does not exist.
    */
   public ChannelType getChannelType(String sName)
   {
      ChannelType channelType = (ChannelType) m_channelTypeMap.get(sName);

      if (channelType != null)
      {
         return channelType;
      }

      throw new MetadataLookupException("err.meta.channelTypeLookup", sName, this);
   }

   /**
    * @return The channel type count.
    */
   public int getChannelTypeCount()
   {
      return m_channelTypeMap.size();
   }

   /**
    * @return An iterator for the contained channel type objects.
    */
   public Iterator getChannelTypeIterator()
   {
      return m_channelTypeMap.valueIterator();
   }

   /**
    * Adds a new channel type XML element mapping to the metadata.
    * @param sElement The XML element name.
    * @param channelType The channel type object.
    * @throws MetadataException if a duplicate entry is encountered.
    */
   public void addChannelTypeElement(String sElement, ChannelType channelType)
   {
      verifyNotReadOnly();

      m_channelTypeElementMap.put(channelType, sElement);

      if (sElement.equals("Channel"))
      {
         return;
      }

      Object oldChannelType = m_channelTypeElementMap.put(sElement, channelType);

      if (oldChannelType != null)
      {
         m_channelTypeElementMap.put(sElement, oldChannelType);

         throw new MetadataException("err.meta.channelTypeElementDup", new Object[]
         {
            sElement,
            channelType.getName(),
            getName()
         });
      }
   }

   /**
    * Gets a channel type by element name.
    * @param element The channel type element.
    * @return The channel type object.
    * @throws MetadataLookupException if the channel type does not exist.
    */
   public ChannelType getChannelTypeByElement(Element element)
   {
      if (element.getNodeName().equals("Channel"))
      {
         return getChannelType(XMLUtil.getReqStringAttr(element, "type"));
      }

      ChannelType channelType = (ChannelType)m_channelTypeMap.get(element.getNodeName());

      if (channelType != null)
      {
         return channelType;
      }

      throw new MetadataLookupException("err.meta.channelTypeElementLookup", element.getNodeName(), this);
   }

   /**
    * Gets a channel type element name.
    * @param type The channel type.
    */
   public String getChannelTypeElement(ChannelType type)
   {
      return (String)m_channelTypeElementMap.get(type);
   }

   /**
    * Adds a new channel to the metadata.
    * @param channel The channel to add.
    * @throws MetadataException if a channel
    * with the same name already exists.
    */
   public void addChannel(Channel channel)
   {
      verifyNotReadOnly();

      Object oldChannel = m_channelMap.put(channel.getName(), channel);

      if (oldChannel != null)
      {
         m_channelMap.put(channel.getName(), oldChannel);

         throw new MetadataException("err.meta.channelDup", new Object[]
         {
            channel.getName(),
            getName()
         });
      }
   }

   /**
    * Gets a channel by name.
    * @param sName The channel name.
    * @return The channel object.
    * @throws MetadataLookupException if the channel does not exist.
    */
   public Channel getChannel(String sName)
   {
      Channel channel = findChannel(sName);

      if (channel != null)
      {
         return channel;
      }

      throw new MetadataLookupException("err.meta.channelLookup", sName, this);
   }
  
   /**
    * Finds a channel by name.
    * @param sName The channel name.
    * @return The channel object, null if not found.
    */
   public Channel findChannel(String sName)
   {
      return (Channel) m_channelMap.get(sName);
   }

   /**
    * @return The channel count.
    */
   public int getChannelCount()
   {
      return m_channelMap.size();
   }

   /**
    * @return An iterator for the contained channel objects.
    */
   public Iterator getChannelIterator()
   {
      return m_channelMap.valueIterator();
   }

   /**
    * @return The dump file resource map.
    */
   public Lookup getDumpResourceMap()
   {
      return m_dumpResMap;
   }

   /**
    * @see nexj.core.meta.Metadata#getFile()
    */
   public Binary getFile(String sName)
   {
      InputStream istream = null;
      ByteArrayOutputStream dataStream = null;

      try
      {
         if (sName != null && sName.length() > 0 && !sName.contains(".."&& !sName.startsWith("/") && !sName.startsWith("\\"))
         {
            XMLMetadataHelper helper = getHelper();

            istream = helper.getResourceAsStream("files/" + sName);
            dataStream = new ByteArrayOutputStream();

            IOUtil.copy(dataStream, istream);

            return new Binary(dataStream.toByteArray());
         }

         throw new MetadataValidationException("err.meta.invalidFilePath", new Object[] { sName });
      }
      catch (Exception e)
      {
         throw new MetadataException("err.meta.fileLookup", new Object[] { sName, this }, e);
      }
      finally
      {
         IOUtil.close(istream);
      }
   }

   /**
    * Adds a new message format to the metadata.
    * @param format The message format to add.
    * @throws MetadataException if a message format
    * with the same name already exists.
    */
   public void addFormat(Format format)
   {
      verifyNotReadOnly();

      Object oldMessageFormat = m_formatMap.put(format.getName(), format);

      if (oldMessageFormat != null)
      {
         m_formatMap.put(format.getName(), oldMessageFormat);

         throw new MetadataException("err.meta.messageFormatDup", new Object[]
         {
            format.getName(),
            getName()
         });
      }

      format.setMetadata(this);
   }

   /**
    * Gets a message format by name.
    * @param sName The message format name.
    * @return The message format object.
    * @throws MetadataLookupException if the message format does not exist.
    */
   public Format getFormat(String sName)
   {
      Format messageFormat = (Format) m_formatMap.get(sName);

      if (messageFormat != null)
      {
         return messageFormat;
      }

      throw new MetadataLookupException("err.meta.messageFormatLookup", sName, this);
   }

   /**
    * @return The message format count.
    */
   public int getFormatCount()
   {
      return m_formatMap.size();
   }

   /**
    * @return An iterator for the contained message format objects.
    */
   public Iterator getFormatIterator()
   {
      return m_formatMap.valueIterator();
   }

   /**
    * Adds a new message to the metadata.
    * @param message The message to add.
    * @throws MetadataException if a message
    * with the same name already exists.
    */
   public void addMessage(Message message)
   {
      verifyNotReadOnly();

      Object oldMessage = m_messageMap.put(message.getName(), message);

      if (oldMessage != null)
      {
         m_messageMap.put(message.getName(), oldMessage);

         throw new MetadataException("err.meta.messageDup", new Object[]
         {
            message.getName(),
            getName()
         });
      }

      message.setMetadata(this);
   }

   /**
    * @see nexj.core.meta.Metadata#findMessage(java.lang.String)
    */
   public Message findMessage(String sName)
   {
      return (Message)m_messageMap.get(sName);
   }

   /**
    * Gets a message by name.
    * @param sName The message name.
    * @return The message object.
    * @throws MetadataLookupException if the message does not exist.
    */
   public Message getMessage(String sName)
   {
      Message message = (Message) m_messageMap.get(sName);

      if (message != null)
      {
         return message;
      }

      throw new MetadataLookupException("err.meta.messageLookup", sName, this);
   }

   /**
    * @return The message count.
    */
   public int getMessageCount()
   {
      return m_messageMap.size();
   }

   /**
    * @return An iterator for the contained message objects.
    */
   public Iterator getMessageIterator()
   {
      return m_messageMap.valueIterator();
   }

   /**
    * Adds a new transformation to the metadata.
    * @param transformation The transformation to add.
    * @throws MetadataException if a transformation
    * with the same name already exists.
    */
   public void addTransformation(Transformation transformation)
   {
      verifyNotReadOnly();

      Object oldTransformation = m_transformationMap.put(transformation.getName(), transformation);

      if (oldTransformation != null)
      {
         m_transformationMap.put(transformation.getName(), oldTransformation);

         throw new MetadataException("err.meta.transformationDup", new Object[]
         {
            transformation.getName(),
            getName()
         });
      }
   }

   /**
    * Gets a transformation by name.
    * @param sName The transformation name.
    * @return The transformation object.
    * @throws MetadataLookupException if the transformation does not exist.
    */
   public Transformation getTransformation(String sName)
   {
      Transformation transformation = (Transformation) m_transformationMap.get(sName);

      if (transformation != null)
      {
         return transformation;
      }

      throw new MetadataLookupException("err.meta.transformationLookup", sName, this);
   }

   /**
    * @see nexj.core.meta.Metadata#findTransformation(java.lang.String)
    */
   public Transformation findTransformation(String sName)
   {
      return (Transformation) m_transformationMap.get(sName);
   }

   /**
    * @return The transformation count.
    */
   public int getTransformationCount()
   {
      return m_transformationMap.size();
   }

   /**
    * @return An iterator for the contained transformation objects.
    */
   public Iterator getTransformationIterator()
   {
      return m_transformationMap.valueIterator();
   }

   /**
    * Adds a new service interface to the metadata.
    * @param iface The service interface to add.
    * @throws MetadataException if a service interface
    * with the same name already exists.
    */
   public void addInterface(Interface iface)
   {
      verifyNotReadOnly();

      Object oldInterface = m_interfaceMap.put(iface.getName(), iface);

      if (oldInterface != null)
      {
         m_interfaceMap.put(iface.getName(), oldInterface);

         throw new MetadataException("err.meta.interfaceDup", new Object[]
         {
            iface.getName(),
            getName()
         });
      }

      iface.setMetadata(this);
   }

   /**
    * @see nexj.core.meta.Metadata#defineInterface(java.lang.String, MetadataObject)
    */
   public Interface defineInterface(String sName, MetadataObject referrer)
   {
      Interface iface = (Interface)m_interfaceMap.get(sName);

      if (iface == null)
      {
         iface = new Interface(sName);
         iface.setForward(true);
         addInterface(iface);
      }

      iface.addReferrer(referrer);

      return iface;
   }

   /**
    * Gets a service interface by name.
    * @param sName The service interface name.
    * @return The service interface object.
    * @throws MetadataLookupException if the service interface does not exist.
    */
   public Interface getInterface(String sName)
   {
      Interface iface = (Interface) m_interfaceMap.get(sName);

      if (iface != null)
      {
         return iface;
      }

      throw new MetadataLookupException("err.meta.interfaceLookup", sName, this);
   }

   /**
    * @return The service interface count.
    */
   public int getInterfaceCount()
   {
      return m_interfaceMap.size();
   }

   /**
    * @return An iterator for the contained service interface objects.
    */
   public Iterator getInterfaceIterator()
   {
      return m_interfaceMap.valueIterator();
   }

   /**
    * @see Metadata#addService(Service)
    */
   public void addService(Service service)
   {
      verifyNotReadOnly();

      Service oldService = (Service)m_serviceMap.put(service.getName(), Primitive.createInteger(service.getVersion()), service);

      if (oldService != null)
      {
         m_serviceMap.put(service.getName(), Primitive.createInteger(service.getVersion()), oldService);
         throw new MetadataException("err.meta.serviceDup",
            new Object[]{service.getFullName(), getName()});
      }

      oldService = (Service)m_currentServiceMap.put(service.getName(), service);

      if (oldService != null && service.getVersion() < oldService.getVersion())
      {
         m_currentServiceMap.put(service.getName(), oldService);
      }
   }

   /**
    * @see Metadata#findService(String, int)
    */
   public Service findService(String sName, int nVersion)
   {
      return (Service)m_serviceMap.get(sName, Primitive.createInteger(nVersion));
   }

   /**
    * @see Metadata#getService(String, int)
    */
   public Service getService(String sName, int nVersion)
   {
      Service service = (Service)m_serviceMap.get(sName, Primitive.createInteger(nVersion));

      if (service != null)
      {
         return service;
      }

      throw new MetadataLookupException("err.meta.serviceLookup", sName + "." + nVersion, this);
   }

   /**
    * @see Metadata#getService(String)
    */
   public Service getService(String sName)
   {
      Service service = (Service)m_currentServiceMap.get(sName);

      if (service != null)
      {
         return service;
      }

      throw new MetadataLookupException("err.meta.serviceLookup", sName, this);
   }

   /**
    * @return The service count.
    */
   public int getServiceCount()
   {
      return m_serviceMap.size();
   }

   /**
    * @see Metadata#getServiceIterator()
    */
   public Iterator getServiceIterator()
   {
      return m_serviceMap.valueIterator();
   }

   /**
    * Adds a new component to the metadata.
    * @param component The component to add.
    * @throws MetadataException if a component
    * with the same name already exists.
    */
   public void addComponent(Component component)
   {
      verifyNotReadOnly();

      Object oldComponent = m_componentMap.put(component.getName(), component);

      if (oldComponent != null)
      {
         m_componentMap.put(component.getName(), oldComponent);

         throw new MetadataException("err.meta.componentDup", new Object[] { component.getName(), getName()});
      }

      component.setMetadata(this);
   }

   /**
    * Gets a component by name.
    * @param sName The component name.
    * @return The component object.
    * @throws MetadataLookupException if the component does not exist.
    */
   public Component getComponent(String sName)
   {
      Component component = (Component)m_componentMap.get(sName);

      if (component != null)
      {
         return component;
      }

      throw new MetadataLookupException("err.meta.componentLookup", sName, this);
   }

   /**
    * Finds a component by name.
    * @param sName The name of the component.
    * @return The component or null if not found.
    */
   public Component findComponent(String sName)
   {
      return (Component)m_componentMap.get(sName);
   }

   /**
    * @return The component count.
    */
   public int getComponentCount()
   {
      return m_componentMap.size();
   }

   /**
    * @return An iterator for the contained component objects.
    */
   public Iterator getComponentIterator()
   {
      return m_componentMap.valueIterator();
   }

   /**
    * @see nexj.core.meta.Metadata#addExternalLibrary(nexj.core.meta.ExternalLibrary)
    */
   public void addExternalLibrary(ExternalLibrary externalLibrary)
   {
      verifyNotReadOnly();

      Object oldExternalLibrary = m_externalLibraryMap.put(externalLibrary.getName(), externalLibrary);

      if (oldExternalLibrary != null)
      {
         m_externalLibraryMap.put(externalLibrary.getName(), oldExternalLibrary);

         throw new MetadataException("err.meta.externalLibraryDup", new Object[]
         {
            externalLibrary.getName(),
            getName()
         });
      }

      externalLibrary.setMetadata(this);
   }

   /**
    * @see nexj.core.meta.Metadata#getExternalLibrary(java.lang.String)
    */
   public ExternalLibrary getExternalLibrary(String sName)
   {
      ExternalLibrary externalLibrary = (ExternalLibrary)m_externalLibraryMap.get(sName);

      if (externalLibrary != null)
      {
         return externalLibrary;
      }

      throw new MetadataLookupException("err.meta.externalLibraryLookup", sName, this);
   }

   /**
    * @see nexj.core.meta.Metadata#getExternalLibraryCount()
    */
   public int getExternalLibraryCount()
   {
      return m_externalLibraryMap.size();
   }

   /**
    * @see nexj.core.meta.Metadata#getExternalLibraryIterator()
    */
   public Iterator getExternalLibraryIterator()
   {
      return m_externalLibraryMap.valueIterator();
   }

   /**
    * @return The upgrade resource map.
    */
   public Lookup getUpgradeResourceMap()
   {
      return m_upgradeResMap;
   }

   /**
    * Adds an upgrade resource path.
    * @param sName The upgrade name.
    * @param sPath The upgrade path.
    * @throws MetadataException if the named test already exists.
    */
   public void addUpgrade(String sName, String sPath)
   {
      verifyNotReadOnly();

      Object oldPath = m_upgradeResMap.put(sName, sPath);

      if (oldPath != null)
      {
         m_upgradeResMap.put(sName, oldPath);

         throw new MetadataException("err.meta.upgradeDup", new Object[] {sName, getName()});
      }
   }

   /**
    * @see nexj.core.meta.Metadata#getUpgrade(String)
    */
   public Upgrade getUpgrade(String sName)
   {
      String sResource = (String)m_upgradeResMap.get(sName);

      if (sResource == null)
      {
         throw new MetadataLookupException("err.meta.upgradeLookup", sName, this);
      }

      final Upgrade[] upgradeArray = new Upgrade[1];
      final XMLMetadataHelper helper = getHelper();
      final XMLUpgradeMetadataLoader loader = new XMLUpgradeMetadataLoader(this, helper);

      helper.setMetadataCoreVersion(getCoreVersion(), getUpgradeResources()); //need upgrades loaded
      helper.loadResource(
         sResource, sName, helper.new UpgradingResourceCharacterStreamHandler(new ResourceHandler()
      {
         public void handleResource(Element element, String sName)
         {
            upgradeArray[0] = loader.loadUpgrade(sName, element);
         }
      }));

      if (m_validationHelper == null)
      {
         helper.checkForError();
      }

      return upgradeArray[0];
   }

   /**
    * @see nexj.core.meta.Metadata#addLocale(java.lang.String)
    */
   public Locale addLocale(String sName)
   {
      verifyNotReadOnly();

      Locale locale = LocaleUtil.parse(sName);
      Object oldLocale = m_localeMap.put(sName, locale);

      if (oldLocale != null)
      {
         m_localeMap.put(sName, oldLocale);

         throw new MetadataException("err.meta.localeDup", new Object[] {sName, getName()});
      }

      return locale;
   }

   /**
    * @see nexj.core.meta.ContextMetadata#isLocaleSupported(java.lang.String)
    */
   public boolean isLocaleSupported(String sLocale)
   {
      return m_localeMap.contains(sLocale);
   }

   /**
    * @see nexj.core.meta.ContextMetadata#getLocaleName(java.lang.String)
    */
   public String getLocaleName(String sLocale)
   {
      if (sLocale == null)
      {
         sLocale = DEFAULT_LOCALE;
      }

      for (;;)
      {
         if (m_localeMap.contains(sLocale) ||
            sLocale == DEFAULT_LOCALE)
         {
            return sLocale;
         }

         sLocale = LocaleUtil.getBase(sLocale, DEFAULT_LOCALE);
      }
   }

   /**
    * @see nexj.core.meta.ContextMetadata#getLocale(java.lang.String)
    */
   public Locale getLocale(String sLocale)
   {
      Locale locale;

      if (sLocale == null)
      {
         sLocale = DEFAULT_LOCALE;
      }

      for (;;)
      {
         locale = (Locale)m_localeMap.get(sLocale);

         if (locale != null)
         {
            return locale;
         }

         if (sLocale == DEFAULT_LOCALE)
         {
            return LocaleUtil.parse(DEFAULT_LOCALE);
         }

         sLocale = LocaleUtil.getBase(sLocale, DEFAULT_LOCALE);
      }
   }

   /**
    * @see nexj.core.meta.ContextMetadata#getLocaleCount()
    */
   public int getLocaleCount()
   {
      return m_localeMap.size();
   }

   /**
    * @see nexj.core.meta.ContextMetadata#getLocaleIterator()
    */
   public Iterator getLocaleIterator()
   {
      return m_localeMap.valueIterator();
   }

   /**
    * Add String resource path.
    * @param sName Locale name.
    * @param sPath String resource path.
    */
   public void addString(String sLocale, String sPath)
   {
      verifyNotReadOnly();

      Lookup map = (Lookup)m_stringMap.get(sLocale);

      if (map == null)
      {
         map = new HashTab();
         m_stringMap.put(sLocale, map);
      }

      Object oldPath = map.put(sPath, sPath);

      if (oldPath != null)
      {
         throw new MetadataException("err.meta.stringDup", new Object[] {sLocale, getName()});
      }
   }

   /**
    * Gets the string table for the specified locale.
    * @param sLocale The locale name.
    * @return The string table.
    * @throws MetadataException if the string resource
    * for the default locale was not found.
    */
   public StringTable getStringTable(String sLocale)
   {
      String sInitialLocale = sLocale;
      Object obj = null;

      synchronized (m_stringMetaMap)
      {
         while (sLocale.length() > 0)
         {
            if (sLocale.charAt(sLocale.length() - 1) == '_')
            {
               sLocale = sLocale.substring(0, sLocale.length() - 1);

               continue;
            }

            obj = m_stringMetaMap.get(sLocale);

            if (obj != null)
            {
               break;
            }

            int i = sLocale.lastIndexOf('_');

            if (i > 0)
            {
               sLocale = sLocale.substring(0, i);
            }
            else
            {
               break;
            }
         }

         if (obj == null && !sLocale.equals(DEFAULT_LOCALE))
         {
            sLocale = DEFAULT_LOCALE;
            obj = m_stringMetaMap.get(sLocale);
         }

         if (obj != null && obj != Undefined.VALUE)
         {
            // Comparison by reference is sufficient here
            if (sLocale != sInitialLocale)
            {
               m_stringMetaMap.put(sInitialLocale, obj);
            }

            return (StringTable)obj;
         }
      }

      StringTable stringTable = null;
      int i = sLocale.lastIndexOf('_');

      if (i > 0)
      {
         stringTable = getStringTable(sLocale.substring(0, i));
      }
      else if (!sLocale.equals(DEFAULT_LOCALE))
      {
         stringTable = getStringTable(DEFAULT_LOCALE);
      }

      InputStream in = null;
      Properties properties = new Properties();
      URL sysURL = null;

      // Load the system string table

      try
      {
         sysURL = SystemResources.find("client." + sLocale + ".strings");

         if (sysURL != null)
         {
            in = URLUtil.openStream(sysURL);
            properties.load(in);
         }
      }
      catch (Exception e)
      {
         if (in != null)
         {
            throw new MetadataException("err.meta.stringTableLoad", new Object[]{sysURL.toString()}, e);
         }
      }
      finally
      {
         IOUtil.close(in);
         in = null;
      }

      try
      {
         sysURL = SystemResources.find("server." + sLocale + ".strings");

         if (sysURL != null)
         {
            in = URLUtil.openStream(sysURL);
            properties.load(in);
         }
      }
      catch (Exception e)
      {
         if (in != null)
         {
            throw new MetadataException("err.meta.stringTableLoad", new Object[]{sysURL.toString()}, e);
         }
      }
      finally
      {
         IOUtil.close(in);
         in = null;
      }

      try
      {
         sysURL = SystemResources.find(sLocale + ".strings");

         if (sysURL != null)
         {
            in = URLUtil.openStream(sysURL);
            properties.load(in);
         }
      }
      catch (Exception e)
      {
         if (in != null)
         {
            throw new MetadataException("err.meta.stringTableLoad", new Object[]{sysURL.toString()}, e);
         }
      }
      finally
      {
         IOUtil.close(in);
         in = null;
      }

      // Overwrite the system string table with the repository string table

      Lookup map = (Lookup)m_stringMap.get(sLocale);
      String sResName = null;

      if (map != null)
      {
         try
         {
            for (Iterator itr = map.iterator(); itr.hasNext();)
            {
               sResName = (String)itr.next();
               in = getHelper().getResourceAsStream(sResName);
               properties.load(in);
               in.close();
               in = null;
            }
         }
         catch (IOException e)
         {
            throw new MetadataException("err.meta.stringTableLoad", new Object[]{sResName}, e);
         }
         finally
         {
            IOUtil.close(in);
         }
      }

      if (!properties.isEmpty())
      {
         stringTable = new StringTable(properties, stringTable, LocaleUtil.parse(sLocale));
      }

      synchronized (m_stringMetaMap)
      {
         Object oldMap = m_stringMetaMap.put(sLocale, stringTable);

         if (oldMap != null && oldMap != Undefined.VALUE)
         {
            m_stringMetaMap.put(sLocale, oldMap);

            stringTable = (StringTable)oldMap;
         }
      }

      return stringTable;
   }

   /**
    * @see nexj.core.meta.ui.UIMetadata#getClassMeta(java.lang.String)
    */
   public ClassMeta getClassMeta(String sName)
   {
      return getMetaclass(sName);
   }

   /**
    * @see nexj.core.meta.ui.UIMetadata#findClassMeta(java.lang.String)
    */
   public ClassMeta findClassMeta(String sName)
   {
      return findMetaclass(sName);
   }

   /**
    * @see nexj.core.meta.MetadataObject#makeReadOnly()
    */
   public void makeReadOnly()
   {
      m_listing.makeReadOnly();
      m_globalEnv.makeReadOnly();

      for (Iterator itr = getDataSourceTypeIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getDataSourceIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getPrivilegeIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getMetaclassIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getClassAspectIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getWorkflowIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getFlowMacroIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getChannelTypeIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getChannelIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getServiceIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getInterfaceIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getFormatIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getMessageIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getTransformationIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getComponentIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      super.makeReadOnly();
   }

   /**
    * @see nexj.core.meta.MetadataObject#validate(nexj.core.meta.ContextMetadata, nexj.core.util.ExceptionHolder)
    */
   public void validate(ContextMetadata metadata, ExceptionHolder warnings)
   {
      MetadataObject.validate(getMetaclassIterator(), this, warnings);
      MetadataObject.validate(getMessageIterator(), this, warnings);
      MetadataObject.validate(getInterfaceIterator(), this, warnings);
      MetadataObject.validate(getTransformationIterator(), this, warnings);
      MetadataObject.validate(getServiceIterator(), this, warnings);
      MetadataObject.validate(getWorkflowIterator(), this, warnings);
      MetadataObject.validate(getChannelIterator(), this, warnings);
      MetadataObject.validate(getDataSourceIterator(), this, warnings);

      if (warnings != null)
      {
         if (m_genericRPCPrivilege == null)
         {
            MetadataValidationException e = new MetadataValidationException(
               "err.meta.missingRPCPrivilege", new Object[]{m_sName});

            setProperties(e);
            e.setProperty("item", "rpcPrivilege");

            if (m_configURL != null)
            {
               e.setResourceName(m_configURL.toString());
            }

            warnings.addException(e);
         }
      }
   }

   /**
    * Checks the loaded metadata against a compatible model.
    * @param compatible The compatible model to check against.
    * @throws MetadataCompoundValidationException if any compatibility errors
    *    have been accumulated and a validation helper is not set.
    */
   public void checkCompatibility(Metadata compatible)
   {
      final XMLMetadataHelper helper = getHelper();

      for (Iterator itr = compatible.getMetaclassIterator(); itr.hasNext();)
      {
         final Metaclass compatibleMetaclass = (Metaclass)itr.next();
         Metaclass metaclass = findMetaclass(compatibleMetaclass.getName());

         ExceptionHolder eh;

         if (metaclass != null)
         {
            eh = new ExceptionHolder()
            {
               public void addException(Throwable e)
               {
                  if (e instanceof MetadataValidationException)
                  {
                     ((MetadataValidationException)e).setResourceName(compatibleMetaclass.getResourceName());
                  }

                  helper.getException().addException(e);
               }

               public int getExceptionCount()
               {
                  return helper.getException().getExceptionCount();
               }

               public Iterator getExceptionIterator()
               {
                  return helper.getException().getExceptionIterator();
               }
            };
         }
         else
         {
            eh = helper.getException();
         }

         compatibleMetaclass.checkCompatibility(metaclass, eh);
      }

      if (m_validationHelper == null)
      {
         helper.checkForError();
      }
   }

   /**
    * @see nexj.core.meta.MetadataObject#setProperties(nexj.core.meta.MetadataMarker)
    */
   public void setProperties(MetadataMarker marker)
   {
      marker.setTypeName("Metadata");
   }

   /**
    * @see nexj.core.meta.NamedMetadataObject#toString()
    */
   public String toString()
   {
      StringBuffer buf = new StringBuffer(64);

      buf.append("XMLMetadata \"");
      buf.append(m_sName);
      buf.append("\"");

      if (m_sRevision != null)
      {
         buf.append(' ');
         buf.append(m_sRevision);
      }

      return buf.toString();
   }

   /**
    * @return A list of resources that are potential metadata upgrade classes.
    */
   public List getUpgradeResources()
   {
      List upgradeResourceList = new ArrayList();

      try
      {
         URLUtil.addResourceNames(upgradeResourceList, XMLMetadata.class, METADATA_UPGRADE_PATH);
      }
      catch (Exception e)
      {
         ObjUtil.rethrow(e);
      }

      return upgradeResourceList;
   }

   /**
    * @return An iterator with the names of unit test resources.
    */
   public Iterator getUnitTestNameIterator()
   {
      return m_unitTestResMap.iterator();
   }

   /**
    * Adds a unit test resource path.
    *
    * @param sName The test name.
    * @param sPath The test path.
    * @throws MetadataException if the named test already exists.
    */
   public void addUnitTest(String sName, String sPath)
   {
      verifyNotReadOnly();

      Object oldPath = m_unitTestResMap.put(sName, sPath);

      if (oldPath != null)
      {
         m_unitTestResMap.put(sName, oldPath);

         throw new MetadataException("err.meta.testing.unit.unitTestDup", new Object[]
         {
            sName,
            getName()
         });
      }
   }

   /**
    * @see nexj.core.meta.EnterpriseMetadata#getUnitTest(java.lang.String)
    */
   public UnitTest getUnitTest(String sName)
   {
      UnitTest utest = null;

      synchronized (m_unitTestMap)
      {
         utest = (UnitTest)m_unitTestMap.get(sName);
      }

      if (utest != null)
      {
         return utest;
      }

      String sResource = (String)m_unitTestResMap.get(sName);

      if (sResource == null)
      {
         throw new MetadataLookupException("err.meta.testing.unit.unitTestLookup", sName, this);
      }

      final UnitTest[] utestArray = new UnitTest[1];
      final XMLMetadataHelper helper = getHelper();
      final XMLUnitTestMetadataLoader loader = new XMLUnitTestMetadataLoader(this, helper);

      helper.loadResource(sResource, sName, new ResourceHandler()
      {
         public void handleResource(Element element, String sName)
         {
            utestArray[0] = loader.unitTest(sName, element);
         }
      });

      if (m_validationHelper == null)
      {
         helper.checkForError();
      }

      utestArray[0].makeReadOnly();
      sName = utestArray[0].getName(); // reuse interned value

      synchronized (m_unitTestMap)
      {
         utest = (UnitTest)m_unitTestMap.put(sName, utestArray[0]);

         if (utest == null)
         {
            utest = utestArray[0];
         }
         else
         {
            m_unitTestMap.put(sName, utest);
         }
      }

      return utest;
   }
  
   /**
    * @return The unit test resource map.
    */
   public Lookup getUnitTestResourceMap()
   {
      return m_unitTestResMap;
   }

   /**
    * @see nexj.core.meta.Metadata#getProperties()
    */
   public Properties getProperties()
   {
      return m_properties;
   }
}
TOP

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

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.