/***********************************************************
 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.framework;

// PoolMan Imports
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

import com.verisign.epp.codec.gen.EPPCodec;
import com.verisign.epp.codec.gen.EPPComponentNotFoundException;
import com.verisign.epp.codec.gen.EPPDecodeException;
import com.verisign.epp.codec.gen.EPPEncodeException;
import com.verisign.epp.codec.gen.EPPMessage;
import com.verisign.epp.exception.EPPException;
import com.verisign.epp.pool.parser.EPPSchemaCachingParserPool;
import com.verisign.epp.pool.transformer.EPPTransformerPool;
import com.verisign.epp.serverstub.SessionData;
import com.verisign.epp.util.EPPEnv;
import com.verisign.epp.util.EPPSendReceiveLogger;
import com.verisign.epp.util.EPPXMLStream;

/**
 * The {@code EPPXMLAssembler} class provides an implementation of
 * {@link EPPAssembler} that can assemble/disassemble {@code EPPMessage}s and
 * {@code EPPEventResponse}s from java Input and Output streams that contain
 * streamed XML.
 * @see EPPAssembler
 */
public class EPPXMLAssembler implements EPPAssembler {
  /** Category for logging */
  private static Logger cat = LoggerFactory.getLogger(EPPXMLAssembler.class);

  /** Has the XML parser been initialized? */
  private static boolean poolsInitialized = false;

  /**
   * An EPPCodec is delegated to to do the real work.
   * {@code EPPXMLAssemler} just wraps it to provide the EPPAssembler
   * interface.
   */
  private EPPCodec codec;

  /**
   * Log the packet logging and receiver.
   */
  private EPPSendReceiveLogger sendReceiveLogger;

  /**
   * Construct and instance of an {@code EPPXMLAssembler}
   */
  public EPPXMLAssembler() {
    codec = EPPCodec.getInstance();

    initXmlPools();

    this.sendReceiveLogger = EPPEnv.getSendReceiveLogger();
  }

  /**
   * Takes an {@code  EPPEventResponse } and serializes it to an
   * {@code OutputStream } in XML Format.
   * 
   * @param aResponse
   *           The response that will be serialized
   * @param aOutputStream
   *           The OutputStream that the response will be serialized to.
   * @param aData
   *           Session data
   * 
   * @exception EPPAssemblerException
   *               Error serializing the {@code EPPEventResponse}
   */
  public void toStream(EPPEventResponse aResponse, OutputStream aOutputStream, Object aData)
        throws EPPAssemblerException {
    cat.debug("toStream(EPPEventResponse, OutputStream): Enter");

    try {
      /**
       * First, get the message and convert it to a DOM Document using the
       * codec
       */
      EPPMessage response = aResponse.getResponse();
      Document domDocument = codec.encode(response);

      /** Now, serialize the DOM Document through the output stream */
      EPPXMLStream xmlStream = new EPPXMLStream(EPPSchemaCachingParserPool.getInstance().getPool(),
            EPPTransformerPool.getInstance().getPool());
      xmlStream.write(domDocument, aOutputStream, response);      
    }
    catch (EPPEncodeException e) {
      cat.error("toStream(EPPEventResponse, OutputStream)", e);
      throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.MISSINGPARAMETER);
    }
    catch (EPPException e) {
      cat.error("toStream(EPPEventResponse, OutputStream)", e);
      throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.FATAL);
    }

    cat.debug("toStream(EPPEventResponse, OutputStream): Return");
  }

  /**
   * Takes an {@code InputStream} and reads XML from it to create an
   * {@code EPPEvent}
   * 
   * @param aStream
   *           The InputStream to read data from.
   * @param aData
   *           Session data
   * 
   * @return EPPEvent The {@code  EPPEvent} that is created from the
   *         InputStream
   * 
   * @exception EPPAssemblerException
   *               Error creating the {@code EPPEvent}
   */
  public EPPEvent toEvent(InputStream aStream, Object aData) throws EPPAssemblerException {
    cat.debug("toEvent(InputStream): Enter");

    EPPMessage message = null;

    /**
     * First, take an XML input stream and convert it to a DOM Document
     */
    try {
      /** Declare an instance of the EPPXMLStream class */
      EPPXMLStream xmlStream = new EPPXMLStream(EPPSchemaCachingParserPool.getInstance().getPool(),
            EPPTransformerPool.getInstance().getPool());

      /**
       * Take the DOM Document and convert it to an EPPMessage using the
       * EPPCodec
       */
      byte[] thePacket = xmlStream.readPacket(aStream);
      Document domDocument = xmlStream.decodePacket(thePacket);
      message = codec.decode(domDocument);

      this.sendReceiveLogger.logReceive(thePacket, message);

      // Store the original XML packet in the session data for future use.
      if ((aData != null) && (aData instanceof SessionData)) {
        SessionData sessionData = (SessionData) aData;
        sessionData.setAttribute("PACKET", thePacket);
      }
    }
    catch (EPPComponentNotFoundException e) {
      cat.error("toEvent(InputStream):", e);

      switch (e.getKind()) {
        case EPPComponentNotFoundException.COMMAND:
          throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.COMMANDNOTFOUND);
        case EPPComponentNotFoundException.EXTENSION:
          throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.EXTENSIONNOTFOUND);
        case EPPComponentNotFoundException.RESPONSE:
          throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.RESPONSENOTFOUND);
      }

    }
    catch (EPPDecodeException e) {
      cat.error("toEvent(InputStream):", e);
      throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.MISSINGPARAMETER);
    }
    catch (EPPAssemblerException e) {
      cat.error("toEvent(InputStream):", e);
      throw e;
    }
    catch (EPPException e) {
      cat.error("toEvent(InputStream):", e);
      throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.XML);
    }
    catch (InterruptedIOException e) {
      cat.debug("toEvent(InputStream):", e);
      throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.INTRUPTEDIO);
    }
    catch (IOException e) {
      cat.error("toEvent(InputStream):", e);
      throw new EPPAssemblerException(e.getMessage(), EPPAssemblerException.CLOSECON);
    }

    cat.debug("toEvent(InputStream): Return");

    return new EPPEvent(message);
  }

  /**
   * Initialize the pools, which include the parser and transformer pools.
   */
  private void initXmlPools() {
    // Pools not initialized?
    if (!poolsInitialized) {

      // Get reference to each of the pool instances, which will initialize
      // them if they are not already initialized.
      EPPSchemaCachingParserPool.getInstance();
      EPPTransformerPool.getInstance();

      poolsInitialized = true;
    }
  }

}
