diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java index 38935c76fe8..5adf201a403 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ServerProperties.java @@ -262,6 +262,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord if (getSession().getTimeout() != null) { container.setSessionTimeout(getSession().getTimeout()); } + container.setPersistSession(getSession().isPersistent()); if (getSsl() != null) { container.setSsl(getSsl()); } @@ -322,6 +323,11 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord */ private Set trackingModes; + /** + * Persist session data between restarts. + */ + private boolean persistent; + private Cookie cookie = new Cookie(); public Cookie getCookie() { @@ -344,6 +350,14 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord this.trackingModes = trackingModes; } + public boolean isPersistent() { + return this.persistent; + } + + public void setPersistent(boolean persistent) { + this.persistent = persistent; + } + public static class Cookie { /** diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 6fb2183e273..2fdd5624ab7 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -78,6 +78,7 @@ content into your application; rather pick only the properties that you need. server.jsp-servlet.registered=true # Whether or not the JSP servlet is registered server.servlet-path= # the servlet path, defaults to '/' server.display-name= # the display name of the application + server.session.persistent= # true if session should be saved across restarts server.session.timeout= # session timeout in seconds server.session.tracking-modes= # tracking modes (one or more of "cookie" ,"url", "ssl") server.session.cookie.name= # session cookie name diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index d4a3cb3fbb1..88ed0943110 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1581,7 +1581,7 @@ Common server settings include: * `server.port` -- The listen port for incoming HTTP requests. * `server.address` -- The interface address to bind to. -* `server.sessionTimeout` -- A session timeout. +* `server.session.timeout` -- A session timeout. See the {sc-spring-boot-autoconfigure}/web/ServerProperties.{sc-ext}[`ServerProperties`] class for a complete list. diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java index 642a6ad0756..1b8f351e502 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/AbstractConfigurableEmbeddedServletContainer.java @@ -64,6 +64,8 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements private int sessionTimeout = DEFAULT_SESSION_TIMEOUT; + private boolean persistSession; + private Ssl ssl; private JspServlet jspServlet = new JspServlet(); @@ -178,6 +180,15 @@ public abstract class AbstractConfigurableEmbeddedServletContainer implements return this.sessionTimeout; } + @Override + public void setPersistSession(boolean persistSession) { + this.persistSession = persistSession; + } + + public boolean isPersistSession() { + return this.persistSession; + } + @Override public void setInitializers(List initializers) { Assert.notNull(initializers, "Initializers must not be null"); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java index fc2732edf80..d3c0bec1581 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/ConfigurableEmbeddedServletContainer.java @@ -73,6 +73,12 @@ public interface ConfigurableEmbeddedServletContainer { */ void setSessionTimeout(int sessionTimeout, TimeUnit timeUnit); + /** + * Sets if session data should be persisted between restarts. + * @param persistSession {@code true} if session data should be persisted + */ + void setPersistSession(boolean persistSession); + /** * Sets the specific network address that the server should bind to. * @param address the address to set (defaults to {@code null}) diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index c812ab52fda..64e576a9f8a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.server.SessionManager; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.server.session.HashSessionManager; import org.eclipse.jetty.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; @@ -50,6 +51,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.webapp.AbstractConfiguration; import org.eclipse.jetty.webapp.Configuration; import org.eclipse.jetty.webapp.WebAppContext; +import org.springframework.boot.ApplicationTemp; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.EmbeddedServletContainer; @@ -277,12 +279,31 @@ public class JettyEmbeddedServletContainerFactory extends Configuration[] configurations = getWebAppContextConfigurations(context, initializersToUse); context.setConfigurations(configurations); - int sessionTimeout = (getSessionTimeout() > 0 ? getSessionTimeout() : -1); - SessionManager sessionManager = context.getSessionHandler().getSessionManager(); - sessionManager.setMaxInactiveInterval(sessionTimeout); + configureSession(context); postProcessWebAppContext(context); } + private void configureSession(WebAppContext context) { + SessionManager sessionManager = context.getSessionHandler().getSessionManager(); + int sessionTimeout = (getSessionTimeout() > 0 ? getSessionTimeout() : -1); + sessionManager.setMaxInactiveInterval(sessionTimeout); + if (isPersistSession()) { + Assert.isInstanceOf(HashSessionManager.class, sessionManager, + "Unable to use persistent sessions"); + configurePersistSession(sessionManager); + } + } + + private void configurePersistSession(SessionManager sessionManager) { + try { + File storeDirectory = new ApplicationTemp().getFolder("jetty-sessions"); + ((HashSessionManager) sessionManager).setStoreDirectory(storeDirectory); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + private File getTempDirectory() { String temp = System.getProperty("java.io.tmpdir"); return (temp == null ? null : new File(temp)); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index d76cc6ac028..641ad0ac95f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -39,10 +39,12 @@ import org.apache.catalina.Host; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Manager; import org.apache.catalina.Valve; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Connector; import org.apache.catalina.loader.WebappLoader; +import org.apache.catalina.session.StandardManager; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat.FixContextListener; import org.apache.coyote.AbstractProtocol; @@ -50,6 +52,7 @@ import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11JsseProtocol; import org.apache.coyote.http11.AbstractHttp11Protocol; import org.springframework.beans.BeanUtils; +import org.springframework.boot.ApplicationTemp; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.EmbeddedServletContainer; @@ -379,17 +382,38 @@ public class TomcatEmbeddedServletContainerFactory extends for (MimeMappings.Mapping mapping : getMimeMappings()) { context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); } - long sessionTimeout = getSessionTimeout(); - if (sessionTimeout > 0) { - // Tomcat timeouts are in minutes - sessionTimeout = Math.max(TimeUnit.SECONDS.toMinutes(sessionTimeout), 1L); - } - context.setSessionTimeout((int) sessionTimeout); + configureSession(context); for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { customizer.customize(context); } } + private void configureSession(Context context) { + long sessionTimeout = getSessionTimeoutInMinutes(); + context.setSessionTimeout((int) sessionTimeout); + if (isPersistSession()) { + Manager manager = context.getManager(); + if (manager == null) { + manager = new StandardManager(); + context.setManager(manager); + } + Assert.state(manager instanceof StandardManager, + "Unable to persist HTTP session state using manager type " + + manager.getClass().getName()); + File folder = new ApplicationTemp().getFolder("tomcat-sessions"); + File file = new File(folder, "SESSIONS.ser"); + ((StandardManager) manager).setPathname(file.getAbsolutePath()); + } + } + + private long getSessionTimeoutInMinutes() { + long sessionTimeout = getSessionTimeout(); + if (sessionTimeout > 0) { + sessionTimeout = Math.max(TimeUnit.SECONDS.toMinutes(sessionTimeout), 1L); + } + return sessionTimeout; + } + /** * Post process the Tomcat {@link Context} before it used with the Tomcat Server. * Subclasses can override this method to apply additional processing to the diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/FileSessionPersistence.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/FileSessionPersistence.java new file mode 100644 index 00000000000..4de8a7b19d2 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/FileSessionPersistence.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.embedded.undertow; + +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.api.SessionPersistenceManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * {@link SessionPersistenceManager} that stores session information in a file. + * + * @author Phillip Webb + * @since 1.3.0 + */ +public class FileSessionPersistence implements SessionPersistenceManager { + + private final File folder; + + public FileSessionPersistence(File folder) { + this.folder = folder; + } + + @Override + public void persistSessions(String deploymentName, + Map sessionData) { + try { + save(sessionData, getSessionFile(deploymentName)); + } + catch (Exception ex) { + UndertowServletLogger.ROOT_LOGGER.failedToPersistSessions(ex); + } + } + + private void save(Map sessionData, File file) + throws IOException { + ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(file)); + try { + save(sessionData, stream); + } + finally { + stream.close(); + } + } + + private void save(Map sessionData, + ObjectOutputStream stream) throws IOException { + Map session = new LinkedHashMap(); + for (Map.Entry entry : sessionData.entrySet()) { + session.put(entry.getKey(), + new SerializablePersistentSession(entry.getValue())); + } + stream.writeObject(session); + } + + @Override + public Map loadSessionAttributes(String deploymentName, + final ClassLoader classLoader) { + try { + File file = getSessionFile(deploymentName); + if (file.exists()) { + return load(file); + } + } + catch (Exception ex) { + UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(ex); + } + return null; + } + + private Map load(File file) throws IOException, + ClassNotFoundException { + ObjectInputStream stream = new ObjectInputStream(new FileInputStream(file)); + try { + return load(stream); + } + finally { + stream.close(); + } + } + + private Map load(ObjectInputStream stream) + throws ClassNotFoundException, IOException { + Map session = readSession(stream); + long time = System.currentTimeMillis(); + Map result = new LinkedHashMap(); + for (Map.Entry entry : session.entrySet()) { + PersistentSession entrySession = entry.getValue().getPersistentSession(); + if (entrySession.getExpiration().getTime() > time) { + result.put(entry.getKey(), entrySession); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private Map readSession( + ObjectInputStream stream) throws ClassNotFoundException, IOException { + return ((Map) stream.readObject()); + } + + private File getSessionFile(String deploymentName) { + if (!this.folder.exists()) { + this.folder.mkdirs(); + } + return new File(this.folder, deploymentName + ".session"); + } + + @Override + public void clear(String deploymentName) { + getSessionFile(deploymentName).delete(); + } + + static class SerializablePersistentSession implements Serializable { + + private static final long serialVersionUID = 0L; + + private final Date expiration; + + private final Map sessionData; + + public SerializablePersistentSession(PersistentSession session) { + this.expiration = session.getExpiration(); + this.sessionData = new LinkedHashMap(session.getSessionData()); + } + + public PersistentSession getPersistentSession() { + return new PersistentSession(this.expiration, this.sessionData); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java index 128c6ac9e8c..e9fe09f5358 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -213,8 +213,14 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine @Override public synchronized void stop() throws EmbeddedServletContainerException { if (this.started) { - this.started = false; - this.undertow.stop(); + try { + this.started = false; + this.manager.stop(); + this.undertow.stop(); + } + catch (Exception ex) { + throw new EmbeddedServletContainerException("Unable to stop undertow", ex); + } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index 6fef6b9ba80..47c58060291 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -62,6 +62,7 @@ import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import org.springframework.boot.ApplicationTemp; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; @@ -355,6 +356,10 @@ public class UndertowEmbeddedServletContainerFactory extends if (isAccessLogEnabled()) { configureAccessLog(deployment); } + if (isPersistSession()) { + File folder = new ApplicationTemp().getFolder("undertow-sessions"); + deployment.setSessionPersistenceManager(new FileSessionPersistence(folder)); + } DeploymentManager manager = Servlets.defaultContainer().addDeployment(deployment); manager.deploy(); SessionManager sessionManager = manager.getDeployment().getSessionManager(); diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index 95444b6b53a..fc37491cd0a 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; @@ -38,13 +39,17 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.InputStreamFactory; +import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; +import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContextBuilder; import org.junit.After; import org.junit.Rule; @@ -96,6 +101,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected EmbeddedServletContainer container; + private final HttpClientContext httpClientContext = HttpClientContext.create(); + @After public void teardown() { if (this.container != null) { @@ -529,6 +536,28 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { assertThat(getFactory().getSessionTimeout(), equalTo(30 * 60)); } + @Test + public void persistSession() throws Exception { + AbstractEmbeddedServletContainerFactory factory = getFactory(); + factory.setPersistSession(true); + this.container = factory + .getEmbeddedServletContainer(sessionServletRegistration()); + this.container.start(); + String s1 = getResponse(getLocalUrl("/session")); + String s2 = getResponse(getLocalUrl("/session")); + this.container.stop(); + this.container = factory + .getEmbeddedServletContainer(sessionServletRegistration()); + this.container.start(); + String s3 = getResponse(getLocalUrl("/session")); + System.out.println(s1); + System.out.println(s2); + System.out.println(s3); + String message = "Session error s1=" + s1 + " s2=" + s2 + " s3=" + s3; + assertThat(message, s2.split(":")[0], equalTo(s1.split(":")[1])); + assertThat(message, s3.split(":")[0], equalTo(s2.split(":")[1])); + } + @Test public void compression() throws Exception { assertTrue(doTestCompression(10000, null, null)); @@ -632,7 +661,14 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { protected ClientHttpResponse getClientResponse(String url) throws IOException, URISyntaxException { - return getClientResponse(url, new HttpComponentsClientHttpRequestFactory()); + return getClientResponse(url, new HttpComponentsClientHttpRequestFactory() { + + @Override + protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { + return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext; + } + + }); } protected ClientHttpResponse getClientResponse(String url, @@ -640,6 +676,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { URISyntaxException { ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpMethod.GET); + request.getHeaders().add("Cookie", "JSESSIONID=" + "123"); ClientHttpResponse response = request.execute(); return response; } @@ -655,16 +692,37 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @SuppressWarnings("serial") private ServletContextInitializer errorServletRegistration() { ServletRegistrationBean bean = new ServletRegistrationBean(new ExampleServlet() { + @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { throw new RuntimeException("Planned"); } + }, "/bang"); bean.setName("error"); return bean; } + private ServletContextInitializer sessionServletRegistration() { + ServletRegistrationBean bean = new ServletRegistrationBean(new ExampleServlet() { + + @Override + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException { + HttpSession session = ((HttpServletRequest) request).getSession(true); + long value = System.currentTimeMillis(); + Object existing = session.getAttribute("boot"); + session.setAttribute("boot", value); + PrintWriter writer = response.getWriter(); + writer.append(String.valueOf(existing) + ":" + value); + } + + }, "/session"); + bean.setName("session"); + return bean; + } + private class TestGzipInputStreamFactory implements InputStreamFactory { private final AtomicBoolean requested = new AtomicBoolean(false); diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/FileSessionPersistenceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/FileSessionPersistenceTests.java new file mode 100644 index 00000000000..c8a64a488d8 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/FileSessionPersistenceTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.embedded.undertow; + +import io.undertow.servlet.api.SessionPersistenceManager.PersistentSession; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link FileSessionPersistence}. + * + * @author Phillip Webb + */ +public class FileSessionPersistenceTests { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private File folder; + + private FileSessionPersistence persistence; + + private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + private Date expiration = new Date(System.currentTimeMillis() + 10000); + + @Before + public void setup() throws IOException { + this.folder = this.temp.newFolder(); + this.persistence = new FileSessionPersistence(this.folder); + } + + @Test + public void loadsNullForMissingFile() throws Exception { + Map attributes = this.persistence + .loadSessionAttributes("test", this.classLoader); + assertThat(attributes, nullValue()); + } + + @Test + public void presistAndLoad() throws Exception { + Map sessionData = new LinkedHashMap(); + Map data = new LinkedHashMap(); + data.put("spring", "boot"); + PersistentSession session = new PersistentSession(this.expiration, data); + sessionData.put("abc", session); + this.persistence.persistSessions("test", sessionData); + Map restored = this.persistence.loadSessionAttributes( + "test", this.classLoader); + assertThat(restored, notNullValue()); + assertThat(restored.get("abc").getExpiration(), equalTo(this.expiration)); + assertThat(restored.get("abc").getSessionData().get("spring"), + equalTo((Object) "boot")); + } + + @Test + public void dontRestoreExpired() throws Exception { + Date expired = new Date(System.currentTimeMillis() - 1000); + Map sessionData = new LinkedHashMap(); + Map data = new LinkedHashMap(); + data.put("spring", "boot"); + PersistentSession session = new PersistentSession(expired, data); + sessionData.put("abc", session); + this.persistence.persistSessions("test", sessionData); + Map restored = this.persistence.loadSessionAttributes( + "test", this.classLoader); + assertThat(restored, notNullValue()); + assertThat(restored.containsKey("abc"), equalTo(false)); + } + + @Test + public void deleteFileOnClear() throws Exception { + File sessionFile = new File(this.folder, "test.session"); + Map sessionData = new LinkedHashMap(); + this.persistence.persistSessions("test", sessionData); + assertThat(sessionFile.exists(), equalTo(true)); + this.persistence.clear("test"); + assertThat(sessionFile.exists(), equalTo(false)); + } + +}