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

org.dellroad.stuff.vaadin23.servlet.SimpleSpringServlet Maven / Gradle / Ivy

The newest version!

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

package org.dellroad.stuff.vaadin23.servlet;

import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.ServiceException;
import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinServletService;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.spring.SpringVaadinServletService;
import com.vaadin.flow.spring.VaadinScopesConfig;
import com.vaadin.flow.spring.annotation.EnableVaadin;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.dellroad.stuff.spring.Springleton;
import org.dellroad.stuff.vaadin23.data.VaadinSessionDataProvider;
import org.dellroad.stuff.vaadin23.util.VaadinUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.web.context.ContextLoaderListener;

/**
 * A variant of {@link com.vaadin.flow.spring.SpringServlet} that doesn't require Spring Boot, supports traditional
 * XML configuration for Spring and servlets via {@code web.xml}, and adds support for tracking and limiting sessions.
 *
 * 
 * 
 * 
 *
 * 

* This class needs a way to find the {@link ApplicationContext}; this is done by {@link #findApplicationContext}. * The implementation in this class obtains the {@link ApplicationContext} from the * a {@link Springleton} bean which must be declared within it. * *

* In addition, your {@link ApplicationContext} may need to have a bean with an {@link EnableVaadin @EnableVaadin} * annotation, in order to bring in other beans required by Vaadin Spring, such as {@link VaadinScopesConfig}. * *

* You can accomplish both of these tasks by including a class like this in your application context: * *


 * import com.vaadin.flow.spring.annotation.EnableVaadin;
 * import org.dellroad.stuff.spring.Springleton;
 *
 * @EnableVaadin
 * public class MySpringleton extends Springleton {
 *
 *     public static MySpringleton getInstance() {
 *         return (MySpringleton)Springleton.getInstance();
 *     }
 * }
 * 
* *

* You may also need to include an {@link AppShellConfigurator} bean with the appropriate application-wide annotations. * *

* The {@link ApplicationContext} can be created however you want; traditionally, this is done via {@link ContextLoaderListener}. * *

* Note: Unlike {@link com.vaadin.flow.spring.SpringServlet}, this servlet does not pull servlet parameters like {@code FooBar} * from corresponding {@code vaadin.foo_bar} variables found in the {@link ApplicationContext}'s {@link Environment}. * *

* The servlet associated with the current thread can be found via {@link #forCurrentSession}. * *

* Additional supported servlet parameters: *

* * * * * * * * * * * * * * * * *
Parameter NameRequired?Description
{@code sessionTracking}No * Boolean value that configures whether to track Vaadin sessions; default {@code false}. * If set to {@code true}, then {@link #getSessions} can be used to access all active sessions. * Session tracking should not be used unless sessions are normally kept in memory; e.g., don't use session tracking * when sessions are being serialized and persisted, or when this servlet may be serialized. * See also {@link VaadinSessionDataProvider}. *
{@code maxSessions}No * Configures a limit on the number of simultaneous Vaadin sessions that may exist at one time. Going over this * limit will result in a {@link ServiceException} being thrown. A zero or negative number * means there is no limit (this is the default). *
*
* * @see VaadinSessionDataProvider * @see vaadin-spring issue #560 */ public class SimpleSpringServlet extends VaadinServlet { /** * Servlet initialization parameter ({@value #SESSION_TRACKING_PARAMETER}) that enables * tracking of all Vaadin session. * *

* This parameter is optional, and defaults to false. */ public static final String SESSION_TRACKING_PARAMETER = "sessionTracking"; /** * Servlet initialization parameter ({@value #MAX_SESSIONS_PARAMETER}) that configures the * maximum number of simultaneous Vaadin sessions. Requires {@link #SESSION_TRACKING_PARAMETER} to be set to {@code true}. * This parameter is optional, and defaults to zero, which means no limit. */ public static final String MAX_SESSIONS_PARAMETER = "maxSessions"; private static final long serialVersionUID = 2837283478619038364L; private transient AtomicInteger sessionCounter = new AtomicInteger(); // We use weak references here to avoid possible Vaadin bugs private transient WeakHashMap liveSessions = new WeakHashMap<>(); // Methods /** * Get the current count of active {@link VaadinSession}s associated with this instance. * * @return current number of sessions */ public int getSessionCount() { return this.sessionCounter.get(); } /** * Get all live {@link VaadinSession}s associated with this servlet. * * @return live tracked sessions, or an empty collection if session tracking is not enabled * @see VaadinSessionDataProvider * @throws IllegalStateException if this servlet instance has been serialized */ public List getSessions() { if (this.liveSessions == null) throw new IllegalStateException("servlet was serialized"); synchronized (this.liveSessions) { return this.liveSessions.keySet().stream() .filter(Objects::nonNull) // probably not needed, but just in case .collect(Collectors.toList()); } } /** * Find the root {@link ApplicationContext} to be used by this servlet. * *

* The implementation in {@link SimpleSpringServlet} delegates to {@link Springleton#getApplicationContext}. * * @return the {@link ApplicationContext} for this servlet to use * @throws IllegalStateException if no application context can be found or has been established yet */ protected ApplicationContext findApplicationContext() { return Springleton.getInstance().getApplicationContext(); } // VaadinServlet @Override protected VaadinServletService createServletService(DeploymentConfiguration deploymentConfiguration) throws ServiceException { // Get session tracking parameters final Properties params = deploymentConfiguration.getInitParameters(); final boolean sessionTracking = SimpleSpringServlet.isSessionTracking(params); final int maxSessions = SimpleSpringServlet.getMaxSessions(params); // Get application context final ApplicationContext context = this.findApplicationContext(); // Return a VaadinServletService that tracks sessions (if enabled) @SuppressWarnings("serial") final VaadinServletService service = new SpringVaadinServletService(this, deploymentConfiguration, context) { @Override protected VaadinSession createVaadinSession(VaadinRequest request) { // Increment counter final int counterCount = SimpleSpringServlet.this.sessionCounter.incrementAndGet(); // Check max number of sessions if (maxSessions > 0 && counterCount > maxSessions) { SimpleSpringServlet.this.sessionCounter.decrementAndGet(); throw new RuntimeException("The maximum number of active sessions (" + maxSessions + ") has been reached"); } // Create new session final VaadinSession session = super.createVaadinSession(request); // Track session, if enabled if (sessionTracking && SimpleSpringServlet.this.liveSessions != null) { synchronized (SimpleSpringServlet.this.liveSessions) { SimpleSpringServlet.this.liveSessions.put(session, null); } } // Done return session; } @Override public void fireSessionDestroy(VaadinSession session) { // Decrement counter SimpleSpringServlet.this.sessionCounter.decrementAndGet(); // Untrack ssession, if enabled if (sessionTracking && SimpleSpringServlet.this.liveSessions != null) { synchronized (SimpleSpringServlet.this.liveSessions) { SimpleSpringServlet.this.liveSessions.remove(session); } } // Proceed super.fireSessionDestroy(session); } }; service.init(); return service; } /** * Get the {@link SimpleSpringServlet} that is associated with the given {@link VaadinSession}. * * @param session Vaadin session * @return the assocated {@link SimpleSpringServlet} * @throws IllegalStateException if the {@link VaadinServlet} associated with {@code session} is not a * {@link SimpleSpringServlet} * @throws IllegalArgumentException if {@code session} is null */ public static SimpleSpringServlet forSession(VaadinSession session) { if (session == null) throw new IllegalArgumentException("null session"); if (!(session.getService() instanceof VaadinServletService)) throw new IllegalStateException("the VaadinService associated with the session is not a VaadinServletService instance"); final VaadinServletService service = (VaadinServletService)session.getService(); if (!(service.getServlet() instanceof SimpleSpringServlet)) throw new IllegalStateException("the VaadinServlet associated with the session is not a SimpleSpringServlet instance"); return (SimpleSpringServlet)service.getServlet(); } /** * Get the {@link SimpleSpringServlet} that is associated with the current {@link VaadinSession}. * * @return the {@link SimpleSpringServlet} associated with the current thread * @throws IllegalStateException if there is no {@link VaadinSession} associated with the current thread * @throws IllegalStateException if the current {@link VaadinServlet} is not a {@link SimpleSpringServlet} */ public static SimpleSpringServlet forCurrentSession() { return SimpleSpringServlet.forSession(VaadinUtil.getCurrentSession()); } // Utility methods private static boolean isSessionTracking(Properties params) { return Boolean.valueOf(params.getProperty(SESSION_TRACKING_PARAMETER)); } private static int getMaxSessions(Properties params) { try { return Integer.parseInt(params.getProperty(MAX_SESSIONS_PARAMETER)); } catch (Exception e) { return 0; } } // Serialization private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } // If we are (de)serialized, we just forget all the sessions private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { input.defaultReadObject(); this.sessionCounter = new AtomicInteger(); this.liveSessions = new WeakHashMap<>(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy