/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.ws.axis2.jaxws.description.impl;

import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.i18n.Messages;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.commons.schema.resolver.URIResolver;
import org.xml.sax.InputSource;

/** This class is used to locate xml schemas that are imported by wsdl documents. */
public class URIResolverImpl implements URIResolver {

    private final String HTTP_PROTOCOL = "http";

    private final String HTTPS_PROTOCOL = "https";

    private final String FILE_PROTOCOL = "file";

    private final String JAR_PROTOCOL = "jar";

    private ClassLoader classLoader;

    private static final Log log = LogFactory.getLog(URIResolverImpl.class);

    public URIResolverImpl() {
    }

    public URIResolverImpl(final ClassLoader cl) {
        classLoader = cl;
    }

    public InputSource resolveEntity(final String namespace, final String schemaLocation,
                                     final String baseUri) {
        //TODO: Temporary, please change the following log.info to log.debug
        log.info("resolveEntity: ["+ namespace + "]["+ schemaLocation + "][ " + baseUri+ "]");

        InputStream is = null;
        URI pathURI = null;
        String pathURIStr = null;
        if(log.isDebugEnabled()) {
            log.debug("Import location: " + schemaLocation + " parent document: " +
                    baseUri);
        }
        if (baseUri != null) {
            try {
                // if the location is an absolute path, build a URL directly
                // from it
            	if(log.isDebugEnabled()){
            		log.debug("Base URI not null");
            	}
                if (isAbsolute(schemaLocation)) {
                    if(log.isDebugEnabled()) {
                        log.debug("Retrieving input stream for absolute schema location: "
                                + schemaLocation);
                    }
                    is = getInputStreamForURI(schemaLocation);
                }

                else {
                    if(log.isDebugEnabled()){
                        log.debug("schemaLocation not in absolute path");
                    }
                    try{
                        pathURI = new URI(baseUri);
                    }catch(URISyntaxException e){
                        // Got URISyntaxException, Creation of URI requires
                        // that we use special escape characters in path.
                        // The URI constructor below does this for us, so lets use that.
                        if(log.isDebugEnabled()){
                            log.debug("Got URISyntaxException. Exception Message = "+e.getMessage());
                            log.debug("Implementing alternate way to create URI");
                        }
                       pathURI = new URI(null, null, baseUri, null);
                     }
                    pathURIStr = schemaLocation;
                    // If this is absolute we need to resolve the path without the
                    // scheme information
                    if (pathURI.isAbsolute()) {
                        if(log.isDebugEnabled()) {
                            log.debug("Parent document is at absolute location: " +
                                    pathURI.toString());
                        }
                        URL url = new URL(baseUri);
                        if (url != null) {
                            URI tempURI;
                            try{
                                tempURI = new URI(url.getPath());
                            }catch(URISyntaxException e){
                                //Got URISyntaxException, Creation of URI requires
                                // that we use special escape characters in path.
                                // The URI constructor below does this for us, so lets use that.
                                if(log.isDebugEnabled()){
                                    log.debug("Got URISyntaxException. Exception Message = "+e.getMessage());
                                    log.debug("Implementing alternate way to create URI");
                                }
                                tempURI = new URI(null, null, url.getPath(), null);
                            }
                            URI resolvedURI = tempURI.resolve(schemaLocation);
                            // Add back the scheme to the resolved path
                            pathURIStr = constructPath(url, resolvedURI);
                            if(log.isDebugEnabled()) {
                                log.debug("Resolved this path to imported document: " +
                                        pathURIStr);
                            }
                        }
                    } else {
                        if(log.isDebugEnabled()) {
                            log.debug("Parent document is at relative location: " +
                                    pathURI.toString());
                        }
                        pathURI = pathURI.resolve(schemaLocation);
                        pathURIStr = pathURI.toString();
                        if(log.isDebugEnabled()) {
                            log.debug("Resolved this path to imported document: " +
                                    pathURIStr);
                        }
                    }
                    // If path is absolute, build URL directly from it
                    if (isAbsolute(pathURIStr)) {
                        is = getInputStreamForURI(pathURIStr);
                    }

                    // if the location is relative, we need to resolve the
                    // location using
                    // the baseURI, then use the loadStrategy to gain an input
                    // stream
                    // because the URI will still be relative to the module
                    else {
                        is = classLoader
                                .getResourceAsStream(pathURI.toString());
                    }
                }
            } catch (Exception e) {
                if(log.isDebugEnabled()) {
                	log.debug("Exception occured in resolveEntity, ignoring exception continuing processing "+e.getMessage());
                    log.debug(e);
                }
            }
        }
        if(is == null) {
            if(log.isDebugEnabled()) {
                log.debug("XSD input stream is null after resolving import for: " +
                        schemaLocation + " from parent document: " + baseUri);
            }
        }
        else {
            if(log.isDebugEnabled()) {
                log.debug("XSD input stream is not null after resolving import for: " +
                        schemaLocation + " from parent document: " + baseUri);
            }
        }

        InputSource returnInputSource = new InputSource(is);
        // We need to set the systemId.  XmlSchema will use this value to maintain a collection of
        // imported XSDs that have been read.  If this value is null, then circular XSDs will
        // cause infinite recursive reads.
        returnInputSource.setSystemId(pathURIStr != null ? pathURIStr : schemaLocation);
        //TODO: Temporary, please change the following log.info to log.debug
        log.info("returnInputSource :" + returnInputSource.getSystemId());
        return returnInputSource;
    }

    /**
     * Checks to see if the location given is an absolute (actual) or relative path.
     *
     * @param location
     * @return
     */
    private boolean isAbsolute(final String location) {
        boolean absolute = false;
        if (location.indexOf(":/") != -1) {
            absolute = true;
        } else if (location.indexOf(":\\") != -1) {
            absolute = true;
        } else if (location.indexOf("file:") != -1) {
            absolute = true;
        }
        return absolute;
    }

    /**
     * Gets input stream from the uri given. If we cannot find the stream, <code>null</code> is
     * returned.
     *
     * @param uri
     * @return
     */
    private InputStream getInputStreamForURI(final String uri) {
        URL streamURL = null;
        InputStream is = null;
        URI pathURI = null;

        try {
            streamURL = new URL(uri);
            is = streamURL.openStream();
        } catch (Throwable t) {
            //Exception handling not needed
        }

        if (is == null) {
            try {
                pathURI = new URI(uri);
                streamURL = pathURI.toURL();
                is = streamURL.openStream();
            } catch (Throwable t) {
                //Exception handling not needed
            }
        }

        if (is == null) {
            try {
                File file = new File(uri);
                streamURL = file.toURL();
                is = streamURL.openStream();
            } catch (Throwable t) {
                //Exception handling not needed
            }
        }
        return is;
    }

    private String constructPath(final URL baseURL, final URI resolvedURI) {
        String importLocation = null;
        URL url = null;
        try {
            // Allow for http or https
            if (baseURL.getProtocol() != null && (baseURL.getProtocol().equals(
                    HTTP_PROTOCOL) || baseURL.getProtocol().equals(HTTPS_PROTOCOL))) {
            	if(log.isDebugEnabled()){
            		log.debug("Constructing path with http/https protocol");
            	}
                url = new URL(baseURL.getProtocol(), baseURL.getHost(), baseURL.getPort(),
                              resolvedURI.toString());
                if (log.isDebugEnabled()) {
					log.debug("URL = " + url);
				}

            }
            // Check for file
            else if (baseURL.getProtocol() != null && baseURL.getProtocol().equals(FILE_PROTOCOL)) {
            	if(log.isDebugEnabled()){
            		log.debug("Constructing path with file protocol");
            	}
                url = new URL(baseURL.getProtocol(), baseURL.getHost(), resolvedURI.toString());
            }
            //Check for jar
            else if (baseURL.getProtocol() != null && baseURL.getProtocol().equals(JAR_PROTOCOL)) {
            	if(log.isDebugEnabled()){
            		log.debug("Constructing path with jar protocol");
            	}
            	url = new URL(baseURL.getProtocol(), baseURL.getHost(), resolvedURI.toString());
            }
            else{
            	if(log.isDebugEnabled()){
            		if(baseURL !=null){
            			log.debug("unknown protocol in url "+ baseURL.getProtocol());
            		}else{
            			log.debug("baseURL is NULL");
            		}
            	}
            }

        }
        catch (MalformedURLException e) {
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("schemaImportError",
                                                                               resolvedURI.toString(),
                                                                               baseURL.toString()),
                                                           e);
        }
        if (url == null) {
            throw ExceptionFactory.makeWebServiceException(Messages.getMessage("schemaImportError",
                                                                               resolvedURI.toString(),
                                                                               baseURL.toString()));
        }
        importLocation = url.toString();
        return importLocation;
    }

}
