/*******************************************************************************
 * Copyright (c) 2018 Liferay, Inc.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Liferay, Inc. - Bug 530063 - CNFE when session replication
 *                    is used with equinox.http.servlet in bridge mode
 ******************************************************************************/

package org.eclipse.equinox.http.servlet.internal.servlet;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.*;
import org.eclipse.equinox.http.servlet.internal.HttpServiceRuntimeImpl;
import org.eclipse.equinox.http.servlet.internal.context.ContextController;
import org.eclipse.equinox.http.servlet.internal.util.EventListeners;
import org.eclipse.equinox.http.servlet.session.HttpSessionInvalidator;

/**
 * @since 1.5
 */
public class HttpSessionTracker implements HttpSessionInvalidator {

	public HttpSessionTracker(HttpServiceRuntimeImpl httpServiceRuntime) {
		this.httpServiceRuntime = httpServiceRuntime;
	}

	@Override
	public void invalidate(String sessionId, boolean invalidateParent) {
		Set<HttpSessionAdaptor> httpSessionAdaptors =
			httpSessionAdaptorsMap.remove(sessionId);

		if (httpSessionAdaptors == null) {
			return;
		}

		for (HttpSessionAdaptor httpSessionAdaptor : httpSessionAdaptors) {
			ContextController contextController =
				httpSessionAdaptor.getController();

			EventListeners eventListeners =
				contextController.getEventListeners();

			List<HttpSessionListener> httpSessionListeners = eventListeners.get(
				HttpSessionListener.class);

			if (!httpSessionListeners.isEmpty()) {
				HttpSessionEvent httpSessionEvent = new HttpSessionEvent(
					httpSessionAdaptor);

				for (HttpSessionListener listener : httpSessionListeners) {
					try {
						listener.sessionDestroyed(httpSessionEvent);
					}
					catch (IllegalStateException ise) {
						// outer session is already invalidated
					}
				}
			}

			List<HttpSessionAttributeListener> httpSessionAttributeListeners =
				eventListeners.get(HttpSessionAttributeListener.class);

			if (!httpSessionListeners.isEmpty()) {
				Enumeration<String> enumeration =
					httpSessionAdaptor.getAttributeNames();

				while (enumeration.hasMoreElements()) {
					HttpSessionBindingEvent httpSessionBindingEvent =
						new HttpSessionBindingEvent(
							httpSessionAdaptor, enumeration.nextElement());

					for (HttpSessionAttributeListener
							httpSessionAttributeListener :
								httpSessionAttributeListeners) {

						httpSessionAttributeListener.attributeRemoved(
							httpSessionBindingEvent);
					}
				}
			}

			contextController.removeActiveSession(
				httpSessionAdaptor.getSession());

			if (invalidateParent) {
				try {
					httpSessionAdaptor.getSession().invalidate();
				}
				catch (IllegalStateException ise) {
					httpServiceRuntime.log(
						"Session was already invalidated!", ise); //$NON-NLS-1$
				}
			}
		}
	}

	public void addHttpSessionAdaptor(
		String sessionId, HttpSessionAdaptor httpSessionAdaptor) {

		Set<HttpSessionAdaptor> httpSessionAdaptors =
			httpSessionAdaptorsMap.get(sessionId);

		if (httpSessionAdaptors == null) {
			httpSessionAdaptors = Collections.newSetFromMap(
				new ConcurrentHashMap<HttpSessionAdaptor, Boolean>());

			Set<HttpSessionAdaptor> previousHttpSessionAdaptors =
				httpSessionAdaptorsMap.putIfAbsent(
					sessionId, httpSessionAdaptors);

			if (previousHttpSessionAdaptors != null) {
				httpSessionAdaptors = previousHttpSessionAdaptors;
			}
		}

		httpSessionAdaptors.add(httpSessionAdaptor);
	}

	public void clear() {
		// At this point there should be no left over sessions. If
		// there are we'll log it because there's some kind of leak.
		if (!httpSessionAdaptorsMap.isEmpty()) {
			httpServiceRuntime.log(
				"There are HttpSessionAdaptors left over. There might be a context or session leak!"); //$NON-NLS-1$
		}
	}

	public boolean removeHttpSessionAdaptor(
		String sessionId, HttpSessionAdaptor httpSessionAdaptor) {

		Set<HttpSessionAdaptor> httpSessionAdaptors =
			httpSessionAdaptorsMap.get(sessionId);

		if (httpSessionAdaptors == null) {
			return false;
		}

		try {
			return httpSessionAdaptors.remove(httpSessionAdaptor);
		}
		finally {
			if (httpSessionAdaptors.isEmpty()) {
				httpSessionAdaptorsMap.remove(sessionId, httpSessionAdaptors);
			}
		}
	}

	private final ConcurrentMap<String, Set<HttpSessionAdaptor>>
		httpSessionAdaptorsMap =
			new ConcurrentHashMap<String, Set<HttpSessionAdaptor>>();
	private final HttpServiceRuntimeImpl httpServiceRuntime;

}