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

import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import com.verisign.epp.codec.gen.EPPGreeting;
import com.verisign.epp.codec.gen.EPPMessage;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.util.EPPCatFactory;

/**
 * A Singleton class that delegates message assembly to an EPPAssembler then
 * routes messages to the appropriate EPPEventHandler. <br>
 * <br>
 * 
 * @author Srikanth Veeramachaneni
 * @version 1.0 Dec 04, 2006
 * @see EPPEventHandler
 * @see EPPEvent
 */
public class EPPByteArrayDispatcher {

	/** The one and only instance of the EPPByteArrayDispatcher */
	private static final EPPByteArrayDispatcher _instance = new EPPByteArrayDispatcher();

	/** Class logger */
	private static Logger LOG = Logger.getLogger(EPPByteArrayDispatcher.class.getName(),
	      EPPCatFactory.getInstance().getFactory());

	/**
	 * eventHandlers is a Hashtable where the key is a string that defines the
	 * Namespace and value is the EPPEventHandler defined for that Namespace.
	 */
	private Map eventHandlers;

	/**
	 * theAssembler makes Message-to-Byte-Array/Byte-Array-to-Message
	 * transformation possible.
	 */
	private EPPByteArrayAssembler theAssembler;

	/**
	 * Creates the EPPByteArrayDispatcher.
	 */
	private EPPByteArrayDispatcher() {
		this.eventHandlers = new HashMap();
	}

	/**
	 * Gets the one and only instance of the EPPByteArrayDispatcher
	 * 
	 * @return Dispatcher The dispatcher
	 */
	public static EPPByteArrayDispatcher getInstance() {
		return _instance;
	}

	/**
	 * Sets the ByteArrayAssembler.
	 * 
	 * @param aByteArrayAssembler
	 *           The ByteArrayAssembler to use.
	 */
	public void setAssembler(EPPByteArrayAssembler aByteArrayAssembler) {
		this.theAssembler = aByteArrayAssembler;
	}

	/**
	 * Creates an {@code EPPMessage}, sends the message to the appropriate
	 * {@code EPPEventHandler}, and then returns the response as a {@code byte}
	 * array.
	 * 
	 * @param aInputBytes
	 *           The byte array containing the xml input
	 * @param aData
	 *           Any additional data that may be required by the EPPEventHandler
	 * @return {@code byte} array containing the response to the message.
	 * @exception EPPEventException
	 *               Exception related to the handling of an event
	 * @exception EPPAssemblerException
	 *               Exception related to the assembling/de-assembling of
	 *               messages. {@code EPPAssemberException.isFatal} can be called
	 *               to determine if the exception is a fatal exception for the
	 *               client session.
	 */
	public byte[] processMessage(byte[] aInputBytes, Object aData) throws EPPEventException, EPPAssemblerException {
		LOG.debug("processMessage(byte[], Object): Enter");

		if (this.theAssembler == null) {
			throw new EPPAssemblerException("No Assembler registered with EPPByteArrayDispatcher",
			      EPPAssemblerException.FATAL);
		}

		EPPEvent event = this.theAssembler.decode(aInputBytes, aData);

		EPPMessage message = event.getMessage();

		// Is the message not a supported type for dispatching?
		if (message instanceof EPPResponse) {
			LOG.info("processMessage(): Invalid message type " + message.getClass().getName() + " for dispatching");
			throw new EPPEventException("Invalid request message type of response");
		}
		if (message instanceof EPPGreeting) {
			LOG.info("processMessage(): Invalid message type " + message.getClass().getName() + " for dispatching");
			throw new EPPEventException("Invalid request message type of greeting");
		}

		// Get the handler for the message
		String namespace = message.getNamespace();
		LOG.debug("Sending event for Namespace " + namespace);

		EPPEventHandler handler = (EPPEventHandler) this.eventHandlers.get(namespace);
		if (handler == null) {
			LOG.info("processMessage(): Handler not found for Namespace " + namespace);
			throw new EPPEventException("Handler not found for Namespace " + namespace);
		}

		// Send the event to the message handler
		byte[] responseBytes = null;
		EPPEventResponse eventResponse = handler.handleEvent(event, aData);
		if (eventResponse != null && eventResponse.getResponse() != null) {
			LOG.debug("processMessage(): Sending response to Assembler");
			responseBytes = theAssembler.encode(eventResponse, aData);
		}
		else {
			LOG.debug("processMessage(): No response to send to Assembler");
		}

		LOG.debug("processMessage(): Return");

		return responseBytes;
	}

	/**
	 * Registers an {@code EPPEvenHandler} for notification of Events.
	 * 
	 * @param aHandler
	 *           The {@code EPPEventhandler} to register
	 */
	public void registerHandler(EPPEventHandler aHandler) {
		this.eventHandlers.put(aHandler.getNamespace(), aHandler);
	}

	/**
	 * Encodes an EPP message to a {@code byte} array.
	 * 
	 * @param aMessage
	 *           EPP Message to send to the client
	 * @return Encoded {@code aMessage} as a {@code byte[]}.
	 * @exception EPPAssemblerException
	 *               Error encoding the EPP message
	 */
	public byte[] toBytes(EPPMessage aMessage) throws EPPAssemblerException {
		return this.theAssembler.encode(new EPPEventResponse(aMessage), null);
	}

	/**
	 * Encodes an EPP message to a {@code byte} array.
	 * 
	 * @param aMessage
	 *           EPP Message to send to the client
	 * @param aData
	 *           Any additional data that may be required by the handler.
	 * @return Encoded {@code aMessage} as a {@code byte[]}.
	 * @exception EPPAssemblerException
	 *               Error encoding the EPP message
	 */
	public byte[] toBytes(EPPMessage aMessage, Object aData) throws EPPAssemblerException {
		return this.theAssembler.encode(new EPPEventResponse(aMessage), aData);
	}
}
