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

org.dellroad.stuff.vaadin7.VaadinSessionContainer Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2022 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.stuff.vaadin7;

import com.vaadin.server.SessionDestroyEvent;
import com.vaadin.server.SessionDestroyListener;
import com.vaadin.server.SessionInitEvent;
import com.vaadin.server.SessionInitListener;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

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

/**
 * Container containing active {@link VaadinSession}s.
 *
 * 

* This class is useful when you need to display sessions in your GUI, for example, a table showing all logged-in users. * Due to the fact that each {@link VaadinSession} has its own lock, building such a container without race conditions * and deadlocks is somewhat tricky. This class performs the locking required during updates and provides thread-safe * {@link #update} and {@link #reload} methods. Information about active {@link VaadinSession}s comes from the session * tracking feature of the {@link SpringVaadinServlet}. * *

* By subclassing this class and {@link VaadinSessionInfo}, additional session-related properties, * such as logged-in user, can be added to the container by annotating the {@link VaadinSessionInfo} * subclass with {@link ProvidesProperty @ProvidesProperty} annotations. * *

* Note the following: *

    *
  • This class only works with configurations where Vaadin sessions are stored in memory, * and the {@link SpringVaadinServlet} must be used and configured with init parameter * {@link SpringVaadinServlet#SESSION_TRACKING_PARAMETER} set to {@code true}.
  • *
  • The {@link #connect} and {@link #disconnect} methods must be invoked before and after (respectively) * this container is used; typically these would be invoked in the {@link com.vaadin.ui.Component#attach} * and {@link com.vaadin.ui.Component#detach} methods of a corresponding widget. *
  • Connected instances retain a strong reference to each {@link VaadinSession} therein. * Therefore, to avoid leaking closed sessions, instances should be periodically {@linkplain #reload reloaded}. *
* * @param the type of the Java objects that back each {@link com.vaadin.data.Item} in the container */ @SuppressWarnings("serial") public abstract class VaadinSessionContainer extends SelfKeyedContainer { /** * The {@link VaadinSession} that this container instance is associated with. * Determined by whatever {@link VaadinSession} is associated with the current thread at construction time. */ protected final VaadinSession session; private final Logger log = LoggerFactory.getLogger(this.getClass()); private final SessionEventListener listener = new SessionEventListener(); /** * Constructor. * *

* Properties will be determined by the {@link ProvidesProperty @ProvidesProperty} and * {@link ProvidesPropertySort @ProvidesPropertySort} annotated methods in the given class. * * @param type class to introspect for annotated methods * @throws IllegalArgumentException if {@code type} is null * @throws IllegalArgumentException if {@code type} has two {@link ProvidesProperty @ProvidesProperty} * or {@link ProvidesPropertySort @ProvidesPropertySort} annotated methods for the same property * @throws IllegalArgumentException if a {@link ProvidesProperty @ProvidesProperty}-annotated method with no * {@linkplain ProvidesProperty#value property name specified} has a name which cannot be interpreted as a bean * property "getter" method * @throws IllegalStateException if there is no {@link VaadinSession} associated with the current thread * @throws IllegalStateException if there is no {@link SpringVaadinServlet} associated * with the current thread's {@link VaadinSession} * @see ProvidesProperty * @see ProvidesPropertySort * @see ProvidesPropertyScanner */ protected VaadinSessionContainer(Class type) { super(type); this.session = VaadinUtil.getCurrentSession(); SpringVaadinServlet.getServlet(this.session); } /** * Constructor. * * @param propertyExtractor used to extract properties from the underlying Java objects; * may be null but then container is not usable until one is configured via * {@link #setPropertyExtractor setPropertyExtractor()} * @param propertyDefs container property definitions; null is treated like the empty set */ protected VaadinSessionContainer(PropertyExtractor propertyExtractor, Collection> propertyDefs) { super(propertyExtractor, propertyDefs); this.session = VaadinUtil.getCurrentSession(); SpringVaadinServlet.getServlet(this.session); } /** * Connect this container and start tracking sessions. */ @Override public void connect() { super.connect(); this.listener.register(); this.reload(); } /** * Disconnect this container and stop tracking sessions. */ @Override public void disconnect() { this.listener.unregister(); this.load(Collections.emptyList()); // avoid referencing other sesssions while disconnected super.disconnect(); } /** * Asynchronously reload this container. * *

* This method can be invoked from any thread. It creates a new thread to do the actual * reloading via {@link #doReload} to avoid potential deadlocks. */ public void reload() { new Thread("VaadinSessionContainer.reload()") { @Override public void run() { try { VaadinSessionContainer.this.doReload(); } catch (ThreadDeath t) { throw t; } catch (Throwable t) { VaadinSessionContainer.this.log.error("error reloading container " + this, t); } } }.start(); } /** * Asynchronously update this container's items. * *

* This method can be invoked from any thread. It creates a new thread to do the actual * updating via {@link #doUpdate} to avoid potential deadlocks. */ public void update() { new Thread("VaadinSessionContainer.update()") { @Override public void run() { try { VaadinSessionContainer.this.doUpdate(); } catch (ThreadDeath t) { throw t; } catch (Throwable t) { VaadinSessionContainer.this.log.error("error updating container " + this, t); } } }.start(); } /** * Update each {@link VaadinSessionInfo} instance in this container. * Using this method is more efficient than reloading the entire container. * *

* This method handles the complicated locking required to avoid deadlocks: first, for each {@link VaadinSessionInfo}, * {@link VaadinSessionInfo#updateInformation} is invoked while the {@link VaadinSession} * corresponding to that {@link VaadinSessionInfo} instance is locked, so that information from that session * can be safely gathered; then, {@link VaadinSessionInfo#makeUpdatesVisible} is invoked while the {@link VaadinSession} * associated with this container is locked, so item properties can be safely updated, etc. * *

* This method must not be invoked while any {@link VaadinSession} is locked. * For example, it may be invoked by a regular timer (only while this container is {@linkplain #connect connected}). * * @throws IllegalStateException if there is a locked {@link VaadinSession} associated with the current thread */ protected void doUpdate() { // Sanity check if (VaadinSession.getCurrent() != null && VaadinSession.getCurrent().hasLock()) throw new IllegalStateException("inside locked VaadinSession"); // Snapshot the set of sessions in this container while holding the lock to this container's session final ArrayList sessionInfoList = new ArrayList<>(); VaadinSessionContainer.this.session.accessSynchronously(new Runnable() { @Override public void run() { sessionInfoList.addAll(VaadinSessionContainer.this.getItemIds()); } }); // Update each session's information while holding that session's lock for (T sessionInfo : sessionInfoList) { final T sessionInfo2 = sessionInfo; sessionInfo.getVaadinSession().accessSynchronously(new Runnable() { @Override public void run() { sessionInfo2.updateInformation(); } }); } // Now update this container with the newly gathered information while again holding the lock to this container's session VaadinSessionContainer.this.session.accessSynchronously(new Runnable() { @Override public void run() { for (T sessionInfo : sessionInfoList) sessionInfo.makeUpdatesVisible(); } }); } /** * Reload this container. Reloads this container with {@link VaadinSessionInfo} instances for each {@link VaadinSession} * (created via {@link #createVaadinSessionInfo}), and then invokes {@link #doUpdate}. * *

* This method must not be invoked while any {@link VaadinSession} is locked. * * @throws IllegalStateException if there is a locked {@link VaadinSession} associated with the current thread */ protected void doReload() { // Sanity check if (VaadinSession.getCurrent() != null && VaadinSession.getCurrent().hasLock()) throw new IllegalStateException("inside locked VaadinSession"); // Create a VaadinSessionInfo object for each session, but doing so while that session is locked final ArrayList sessionInfoList = new ArrayList<>(); for (VaadinSession otherSession : SpringVaadinServlet.getServlet(this.session).getSessions()) { otherSession.accessSynchronously(new Runnable() { @Override public void run() { sessionInfoList.add(VaadinSessionContainer.this.createVaadinSessionInfo()); } }); } // Reload this container this.session.access(new Runnable() { @Override public void run() { VaadinSessionContainer.this.load(sessionInfoList); } }); // Update this container this.doUpdate(); } /** * Create a {@link VaadinSessionInfo} backing object for the {@link VaadinSession} associated with the current thread. * The {@link VaadinSession} will be already locked. * * @return {@link VaadinSessionInfo} backing object for the current session */ protected abstract T createVaadinSessionInfo(); // SessionEventListener private class SessionEventListener extends VaadinExternalListener implements SessionInitListener, SessionDestroyListener { SessionEventListener() { super(VaadinUtil.getCurrentSession().getService()); } @Override public void sessionInit(SessionInitEvent event) { VaadinSessionContainer.this.reload(); } @Override public void sessionDestroy(SessionDestroyEvent event) { VaadinSessionContainer.this.reload(); } @Override protected void register(VaadinService vaadinService) { vaadinService.addSessionInitListener(this); vaadinService.addSessionDestroyListener(this); } @Override protected void unregister(VaadinService vaadinService) { vaadinService.removeSessionInitListener(this); vaadinService.removeSessionDestroyListener(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy