/*****************************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ****************************************************************************************/
package gov.va.med.esr.messaging.xml;

// Java Classes
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.Validate;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.springframework.core.io.ClassPathResource;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import org.apache.commons.io.IOUtils;

import gov.va.med.fw.util.StopWatchLogger;
import gov.va.med.fw.util.builder.AbstractBuilder;
import gov.va.med.fw.util.builder.Builder;
import gov.va.med.fw.util.builder.BuilderException;
import gov.va.med.fw.util.builder.Validator;
import gov.va.med.fw.util.builder.ValidatorException;

/**
 * XmlFormatter created on Dec 7, 2006
 * @author DNS   LEV
 */
public class XmlFormatter extends AbstractBuilder implements Builder, Validator {
   
   /**
    * An instance of serialVersionUID
    */
   private static final long serialVersionUID = -8695546825091545853L;

   /**
    * An instance of PARAMETER_SCHEMA_LOCATION 
    */
   private static final String PARAMETER_SCHEMA_LOCATION = "{urn:esr-vha-gov:internal:xslt:hl7-messages}schemaLocation";

   /**
    * An instance of factory 
    */
   private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
   
   /**
    * An instance of ENTITY_RESOLVER 
    */
   private static final ClassPathEntityResolver ENTITY_RESOLVER = new ClassPathEntityResolver();
   
   /**
    * An instance of URI_RESOLVER 
    */
   private static final ClassPathURIResolver URI_RESOLVER = new ClassPathURIResolver();
   
   static {
      TRANSFORMER_FACTORY.setURIResolver( URI_RESOLVER );
   }
   
   private Templates templates = null;
   
   /**
    * An instance of schemaLocation 
    */
   private String schemaLocation;

   /**
    * An instance of xsltLocation 
    */
   private String xsltLocation;

   /**
    * Comment for <code>errorHandler</code>
    */
   private ErrorHandler errorHandler = null;

   /**
    * Comment for <code>schemaLanguage</code>
    */
   private String schemaLanguage = null;

   /**
    * An instance of xmlSchema 
    */
   private String xmlSchema = null;

   /**
    * An instance of preLoadTransformer 
    */
   private boolean preLoadTransformer = false;
   
   /**
    * A default constructor  
    */
   public XmlFormatter() {
      super();
   }

   /**
    * @return Returns the schemaLocation.
    */
   public String getSchemaLocation() {
      return this.schemaLocation;
   }

   /**
    * @param schemaLocation the schemaLocation to set.
    */
   public void setSchemaLocation(String schemaLocation) {
      this.schemaLocation = schemaLocation;
   }

   /**
    * @return Returns the xsltLocation.
    */
   public String getXsltLocation() {
      return this.xsltLocation;
   }

   /**
    * @param xsltLocation the xsltLocation to set.
    */
   public void setXsltLocation(String xsltLocation) {
      this.xsltLocation = xsltLocation;
   }

   /**
    * Returns an instance of errorHandler
    * @return ErrorHandler errorHandler.
    */
   public ErrorHandler getErrorHandler() {
      return errorHandler;
   }

   /**
    * Sets the errorHandler of type ErrorHandler
    * @param errorHandler The errorHandler to set.
    */
   public void setErrorHandler(ErrorHandler errorHandler) {
      this.errorHandler = errorHandler;
   }

   /**
    * Returns an instance of schemaLanguage
    * @return String schemaLanguage.
    */
   public String getSchemaLanguage() {
      return schemaLanguage;
   }

   /**
    * Sets the schemaLanguage of type String
    * @param schemaLanguage The schemaLanguage to set.
    */
   public void setSchemaLanguage(String schemaLanguage) {
      this.schemaLanguage = schemaLanguage;
   }

   /**
    * Returns an instance of xmlSchema
    * @return String xmlSchema.
    */
   public String getXmlSchema() {
      return xmlSchema;
   }

   /**
    * Sets the xmlSchema of type String
    * @param xmlSchema The xmlSchema to set.
    */
   public void setXmlSchema(String xmlSchema) {
      this.xmlSchema = xmlSchema;
   }

   /**
    * Returns an instance of preLoadTransformer
    * @return boolean preLoadTransformer.
    */
   public boolean isPreLoadTransformer() {
      return preLoadTransformer;
   }

   /**
    * Sets the preLoadTransformer of type boolean
    * @param preLoadTransformer The preLoadTransformer to set.
    */
   public void setPreLoadTransformer(boolean preLoadTransformer) {
      this.preLoadTransformer = preLoadTransformer;
   }

   /**
    * @see gov.va.med.fw.service.AbstractComponent#afterPropertiesSet()
    */
   public void afterPropertiesSet() throws Exception {
      super.afterPropertiesSet();
      if( isPreLoadTransformer() ) {
         this.templates = this.getTemplates();
      }
   }

   /**
    * @param input
    * @return
    * @throws BuilderException
    */
   public String build(String input) throws BuilderException {

      Validate.notNull(input, "input must be a non-null string");

      String transformedString = null;
      StopWatchLogger watch = null;
      if( logger.isDebugEnabled() ) {
         watch = new StopWatchLogger( ClassUtils.getShortClassName( getClass() ) + ".format" );
         if (watch != null) {
        	 watch.start();
         }
      }
      try {
    	  // CCR 11547 sync all XML operations
    	  // Building a XML DOM object 
    	  DocumentFactory factory = DocumentFactory.getInstance();
    	  synchronized(factory) {
    		  Document document = factory.createDocument();
    		  Namespace namespace = new Namespace("", "Message");
    		  Element root = factory.createElement(new QName("message", namespace));
    		  root.setText(input);
    		  document.setRootElement(root);

    		  // Transform a HL7 new-line delimited string to a XML string
    		  final Transformer transformer = this.getTemplates().newTransformer();
    		  transformer.setParameter( XmlFormatter.PARAMETER_SCHEMA_LOCATION, schemaLocation );

    		  final StringWriter writer = new StringWriter();
    		  transformer.transform( new StreamSource( new StringReader( document.asXML() ) ),
    				  new StreamResult(writer) );
    		  transformedString = writer.toString();
    	  }
    	  validate( (transformedString) );
      }     
      catch( TransformerException e ) {
         throw new BuilderException( "Failed to transform the message", e);
      }
      catch( ValidatorException ve ) {
// INC000001026716 - ESR 4.0_CodeCR3459
// copy message to data property of BuilderException
    	  BuilderException be = new BuilderException( "Failed to validate the xml document", ve );
    	  be.setData(transformedString);
          throw be;
      }
      
      if( logger.isDebugEnabled() && watch != null) {
         watch.stopAndLog( "Total time to format " + this.getBeanName() );
      }
      return transformedString;
   }

   public void validate( Object input ) throws ValidatorException {
      
      // Validate the xml string
      if( input instanceof String ) {
    	  try {
    		  XMLReader reader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
    		  // CCR 11547 - have to lock parsers
    		  synchronized(reader) {
    			  reader.setEntityResolver( ENTITY_RESOLVER );
    			  reader.setFeature("http://xml.org/sax/features/namespaces", true);
    			  reader.setFeature("http://xml.org/sax/features/validation", true);
    			  reader.setFeature("http://apache.org/xml/features/validation/schema", true);
    			  reader.setProperty(this.getSchemaLanguage(), this.getXmlSchema());
    			  reader.setErrorHandler(this.errorHandler);
    			  reader.parse( new InputSource( new StringReader( (String)input ) ) );
    		  }
    	  }
         catch( SAXException e ) {
            throw new ValidatorException( "Failed to validate the xml document", e );
         }
         catch( IOException e ) {
            throw new ValidatorException( "Failed to load the schema", e );
         }
      }
   }
   
   private Templates getTemplates() throws TransformerConfigurationException {
      
      if( templates == null ) {
    	 InputStream istream = null;
         synchronized( this ) {
            try {
               ClassPathResource xslt = new ClassPathResource( getXsltLocation() );
               istream = xslt.getInputStream();
               this.templates = TRANSFORMER_FACTORY.newTemplates( new StreamSource( new BufferedInputStream(istream) ) );
            }
            catch( IOException e ) {
               throw new TransformerConfigurationException( "Failed to get a template", e );
            } finally {
            	/* Reverted Fortify resource leak finding change.
                   Input stream need to be open throughout the WLS server's life cycle. 

            	if (istream != null) {
            		IOUtils.closeQuietly(istream);
            	}
            	*/
            } 
         }
      }
      return this.templates;
   }
}