All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.clustering.tomcat.catalina.DistributableSession Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package org.wildfly.clustering.tomcat.catalina;

import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionIdListener;

import org.apache.catalina.Context;
import org.apache.catalina.Manager;
import org.apache.catalina.SessionListener;
import org.wildfly.clustering.cache.batch.Batch;
import org.wildfly.clustering.cache.batch.BatchContext;
import org.wildfly.clustering.cache.batch.SuspendedBatch;
import org.wildfly.clustering.session.Session;

/**
 * Adapts a WildFly distributable Session to Tomcat's Session interface.
 * @author Paul Ferraro
 */
public class DistributableSession implements CatalinaSession {

	private final CatalinaManager manager;
	private final AtomicReference> session;
	private final String internalId;
	private final SuspendedBatch batch;
	private final Runnable invalidateAction;
	private final Instant startTime;

	public DistributableSession(CatalinaManager manager, Session session, String internalId, SuspendedBatch batch, Runnable invalidateAction) {
		this.manager = manager;
		this.session = new AtomicReference<>(session);
		this.internalId = internalId;
		this.batch = batch;
		this.invalidateAction = invalidateAction;
		this.startTime = session.getMetaData().isNew() ? session.getMetaData().getCreationTime() : Instant.now();
	}

	@Override
	public boolean isNew() {
		return this.session.get().getMetaData().isNew();
	}

	@Override
	public String getAuthType() {
		return this.session.get().getContext().getAuthType();
	}

	@Override
	public void setAuthType(String authType) {
		this.session.get().getContext().setAuthType(authType);
	}

	@Override
	public long getCreationTime() {
		Session session = this.session.get();
		try {
			return session.getMetaData().getCreationTime().toEpochMilli();
		} catch (IllegalStateException e) {
			// If session was invalidated by a concurrent request, Tomcat may not trigger Session.endAccess(), so we need to close the session here
			if (!session.isValid()) {
				session.close();
			}
			throw e;
		}
	}

	@Override
	public String getId() {
		return this.session.get().getId();
	}

	@Override
	public String getIdInternal() {
		return this.internalId;
	}

	@Override
	public long getLastAccessedTime() {
		Session session = this.session.get();
		try {
			return session.getMetaData().getLastAccessStartTime().toEpochMilli();
		} catch (IllegalStateException e) {
			// If session was invalidated by a concurrent request, Tomcat may not trigger Session.endAccess(), so we need to close the session here
			if (!session.isValid()) {
				session.close();
			}
			throw e;
		}
	}

	@Override
	public Manager getManager() {
		return this.manager;
	}

	@Override
	public int getMaxInactiveInterval() {
		Session session = this.session.get();
		try {
			return (int) session.getMetaData().getTimeout().getSeconds();
		} catch (IllegalStateException e) {
			// If session was invalidated by a concurrent request, Tomcat may not trigger Session.endAccess(), so we need to close the session here
			if (!session.isValid()) {
				session.close();
			}
			throw e;
		}
	}

	@Override
	public void setMaxInactiveInterval(int interval) {
		Session session = this.session.get();
		try {
			session.getMetaData().setTimeout((interval > 0) ? Duration.ofSeconds(interval) : Duration.ZERO);
		} catch (IllegalStateException e) {
			// If session was invalidated by a concurrent request, Tomcat may not trigger Session.endAccess(), so we need to close the session here
			if (!session.isValid()) {
				session.close();
			}
			throw e;
		}
	}

	@Override
	public Principal getPrincipal() {
		return this.session.get().getContext().getPrincipal();
	}

	@Override
	public void setPrincipal(Principal principal) {
		this.session.get().getContext().setPrincipal(principal);
	}

	@Override
	public HttpSession getSession() {
		return new HttpSessionAdapter<>(this.session, this.manager, this.batch, this.invalidateAction);
	}

	@Override
	public boolean isValid() {
		return this.session.get().isValid();
	}

	@Override
	public void addSessionListener(SessionListener listener) {
		this.session.get().getContext().getSessionListeners().add(listener);
	}

	@Override
	public void endAccess() {
		try (Batch batch = this.batch.resume()) {
			try (Session session = this.session.get()) {
				if (session.isValid()) {
					// According to §7.6 of the servlet specification:
					// The session is considered to be accessed when a request that is part of the session is first handled
					// by the servlet container.
					session.getMetaData().setLastAccess(this.startTime, Instant.now());
				}
			}
		} catch (Throwable e) {
			// Don't propagate exceptions at the stage, since response was already committed
			this.manager.getContext().getLogger().warn(e.getLocalizedMessage(), e);
		}
	}

	@Override
	public void expire() {
		// Expiration not handled here
		throw new IllegalStateException();
	}

	@Override
	public Object getNote(String name) {
		return this.session.get().getContext().getNotes().get(name);
	}

	@Override
	public Iterator getNoteNames() {
		return this.session.get().getContext().getNotes().keySet().iterator();
	}

	@Override
	public void removeNote(String name) {
		this.session.get().getContext().getNotes().remove(name);
	}

	@Override
	public void removeSessionListener(SessionListener listener) {
		this.session.get().getContext().getSessionListeners().remove(listener);
	}

	@Override
	public void setNote(String name, Object value) {
		this.session.get().getContext().getNotes().put(name, value);
	}

	@Override
	public void tellChangedSessionId(String newId, String oldId, boolean notifySessionListeners, boolean notifyContainerListeners) {
		try (BatchContext context = this.batch.resumeWithContext()) {
			Session oldSession = this.session.get();
			Session newSession = this.manager.getSessionManager().createSession(newId);
			try {
				for (Map.Entry entry : oldSession.getAttributes().entrySet()) {
					newSession.getAttributes().put(entry.getKey(), entry.getValue());
				}
				newSession.getMetaData().setTimeout(oldSession.getMetaData().getTimeout());
				newSession.getMetaData().setLastAccess(oldSession.getMetaData().getLastAccessStartTime(), oldSession.getMetaData().getLastAccessTime());
				newSession.getContext().setAuthType(oldSession.getContext().getAuthType());
				newSession.getContext().setPrincipal(oldSession.getContext().getPrincipal());
				this.session.set(newSession);
				oldSession.invalidate();
			} catch (IllegalStateException e) {
				if (!oldSession.isValid()) {
					// If session was invalidated by a concurrent request, Tomcat may not trigger Session.endAccess(), so we need to close the session here
					oldSession.close();
				}
				newSession.invalidate();
			}
		}

		// Invoke listeners outside of the context of the batch associated with this session
		Context context = this.manager.getContext();

		if (notifyContainerListeners) {
			context.fireContainerEvent(Context.CHANGE_SESSION_ID_EVENT, new String[] { oldId, newId });
		}

		if (notifySessionListeners) {
			HttpSessionEvent event = new HttpSessionEvent(this.getSession());
			Stream.of(context.getApplicationEventListeners()).filter(listener -> listener instanceof HttpSessionIdListener).map(listener -> (HttpSessionIdListener) listener).forEach(listener -> {
				try {
					listener.sessionIdChanged(event, oldId);
				} catch (Throwable e) {
					context.getLogger().warn(e.getMessage(), e);
				}
			});
		}
	}

	@Override
	public boolean isAttributeDistributable(String name, Object value) {
		return this.manager.getMarshallability().isMarshallable(value);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy