/*******************************************************************************
 * Copyright (c) 1998, 2012 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/
package org.eclipse.persistence.sdo.helper;

import java.util.ArrayList;
import java.util.List;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.persistence.exceptions.SDOException;
import org.xml.sax.InputSource;

/**
 * <p><b>Purpose</b>: Allow the contained schema resolver to resolve a schema based on a given namespace and schema location, and
 * return either the resolved schema source or null, depending on whether the schema had been processed previously. 
 * <p><b>Responsibilities</b>:<ul>
 * <li> Allow the contained schema resolver to return the referenced Schema given the source schema and namespace and 
 * schemaLocation values from an import or include
 * <li> Keep track of previously processed schemas to avoid multiple processing and infinite looping
 * </ul>
 *
 * @see org.eclipse.persistence.sdo.helper.SchemaResolver
 * @see org.eclipse.persistence.sdo.helper.DefaultSchemaResolver
 */
public class SchemaResolverWrapper {
    private SchemaResolver schemaResolver;
    private List<String> systemIdList;

    /**
     * This constructor sets schemaResolver to the given value.
     * 
     * @param resolver the SchemaResolver implementation that will be used to resolve
     * imports/includes from a give source schema.
     */
    public SchemaResolverWrapper(SchemaResolver resolver) {
        schemaResolver = resolver;
        systemIdList = new ArrayList<String>();
    }

    /**
     * Resolve the Source if only a system ID is specified.
     */
    public Source resolveSchema(Source sourceXSD) {
        Source resolvedSchemaSource = null;
        if(sourceXSD instanceof StreamSource) {
            StreamSource streamSource = (StreamSource) sourceXSD;
            if(null == streamSource.getInputStream() && null == streamSource.getReader()) {
                resolvedSchemaSource = resolveSchema(streamSource.getPublicId(), streamSource.getSystemId());
            }
        } else if(sourceXSD instanceof SAXSource) {
            SAXSource saxSource = (SAXSource) sourceXSD;
            InputSource inputSource = saxSource.getInputSource(); 
            if(null == inputSource) {
               resolvedSchemaSource = resolveSchema(saxSource.getSystemId());
            } else if(null == inputSource.getByteStream() && null == inputSource.getCharacterStream()){
                resolvedSchemaSource = resolveSchema(inputSource.getPublicId(), inputSource.getSystemId());
            }
        } else if(sourceXSD instanceof DOMSource) {
            DOMSource domSource = (DOMSource) sourceXSD;
            if(null == domSource.getNode()) {
                resolvedSchemaSource = resolveSchema(domSource.getSystemId());
            }
        } else if(null != sourceXSD.getSystemId()) {
            resolvedSchemaSource = resolveSchema(sourceXSD.getSystemId());
        }
        if(resolvedSchemaSource != null) {
            return resolvedSchemaSource;
        }
        return sourceXSD;
    }

    
    /**
     * Allow the SchemaResolver implementation to attempt to return the referenced Schema based on 
     * given source schema, namespace and schemaLocation values from an import or include.  If the
     * resolver fails, this method will attempt to resolve the schema 
     * 
     * @param sourceXSD The Source object of the source schema
     * @param namespace The namespace portion of the import/include
     * @param schemaLocation The schemaLocation portion of the import/include
     * @return Source for the referenced Schema or null if processing the referenced
     * schema should be skipped
     */
    public Source resolveSchema(Source sourceXSD, String namespace, String schemaLocation) {
        // Add sourceXSD's SystemId to the processed schema list to avoid re-processing
        addSchemaToList(sourceXSD.getSystemId());

        Source schemaSource = schemaResolver.resolveSchema(sourceXSD, namespace, schemaLocation);
        if (schemaSource != null) {
            String sysId = schemaSource.getSystemId();
            if (shouldProcessSchema(sysId)) {
                return schemaSource;
            }
        }
        return null;
    }

    public Source resolveSchema(String systemId) {
        return resolveSchema(null, systemId);
    }

    public Source resolveSchema(String publicId, String systemId) {
        if(!addSchemaToList(systemId)) {
            return null;
        }
        try {
            InputSource inputSource = schemaResolver.resolveEntity(publicId, systemId);
            if(inputSource != null) {
                return new SAXSource(inputSource);
            }
        } catch(Exception ex) {
            throw SDOException.errorResolvingSchema(ex);
        } 
        return null;
    }

    /**
     * Indicates if the schema represented by the given systemId (schema URL string) should be processed.
     * If systemId exists in the list, it should not be processed; otherwise, it will be added to the 
     * list and true returned to indicate processing is required. 
     *  
     * @param systemId a String that conforms to the URI syntax
     * @return true if systemId is null or does not exist in the list of previously processed schemas, 
     * false otherwise 
     */
    private boolean shouldProcessSchema(String systemId) {
        if (systemId == null) {
            return true;
        }
        return addSchemaToList(systemId);
    }

    /**
     * Add the given SystemId to the list of processed schemas, if it isn't already in the list.  
     * 
     * @param systemId a String that conforms to the URI syntax
     * @return true if systemId was added to the list (hence the associated schema should be processed), 
     * false if the id was not added to the list or systemId is null.
     */
    private boolean addSchemaToList(String systemId) {
        if (systemId == null || systemIdList.contains(systemId)) {
            return false;
        }
        systemIdList.add(systemId);
        return true;
    }

    /**
     * Return the SchemaResolver for this wrapper instance.
     * 
     * @return
     */
    public SchemaResolver getSchemaResolver() {
        return schemaResolver;
    }
}
