/*******************************************************************************

* Copyright (c) 2006 IONA Technologies PLC

* All rights reserved. This program and the accompanying materials

* are made available under the terms of the Eclipse Public License v1.0

* which accompanies this distribution, and is available at

* http://www.eclipse.org/legal/epl-v10.html

* 

* Contributors:

*     IONA Technologies PLC - initial API and implementation

*******************************************************************************/
package org.eclipse.stp.sc.common.validator;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.URL;
import java.util.Hashtable;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.eclipse.core.runtime.Platform;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Valide xml input according to schema.
 * return the error message
 * @author jma
 *
 */
public class XMLSchemaValidator {
	
	static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";

	static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
	
	static final String SCHEMA_FILE_EXD = ".xsd";
	
	/**
	 * Todo:we define the schema location to plugin/etc/schema dir for now.
	 * will change to load from perference page later.
	 */
	static final String SCHEMA_LOCATION = "etc/validator/";
	
	private static final LoggingProxy LOG = LoggingProxy.getlogger(XMLSchemaValidator.class);
	
	Hashtable<String, Schema> schemaTable;
	MultiErrorHandler errorHandler;
		
	
	public XMLSchemaValidator() {
		schemaTable = new Hashtable<String, Schema>();
		errorHandler = new MultiErrorHandler();
	}
	
	public boolean validateXML(Document doc) {
		try {
			Element root = (Element) doc.getFirstChild();
			String namespace = root.getAttribute(AnnXMLTreeBuilder.ATTR_XMLNS
                                                 + ":"
                                                 + AnnXMLTreeBuilder.ATTR_PREFIX);
			Schema schema = getSchemaByNamespace(namespace);
			if (schema == null) {
				LOG.error("can't find schema for xml. return valid");
				return true;
			}
		    return validateXML(doc, schema);
		
        } catch (Exception e) {
			LOG.error("error during valid xml.", e);
			return true;
		}
	}
	
	private Schema getSchemaByNamespace(String namespace) throws Exception {
		Schema schema = schemaTable.get(namespace);
		if (schema != null) {
			return schema;
		}
		
		//convert namespace to fileName;
		String fileName = getFileNameFromNS(namespace);
		LOG.debug("schema file try to load:" + fileName);
		URL baseURL = Platform.getBundle("org.eclipse.stp.sc.jaxws").getEntry("/");
		URL fileURL = new URL(baseURL, fileName);

		try {
			InputStream is = fileURL.openStream();
			SchemaFactory schemaFactory = SchemaFactory.newInstance(W3C_XML_SCHEMA);
			// schemaFactory.
			schema = schemaFactory.newSchema(new StreamSource(is));
			LOG.debug("schema loaded for file:" + fileName);
			schemaTable.put(namespace, schema);

		} catch (FileNotFoundException fnfe) {
			LOG.debug("can find schema file for annotation:" + fnfe.toString() );
			return null;
			
		}

		return schema;
	}
	
	private String getFileNameFromNS(String namespace) {
		LOG.debug("namespace:" + namespace);
		String prefix = "http://";
		String seperator = "/";
		String ns = namespace.substring(namespace.indexOf(prefix) + prefix.length(),
                                        namespace.length());
		StringTokenizer st = new StringTokenizer(ns, seperator);
		String fileName = ".xsd";
	    while (st.hasMoreTokens()) {
	    	if (!fileName.startsWith(".")) {
	    		fileName = "_" + fileName;
	    	}
	    	fileName = st.nextToken() + fileName;
	    }
	    LOG.debug("namespace:" + namespace + "|| schema fileName:" + fileName);
	    fileName = SCHEMA_LOCATION + fileName;
		return fileName;
	}
	
    
    public boolean validateXML(InputStream xmlStream, InputStream schemaStream) throws Exception {
    	return validateXML(xmlStream, new StreamSource(schemaStream));
    }
    
    public boolean validateXML(Document inputXmlDoc, Schema schema) throws Exception {
		Validator validator = schema.newValidator();
		
		errorHandler.reset();
		validator.setErrorHandler(errorHandler);

		DOMSource source = new DOMSource(inputXmlDoc);
		DOMResult result = new DOMResult();
				
        //Validate against schema
		validator.validate(source, result);
				
		// Error checking
		if ( errorHandler.getErrorCount() > 0) {
		    // errors occured during the parsing
		    LOG.debug("XML file is not valid:" + errorHandler.getErrorMsg());
		    return false;
		}  else {
			LOG.debug("it is valid xml");
			return true;
		}
    }
	
	public boolean validateXML(InputStream inputXmlFile, Source schemaSource) throws Exception {
		DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		domFactory.setNamespaceAware(true);
		DocumentBuilder builder = domFactory.newDocumentBuilder();
		Document doc = builder.parse(inputXmlFile);
		SchemaFactory schemaFactory = SchemaFactory.newInstance(W3C_XML_SCHEMA);
		Schema schema = schemaFactory.newSchema(schemaSource);
	    return validateXML(doc, schema);
	}
	
	public String getErrorMessage() {
		return errorHandler.getErrorMsg();
	}
}