/**
 * WPCLValidator.java
 * Created Jan 23, 2007
 * Copyright (C) Tandberg Television 2007
 */
package com.tandbergtv.workflow.message.util;

import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.tandbergtv.workflow.message.WorkflowMessage;

/**
 * Validates a WPCL message against a schema
 * 
 * @author Sahil Verma
 */
public class WPCLValidator {

	private static final String XSD_PATH = "com/tandbergtv/workflow/message/wpcl.xsd";
	
	/**
	 * Creates a WPCLValidator
	 */
	private WPCLValidator() {
		super();
	}
	
	/**
	 * Creates and returns a new instance of the validator
	 * 
	 * @return The validator instance
	 */
	public static WPCLValidator newInstance() {
		return new WPCLValidator();
	}
	
	/**
	 * Validates the specified document against the latest well-known WPCL schema
	 * 
	 * @param document the workflow message DOM tree
	 * @throws WPCLValidationException if the schema validation fails
	 */
	public void validate(Document document) throws WPCLValidationException {
		try {
			getSchema().newValidator().validate(new DOMSource(document));
		} catch (Exception e) {
			throw new WPCLValidationException("WPCL validation failed", e);
		}
	}

	/**
	 * Validates the specified message against the latest well-known WPCL schema
	 * 
	 * @param message
	 * @throws WPCLValidationException 
	 */
	public void validate(WorkflowMessage message) throws WPCLValidationException {
		/* I hope this makes sense to someone, because I DON'T FUCKING GET IT! */
		Document document = null;
		
		try {
			Document doc = Marshaller.newMarshaller().marshal(message);
			String xml = convertToString(doc);
			document = loadXml(xml);
		} catch (MarshalException e) {
			throw new WPCLValidationException("Failed to marshal the message to WPCL", e);
		} catch (Exception e) {
			throw new WPCLValidationException("Failed to load the message into XML", e);
		}
		
		validate(document);
	}
	
	/**
	 * Returns the latest well-known WPCL schema
	 * 
	 * @return The schema
	 * @throws Exception
	 */
	public Schema getSchema() throws Exception {
		InputStream stream = WPCLValidator.class.getClassLoader().getResourceAsStream(XSD_PATH);
		return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
				new StreamSource(stream));
	}
	
	private String convertToString(Node node) throws Exception {
		StringWriter w = new StringWriter();
		Transformer transformer = TransformerFactory.newInstance().newTransformer();
		Document document = (node instanceof Document) ? Document.class.cast(node) : node
				.getOwnerDocument();

		if (document.getXmlEncoding() != null)
			transformer.setOutputProperty(OutputKeys.ENCODING, document.getXmlEncoding());

		transformer.transform(new DOMSource(node), new StreamResult(w));

		return w.getBuffer().toString();
	}
	
	private Document loadXml(String xml) throws Exception {
		return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
			new InputSource(new StringReader(xml)));
	}
}
