/***********************************************************
Copyright (C) 2024 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

Quic://www.verisign.com/nds/naming/namestore/techdocs.html
***********************************************************/
package com.verisign.epp.pool;

import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.verisign.epp.interfaces.EPPCommandException;
import com.verisign.epp.interfaces.EPPSession;

/**
 * Customized {@code EPPSessionPoolableFactory} for creating a pool of
 * {@link com.verisign.epp.interfaces.EPPQuicSession} objects.
 */
public class EPPQuicSessionPoolableFactory extends EPPSessionPoolableFactory
      implements PooledObjectFactory<EPPPooledSession> {

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


	/**
	 * Default constructor. 
	 */
	public EPPQuicSessionPoolableFactory() {
		super();
	}

	/**
	 * Create an EPP session poolable factory with the client id, password used
	 * to authenticate the session. The kind of EPP session created default to
	 * {@code KIND_GENERIC}.
	 *
	 * @param aClientId
	 *           Login id used to authenticate
	 * @param aPassword
	 *           Password used to authenticate
	 * @param aAbsoluteTimeout
	 *           Session absolute timeout
	 * @param aIdleTimeout
	 *           Session idle timeout
	 */
	public EPPQuicSessionPoolableFactory(String aClientId, String aPassword, long aAbsoluteTimeout, long aIdleTimeout) {
		super.clientId = aClientId;
		super.password = aPassword;
		super.absoluteTimeout = aAbsoluteTimeout;
		super.idleTimeout = aIdleTimeout;
	}

	/**
	 * Create an EPP session poolable factory with the client id, password, and
	 * target URL for an Quic connection. The session kind is automatically set
	 * to {@code KIND_Quic}.
	 *
	 * @param aClientId
	 *           Login id used to authenticate
	 * @param aPassword
	 *           Password used to authenticate
	 * @param aMinAbsoluteTimeout
	 *           Session minimum absolute timeout
	 * @param aMaxAbsoluteTimeout
	 *           Session maximum absolute timeout
	 * @param aIdleTimeout
	 *           Session idle timeout
	 * @param aServerName
	 *           Server URL name (e.g., Quics://epp.example.com)
	 */
	public EPPQuicSessionPoolableFactory(String aClientId, String aPassword, long aMinAbsoluteTimeout,
	      long aMaxAbsoluteTimeout, long aIdleTimeout, String aServerName) {
		super.clientId = aClientId;
		super.password = aPassword;
		super.minAbsoluteTimeout = aMinAbsoluteTimeout;
		super.maxAbsoluteTimeout = aMaxAbsoluteTimeout;
		super.idleTimeout = aIdleTimeout;
		super.serverName = aServerName;
	}

	/**
	 * Session being borrowed from the pool.
	 *
	 * @param aSession
	 *           Session being returned
	 */
	@Override
	public void activateObject(PooledObject<EPPPooledSession> aSession) throws Exception {
		// Nothing for now
	}

	/**
	 * Destroy object from the pool.
	 *
	 * @param aSession
	 *           Session being destroyed
	 */
	@Override
	public void destroyObject(PooledObject<EPPPooledSession> aSession) throws Exception {
		log.debug("destroyObject(): enter, EPPPooledSessionession id = " + aSession.getObject());

		// Automatically end the session?
		if (this.initSessionOnMake) {
			EPPSession theSession = (EPPSession) aSession.getObject();

			// Try to end the session gracefully
			try {
				// Set the client transaction identifier?
				if (this.clientTransIdGenerator != null) {
					theSession.setTransId(this.clientTransIdGenerator.genClientTransId());
				}

				log.debug("destroyObject(): Calling end session");
				theSession.endSession();
			}
			catch (Exception ex) {
				log.debug("destroyObject(): Exception ending session: " + ex);
			}
		}
		else {
			log.debug("destroyObject(): initSessionOnMake is false, so don't end the session automatically on destroy");
		}
	}

	/**
	 * Creates a new session object.
	 */
	@Override
	public PooledObject<EPPPooledSession> makeObject() throws Exception {

		log.debug("makeObject(): enter");

		EPPSession theSession = (EPPSession) this.makeSession();

		log.debug("makeObject(): Make session with id = " + theSession);

		theSession.setClientID(this.clientId);
		theSession.setPassword(this.password);

		// Set the client transaction identifier?
		if (this.clientTransIdGenerator != null) {
			theSession.setTransId(this.clientTransIdGenerator.genClientTransId());
		}

		// Initialize the session via an EPP login?
		if (this.initSessionOnMake) {

			log.debug("makeObject(): establishing session, with session class " + theSession.getClass().getName());

			// Establish authenticated session
			try {
				theSession.initSession();

				log.debug("makeObject(): established session, with session class " + theSession.getClass().getName());
			}
			catch (EPPCommandException ex) {
				log.error("makeObject(): error initializing session " + theSession.getClass().getName() + ": " + ex);

				// Ensure that the connection is closed
				try {
					theSession.endConnection();
				}
				catch (EPPCommandException ex1) {
					// Ignore
				}

				throw ex;
			}
		}

		log.debug("makeObject(): exit");
		return new DefaultPooledObject<>((EPPPooledSession) theSession);
	}

	/**
	 * Session is being returned to the pool.
	 *
	 * @param aSession
	 *           Session being returned
	 */
	@Override
	public void passivateObject(PooledObject<EPPPooledSession> aSession) throws Exception {
		// Nothing for now
	}

	/**
	 * Validates a session by sending a keep alive. If an exception occurs from
	 * the keep alive, than the session is not valid.
	 *
	 * @param aSession
	 *           Session to validate
	 *
	 * @return {@code true} if the session is valid; {@code false} otherwise.
	 */
	@Override
	public boolean validateObject(PooledObject<EPPPooledSession> aSession) {
		log.debug("validateObject(): enter, session id = " + aSession);
		EPPPooledSession theSession = aSession.getObject();
		long currentTime = System.currentTimeMillis();
		boolean isValid;

		try {
			// Is session past absolute timeout?
			if (currentTime - theSession.getCreatedTime() > theSession.getAbsoluteTimeout()) {
				log.debug("validateObject(): session id = " + aSession + " is past absolute timeout");
				isValid = false;
			} // Idle timeout?
			else if (System.currentTimeMillis() - theSession.getLastTouched() > this.getIdleTimeout()) {
				log.debug("validateObject(): session id = " + aSession + " is past idle timeout, sending hello");
				theSession.hello();
				theSession.touch();
				isValid = true;
			}
			else {
				log.debug("validateObject(): session id = " + aSession + " is valid");
				isValid = true;
			}

		}
		catch (Exception ex) {
			log.debug("validateObject(): session id = " + aSession + " caused Exception: " + ex);
			isValid = false;
		}

		log.debug("validateObject(): exit, isValid = " + isValid);
		return isValid;
	}

	/**
	 * Make an Quic EPP session instance for pool.
	 *
	 * @return {@code EPPQuicSession} instance
   * @throws Exception if something goes wrong
	 */
	protected EPPPooledSession makeSession() throws Exception {
		log.debug("makeSession(): enter");
		String theServerName = this.serverName;
		if (theServerName != null) {
			log.debug("makeSession(): Creating with specified Server Name URL " + theServerName);
			return new EPPPooledGenericQuicSession(theServerName);
		}
		else {
			log.debug("makeSession(): Creating with default Server Name URL");
			return new EPPPooledGenericQuicSession();
		}
	}

}
