/***********************************************************
 Copyright (C) 2004 VeriSign, Inc.

 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 (at your option) 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

 http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/

package com.verisign.epp.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.pool2.BaseObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.verisign.epp.exception.EPPException;
import com.verisign.epp.framework.EPPAssemblerException;
import com.verisign.epp.pool.transformer.EPPTransformerPool;

/**
 * {@code EPPXMLByteArray} is a utility class for reading and writing EPP
 * messages to/from byte arrays. DOM Document is converted to and from byte
 * arrays. An XML parser is required when reading from the stream. There is one
 * constructor that will create an XML parser per call to
 * {@code read(InputStream)} and one that will use a parser pool. Use of a
 * parser pool is recommended.
 * 
 * @author Srikanth Veeramachaneni
 * @version 1.0 Dec 04, 2006
 */
public class EPPXMLByteArray {

	/** Category for logging */
	private static Logger LOG = LoggerFactory.getLogger(EPPXMLByteArray.class);

	private static Logger PACKET_LOG = LoggerFactory.getLogger(EPPXMLByteArray.class.getName() + ".packet");

	/**
	 * XML parser pool to use
	 */
	private BaseObjectPool<DocumentBuilder> parserPool = null;

	/**
	 * XML transformer pool to use
	 */
	private BaseObjectPool<Transformer> transformerPool = null;

	/**
	 * Log the packet logging and receiver.
	 */
	private static EPPSendReceiveLogger sendReceiveLogger = EPPEnv.getSendReceiveLogger();

	/**
	 * Default constructor for {@code EPPXMLByteArray}. When using this
	 * constructor, a parser instance will be created on each call to
	 * {@code decode(byte[])} and a transformer instance will be created on each
	 * call to {@code encode()}. .
	 */
	public EPPXMLByteArray() {
	}

	/**
	 * Construct {@code EPPXMLByteArray} to use a parser pool and a default
	 * transformer pool. The {@code aParserPoolName} parameter has to be a pool
	 * of {@code EPPParserPool} subclasses. When using this constructor, a parser
	 * instance will be checked out and checked in as needed on each call to
	 * {@code decode(byte[])}. The {@link EPPTransformerPool} is used by default
	 * for the transformer pool when using {@code encode(Document)}.
	 * 
	 * @param aParserPool
	 *           Parser pool to use
	 */
	public EPPXMLByteArray(BaseObjectPool<? extends DocumentBuilder> aParserPool) {
		this.parserPool = (BaseObjectPool<DocumentBuilder>) aParserPool;
		this.transformerPool = EPPTransformerPool.getInstance().getPool();
	}

	/**
	 * Construct {@code EPPXMLByteArray} to use a parser pool and a transformer
	 * pool. When using this constructor, a parser instance will be checked out
	 * and checked in as needed on each call to {@code decode(byte[])} and a
	 * transformer instance will be checked out and checked in as needed on each
	 * call to {@code encode(Document)}.
	 * 
	 * @param aParserPool
	 *           Parser pool to use
	 * @param aTransformerPool
	 *           Transformer pool to use
	 */
	public EPPXMLByteArray(BaseObjectPool<? extends DocumentBuilder> aParserPool,
	      BaseObjectPool<? extends Transformer> aTransformerPool) {
		this.parserPool = (BaseObjectPool<DocumentBuilder>) aParserPool;
		this.transformerPool = (BaseObjectPool<Transformer>) aTransformerPool;
	}

	/**
	 * Decodes(parses) and validates the {@code aPacket} parameter and returns
	 * the associated DOM Document. The XML parser is either created per call or
	 * is retrieved from a parser pool. Use of a parser pool is recommended.
	 * 
	 * @param aPacket
	 *           The byte array containing the EPP packet.
	 * @return Parsed DOM Document of packet
	 * @exception EPPException
	 *               Error with received packet or end of stream. It is
	 *               recommended that the stream be closed.
	 * @exception EPPAssemblerException
	 *               Error parsing packet
	 * @exception IOException
	 *               Error reading packet from stream
	 */
	public Document decode(byte[] aPacket) throws EPPAssemblerException, EPPException, IOException {
		LOG.debug("decode(): enter");

		// Validate argument
		if (aPacket == null) {
			throw new EPPException("decode(): BAD ARGUMENT (aPacket)");
		}

		DocumentBuilder theBuilder = null;
		Document theDoc = null;

		try {

			// Parser pool specified?
			if (this.parserPool != null) {
				try {
					theBuilder = this.parserPool.borrowObject();
					theBuilder.setErrorHandler(new EPPXMLErrorHandler());
					LOG.debug("decode(): Parser " + theBuilder + " checked out from pool");
				}
				catch (Exception ex) {
					LOG.error("decode(): Exception borrowing parser: ", ex);
					throw new EPPAssemblerException(ex.getMessage(), EPPAssemblerException.XML);
				}
			}
			else {
				// Create new parser instance.
				theBuilder = new EPPSchemaCachingParser();
				theBuilder.setErrorHandler(new EPPXMLErrorHandler());
				LOG.debug("decode(): Created new EPPSchemaCachingParser");
			}

			try {
				// Parse/validate EPP Packet and create DOM document
				theDoc = theBuilder.parse(new ByteArrayInputStream(aPacket));
			}
			catch (SAXParseException ex) {
				LOG.debug("decode(): [SAXParseException]", ex);
				throw new EPPAssemblerException(ex.getMessage(), EPPAssemblerException.XML);
			}
			catch (SAXException ex) {
				LOG.debug("decode(): [SAXException]", ex);
				throw new EPPAssemblerException(ex.getMessage(), EPPAssemblerException.XML);
			}
		}
		finally {
			// Check in pool object
			if (this.parserPool != null) {
				try {
					this.parserPool.returnObject(theBuilder);
					LOG.debug("decode(): Parser " + theBuilder + " returned to pool");
				}
				catch (Exception ex) {
					LOG.error("decode(): Exception returning Parser " + theBuilder + " to pool: " + ex);
				}
			}
		}

		LOG.debug("decode(): exit");
		return theDoc;
	}

	/**
	 * Encodes(converts) a DOM Document to a {@code byte} array. The DOM Document
	 * will be serialized to XML and converted into a {@code byte} array.
	 * 
	 * @param aDoc
	 *           DOM Document to convert to {@code byte} array.
	 * @exception EPPException
	 *               Error writing to stream. It is recommended that the stream
	 *               be closed.
	 * @return Encoded XML as a {@code byte[]} from the DOM {@code Document}.
	 */
	public byte[] encode(Document aDoc) throws EPPException {
		LOG.debug("encode(): enter");

		if (aDoc == null) {
			LOG.debug("encode(): aDoc == null");
			throw new EPPException("encode(): BAD ARGUMENT (aDoc)");
		}

		// Serialize DOM Document to stream
		ByteArrayOutputStream theBuffer = new ByteArrayOutputStream();

		Transformer trans = null;

		try {
			if (this.transformerPool != null) {
				try {
					trans = (Transformer) this.transformerPool.borrowObject();
					LOG.debug("encode(): Transformer " + trans + " checked out from pool");
				}
				catch (Exception ex) {
					LOG.error("decode(): Exception borrowing transformer: ", ex);
					throw new EPPException("Exception borrowing transformer: " + ex.getMessage());
				}
			}
			else {
				TransformerFactory transFac = TransformerFactory.newInstance();
				trans = transFac.newTransformer();
				LOG.debug("encode(): Created new Transformer");
			}

			trans.transform(new DOMSource(aDoc.getDocumentElement()), new StreamResult(theBuffer));
			theBuffer.close();
		}
		catch (

		Exception ex) {
			LOG.debug("encode() : serialize() :" + ex.getMessage(), ex);
			throw new EPPException("encode: serialize() " + ex.getMessage());
		}
		finally {
			if (this.transformerPool != null) {
				try {
					this.transformerPool.returnObject(trans);
					LOG.debug("encode(): Transformer " + trans + " returned to pool");
				}
				catch (Exception ex) {
					LOG.error("decode(): Exception returning Transformer " + trans + " to pool: " + ex);
				}
			}
		}

		byte[] thePacket = theBuffer.toByteArray();
		LOG.debug("encode(): exit");
		return thePacket;
	}

} 
