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

import java.util.Enumeration;
import java.util.Vector;

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



/**
 * The EPP &lt;login&gt; command is used to establish a session with an EPP
 * server in response to a greeting issued by the server. A &lt;login&gt;
 * command MUST be sent to a server before any other EPP command to establish an
 * ongoing session. A server operator MAY limit the number of failed login
 * attempts N, 1 &lt;= N &lt;= infinity, after which a login failure results in
 * the connection to the server (if a connection exists) being closed. <br>
 * A client identifier and initial password MUST be created on the server before
 * a client can successfully complete a &lt;login&gt; command. The client
 * identifier and initial password MUST be delivered to the client using an
 * out-of-band method that protects the identifier and password from inadvertent
 * disclosure. <br>
 * In addition to the standard EPP command elements, the &lt;login&gt; command
 * contains the following child elements: <br>
 * <br>
 * 
 * <ul>
 * <li>A &lt;clID&gt; element that contains the client identifier assigned to
 * the client by the server.</li>
 * <li>A &lt;pw&gt; element that contains the client's plain text password. The
 * value of this element is case sensitive.</li>
 * <li>An OPTIONAL &lt;newPW&gt; element that contains a new plain text password
 * to be assigned to the client for use with subsequent &lt;login&gt; commands.
 * The value of this element is case sensitive.</li>
 * </ul>
 * 
 * <br>
 * 
 * <ul>
 * <li>An &lt;options&gt; element that contains the following child
 * elements:</li>
 * <li>A &lt;version&gt; element that contains the protocol version to be used
 * for the command or ongoing server session.</li>
 * <li>A &lt;lang&gt; element that contains the text response language to be
 * used for the command or ongoing server session commands.</li>
 * </ul>
 * 
 * <br>
 * The values of the &lt;version&gt; and &lt;lang&gt; elements MUST exactly
 * match one of the values presented in the EPP greeting. <br>
 * 
 * <ul>
 * <li>A &lt;svcs&gt; element that contains one or more &lt;objURI&gt; elements
 * that contain namespace URIs representing the objects to be managed during the
 * session. The &lt;svcs&gt; element MAY contain an OPTIONAL
 * &lt;svcExtension&gt; element that contains one or more &lt;extURI&gt;
 * elements that identify object extensions to be used during the session.</li>
 * </ul>
 * 
 * <br>
 * The PLAIN SASL mechanism presented in [RFC2595] describes a format for
 * providing a user identifier, an authorization identifier, and a password as
 * part of a single plain text string. The EPP authentication mechanism is
 * similar, though EPP does not require a session-level authorization identifier
 * and the user identifier and password are separated into distinct XML
 * elements. Additional identification and authorization schemes MUST be
 * provided at other protocol layers to provide more robust security services.
 *
 * @author $Author: jim $
 * @version $Revision: 1.4 $
 *
 * @see com.verisign.epp.codec.gen.EPPFactory
 */
public class EPPLoginCmd extends EPPCommand {
	/** XML root tag name for {@code EPPLoginCmd}. */
	final static String ELM_NAME = "login";

	/** XML tag name for the {@code clientId} attribute. */
	private final static String ELM_CLIENT_ID = "clID";

	/** XML tag name for the {@code password} attribute. */
	private final static String ELM_PASSWORD = "pw";

	/** XML tag name for the {@code newPassword} attribute. */
	private final static String ELM_NEW_PASSWORD = "newPW";

	/** XML tag name for the credential options. */
	private final static String ELM_OPTIONS = "options";

	/** XML tag name for the {@code services} attribute. */
	private final static String ELM_SERVICES = "svcs";

	/** XML tag name for the {@code services} attribute. */
	private final static String ELM_EXT_SERVICES = "svcExtension";

	/** XML tag name for the {@code extservices} attribute. */
	private final static String ELM_EXT_URI = "extURI";

	/** Class logger */
	private static Logger cat = LoggerFactory.getLogger(EPPLoginCmd.class);
	      

	/** Client login id */
	private String clientId = null;

	/** Client password */
	private String password = null;

	/** New client password */
	private String newPassword = null;

	/** Desired EPP protocol version */
	private String version = EPPCodec.VERSION;

	/** Desired language for error result messages. */
	private String lang = "en";

	/** Object Services desired by the client. */
	private Vector services;

	/**
	 * Extension (Protocol and Command-Response) Services desired by the client.
	 */
	private Vector extservices;

	/** XML tag name for the {@code version} attribute. */
	private final String ELM_VERSION = "version";

	/** XML tag name for the {@code lang} attribute. */
	private final String ELM_LANG = "lang";

	/**
	 * Allocates a new {@code EPPLoginCmd} with default attribute values. the
	 * defaults include the following: <br>
	 * <br>
	 * 
	 * <ul>
	 * <li>transaction id is set to {@code null}.</li>
	 * <li>client id is set to {@code null}</li>
	 * <li>password is set to {@code null}</li>
	 * <li>new password is set to {@code null}</li>
	 * <li>services is initialized based on the {@code EPPFactory}
	 * configuration.</li>
	 * </ul>
	 * 
	 * <br>
	 * The client id, password, and transaction id must be set before invoking
	 * {@code encode}.
	 */
	public EPPLoginCmd() {
		services = EPPFactory.getInstance().getServices();
		this.extservices = EPPFactory.getInstance().getExtensions();
	}

	/**
	 * Allocates a new {@code EPPLoginCmd} with the required attributes. The
	 * other attributes are initialized as follows: <br>
	 * <br>
	 * 
	 * <ul>
	 * <li>new password is set to {@code null}</li>
	 * <li>services is initialized based on the {@code EPPFactory}
	 * configuration.</li>
	 * </ul>
	 * 
	 *
	 * @param aTransId
	 *           transaction id of the command.
	 * @param aClientId
	 *           Client login id
	 * @param aPassword
	 *           Client password
	 */
	public EPPLoginCmd(String aTransId, String aClientId, String aPassword) {
		super(aTransId);

		services = EPPFactory.getInstance().getServices();
		this.extservices = EPPFactory.getInstance().getExtensions();
		clientId = aClientId;
		password = aPassword;
	}

	/**
	 * Allocates a new {@code EPPLoginCmd} with the required attributes and the
	 * optional new password attribute. The services is initialized based on the
	 * {@code EPPFactory} configuration.
	 *
	 * @param aTransId
	 *           transaction id of the command.
	 * @param aClientId
	 *           Client login id
	 * @param aPassword
	 *           Client password
	 * @param aNewPassword
	 *           New client password
	 */
	public EPPLoginCmd(String aTransId, String aClientId, String aPassword, String aNewPassword) {
		super(aTransId);

		services = EPPFactory.getInstance().getServices();
		this.extservices = EPPFactory.getInstance().getExtensions();
		clientId = aClientId;
		password = aPassword;
		newPassword = aNewPassword;
	}

	/**
	 * Gets the EPP command Namespace associated with {@code EPPLoginCmd}.
	 *
	 * @return EPPCodec.NS
	 */
	public String getNamespace() {
		return EPPCodec.NS;
	}

	/**
	 * Gets the EPP command type associated with {@code EPPLoginCmd}.
	 *
	 * @return {@code EPPCommand.TYPE_LOGIN}
	 */
	public String getType() {
		return EPPCommand.TYPE_LOGIN;
	}

	/**
	 * Gets the client login identifier.
	 *
	 * @return Client login identifier if defined; {@code null} otherwise.
	 */
	public String getClientId() {
		return clientId;
	}

	/**
	 * Sets the client login identifier.
	 *
	 * @param aClientId
	 *           Client login identifier.
	 */
	public void setClientId(String aClientId) {
		clientId = aClientId;
	}

	/**
	 * Gets the client password.
	 *
	 * @return Client password if defined; {@code null} otherwise.
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the client password.
	 *
	 * @param aPassword
	 *           Client password.
	 */
	public void setPassword(String aPassword) {
		password = aPassword;
	}

	/**
	 * Gets the new client password.
	 *
	 * @return New client password if defined; {@code null} otherwise.
	 */
	public String getNewPassword() {
		return newPassword;
	}

	/**
	 * Sets the new client password.
	 *
	 * @param aNewPassword
	 *           New client password.
	 */
	public void setNewPassword(String aNewPassword) {
		newPassword = aNewPassword;
	}

	/**
	 * Is a new password defined?
	 *
	 * @return {@code true} if the new password is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasNewPassword() {
		if (newPassword != null) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Gets the desired EPP version. The default version is set to
	 * {@code EPPCodec.VERSION}.
	 *
	 * @return EPP version identifier if defined; {@code null} otherwise.
	 */
	public String getVersion() {
		return version;
	}

	/**
	 * Sets the desired EPP version. The default version is set to
	 * {@code EPPCodec.VERSION}.
	 *
	 * @param aVersion
	 *           EPP version identifier
	 */
	public void setVersion(String aVersion) {
		version = aVersion;
	}

	/**
	 * Gets the desired EPP language. The EPP language determines the language of
	 * the error description strings and should be one of the supported languages
	 * of the {@code EPPGreeting}. The default language is "en".
	 *
	 * @return The desired EPP language if defined; {@code null} otherwise.
	 */
	public String getLang() {
		return lang;
	}

	/**
	 * Sets the desired EPP language. The EPP language determines the language of
	 * the error description strings and should be one of the supported languages
	 * of the {@code EPPGreeting}. The default language is "en".
	 *
	 * @param aLang
	 *           The desired EPP language
	 */
	public void setLang(String aLang) {
		lang = aLang;
	}

	/**
	 * Gets the login services.
	 *
	 * @return {@code Vector} of {@code EPPService} instances
	 */
	public Vector getServices() {
		return services;
	}

	/**
	 * Sets the login services. The default services are retrieved from
	 * {@code EPPFactory.getServices}.
	 *
	 * @param someServices
	 *           {@code Vector} of desired {@code EPPService} instances
	 */
	public void setServices(Vector someServices) {
		services = someServices;
	}

	/**
	 * Does the login service include a specified service based on the Namespace
	 * URI?
	 * 
	 * @param aNamespaceURI
	 *           Service Namespace URI to search for
	 * 
	 * @return {@code true} if the login services include the service Namespace
	 *         URI; {@code false} otherwise.
	 */
	public boolean hasService(String aNamespaceURI) {
		boolean found = false;

		if (this.services != null) {
			Enumeration theSvcEnum = this.services.elements();
			while (!found && theSvcEnum.hasMoreElements()) {
				EPPService theExtService = (EPPService) theSvcEnum.nextElement();

				if (theExtService.getNamespaceURI().equals(aNamespaceURI)) {
					found = true;
				}
			}
		}

		return found;
	}

	/**
	 * Does the login extension service include a specified extension service
	 * based on the Namespace URI?
	 * 
	 * @param aNamespaceURI
	 *           Extension service Namespace URI to search for
	 * 
	 * @return {@code true} if the login extension services include the extension
	 *         service Namespace URI; {@code false} otherwise.
	 */
	public boolean hasExtensionService(String aNamespaceURI) {
		boolean found = false;

		if (this.extservices != null) {
			Enumeration theExtSvcEnum = this.extservices.elements();
			while (!found && theExtSvcEnum.hasMoreElements()) {
				EPPService theExtService = (EPPService) theExtSvcEnum.nextElement();

				if (theExtService.getNamespaceURI().equals(aNamespaceURI)) {
					found = true;
				}
			}
		}

		return found;
	}

	/**
	 * Gets the list of supported/desired extension services. An EPP Client will
	 * retrieve the list of extension services supported by the EPP Server. An
	 * EPP Server will retrieve the list of extension services desired by the EPP
	 * Client.
	 *
	 * @return Vector of {@code EPPService} instances.
	 */
	public Vector getExtensionServices() {
		return extservices;
	}

	/**
	 * Sets the list of supported/desired extension services. An EPP Client will
	 * set the list of extension services desired. An EPP Server will set the
	 * list of supported extension services.
	 *
	 * @param someExtServices
	 *           Vector of {@code EPPService} instances.
	 */
	public void setExtensionServices(Vector someExtServices) {
		extservices = someExtServices;
	}

	/**
	 * encode {@code EPPLoginCmd} into a DOM element tree. The &lt;login&gt;
	 * element is created and the attribute nodes are appended as children. This
	 * method is part of the Template Design Pattern, where {@code EPPCommand}
	 * provides the public {@code encode} and calls the abstract
	 * {@code doGenEncode}.
	 *
	 * @param aDocument
	 *           DOM Document to create elements from
	 *
	 * @return &lt;login&gt; root element tree.
	 *
	 * @exception EPPEncodeException
	 *               Error encoding the DOM element tree.
	 */
	protected Element doGenEncode(Document aDocument) throws EPPEncodeException {
		// login
		Element root = aDocument.createElementNS(EPPCodec.NS, ELM_NAME);

		// Client Id
		EPPUtil.encodeString(aDocument, root, clientId, EPPCodec.NS, ELM_CLIENT_ID);

		// Password
		EPPUtil.encodeString(aDocument, root, password, EPPCodec.NS, ELM_PASSWORD);

		// New Password
		EPPUtil.encodeString(aDocument, root, newPassword, EPPCodec.NS, ELM_NEW_PASSWORD);

		// Options
		Element optionsElm = aDocument.createElementNS(EPPCodec.NS, ELM_OPTIONS);
		root.appendChild(optionsElm);

		// Version
		EPPUtil.encodeString(aDocument, optionsElm, version, EPPCodec.NS, ELM_VERSION);

		// Language
		EPPUtil.encodeString(aDocument, optionsElm, lang, EPPCodec.NS, ELM_LANG);

		// Services svcs element
		Element servicesElm = aDocument.createElementNS(EPPCodec.NS, ELM_SERVICES);
		root.appendChild(servicesElm);
		EPPUtil.encodeCompVector(aDocument, servicesElm, services);

		if ((extservices != null) && extservices.elements().hasMoreElements()) {
			// svcExtension element
			Element svcExtension = aDocument.createElementNS(EPPCodec.NS, ELM_EXT_SERVICES);

			EPPUtil.encodeCompVector(aDocument, svcExtension, extservices);
			servicesElm.appendChild(svcExtension);
		}

		root.appendChild(servicesElm);

		return root;
	}

	/**
	 * decode {@code EPPLoginCmd} from a DOM element tree. The "login" element
	 * needs to be the value of the {@code aElement} argument. This method is
	 * part of the Template Design Pattern, where {@code EPPCommand} provides the
	 * public {@code decode} and calls the abstract {@code doGenDecode}.
	 *
	 * @param aElement
	 *           &lt;login&gt; root element tree.
	 *
	 * @exception EPPDecodeException
	 *               Error decoding the DOM element tree.
	 */
	protected void doGenDecode(Element aElement) throws EPPDecodeException {
		// Client Id
		clientId = EPPUtil.decodeString(aElement, EPPCodec.NS, ELM_CLIENT_ID);

		// Password
		password = EPPUtil.decodeString(aElement, EPPCodec.NS, ELM_PASSWORD);

		// New Password
		newPassword = EPPUtil.decodeString(aElement, EPPCodec.NS, ELM_NEW_PASSWORD);

		// Options
		NodeList elms = aElement.getElementsByTagNameNS(EPPCodec.NS, ELM_OPTIONS);

		if (elms.getLength() == 1) {
			Element optionsElm = (Element) elms.item(0);

			// Version
			version = EPPUtil.decodeString(optionsElm, EPPCodec.NS, ELM_VERSION);

			// Language
			lang = EPPUtil.decodeString(optionsElm, EPPCodec.NS, ELM_LANG);
		}
		else {
			version = null;
			lang = null;
		}

		// -- Services
		// Default to empty services Vector
		services = new Vector();

		// Default to empty extension services Vector
		extservices = new Vector();

		// Get Services root node
		Element servicesElm = EPPUtil.getElementByTagNameNS(aElement, EPPCodec.NS, ELM_SERVICES);

		if (servicesElm == null) {
			throw new EPPDecodeException("EPPLoginCmd.doGenDecode did not find a " + ELM_SERVICES + " element");
		}

		// Get the list of services
		NodeList serviceElms = servicesElm.getElementsByTagNameNS(EPPCodec.NS,
		      EPPUtil.getLocalName(EPPService.ELM_OBJ_URI));

		for (int i = 0; i < serviceElms.getLength(); i++) {
			EPPService currService = new EPPService();
			currService.setServiceType(EPPService.OBJ_SERVICE);

			currService.decode((Element) serviceElms.item(i));

			services.addElement(currService);
		}

		NodeList svcExtensionNodeList = aElement.getElementsByTagNameNS(EPPCodec.NS,
		      EPPUtil.getLocalName(ELM_EXT_SERVICES));

		if (svcExtensionNodeList.getLength() != 0) {
			Element svcExtension = null;

			for (int i = 0; i < svcExtensionNodeList.getLength(); i++) {
				svcExtension = (Element) svcExtensionNodeList.item(i);

				NodeList extensionElms = svcExtension.getElementsByTagNameNS(EPPCodec.NS,
				      EPPUtil.getLocalName(ELM_EXT_URI));

				for (int k = 0; k < extensionElms.getLength(); k++) {
					EPPService extService = new EPPService();
					extService.setServiceType(EPPService.EXT_SERVICE);
					extService.decode((Element) extensionElms.item(k));
					extservices.addElement(extService);
				}

				// end inner for loop
			}

			// end outer for loop
		}

		// end if
	}

	/**
	 * implements a deep {@code EPPLoginCmd} compare.
	 *
	 * @param aObject
	 *           {@code EPPLoginCmd} instance to compare with
	 *
	 * @return {@code true} if equal; {@code false} otherwise
	 */
	public boolean equals(Object aObject) {
		if (!(aObject instanceof EPPLoginCmd)) {
			return false;
		}

		EPPLoginCmd theLogin = (EPPLoginCmd) aObject;

		// EPPCommand superclass.
		if (!super.equals(aObject)) {
			return false;
		}

		// clientId
		if (!((clientId == null) ? (theLogin.clientId == null) : clientId.equals(theLogin.clientId))) {
			return false;
		}

		// password
		if (!((password == null) ? (theLogin.password == null) : password.equals(theLogin.password))) {
			return false;
		}

		// newPassword
		if (!((newPassword == null) ? (theLogin.newPassword == null) : newPassword.equals(theLogin.newPassword))) {
			return false;
		}

		// version
		if (!((version == null) ? (theLogin.version == null) : version.equals(theLogin.version))) {
			return false;
		}

		// lang
		if (!((lang == null) ? (theLogin.lang == null) : lang.equals(theLogin.lang))) {
			return false;
		}

		// services
		if (!EPPUtil.equalVectors(services, theLogin.services)) {
			return false;
		}

		return true;
	}

	/**
	 * Clone {@code EPPLoginCmd}.
	 *
	 * @return clone of {@code EPPLoginCmd}
	 *
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	public Object clone() throws CloneNotSupportedException {
		EPPLoginCmd clone = null;

		clone = (EPPLoginCmd) super.clone();

		// Services
		clone.services = (Vector) services.clone();

		for (int i = 0; i < services.size(); i++)
			clone.services.setElementAt(((EPPService) services.elementAt(i)).clone(), i);

		return clone;
	}

	/**
	 * Implementation of {@code Object.toString}, which will result in an
	 * indented XML {@code String} representation of the concrete
	 * {@code EPPCodecComponent}.
	 *
	 * @return Indented XML {@code String} if successful; {@code ERROR}
	 *         otherwise.
	 */
	public String toString() {
		return EPPUtil.toString(this);
	}

	/**
	 * Is the {@code EPPLoginCmd} services settings valid as compared with the
	 * services specified in the {@code EPPGreeting}? The services attributes
	 * defined in {@code EPPLoginCmd} must be a subset of the available services
	 * specified in the {@code EPPGreeting}.
	 *
	 * @param aGreeting
	 *           Greeting to compare services with
	 *
	 * @return {@code true} if the service settings are valid; {@code false}
	 *         otherwise.
	 */
	public boolean isValidServices(EPPGreeting aGreeting) {
		cat.debug("isValidServices(): enter");

		EPPServiceMenu greetingServices = aGreeting.getServiceMenu();

		// Language
		if ((lang == null) || (!greetingServices.getLangs().contains(lang))) {
			cat.error("lang mismatch " + lang + " not in " + greetingServices.getLangs());

			return false;
		}

		// Version
		if ((version == null) || (!greetingServices.getVersions().contains(version))) {
			cat.error("version mismatch " + version + " not in " + greetingServices.getVersions());

			return false;
		}

		// Services
		if (!EPPUtil.vectorSubset(services, greetingServices.getObjectServices())) {
			cat.debug("services mismatch " + services + " not in " + greetingServices.getObjectServices());

			return false;
		}

		cat.debug("isValidServices(): exit (true)");

		return true;
	}

	/**
	 * Merge the services defined automatically in the EPP SDK configuration with
	 * services and extension services defined in the EPP Greeting, so that the
	 * login services are sent to only be a subset of the services defined in the
	 * EPP Greeting.
	 * 
	 * @param aGreeting
	 *           EPP Greeting to merge the services in the EPP Login.
	 */
	public void mergeServicesAndExtensionServices(EPPGreeting aGreeting) {
		this.services.retainAll(aGreeting.getServiceMenu().getObjectServices());
		this.extservices.retainAll(aGreeting.getServiceMenu().getExtensionServices());
	}
}
