content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package mf.org.apache.xerces.util;
  
  import org.xml.sax.InputSource;
  import org.xml.sax.SAXException;
  import org.xml.sax.ext.EntityResolver2;
  
  import java.io.IOException;
  
  import mf.javax.xml.parsers.SAXParserFactory;
  import mf.org.apache.xerces.dom.DOMInputImpl;
  import mf.org.apache.xerces.jaxp.SAXParserFactoryImpl;
  import mf.org.apache.xerces.xni.XMLResourceIdentifier;
  import mf.org.apache.xerces.xni.XNIException;
  import mf.org.apache.xerces.xni.parser.XMLEntityResolver;
  import mf.org.apache.xerces.xni.parser.XMLInputSource;
  import mf.org.apache.xml.resolver.Catalog;
  import mf.org.apache.xml.resolver.CatalogManager;
  import mf.org.apache.xml.resolver.readers.OASISXMLCatalogReader;
  import mf.org.apache.xml.resolver.readers.SAXCatalogReader;
  import mf.org.w3c.dom.ls.LSInput;
  import mf.org.w3c.dom.ls.LSResourceResolver;
  
  /**
   * <p>The catalog resolver handles the resolution of external
   * identifiers and URI references through XML catalogs. This
   * component supports XML catalogs defined by the
   * <a href="http://www.oasis-open.org/committees/entity/spec.html">
   * OASIS XML Catalogs Specification</a>. It encapsulates the
   * <a href="http://xml.apache.org/commons/">XML Commons</a> resolver.
   * An instance of this class may be registered on the parser
   * as a SAX entity resolver, as a DOM LSResourceResolver or
   * as an XNI entity resolver by setting the property
   * (http://apache.org/xml/properties/internal/entity-resolver).</p>
   * <p/>
   * <p>It is intended that this class may be used standalone to perform
   * catalog resolution outside of a parsing context. It may be shared
   * between several parsers and the application.</p>
   *
   * @author Michael Glavassevich, IBM
   * @version $Id: XMLCatalogResolver.java 699892 2008-09-28 21:08:27Z mrglavas $
   */
  public class XMLCatalogResolver
          implements XMLEntityResolver, EntityResolver2, LSResourceResolver {
  
      /**
       * Internal catalog manager for Apache catalogs.
       **/
      private CatalogManager fResolverCatalogManager = null;
  
      /**
       * Internal catalog structure.
       **/
      private Catalog fCatalog = null;
  
      /**
       * An array of catalog URIs.
       **/
      private String[] fCatalogsList = null;
  
      /**
       * Indicates whether the list of catalogs has
       * changed since it was processed.
       */
      private boolean fCatalogsChanged = true;
  
      /**
       * Application specified prefer public setting.
       **/
      private boolean fPreferPublic = true;
  
      /**
       * Indicates whether the application desires that
       * the parser or some other component performing catalog
       * resolution should use the literal system identifier
       * instead of the expanded system identifier.
       */
      private boolean fUseLiteralSystemId = true;
  
      /**
       * <p>Constructs a catalog resolver with a default configuration.</p>
       */
      public XMLCatalogResolver() {
          this(null, true);
      }
  
      /**
       * <p>Constructs a catalog resolver with the given
       * list of entry files.</p>
       *
       * @param catalogs an ordered array list of absolute URIs
       */
      public XMLCatalogResolver(String[] catalogs) {
          this(catalogs, true);
      }
  
      /**
       * <p>Constructs a catalog resolver with the given
       * list of entry files and the preference for whether
       * system or public matches are preferred.</p>
       *
       * @param catalogs     an ordered array list of absolute URIs
       * @param preferPublic the prefer public setting
       */
      public XMLCatalogResolver(String[] catalogs, boolean preferPublic) {
          init(catalogs, preferPublic);
      }
  
      /**
       * <p>Returns the initial list of catalog entry files.</p>
       *
       * @return the initial list of catalog entry files
       */
      public final synchronized String[] getCatalogList() {
          return (fCatalogsList != null)
                  ? fCatalogsList.clone() : null;
      }
  
      /**
       * <p>Sets the initial list of catalog entry files.
       * If there were any catalog mappings cached from
       * the previous list they will be replaced by catalog
       * mappings from the new list the next time the catalog
       * is queried.</p>
       *
       * @param catalogs an ordered array list of absolute URIs
       */
      public final synchronized void setCatalogList(String[] catalogs) {
          fCatalogsChanged = true;
          fCatalogsList = (catalogs != null)
                  ? catalogs.clone() : null;
      }
  
      /**
       * <p>Forces the cache of catalog mappings to be cleared.</p>
       */
      public final synchronized void clear() {
          fCatalog = null;
      }
  
      /**
       * <p>Returns the preference for whether system or public
       * matches are preferred. This is used in the absence
       * of any occurrence of the <code>prefer</code> attribute
       * on the <code>catalog</code> entry of a catalog. If this
       * property has not yet been explicitly set its value is
       * <code>true</code>.</p>
       *
       * @return the prefer public setting
       */
      public final boolean getPreferPublic() {
          return fPreferPublic;
      }
  
      /**
       * <p>Sets the preference for whether system or public
       * matches are preferred. This is used in the absence
       * of any occurrence of the <code>prefer</code> attribute
       * on the <code>catalog</code> entry of a catalog.</p>
       *
       * @param preferPublic the prefer public setting
       */
      public final void setPreferPublic(boolean preferPublic) {
          fPreferPublic = preferPublic;
          fResolverCatalogManager.setPreferPublic(preferPublic);
      }
  
      /**
       * <p>Returns the preference for whether the literal system
       * identifier should be used when resolving system
       * identifiers when both it and the expanded system
       * identifier are available. If this property has not yet
       * been explicitly set its value is <code>true</code>.</p>
       *
       * @return the preference for using literal system identifiers
       * for catalog resolution
       * @see #setUseLiteralSystemId
       */
      public final boolean getUseLiteralSystemId() {
          return fUseLiteralSystemId;
      }
  
      /**
       * <p>Sets the preference for whether the literal system
       * identifier should be used when resolving system
       * identifiers when both it and the expanded system
       * identifier are available.</p>
       * <p/>
       * <p>The literal system identifier is the URI as it was
       * provided before absolutization. It may be embedded within
       * an entity. It may be provided externally or it may be the
       * result of redirection. For example, redirection may
       * have come from the protocol level through HTTP or from
       * an application's entity resolver.</p>
       * <p/>
       * <p>The expanded system identifier is an absolute URI
       * which is the result of resolving the literal system
       * identifier against a base URI.</p>
       *
       * @param useLiteralSystemId the preference for using
       *                           literal system identifiers for catalog resolution
       */
      public final void setUseLiteralSystemId(boolean useLiteralSystemId) {
          fUseLiteralSystemId = useLiteralSystemId;
      }
  
      /**
       * <p>Resolves an external entity. If the entity cannot be
       * resolved, this method should return <code>null</code>. This
       * method returns an input source if an entry was found in the
       * catalog for the given external identifier. It should be
       * overridden if other behaviour is required.</p>
       *
       * @param publicId the public identifier, or <code>null</code> if none was supplied
       * @param systemId the system identifier
       * @throws SAXException any SAX exception, possibly wrapping another exception
       * @throws IOException  thrown if some i/o error occurs
       */
      @Override
      public InputSource resolveEntity(String publicId, String systemId)
              throws SAXException, IOException {
  
          String resolvedId = null;
          if (publicId != null && systemId != null) {
              resolvedId = resolvePublic(publicId, systemId);
          } else if (systemId != null) {
              resolvedId = resolveSystem(systemId);
          }
  
          if (resolvedId != null) {
              InputSource source = new InputSource(resolvedId);
              source.setPublicId(publicId);
              return source;
          }
          return null;
      }
  
      /**
       * <p>Resolves an external entity. If the entity cannot be
       * resolved, this method should return <code>null</code>. This
       * method returns an input source if an entry was found in the
       * catalog for the given external identifier. It should be
       * overridden if other behaviour is required.</p>
       *
       * @param name     the identifier of the external entity
       * @param publicId the public identifier, or <code>null</code> if none was supplied
       * @param baseURI  the URI with respect to which relative systemIDs are interpreted.
       * @param systemId the system identifier
       * @throws SAXException any SAX exception, possibly wrapping another exception
       * @throws IOException  thrown if some i/o error occurs
       */
      @Override
      public InputSource resolveEntity(String name, String publicId,
                                       String baseURI, String systemId) throws SAXException, IOException {
  
          String resolvedId = null;
  
          if (!getUseLiteralSystemId() && baseURI != null) {
              // Attempt to resolve the system identifier against the base URI.
              try {
                  URI uri = new URI(new URI(baseURI), systemId);
                  systemId = uri.toString();
              }
              // Ignore the exception. Fallback to the literal system identifier.
              catch (URI.MalformedURIException ex) {
              }
          }
  
          if (publicId != null && systemId != null) {
              resolvedId = resolvePublic(publicId, systemId);
          } else if (systemId != null) {
              resolvedId = resolveSystem(systemId);
          }
  
          if (resolvedId != null) {
              InputSource source = new InputSource(resolvedId);
              source.setPublicId(publicId);
              return source;
          }
          return null;
      }
  
      /**
       * <p>Locates an external subset for documents which do not explicitly
       * provide one. This method always returns <code>null</code>. It
       * should be overrided if other behaviour is required.</p>
       *
       * @param name    the identifier of the document root element
       * @param baseURI the document's base URI
       * @throws SAXException any SAX exception, possibly wrapping another exception
       * @throws IOException  thrown if some i/o error occurs
       */
      @Override
      public InputSource getExternalSubset(String name, String baseURI)
              throws SAXException, IOException {
          return null;
      }
  
      /**
       * <p>Resolves a resource using the catalog. This method interprets that
       * the namespace URI corresponds to uri entries in the catalog.
       * Where both a namespace and an external identifier exist, the namespace
       * takes precedence.</p>
       *
       * @param type         the type of the resource being resolved
       * @param namespaceURI the namespace of the resource being resolved,
       *                     or <code>null</code> if none was supplied
       * @param publicId     the public identifier of the resource being resolved,
       *                     or <code>null</code> if none was supplied
       * @param systemId     the system identifier of the resource being resolved,
       *                     or <code>null</code> if none was supplied
       * @param baseURI      the absolute base URI of the resource being parsed,
       *                     or <code>null</code> if there is no base URI
       */
      @Override
      public LSInput resolveResource(String type, String namespaceURI,
                                     String publicId, String systemId, String baseURI) {
  
          String resolvedId = null;
  
          try {
              // The namespace is useful for resolving namespace aware
              // grammars such as XML schema. Let it take precedence over
              // the external identifier if one exists.
              if (namespaceURI != null) {
                  resolvedId = resolveURI(namespaceURI);
              }
  
              if (!getUseLiteralSystemId() && baseURI != null) {
                  // Attempt to resolve the system identifier against the base URI.
                  try {
                      URI uri = new URI(new URI(baseURI), systemId);
                      systemId = uri.toString();
                  }
                  // Ignore the exception. Fallback to the literal system identifier.
                  catch (URI.MalformedURIException ex) {
                  }
              }
  
              // Resolve against an external identifier if one exists. This
              // is useful for resolving DTD external subsets and other 
              // external entities. For XML schemas if there was no namespace 
              // mapping we might be able to resolve a system identifier 
              // specified as a location hint.
              if (resolvedId == null) {
                  if (publicId != null && systemId != null) {
                      resolvedId = resolvePublic(publicId, systemId);
                  } else if (systemId != null) {
                      resolvedId = resolveSystem(systemId);
                  }
              }
          }
          // Ignore IOException. It cannot be thrown from this method.
          catch (IOException ex) {
          }
  
          if (resolvedId != null) {
              return new DOMInputImpl(publicId, resolvedId, baseURI);
          }
          return null;
      }
  
  
      /**
       * <p>Resolves an external entity. If the entity cannot be
       * resolved, this method should return <code>null</code>. This
       * method only calls <code>resolveIdentifier</code> and returns
       * an input source if an entry was found in the catalog. It
       * should be overridden if other behaviour is required.</p>
       *
       * @param resourceIdentifier location of the XML resource to resolve
       * @throws XNIException thrown on general error
       * @throws IOException  thrown if some i/o error occurs
       */
      @Override
      public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier)
              throws XNIException, IOException {
  
          String resolvedId = resolveIdentifier(resourceIdentifier);
          if (resolvedId != null) {
              return new XMLInputSource(resourceIdentifier.getPublicId(),
                      resolvedId,
                      resourceIdentifier.getBaseSystemId());
          }
          return null;
      }
  
      /**
       * <p>Resolves an identifier using the catalog. This method interprets that
       * the namespace of the identifier corresponds to uri entries in the catalog.
       * Where both a namespace and an external identifier exist, the namespace
       * takes precedence.</p>
       *
       * @param resourceIdentifier the identifier to resolve
       * @throws XNIException thrown on general error
       * @throws IOException  thrown if some i/o error occurs
       */
      public String resolveIdentifier(XMLResourceIdentifier resourceIdentifier)
              throws IOException, XNIException {
  
          String resolvedId = null;
  
          // The namespace is useful for resolving namespace aware
          // grammars such as XML schema. Let it take precedence over
          // the external identifier if one exists.
          String namespace = resourceIdentifier.getNamespace();
          if (namespace != null) {
              resolvedId = resolveURI(namespace);
          }
  
          // Resolve against an external identifier if one exists. This
          // is useful for resolving DTD external subsets and other 
          // external entities. For XML schemas if there was no namespace 
          // mapping we might be able to resolve a system identifier 
          // specified as a location hint.
          if (resolvedId == null) {
              String publicId = resourceIdentifier.getPublicId();
              String systemId = getUseLiteralSystemId()
                      ? resourceIdentifier.getLiteralSystemId()
                      : resourceIdentifier.getExpandedSystemId();
              if (publicId != null && systemId != null) {
                  resolvedId = resolvePublic(publicId, systemId);
              } else if (systemId != null) {
                  resolvedId = resolveSystem(systemId);
              }
          }
          return resolvedId;
      }
  
      /**
       * <p>Returns the URI mapping in the catalog for the given
       * external identifier or <code>null</code> if no mapping
       * exists. If the system identifier is an URN in the
       * <code>publicid</code> namespace it is converted into
       * a public identifier by URN "unwrapping" as specified
       * in the XML Catalogs specification.</p>
       *
       * @param systemId the system identifier to locate in the catalog
       * @return the mapped URI or <code>null</code> if no mapping
       * was found in the catalog
       * @throws IOException if an i/o error occurred while reading
       *                     the catalog
       */
      public final synchronized String resolveSystem(String systemId)
              throws IOException {
  
          if (fCatalogsChanged) {
              parseCatalogs();
              fCatalogsChanged = false;
          }
          return (fCatalog != null)
                  ? fCatalog.resolveSystem(systemId) : null;
      }
  
      /**
       * <p>Returns the URI mapping in the catalog for the given
       * external identifier or <code>null</code> if no mapping
       * exists. Public identifiers are normalized before
       * comparison.</p>
       *
       * @param publicId the public identifier to locate in the catalog
       * @param systemId the system identifier to locate in the catalog
       * @return the mapped URI or <code>null</code> if no mapping
       * was found in the catalog
       * @throws IOException if an i/o error occurred while reading
       *                     the catalog
       */
      public final synchronized String resolvePublic(String publicId, String systemId)
              throws IOException {
  
          if (fCatalogsChanged) {
              parseCatalogs();
              fCatalogsChanged = false;
          }
          return (fCatalog != null)
                  ? fCatalog.resolvePublic(publicId, systemId) : null;
      }
  
      /**
       * <p>Returns the URI mapping in the catalog for the given URI
       * reference or <code>null</code> if no mapping exists.
       * URI comparison is case sensitive. If the URI reference
       * is an URN in the <code>publicid</code> namespace
       * it is converted into a public identifier by URN "unwrapping"
       * as specified in the XML Catalogs specification and then
       * resolution is performed following the semantics of
       * external identifier resolution.</p>
       *
       * @param uri the URI to locate in the catalog
       * @return the mapped URI or <code>null</code> if no mapping
       * was found in the catalog
       * @throws IOException if an i/o error occurred while reading
       *                     the catalog
       */
      public final synchronized String resolveURI(String uri)
              throws IOException {
  
          if (fCatalogsChanged) {
              parseCatalogs();
              fCatalogsChanged = false;
          }
          return (fCatalog != null)
                  ? fCatalog.resolveURI(uri) : null;
      }
  
      /**
       * Initialization. Create a CatalogManager and set all
       * the properties upfront. This prevents JVM wide system properties
       * or a property file somewhere in the environment from affecting
       * the behaviour of this catalog resolver.
       */
      private void init(String[] catalogs, boolean preferPublic) {
          fCatalogsList = (catalogs != null) ? catalogs.clone() : null;
          fPreferPublic = preferPublic;
          fResolverCatalogManager = new CatalogManager();
          fResolverCatalogManager.setAllowOasisXMLCatalogPI(false);
          fResolverCatalogManager.setCatalogClassName("org.apache.xml.resolver.Catalog");
          fResolverCatalogManager.setCatalogFiles("");
          fResolverCatalogManager.setIgnoreMissingProperties(true);
          fResolverCatalogManager.setPreferPublic(fPreferPublic);
          fResolverCatalogManager.setRelativeCatalogs(false);
          fResolverCatalogManager.setUseStaticCatalog(false);
          fResolverCatalogManager.setVerbosity(0);
      }
  
      /**
       * Instruct the <code>Catalog</code> to parse each of the
       * catalogs in the list. Only the first catalog will actually be
       * parsed immediately. The others will be queued and read if
       * they are needed later.
       */
      private void parseCatalogs() throws IOException {
          if (fCatalogsList != null) {
              fCatalog = new Catalog(fResolverCatalogManager);
              attachReaderToCatalog(fCatalog);
              for (int i = 0; i < fCatalogsList.length; ++i) {
                  String catalog = fCatalogsList[i];
                  if (catalog != null && catalog.length() > 0) {
                      fCatalog.parseCatalog(catalog);
                  }
              }
          } else {
              fCatalog = null;
          }
      }
  
      /**
       * Attaches the reader to the catalog.
       */
      private void attachReaderToCatalog(Catalog catalog) {
  
          SAXParserFactory spf = new SAXParserFactoryImpl();
          spf.setNamespaceAware(true);
          spf.setValidating(false);
  
          SAXCatalogReader saxReader = new SAXCatalogReader(spf);
          saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName, "catalog",
                  "org.apache.xml.resolver.readers.OASISXMLCatalogReader");
          catalog.addReader("application/xml", saxReader);
      }
  }
  