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 org.apache.solr.common.util;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import org.apache.solr.common.ResourceLoader;
  
  import org.xml.sax.InputSource;
  import org.xml.sax.EntityResolver;
  import org.xml.sax.ext.EntityResolver2;
  import java.io.File;
  import java.io.IOException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import javax.xml.transform.Source;
  import javax.xml.transform.TransformerException;
  import javax.xml.transform.URIResolver;
  import javax.xml.transform.sax.SAXSource;
  import javax.xml.stream.XMLResolver;
  import javax.xml.stream.XMLStreamException;
  
  /**
   * This is a helper class to support resolving of XIncludes or other hrefs
   * inside XML files on top of a {@link ResourceLoader}. Just plug this class
   * on top of a {@link ResourceLoader} and pass it as {@link EntityResolver} to SAX parsers
   * or via wrapper methods as {@link URIResolver} to XSL transformers or {@link XMLResolver} to STAX parsers.
   * The resolver handles special SystemIds with an URI scheme of {@code solrres:} that point
   * to resources. To produce such systemIds when you initially call the parser, use
   * {@link #createSystemIdFromResourceName} which produces a SystemId that can
   * be included along the InputStream coming from {@link ResourceLoader#openResource}.
   * <p>In general create the {@link InputSource} to be passed to the parser like:</p>
   * <pre class="prettyprint">
   *  InputSource is = new InputSource(loader.openSchema(name));
   *  is.setSystemId(SystemIdResolver.createSystemIdFromResourceName(name));
   *  final DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
   *  db.setEntityResolver(new SystemIdResolver(loader));
   *  Document doc = db.parse(is);
   * </pre>
   */
  public final class SystemIdResolver implements EntityResolver, EntityResolver2 {
    private static final Logger log = LoggerFactory.getLogger(SystemIdResolver.class);
  
    public static final String RESOURCE_LOADER_URI_SCHEME = "solrres";
    public static final String RESOURCE_LOADER_AUTHORITY_ABSOLUTE = "@";
  
    private final ResourceLoader loader;
  
    public SystemIdResolver(ResourceLoader loader) {
      this.loader = loader;
    }
    
    public EntityResolver asEntityResolver() {
      return this;
    }
    
    public URIResolver asURIResolver() {
      return new URIResolver() {
        public Source resolve(String href, String base) throws TransformerException {
          try {
            final InputSource src = SystemIdResolver.this.resolveEntity(null, null, base, href);
            return (src == null) ? null : new SAXSource(src);
          } catch (IOException ioe) {
            throw new TransformerException("Cannot resolve entity", ioe);
          }
        }
      };
    }
    
    public XMLResolver asXMLResolver() {
      return new XMLResolver() {
        public Object resolveEntity(String publicId, String systemId, String baseURI, String namespace) throws XMLStreamException {
          try {
            final InputSource src = SystemIdResolver.this.resolveEntity(null, publicId, baseURI, systemId);
            return (src == null) ? null : src.getByteStream();
          } catch (IOException ioe) {
            throw new XMLStreamException("Cannot resolve entity", ioe);
          }
        }
      };
    }
    
    URI resolveRelativeURI(String baseURI, String systemId) throws IOException,URISyntaxException {
      URI uri;
      
      // special case for backwards compatibility: if relative systemId starts with "/" (we convert that to an absolute solrres:-URI)
      if (systemId.startsWith("/")) {
        uri = new URI(RESOURCE_LOADER_URI_SCHEME, RESOURCE_LOADER_AUTHORITY_ABSOLUTE, "/", null, null).resolve(systemId);
      } else {
        // simply parse as URI
        uri = new URI(systemId);
      }
      
      // do relative resolving
      if (baseURI != null ) {
        uri = new URI(baseURI).resolve(uri);
      }
      
      return uri;
    }
    
    // *** EntityResolver(2) methods:
    
    public InputSource getExternalSubset(String name, String baseURI) {
      return null;
    }
    
    public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws IOException {
      if (systemId == null)
        return null;
      try {
        final URI uri = resolveRelativeURI(baseURI, systemId);
        
        // check schema and resolve with ResourceLoader
        if (RESOURCE_LOADER_URI_SCHEME.equals(uri.getScheme())) {
          String path = uri.getPath(), authority = uri.getAuthority();
          if (!RESOURCE_LOADER_AUTHORITY_ABSOLUTE.equals(authority)) {
            path = path.substring(1);
          }
          try {
            final InputSource is = new InputSource(loader.openResource(path));
            is.setSystemId(uri.toASCIIString());
            is.setPublicId(publicId);
            return is;
          } catch (RuntimeException re) {
            // unfortunately XInclude fallback only works with IOException, but openResource() never throws that one
            throw (IOException) (new IOException(re.getMessage()).initCause(re));
          }
        } else {
          // resolve all other URIs using the standard resolver
          return null;
        }
      } catch (URISyntaxException use) {
        log.warn("An URI systax problem occurred during resolving SystemId, falling back to default resolver", use);
        return null;
      }
    }
  
    public InputSource resolveEntity(String publicId, String systemId) throws IOException {
      return resolveEntity(null, publicId, null, systemId);
    }
    
    public static String createSystemIdFromResourceName(String name) {
      name = name.replace(File.separatorChar, '/');
      final String authority;
      if (name.startsWith("/")) {
        // a hack to preserve absolute filenames and keep them absolute after resolving, we set the URI's authority to "@" on absolute filenames:
        authority = RESOURCE_LOADER_AUTHORITY_ABSOLUTE;
      } else {
        authority = null;
        name = "/" + name;
      }
      try {
        return new URI(RESOURCE_LOADER_URI_SCHEME, authority, name, null, null).toASCIIString();
      } catch (URISyntaxException use) {
        throw new IllegalArgumentException("Invalid syntax of Solr Resource URI", use);
      }
    }
  
  }
  