org.dellroad.stuff.vaadin7.SpringVaadinServlet Maven / Gradle / Ivy
Show all versions of dellroad-stuff-vaadin7 Show documentation
/*
* Copyright (C) 2022 Archie L. Cobbs. All rights reserved.
*/
package org.dellroad.stuff.vaadin7;
import com.vaadin.server.DeploymentConfiguration;
import com.vaadin.server.ServiceException;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinServletService;
import com.vaadin.server.VaadinSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.WeakHashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
/**
* A {@link VaadinServlet} that associates and manages a Spring
* {@link org.springframework.web.context.ConfigurableWebApplicationContext} with each
* {@link com.vaadin.server.VaadinSession} (aka, "Vaadin application" in the old terminology).
*
*
*
*
*
*
* The {@code configLocation} servlet parameter may be used to specify the Spring XML config
* file location(s). For example:
*
*
* <servlet>
* <servlet-name>My Vaadin App</servlet-name>
* <servlet-class>org.dellroad.stuff.vaadin7.SpringVaadinServlet</servlet-class>
* <init-param>
* <param-name>UI</param-name>
* <param-value>com.example.MyApplicationUI</param-value>
* </init-param>
* <init-param>
* <param-name>configLocation</param-name>
* <param-value>classpath:com/example/MyApplicationContext.xml</param-value>
* </init-param>
* </servlet>
*
*
*
* The main function of this servlet is to create and register a {@link SpringVaadinSessionListener} as a listener on the
* {@link com.vaadin.server.VaadinService} associated with this servlet. The {@link SpringVaadinSessionListener} in turn detects
* the creation and destruction of Vaadin application instances (represented by {@link com.vaadin.server.VaadinSession}
* instances) and does the work of managing the associated Spring application contexts.
*
*
* Use of this servlet in place of the standard Vaadin servlet is required for the {@link VaadinConfigurable @VaadinConfigurable}
* annotation to work.
*
*
* Supported servlet parameters:
*
*
*
* Parameter Name
* Required?
* Description
*
*
* {@code applicationName}
* No
*
* Vaadin application name. Used for logging purposes and as the name of the XML application context file
* when {@code configLocation} is not specified. If this parameter is not specified, the
* name of the servlet is used.
*
*
*
* {@code configLocation}
* No
*
* Location of Spring application context XML file(s). Multiple locations are separated by whitespace.
* If omitted, {@code /WEB-INF/ServletName.xml} is used, where {@code ServletName} is the name of the Vaadin
* application (see {@code applicationName}).
*
*
*
* {@code listenerClass}
* No
*
* Specify the name of a custom class extending {@link SpringVaadinSessionListener} and having the same constructor arguments.
* If omitted, {@link SpringVaadinSessionListener} is used.
*
*
*
* {@code sessionTracking}
* No
*
* Boolean value that configures whether the {@link SpringVaadinSessionListener} should 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. See also {@link VaadinSessionContainer}.
*
*
*
* {@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 com.vaadin.server.ServiceException} being thrown. A zero or negative number
* means there is no limit (this is the default). Ignored unless {@value #SESSION_TRACKING_PARAMETER} is set to {@code true}.
*
*
*
*
*
*
* Note: if any beans are declared {@code scope="session"} in your application context, you must register Spring's
* {@link org.springframework.web.context.request.RequestContextListener} as a listener in your {@code web.xml}
* (normally, Spring's {@link org.springframework.web.servlet.DispatcherServlet} would handle this task).
*
* @see SpringVaadinSessionListener
* @see VaadinConfigurable
* @see VaadinApplication
*/
@SuppressWarnings("serial")
public class SpringVaadinServlet extends VaadinServlet {
/**
* Servlet initialization parameter ({@value #CONFIG_LOCATION_PARAMETER}
) used to specify
* the location(s) of the Spring application context XML file(s). Multiple XML files may be separated by whitespace.
* This parameter is optional.
*/
public static final String CONFIG_LOCATION_PARAMETER = "configLocation";
/**
* Servlet initialization parameter ({@value #LISTENER_CLASS_PARAMETER}
) used to specify
* the name of an custom subclass of {@link SpringVaadinSessionListener}.
* This parameter is optional.
*/
public static final String LISTENER_CLASS_PARAMETER = "listenerClass";
/**
* Servlet initialization parameter ({@value #APPLICATION_NAME_PARAMETER}
) used to specify
* the name the application.
* This parameter is optional.
*/
public static final String APPLICATION_NAME_PARAMETER = "applicationName";
/**
* 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";
// We use weak references to avoid leaks caused by exceptions in SessionInitListeners; see http://dev.vaadin.com/ticket/12915
private final WeakHashMap liveSessions = new WeakHashMap<>();
private String servletName;
@Override
public void init(ServletConfig config) throws ServletException {
this.servletName = config.getServletName();
if (this.servletName == null)
throw new IllegalArgumentException("null servlet name");
super.init(config);
}
@Override
protected void servletInitialized() throws ServletException {
// Sanity check
if (this.servletName == null)
throw new IllegalArgumentException("servlet not initialized");
// Defer to superclass
super.servletInitialized();
// Get params
final VaadinServletService servletService = this.getService();
final Properties params = servletService.getDeploymentConfiguration().getInitParameters();
final String contextLocation = params.getProperty(CONFIG_LOCATION_PARAMETER);
final String listenerClassName = params.getProperty(LISTENER_CLASS_PARAMETER);
String applicationName = params.getProperty(APPLICATION_NAME_PARAMETER);
if (applicationName == null)
applicationName = this.servletName;
// Detect listener class to use
Class extends SpringVaadinSessionListener> listenerClass = SpringVaadinSessionListener.class;
if (listenerClassName != null) {
try {
listenerClass = Class.forName(listenerClassName, false, Thread.currentThread().getContextClassLoader())
.asSubclass(SpringVaadinSessionListener.class);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new ServletException("error finding class " + listenerClassName, e);
}
}
// Create session listener
SpringVaadinSessionListener sessionListener;
try {
sessionListener = listenerClass.getConstructor(String.class, String.class)
.newInstance(applicationName, contextLocation);
} catch (ReflectiveOperationException e) {
throw new ServletException("error instantiating " + listenerClass, e);
}
// Register session listener
servletService.addSessionInitListener(sessionListener);
servletService.addSessionDestroyListener(sessionListener);
}
@Override
protected VaadinServletService createServletService(DeploymentConfiguration deploymentConfiguration) throws ServiceException {
// Get session tracking parameters
final Properties params = deploymentConfiguration.getInitParameters();
final boolean sessionTracking = Boolean.valueOf(params.getProperty(SESSION_TRACKING_PARAMETER));
int maxSessionsParam = 0;
try {
maxSessionsParam = Integer.parseInt(params.getProperty(MAX_SESSIONS_PARAMETER));
} catch (Exception e) {
// ignore
}
final int maxSessions = maxSessionsParam;
// If not tracking sessions, do the normal thing
if (!sessionTracking)
return super.createServletService(deploymentConfiguration);
// Return a VaadinServletService that tracks sessions
final VaadinServletService service = new VaadinServletService(this, deploymentConfiguration) {
@Override
protected VaadinSession createVaadinSession(VaadinRequest request) throws ServiceException {
final VaadinSession session;
synchronized (SpringVaadinServlet.this.liveSessions) {
if (maxSessions > 0 && SpringVaadinServlet.this.liveSessions.size() >= maxSessions)
throw new ServiceException("The maximum number of active sessions has been reached");
session = super.createVaadinSession(request);
SpringVaadinServlet.this.liveSessions.put(session, null);
}
return session;
}
@Override
public void fireSessionDestroy(VaadinSession session) {
synchronized (SpringVaadinServlet.this.liveSessions) {
SpringVaadinServlet.this.liveSessions.remove(session);
}
super.fireSessionDestroy(session);
}
};
service.init();
return service;
}
/**
* Get all live {@link VaadinSession}s associated with this instance.
*
* @return live tracked sessions, or an empty collection if session tracking is not enabled
* @see VaadinSessionContainer
*/
public List getSessions() {
synchronized (this.liveSessions) {
return new ArrayList<>(this.liveSessions.keySet());
}
}
/**
* Get the {@link SpringVaadinServlet} that is associated with the given {@link VaadinSession}.
*
* @param session Vaadin session
* @return the assocated {@link SpringVaadinServlet}
* @throws IllegalStateException if the {@link VaadinServlet} associated with {@code session} is not a
* {@link SpringVaadinServlet}
*/
public static SpringVaadinServlet getServlet(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 SpringVaadinServlet))
throw new IllegalStateException("the VaadinServlet associated with the session is not a SpringVaadinServlet instance");
return (SpringVaadinServlet)service.getServlet();
}
}