/***********************************************************
Copyright (C) 2018 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.serverstub.registry.v02;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.verisign.epp.codec.gen.EPPCodecComponent;
import com.verisign.epp.codec.gen.EPPResponse;
import com.verisign.epp.codec.gen.EPPResult;
import com.verisign.epp.codec.gen.EPPService;
import com.verisign.epp.codec.gen.EPPTransId;
import com.verisign.epp.codec.registry.policy.EPPRegistryZoneInterface;
import com.verisign.epp.codec.registry.v02.EPPRegistryBatchJob;
import com.verisign.epp.codec.registry.v02.EPPRegistryBatchSchedule;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryCheckResult;
import com.verisign.epp.codec.registry.v02.EPPRegistryContact;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactAddress;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactCity;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactName;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactOrg;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactPostalCode;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactStateProvince;
import com.verisign.epp.codec.registry.v02.EPPRegistryContactStreet;
import com.verisign.epp.codec.registry.v02.EPPRegistryCreateCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryCreateResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryDNSSEC;
import com.verisign.epp.codec.registry.v02.EPPRegistryDS;
import com.verisign.epp.codec.registry.v02.EPPRegistryDeleteCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomain;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomain.HostModelSupported;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainContact;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainHostLimit;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainNSLimit;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainName;
import com.verisign.epp.codec.registry.v02.EPPRegistryDomainPeriod;
import com.verisign.epp.codec.registry.v02.EPPRegistryExceedMaxExDate;
import com.verisign.epp.codec.registry.v02.EPPRegistryExternalHost;
import com.verisign.epp.codec.registry.v02.EPPRegistryGracePeriod;
import com.verisign.epp.codec.registry.v02.EPPRegistryHost;
import com.verisign.epp.codec.registry.v02.EPPRegistryIDN;
import com.verisign.epp.codec.registry.v02.EPPRegistryInfoCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryInfoResp;
import com.verisign.epp.codec.registry.v02.EPPRegistryInternalHost;
import com.verisign.epp.codec.registry.v02.EPPRegistryKey;
import com.verisign.epp.codec.registry.v02.EPPRegistryLanguage;
import com.verisign.epp.codec.registry.v02.EPPRegistryMaxSig;
import com.verisign.epp.codec.registry.v02.EPPRegistryMinMaxLength;
import com.verisign.epp.codec.registry.v02.EPPRegistryPendingDeletePeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryPendingRestorePeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryPeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryPostal;
import com.verisign.epp.codec.registry.v02.EPPRegistryRGP;
import com.verisign.epp.codec.registry.v02.EPPRegistryRedemptionPeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryRegex;
import com.verisign.epp.codec.registry.v02.EPPRegistryReservedNames;
import com.verisign.epp.codec.registry.v02.EPPRegistryServices;
import com.verisign.epp.codec.registry.v02.EPPRegistryServices.EPPRegistryObjURI;
import com.verisign.epp.codec.registry.v02.EPPRegistryServicesExt;
import com.verisign.epp.codec.registry.v02.EPPRegistryServicesExt.EPPRegistryExtURI;
import com.verisign.epp.codec.registry.v02.EPPRegistrySupportedStatus;
import com.verisign.epp.codec.registry.v02.EPPRegistrySupportedStatus.Status;
import com.verisign.epp.codec.registry.v02.EPPRegistrySystemInfo;
import com.verisign.epp.codec.registry.v02.EPPRegistryTransferHoldPeriodType;
import com.verisign.epp.codec.registry.v02.EPPRegistryUpdateCmd;
import com.verisign.epp.codec.registry.v02.EPPRegistryZone;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneData;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneList;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneName;
import com.verisign.epp.codec.registry.v02.EPPRegistryZoneSummary;
import com.verisign.epp.framework.EPPEvent;
import com.verisign.epp.framework.EPPEventResponse;
import com.verisign.epp.framework.registry.v02.EPPRegistryHandler;
import com.verisign.epp.serverstub.RegistryPolicyCompositeAdapter;
import com.verisign.epp.serverstub.SessionData;
import com.verisign.epp.util.Environment;

public class RegistryHandler extends EPPRegistryHandler {
	/**
	 * Hard-coded server transaction id.
	 */
	private static final String svrTransId = "54322-XYZ";

	/**
	 * {@code RegistryZones} stores the set of registry zone information. The
	 * registry zones are keyed by the zone name, which is normalized in upper
	 * case. A zone contains zone information and a list of policy extensions, as
	 * defined by {@link RegistryZone}.
	 */
	public static class RegistryZones {
		/**
		 * Stored set of zones with the zone name used as the key.
		 */
		Map<String, RegistryZone> zones = new HashMap<String, RegistryZone>();

		/**
		 * Default constructor
		 */
		public RegistryZones() {
		}

		/**
		 * Does the zone exist by name?
		 *
		 * @param aName
		 *           Zone name to search for.
		 *
		 * @return {@code true} if zone exists; {@code false} otherwise.
		 */
		public boolean zoneExists(String aName) {
			aName = aName.toUpperCase();
			if (this.zones.containsKey(aName)) {
				return true;
			}
			else {
				return false;
			}
		}

		/**
		 * Gets the set of zone names.
		 *
		 * @return Set of zone names if the zones are defined; {@code null}
		 *         otherwise.
		 */
		public Set<String> getZoneNames() {
			if (this.zones != null) {
				return this.zones.keySet();
			}
			else {
				return null;
			}
		}

		/**
		 * Gets zone by name
		 *
		 * @param aName
		 *           Name of zone to search for
		 *
		 * @return Zone data if found; {@code null} otherwise.
		 */
		public RegistryZone getZone(String aName) {
			aName = aName.toUpperCase();
			return this.zones.get(aName);
		}

		/**
		 * Sets a zone, which add add it if it does not exist or update it if it
		 * does exist.
		 *
		 * @param aName
		 *           Name of zone to set
		 * @param aZone
		 *           Zone data to set
		 */
		public void setZone(String aName, RegistryZone aZone) {
			aName = aName.toUpperCase();
			this.zones.put(aName, aZone);
		}

		/**
		 * Sets the zone info for an existing zone.
		 *
		 * @param aName
		 *           Naming of existing zone
		 * @param aZoneInfo
		 *           Zone information to set for the existing zone
		 * @return {@code true} if the zone information was set; {@code false}
		 *         otherwise.
		 */
		public boolean setZoneInfo(String aName, EPPRegistryZone aZoneInfo) {
			aName = aName.toUpperCase();
			boolean theZoneInfoSet = false;
			RegistryZone theZone = this.zones.get(aName);

			if (theZone != null) {
				theZone.setZoneInfo(aZoneInfo);
				theZoneInfoSet = true;
			}

			return theZoneInfoSet;
		}

		/**
		 * Sets the zone policy extension for an existing zone. If the policy has
		 * not already been set, the {@code setZonePolicyExt} will add it.
		 *
		 * @param aName
		 *           Naming of existing zone
		 * @param aZonePolicy
		 *           Zone policy extension to set for the existing zone
		 */
		public void setZonePolicyExt(String aName, EPPRegistryZoneInterface aZonePolicy) {
			aName = aName.toUpperCase();
			RegistryZone theZone = this.zones.get(aName);
			theZone.setExtension(aZonePolicy);
		}

		/**
		 * Sets the zone policy extensions for an existing zone. If the policy has
		 * not already been set, the {@code setZonePolicyExt} will add it.
		 *
		 * @param aName
		 *           Naming of existing zone
		 * @param aZonePolicies
		 *           Zone policy extensions to set for the existing zone
		 */
		public void setZonePolicyExts(String aName, List<EPPRegistryZoneInterface> aZonePolicies) {
			aName = aName.toUpperCase();
			RegistryZone theZone = this.zones.get(aName);

			// Replace the zone extensions
			theZone.setExtensions(aZonePolicies);
		}

		public void deleteZone(String aName) {
			aName = aName.toUpperCase();
			this.zones.remove(aName);
		}

	}

	/**
	 * {@code RegistryZone} represents an in-memory version of a Registry Zone
	 * with the base zone information along with all policy extensions.
	 */
	public static class RegistryZone {
		/**
		 * Zone information as defined in
		 * {@code draft-gould-carney-regext-registry}.
		 */
		private EPPRegistryZone zoneInfo;

		/**
		 * Policy extensions
		 */
		private List<EPPRegistryZoneInterface> extensions;

		/**
		 * Default constructor. The zone information and the extensions should be
		 * set.
		 */
		public RegistryZone() {
		}

		/**
		 * {@code RegistryZone} constructor that takes the required zone
		 * information.
		 *
		 * @param aZoneInfo
		 *           Zone information as defined in
		 *           {@code draft-gould-carney-regext-registry}.
		 */
		public RegistryZone(EPPRegistryZone aZoneInfo) {
			this.zoneInfo = aZoneInfo;
		}

		/**
		 * {@code RegistryZone} constructor that takes both attributes (zone
		 * information and extensions).
		 *
		 * @param aZoneInfo
		 *           Zone information as defined in
		 *           {@code draft-gould-carney-regext-registry}.
		 * @param aExtensions
		 *           Policy extensions included in the command
		 */
		public RegistryZone(EPPRegistryZone aZoneInfo, List<EPPRegistryZoneInterface> aExtensions) {
			this.zoneInfo = aZoneInfo;
			this.extensions = aExtensions;
		}

		/**
		 * Is the zone information defined?
		 *
		 * @return <code>true</code> if the zone information is defined;
		 *         <code>false</code> otherwise.
		 */
		public boolean hasZoneInfo() {
			return (this.zoneInfo != null ? true : false);
		}

		/**
		 * Gets the zone information as defined in
		 * {@code draft-gould-carney-regext-registry}.
		 *
		 * @return Zone information if defined; {@code null} otherwise.
		 */
		public EPPRegistryZone getZoneInfo() {
			return this.zoneInfo;
		}

		/**
		 * Sets the zone information as defined in
		 * {@code draft-gould-carney-regext-registry}.
		 *
		 * @param aZoneInfo
		 *           Zone information as defined in
		 *           {@code draft-gould-carney-regext-registry}.
		 */
		public void setZoneInfo(EPPRegistryZone aZoneInfo) {
			this.zoneInfo = aZoneInfo;
		}

		/**
		 * Is the extensions list defined?
		 *
		 * @return {@code true} if the extensions list is defined; {@code false}
		 *         otherwise.
		 */
		public boolean hasExtensions() {
			return (this.extensions != null && !this.extensions.isEmpty() ? true : false);
		}

		/**
		 * Gets the extensions list.
		 *
		 * @return Extensions list if defined; {@code null} otherwise.
		 */
		public List<EPPRegistryZoneInterface> getExtensions() {
			return this.extensions;
		}

		/**
		 * Sets the extensions list.
		 *
		 * @param aExtensions
		 *           Policy extensions included with the command. Set to
		 *           {@code null} if undefined.
		 */
		public void setExtensions(List<EPPRegistryZoneInterface> aExtensions) {
			this.extensions = aExtensions;
		}

		/**
		 * Sets an policy extension in the list of extensions. If the policy
		 * extension is already defined based on the policy extension class, it
		 * will be replaced; otherwise it will be added.
		 *
		 * @param aExtension
		 *           The policy extension to set (add or replace)
		 */
		public void setExtension(EPPRegistryZoneInterface aExtension) {
			if (aExtension == null) {
				return;
			}

			if (this.extensions == null) {
				this.addExtension(aExtension);
			}

			Iterator<EPPRegistryZoneInterface> theIter = this.extensions.iterator();

			boolean replaced = false;

			while (theIter.hasNext()) {
				EPPRegistryZoneInterface theExt = theIter.next();

				// Replace policy extension?
				if (theExt.getClass().isInstance(aExtension.getClass())) {
					theIter.remove();
					this.extensions.add(theExt);
					replaced = true;
					break;
				}

			}

			if (!replaced) {
				this.extensions.add(aExtension);
			}

		}

		/**
		 * Gets the policy extension by {@code Class}.
		 *
		 * @param aExtClass
		 *           The policy extension {@code Class} to look for
		 *
		 * @return Policy extension if found; {@code null} otherwise
		 */
		public EPPRegistryZoneInterface getExtension(Class aExtClass) {
			if (this.extensions == null) {
				return null;
			}

			Iterator theIter = this.extensions.iterator();

			while (theIter.hasNext()) {
				EPPRegistryZoneInterface theExtension = (EPPRegistryZoneInterface) theIter.next();

				if (aExtClass.isInstance(theExtension)) {
					return theExtension;
				}
			}

			return null;
		}

		/**
		 * Adds a policy extension to the list of extensions.
		 *
		 * @param aExtension
		 *           Policy extension to add
		 */
		public void addExtension(EPPRegistryZoneInterface aExtension) {
			if (aExtension == null) {
				return;
			}

			if (this.extensions == null) {
				this.extensions = new ArrayList<EPPRegistryZoneInterface>();
			}

			this.extensions.add(aExtension);
		}

		/**
		 * Convert the {@code RegistryZone} into a {@code String} for printing.
		 * 
		 * @return Encoded {@code RegistryZone} instance as a {@code String}.
		 */
		public String toString() {
			String ret = this.zoneInfo.toString();

			for (EPPRegistryZoneInterface ext : this.extensions) {
				ret += "\n" + ext;
			}

			return ret;
		}

	}

	private static RegistryZones zones = new RegistryZones();

	/**
	 * Preset 10 zones with the naming convention "samelezone(#)" by default or
	 * based on the "Ini.Zone.Count" epp.config property.
	 */
	static {
		int len = 10;
		// iterations Property
		String num = Environment.getProperty("Ini.Zone.Count", "10");
		boolean dsDataInterface = true;
		boolean hostObjModel = true;

		if (num != null) {
			len = Integer.parseInt(num);
		}
		for (int i = 0; i < len; i++) {
			EPPRegistryZone zoneInfo = new EPPRegistryZone("PRESET" + i, "client" + i, new Date());
			zoneInfo.setLastUpdatedBy("client" + i);
			zoneInfo.setLastUpdatedDate(new Date());
			zoneInfo.setUnsupportedData(EPPRegistryZone.UnsupportedData.fail);

			EPPRegistryBatchJob batchJob = new EPPRegistryBatchJob("localTzBatch",
			      "Batch with multiple local time schedules (name and offset)");
			batchJob.addSchedule(new EPPRegistryBatchSchedule("04:00:00", "EST5EDT"));
			batchJob.addSchedule(new EPPRegistryBatchSchedule("07:00:00-05:00", null));
			zoneInfo.addBatchJob(batchJob);

			batchJob = new EPPRegistryBatchJob("multiBatchSchedule", "Batch with multiple UTC schedules");
			batchJob.addSchedule(new EPPRegistryBatchSchedule("12:00:00Z", null));
			batchJob
			      .addSchedule(new EPPRegistryBatchSchedule("12:00:00Z", EPPRegistryBatchSchedule.DayOfWeek.SUNDAY, null));
			batchJob.addSchedule(new EPPRegistryBatchSchedule("17:00:00Z", 15, null));
			zoneInfo.addBatchJob(batchJob);

			zoneInfo.setGroup("g" + i);

			EPPRegistryServices services = new EPPRegistryServices();
			services.addObjURI(new EPPRegistryObjURI("http://www.verisign.com/epp/rgp-poll-1.0", Boolean.TRUE));
			services.addObjURI(new EPPRegistryObjURI("urn:ietf:params:xml:ns:host-1.0", Boolean.TRUE));
			services.addObjURI(new EPPRegistryObjURI("urn:ietf:params:xml:ns:contact-1.0", Boolean.TRUE));
			services.addObjURI(new EPPRegistryObjURI("urn:ietf:params:xml:ns:domain-1.0", Boolean.TRUE));
			services.addObjURI(new EPPRegistryObjURI("http://www.verisign.com/epp/lowbalance-poll-1.0", Boolean.FALSE));
			EPPRegistryServicesExt svcExt = new EPPRegistryServicesExt();
			services.setExtension(svcExt);
			svcExt.addExtURI(new EPPRegistryExtURI("http://www.verisign-grs.com/epp/namestoreExt-1.1", Boolean.TRUE));
			svcExt.addExtURI(new EPPRegistryExtURI("urn:ietf:params:xml:ns:rgp-1.0", Boolean.TRUE));
			svcExt.addExtURI(new EPPRegistryExtURI("http://www.verisign.com/epp/sync-1.0", Boolean.TRUE));
			svcExt.addExtURI(new EPPRegistryExtURI("http://www.verisign.com/epp/idnLang-1.0", Boolean.TRUE));
			svcExt.addExtURI(new EPPRegistryExtURI("http://www.verisign.com/epp/premiumdomain-1.0", Boolean.TRUE));
			svcExt.addExtURI(new EPPRegistryExtURI("urn:ietf:params:xml:ns:secDNS-1.1", Boolean.FALSE));
			zoneInfo.setServices(services);

			zoneInfo.setCreatedBy("crId");
			zoneInfo.setCreatedDate(new Date());
			zoneInfo.setLastUpdatedBy("upId");
			zoneInfo.setLastUpdatedDate(new Date());

			zoneInfo.setDomain(buildInfoDomain(i, dsDataInterface, hostObjModel));
			// Swap use of DS Data Interface and Key Data Interface
			if (dsDataInterface) {
				dsDataInterface = false;
			}
			else {
				dsDataInterface = true;
			}

			zoneInfo.setHost(buildInfoHost(i, hostObjModel));

			// Swap use of host object and host attribute models
			if (hostObjModel) {
				hostObjModel = false;
			}
			else {
				hostObjModel = true;
			}

			zoneInfo.setContact(buildContact(i));

			zones.setZone(zoneInfo.getName().getName(), new RegistryZone(zoneInfo));
		}
	}

	/**
	 * Logging category
	 */
	    private static Logger cat = LoggerFactory.getLogger(RegistryHandler.class);
	      

	/**
	 * Used to adapt EPP extensions to zone extensions and vice-versa
	 */
	private RegistryPolicyCompositeAdapter registryPolicyAdapter = new RegistryPolicyCompositeAdapter();

	/**
	 * Gets the registry zones stored in the handler.
	 * 
	 * @return Registry zones containing the stored zone information.
	 */
	public static RegistryZones getRegistryZones() {
		return zones;
	}

	/**
	 * Invoked when a Registry Create command is received. This method will add
	 * the zone to the cache if it doesn't already exist.
	 *
	 * @param aEvent
	 *           The {@link EPPEvent} that is being handled
	 * @param aData
	 *           Any data that a Server needs to send to this
	 *           {@code EPPRegistryHandler}
	 *
	 * @return The {@link EPPEventResponse} that should be sent back to the
	 *         client.
	 */
	@Override
	protected EPPEventResponse doRegistryCreate(EPPEvent aEvent, Object aData) {
		cat.debug("doRegistryCreate: enter");

		EPPRegistryCreateCmd message = (EPPRegistryCreateCmd) aEvent.getMessage();

		// Encode EPPRegistryCreate Response
		EPPRegistryCreateResp theResponse;

		EPPTransId respTransId = new EPPTransId(message.getTransId(), svrTransId);
		String name = message.getZone().getName().getName();
		Date theCreatedDate = new Date();
		theResponse = new EPPRegistryCreateResp(respTransId, name, theCreatedDate);

		if (zones.zoneExists(name)) {
			EPPResponse theErrResponse = new EPPResponse(respTransId);
			theErrResponse.setResult(EPPResult.OBJECT_EXISTS, "TLD name exists");
			return new EPPEventResponse(theErrResponse);
		}

		try {
			EPPRegistryZone zoneInfo = (EPPRegistryZone) message.getZone().clone();
			zoneInfo.setCreatedBy("test");
			zoneInfo.setCreatedDate(theCreatedDate);
			zoneInfo.setLastUpdatedBy(null);
			zoneInfo.setLastUpdatedDate(null);
			zones.setZone(name, new RegistryZone(zoneInfo));
		}
		catch (CloneNotSupportedException ex) {
			cat.error("Exception caching the created zone: " + ex);
		}

		// Process policy extensions
		if (message.hasExtensions()) {
			zones.setZonePolicyExts(name, this.registryPolicyAdapter.eppExtToZoneExt(message.getExtensions()));
		}

		cat.debug("doRegistryCreate: exit");
		return new EPPEventResponse(theResponse);
	}

	/**
	 * Invoked when a Registry Update command is received. This method will
	 * update the zone to the cache if it already exists. An error is returned if
	 * the zone doesn't exist.
	 *
	 * @param aEvent
	 *           The {@link EPPEvent} that is being handled
	 * @param aData
	 *           Any data that a Server needs to send to this
	 *           {@code EPPRegistryHandler}
	 *
	 * @return The {@link EPPEventResponse} that should be sent back to the
	 *         client.
	 */
	@Override
	protected EPPEventResponse doRegistryUpdate(EPPEvent aEvent, Object aData) {
		cat.debug("doRegistryUpdate: enter");
		EPPRegistryUpdateCmd message = (EPPRegistryUpdateCmd) aEvent.getMessage();

		EPPTransId respTransId = new EPPTransId(message.getTransId(), svrTransId);

		// Encode EPPRegistryUpdate Response
		EPPResponse theResponse;
		theResponse = new EPPResponse(respTransId);

		String name = message.getZone().getName().getName();

		// Zone does not exist?
		if (!zones.zoneExists(name)) {
			theResponse.setResult(EPPResult.OBJECT_DOES_NOT_EXIST,
			      "Tld " + message.getZone().getName() + " does not exist. Please create it first");

			cat.debug("doRegistryUpdate: exit");
			return new EPPEventResponse(theResponse);
		}

		theResponse.setResult(EPPResult.SUCCESS);

		// Update zone
		RegistryZone theZone;
		try {
			theZone = new RegistryZone((EPPRegistryZone) message.getZone().clone());
			theZone.getZoneInfo().setLastUpdatedBy("test");
			theZone.getZoneInfo().setLastUpdatedDate(new Date());
			zones.setZone(name, theZone);
		}
		catch (CloneNotSupportedException e) {
			// Swallow. This should not happen
		}

		// Process policy extensions
		if (message.hasExtensions()) {
			zones.setZonePolicyExts(name, this.registryPolicyAdapter.eppExtToZoneExt(message.getExtensions()));
		}

		cat.debug("doRegistryUpdate: exit");
		return new EPPEventResponse(theResponse);
	}

	/**
	 * Invoked when a Registry Check command is received. This method will back
	 * the check results on the zones loaded into the cache.
	 *
	 * @param aEvent
	 *           The {@link EPPEvent} that is being handled
	 * @param aData
	 *           Any data that a Server needs to send to this
	 *           {@code EPPRegistryHandler}
	 *
	 * @return EPPEventResponse The response that should be sent back to the
	 *         client.
	 */
	@Override
	protected EPPEventResponse doRegistryCheck(EPPEvent aEvent, Object aData) {
		EPPRegistryCheckCmd message = (EPPRegistryCheckCmd) aEvent.getMessage();

		// Encode EPPRegistryInfo Response
		EPPRegistryCheckResp theResponse;

		EPPTransId respTransId = new EPPTransId(message.getTransId(), svrTransId);
		List<EPPRegistryZoneName> names = message.getNames();
		List<EPPRegistryCheckResult> results = new ArrayList<EPPRegistryCheckResult>();

		for (EPPRegistryZoneName name : names) {
			if (zones.zoneExists(name.getName())) {
				EPPRegistryCheckResult result = new EPPRegistryCheckResult(name, Boolean.FALSE);
				result.setReason("Already supported");
				results.add(result);
			}
			else {
				EPPRegistryCheckResult result = new EPPRegistryCheckResult(name, Boolean.TRUE);
				results.add(result);
			}

		}

		theResponse = new EPPRegistryCheckResp(respTransId, results);

		return new EPPEventResponse(theResponse);
	}

	/**
	 * Invoked when a Registry Info command is received. This method supports the
	 * three forms of the info command, which include:<br>
	 * <ul>
	 * <li>All - Get a summary list of all zones in the cache.</li>
	 * <li>Zone - Get detailed zone information from a zone in the cache.</li>
	 * <li>System - Get the Registry system information</li>
	 * </ul>
	 *
	 * @param aEvent
	 *           The {@link EPPEvent} that is being handled
	 * @param aData
	 *           Any data that a Server needs to send to this
	 *           {@code EPPRegistryHandler}
	 *
	 * @return EPPEventResponse The response that should be sent back to the
	 *         client.
	 */
	@Override
	protected EPPEventResponse doRegistryInfo(EPPEvent aEvent, Object aData) {
		cat.debug("doRegistryInfo ...");
		EPPRegistryInfoCmd message = (EPPRegistryInfoCmd) aEvent.getMessage();

		SessionData theSessionData = (SessionData) aData;

		// Encode EPPRegistryInfo Response
		EPPResponse theResponse = null;

		EPPTransId respTransId = new EPPTransId(message.getTransId(), svrTransId);

		switch (message.getMode()) {
			case name:
				String name = message.getName().getName();

				RegistryZone zone = zones.getZone(name);

				if (zone != null) {
					theResponse = new EPPRegistryInfoResp(respTransId, new EPPRegistryZoneData(zone.getZoneInfo(), true));
					theResponse.setResult(EPPResult.SUCCESS);
				}
				else {
					cat.warn("zone does not exist: " + message.getName());
					theResponse = new EPPResponse();
					theResponse.setResult(EPPResult.OBJECT_DOES_NOT_EXIST);
				}

				// Process policy extensions
				if (zone.hasExtensions()) {
					Vector<EPPService> theExtURIs = theSessionData.getLoginCmd().getExtensionServices();
					List<EPPCodecComponent> theEppExts = this.registryPolicyAdapter.zoneExtToEppExt(zone.getExtensions());
					for (EPPCodecComponent theExt : theEppExts) {
						for (EPPService theExtURI : theExtURIs) {
							if (theExtURI.getNamespaceURI().equals(theExt.getNamespace())) {
								cat.debug("Adding policy EPP extension to response: " + theExtURI);
								theResponse.addExtension(theExt);
								break;
							}
						}
					}
				}

				break;

			case all:
				EPPRegistryZoneList zoneList = new EPPRegistryZoneList();

				Set<String> theZoneNames = zones.getZoneNames();

				for (String theZoneName : theZoneNames) {
					RegistryZone theZone = zones.getZone(theZoneName);
					EPPRegistryZoneSummary theZoneInfo = new EPPRegistryZoneSummary(theZoneName, true,
					      theZone.getZoneInfo().getCreatedDate(), theZone.getZoneInfo().getLastUpdatedDate());
					zoneList.addZone(theZoneInfo);
				}

				theResponse = new EPPRegistryInfoResp(respTransId, zoneList);

				break;

			case system:
				EPPRegistrySystemInfo theSystemInfo = new EPPRegistrySystemInfo(Integer.valueOf(200), Integer.valueOf(600000),
				      Integer.valueOf(86400000), Integer.valueOf(10000), Integer.valueOf(10), Integer.valueOf(1000));
				theResponse = new EPPRegistryInfoResp(respTransId, theSystemInfo);

				// Add the system policy extensions
				List<EPPCodecComponent> theSystemExts = registryPolicyAdapter.getSystemExts();
				if (theSystemExts != null) {
					for (EPPCodecComponent theSystemExt : theSystemExts) {
						theResponse.addExtension(theSystemExt);
					}
				}

		}

		return new EPPEventResponse(theResponse);
	}

	/**
	 * Invoked when a Registry Delete command is received. This method will
	 * delete the zone from the cache if it exists. An error is returned back if
	 * the zone doesn't already exist.
	 *
	 * @param aEvent
	 *           The {@link EPPEvent} that is being handled
	 * @param aData
	 *           Any data that a Server needs to send to this
	 *           {@code EPPRegistryHandler}
	 *
	 * @return The {@link EPPEventResponse} that should be sent back to the
	 *         client.
	 */
	@Override
	protected EPPEventResponse doRegistryDelete(EPPEvent aEvent, Object aData) {
		EPPRegistryDeleteCmd theMessage = (EPPRegistryDeleteCmd) aEvent.getMessage();

		/**
		 * Create the transId for the response with the client trans id and the
		 * server trans id.
		 */
		EPPTransId transId = new EPPTransId(theMessage.getTransId(), svrTransId);

		// Create Delete Response (Standard EPPResponse)
		EPPResponse theResponse = new EPPResponse(transId);

		String name = theMessage.getName().getName();

		if (zones.zoneExists(name)) {
			zones.deleteZone(name);
			theResponse.setResult(EPPResult.SUCCESS);
		}
		else {
			theResponse.setResult(EPPResult.OBJECT_DOES_NOT_EXIST, "Requested zone \"" + name + "\" does not exist");
		}

		return new EPPEventResponse(theResponse);
	}

	/**
	 * Builds the domain object policy information for a zone.
	 *
	 * @param aSeq
	 *           Unique zone sequence number
	 * @param aDsDataInterface
	 *           Is the DNSSEC DS data interface supported? IF {@code true} the
	 *           DS data interface is supported; otherwise the Key data interface
	 *           is supported.
	 * @param aHostObjModel
	 *           Is the host object model used in RFC 5731? If {@code true} then
	 *           the host object model is used; otherwise the host attribute
	 *           model is used.
	 * 
	 * @return Domain object policy information
	 */
	private static EPPRegistryDomain buildInfoDomain(int aSeq, boolean aDsDataInterface, boolean aHostObjModel) {
		EPPRegistryDomain domain = new EPPRegistryDomain();

		List domainNames = new ArrayList();
		EPPRegistryDomainName domainName = new EPPRegistryDomainName();
		domainName.setLevel(Integer.valueOf(2));
		domainName.setMinLength(Integer.valueOf(5));
		domainName.setMaxLength(Integer.valueOf(50));
		domainName.setAlphaNumStart(Boolean.valueOf(true));
		domainName.setAlphaNumEnd(Boolean.valueOf(false));
		domainName.setALabelSupported(Boolean.valueOf(true));
		domainName.setNameRegex(new EPPRegistryRegex("^[a-zA-Z\\d][a-zA-Z\\d\\-]{4,49}$",
		      "5 to 50 DNS characters starting with alphanumeric"));

		EPPRegistryReservedNames reservedNames = new EPPRegistryReservedNames();
		List rNames = new ArrayList();
		reservedNames.setReservedNames(rNames);
		rNames.add("reserved" + aSeq + "1");
		rNames.add("reserved" + aSeq + "2");
		// reservedNames.setReservedNameURI("http://example.com/reservedNames");

		domainName.setReservedNames(reservedNames);
		domainNames.add(domainName);

		try {
			domainName = (EPPRegistryDomainName) domainName.clone();
			domainName.setLevel(Integer.valueOf(3));
			domainName.getReservedNames().setReservedNames(new ArrayList());
			domainName.getReservedNames().setReservedNameURI("http://testrn.vrsn.com");
			domainNames.add(domainName);
		}
		catch (CloneNotSupportedException e) {
			// Swallow. This should not happen
		}

		domain.setDomainNames(domainNames);

		EPPRegistryIDN idn = new EPPRegistryIDN();
		idn.setIdnVersion("1.1");
		idn.setIdnaVersion("2008");
		idn.setUnicodeVersion("6.0");
		idn.addLanguage(new EPPRegistryLanguage("CHI", "http://www.iana.org/domains/idn-tables/tables/com_zh_1.1.txt",
		      EPPRegistryLanguage.VariantStrategy.restricted));
		idn.addLanguage(new EPPRegistryLanguage("LATN", "http://www.iana.org/domains/idn-tables/tables/eu_latn_1.0.html",
		      EPPRegistryLanguage.VariantStrategy.blocked));
		idn.setCommingleAllowed(Boolean.TRUE);
		domain.setIdn(idn);

		domain.setPremiumSupport(Boolean.valueOf(true));
		domain.setContactsSupported(Boolean.valueOf(true));

		domain.addContact(new EPPRegistryDomainContact(EPPRegistryDomainContact.Type.admin, 1, 4));
		domain.addContact(new EPPRegistryDomainContact(EPPRegistryDomainContact.Type.billing, 2, 5));
		domain.addContact(new EPPRegistryDomainContact(EPPRegistryDomainContact.Type.tech, 3, 6));

		domain.setNameServerLimit(new EPPRegistryDomainNSLimit(1, 16));

		if (aHostObjModel) {
			domain.setChildHostLimit(new EPPRegistryDomainHostLimit(0, null));
		}

		// domain.addPeriod(new EPPRegistryDomainPeriod("create",
		// Boolean.TRUE));
		domain.addPeriod(new EPPRegistryDomainPeriod("create", 1, EPPRegistryPeriodType.Unit.y, 10,
		      EPPRegistryPeriodType.Unit.y, 2, EPPRegistryPeriodType.Unit.y));
		domain.addPeriod(new EPPRegistryDomainPeriod("renew", 1, EPPRegistryPeriodType.Unit.y, 10,
		      EPPRegistryPeriodType.Unit.y, 2, EPPRegistryPeriodType.Unit.y));
		domain.addPeriod(new EPPRegistryDomainPeriod("transfer", 1, EPPRegistryPeriodType.Unit.y, 8,
		      EPPRegistryPeriodType.Unit.y, 2, EPPRegistryPeriodType.Unit.y));

		domain.addExceedMaxExDate(new EPPRegistryExceedMaxExDate(EPPRegistryExceedMaxExDate.Policy.fail, "renew"));
		domain.addExceedMaxExDate(new EPPRegistryExceedMaxExDate(EPPRegistryExceedMaxExDate.Policy.clip, "transfer"));

		domain.setTransferHoldPeriod(new EPPRegistryTransferHoldPeriodType(1, EPPRegistryPeriodType.Unit.y));

		domain.addGracePeriod(new EPPRegistryGracePeriod("create", 1, EPPRegistryPeriodType.Unit.d));
		domain.addGracePeriod(new EPPRegistryGracePeriod("renew", 2, EPPRegistryPeriodType.Unit.m));
		domain.addGracePeriod(new EPPRegistryGracePeriod("transfer", 3, EPPRegistryPeriodType.Unit.h));

		EPPRegistryRGP rgp = new EPPRegistryRGP();
		rgp.setPendingDeletePeriod(new EPPRegistryPendingDeletePeriodType(1, EPPRegistryPeriodType.Unit.m));
		rgp.setRedemptionPeriod(new EPPRegistryRedemptionPeriodType(1, EPPRegistryPeriodType.Unit.m));
		rgp.setPendingRestorePeriod(new EPPRegistryPendingRestorePeriodType(1, EPPRegistryPeriodType.Unit.m));
		domain.setRgp(rgp);

		EPPRegistryDNSSEC dnssec = new EPPRegistryDNSSEC();

		// DS Data Interface?
		if (aDsDataInterface) {
			EPPRegistryDS ds = new EPPRegistryDS(0, 13);
			ds.addAlgorithm(3);
			ds.addDigestType(1);
			dnssec.setDs(ds);
		} // Key Data Interface
		else {
			EPPRegistryKey key = new EPPRegistryKey(0, 13);
			key.addFlags(257);
			key.addProtocol(3);
			key.addAlgorithm(3);
			dnssec.setKey(key);
		}

		dnssec.setMaxSigLife(new EPPRegistryMaxSig(true, 1, 2, 3));
		dnssec.setUrgent(Boolean.TRUE);

		domain.setDnssec(dnssec);
		domain.setMaxCheckDomain(Integer.valueOf(12));

		EPPRegistrySupportedStatus supportedStatus = new EPPRegistrySupportedStatus();
		supportedStatus.addStatus(Status.DOMAIN_CLIENTDELETEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERDELETEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTHOLD);
		supportedStatus.addStatus(Status.DOMAIN_SERVERHOLD);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTRENEWPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERRENEWPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_CLIENTUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_SERVERUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.DOMAIN_INACTIVE);
		supportedStatus.addStatus(Status.DOMAIN_OK);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGCREATE);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGDELETE);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGRENEW);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGTRANSFER);
		supportedStatus.addStatus(Status.DOMAIN_PENDINGUPDATE);
		domain.setSupportedStatus(supportedStatus);

		domain.setAuthInfoRegex(new EPPRegistryRegex("^.*$", "exp"));
		domain.setNullAuthInfoSupported(Boolean.FALSE);
		if (aHostObjModel) {
			domain.setHostModelSupported(HostModelSupported.hostObj);
		}
		else {
			domain.setHostModelSupported(HostModelSupported.hostAttr);
		}

		return domain;
	}

	/**
	 * Builds the host object policy information for a zone.
	 *
	 * @param aSeq
	 *           Unique zone sequence number
	 * @param aHostObjModel
	 *           Is the host object model used in RFC 5731? If {@code true} then
	 *           the host object model is used; otherwise the host attribute
	 *           model is used.
	 * 
	 * @return Host object policy information
	 */
	private static EPPRegistryHost buildInfoHost(int aSeq, boolean aHostObjModel) {
		EPPRegistryHost host = new EPPRegistryHost();

		if (aHostObjModel) {
			host.setInternal(
			      new EPPRegistryInternalHost(5, 15, EPPRegistryInternalHost.SharePolicy.perZone, Boolean.FALSE));
			host.setExternal(
			      new EPPRegistryExternalHost(2, 12, EPPRegistryExternalHost.SharePolicy.perZone, Boolean.FALSE));

			host.setMaxCheckHost(Integer.valueOf(15));

			EPPRegistrySupportedStatus supportedStatus = new EPPRegistrySupportedStatus();
			supportedStatus.addStatus(Status.HOST_CLIENTDELETEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_SERVERDELETEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_CLIENTUPDATEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_SERVERUPDATEPROHIBITED);
			supportedStatus.addStatus(Status.HOST_LINKED);
			supportedStatus.addStatus(Status.HOST_OK);
			supportedStatus.addStatus(Status.HOST_PENDINGCREATE);
			supportedStatus.addStatus(Status.HOST_PENDINGDELETE);
			supportedStatus.addStatus(Status.HOST_PENDINGTRANSFER);
			supportedStatus.addStatus(Status.HOST_PENDINGUPDATE);
			host.setSupportedStatus(supportedStatus);
		} // Host Attribute Model
		else {
			host.setInternal(new EPPRegistryInternalHost(5, 15, null, Boolean.FALSE));
			host.setExternal(new EPPRegistryExternalHost(2, 12, null, Boolean.FALSE));
		}

		host.setNameRegex(new EPPRegistryRegex("^.*$"));
		host.addInvalidIP("http://www.example.com/invalidip-1.txt");
		host.addInvalidIP("http://www.example.com/invalidip-2.txt");

		return host;
	}

	/**
	 * Builds the contact object policy information for a zone.
	 *
	 * @param aSeq
	 *           Unique zone sequence number
	 * @return Domain object policy information
	 */
	private static EPPRegistryContact buildContact(int aSeq) {
		EPPRegistryContact contact = new EPPRegistryContact();

		contact.setContactIdRegex(new EPPRegistryRegex("^.*$"));
		contact.setContactIdPrefix("EX");
		contact.setSharePolicy(EPPRegistryContact.SharePolicy.perZone);

		contact.setIntPostalInfoTypeSupport(EPPRegistryContact.PostalInfoTypeSupport.locOrIntSupport);

		contact.setAuthInfoRegex(new EPPRegistryRegex("^.*$", "exp"));

		contact.setMaxCheckContact(Integer.valueOf(15));

		EPPRegistryPostal postalInfo = new EPPRegistryPostal();
		postalInfo.setLocCharRegex(new EPPRegistryRegex("^.*$"));
		postalInfo.setName(new EPPRegistryContactName(6, 16));
		postalInfo.setOrg(new EPPRegistryContactOrg(2, 12));
		postalInfo.setVoiceRequired(Boolean.TRUE);
		postalInfo.setVoiceExt(new EPPRegistryMinMaxLength(5, 15));
		postalInfo.setFaxExt(new EPPRegistryMinMaxLength(5, 15));
		new ArrayList();
		postalInfo.setEmailRegex(new EPPRegistryRegex("^.+\\..+$"));

		EPPRegistryContactAddress address = new EPPRegistryContactAddress();
		address.setStreet(new EPPRegistryContactStreet(2, 12, 0, 3));
		address.setCity(new EPPRegistryContactCity(5, 15));
		address.setStateProvince(new EPPRegistryContactStateProvince(1, 11));
		address.setPostalCode(new EPPRegistryContactPostalCode(2, 12));

		postalInfo.setAddress(address);

		contact.setMaxCheckContact(Integer.valueOf(5));

		contact.setPostalInfo(postalInfo);

		EPPRegistrySupportedStatus supportedStatus = new EPPRegistrySupportedStatus();
		supportedStatus.addStatus(Status.CONTACT_CLIENTDELETEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_SERVERDELETEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_CLIENTTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_SERVERTRANSFERPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_CLIENTUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_SERVERUPDATEPROHIBITED);
		supportedStatus.addStatus(Status.CONTACT_LINKED);
		supportedStatus.addStatus(Status.CONTACT_OK);
		supportedStatus.addStatus(Status.CONTACT_PENDINGCREATE);
		supportedStatus.addStatus(Status.CONTACT_PENDINGDELETE);
		supportedStatus.addStatus(Status.CONTACT_PENDINGTRANSFER);
		supportedStatus.addStatus(Status.CONTACT_PENDINGUPDATE);
		contact.setSupportedStatus(supportedStatus);

		return contact;
	}

}
