diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java index b75649479de..1954894aafe 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -24,7 +24,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; @@ -37,6 +36,7 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.ClassLoadingObjectInputStream; import org.eclipse.jetty.util.log.Logger; @@ -641,37 +641,4 @@ public class HashSessionManager extends AbstractSessionManager } } } - - - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - protected class ClassLoadingObjectInputStream extends ObjectInputStream - { - /* ------------------------------------------------------------ */ - public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException - { - super(in); - } - - /* ------------------------------------------------------------ */ - public ClassLoadingObjectInputStream () throws IOException - { - super(); - } - - /* ------------------------------------------------------------ */ - @Override - public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException - { - try - { - return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); - } - catch (ClassNotFoundException e) - { - return super.resolveClass(cl); - } - } - } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java index b4677fa3dd4..afe7ae2c0ae 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java @@ -29,12 +29,8 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -46,6 +42,7 @@ import javax.servlet.http.HttpSessionListener; import org.eclipse.jetty.server.SessionIdManager; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.ClassLoadingObjectInputStream; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -387,38 +384,6 @@ public class JDBCSessionManager extends AbstractSessionManager - /** - * ClassLoadingObjectInputStream - * - * Used to persist the session attribute map - */ - protected class ClassLoadingObjectInputStream extends ObjectInputStream - { - public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException - { - super(in); - } - - public ClassLoadingObjectInputStream () throws IOException - { - super(); - } - - @Override - public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException - { - try - { - return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); - } - catch (ClassNotFoundException e) - { - return super.resolveClass(cl); - } - } - } - - /** * Set the time in seconds which is the interval between * saving the session access time to the database. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java new file mode 100644 index 00000000000..5020795ad1f --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; + + +/** + * ClassLoadingObjectInputStream + * + * For re-inflating serialized objects, this class uses the thread context classloader + * rather than the jvm's default classloader selection. + * + */ +public class ClassLoadingObjectInputStream extends ObjectInputStream +{ + /* ------------------------------------------------------------ */ + public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException + { + super(in); + } + + /* ------------------------------------------------------------ */ + public ClassLoadingObjectInputStream () throws IOException + { + super(); + } + + /* ------------------------------------------------------------ */ + @Override + public Class resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException + { + try + { + return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader()); + } + catch (ClassNotFoundException e) + { + return super.resolveClass(cl); + } + } + + /* ------------------------------------------------------------ */ + @Override + protected Class resolveProxyClass(String[] interfaces) + throws IOException, ClassNotFoundException + { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + + ClassLoader nonPublicLoader = null; + boolean hasNonPublicInterface = false; + + // define proxy in class loader of non-public interface(s), if any + Class[] classObjs = new Class[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) + { + Class cl = Class.forName(interfaces[i], false, loader); + if ((cl.getModifiers() & Modifier.PUBLIC) == 0) + { + if (hasNonPublicInterface) + { + if (nonPublicLoader != cl.getClassLoader()) + { + throw new IllegalAccessError( + "conflicting non-public interface class loaders"); + } + } + else + { + nonPublicLoader = cl.getClassLoader(); + hasNonPublicInterface = true; + } + } + classObjs[i] = cl; + } + try + { + return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : loader,classObjs); + } + catch (IllegalArgumentException e) + { + throw new ClassNotFoundException(null, e); + } + } +} \ No newline at end of file diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java new file mode 100644 index 00000000000..e8101e7d2e7 --- /dev/null +++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java @@ -0,0 +1,78 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session; + +import java.io.File; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.junit.Test; + +/** + * ProxySerializationTest + * + * + */ +public class ProxySerializationTest extends AbstractProxySerializationTest +{ + /** + * @see org.eclipse.jetty.server.session.AbstractProxySerializationTest#createServer(int, int, int) + */ + @Override + public AbstractTestServer createServer(int port, int max, int scavenge) + { + return new HashTestServer(port,max,scavenge); + } + + + + + @Override + public void customizeContext(ServletContextHandler c) + { + if (c == null) + return; + + //Ensure that the HashSessionManager will persist sessions on passivation + HashSessionManager manager = (HashSessionManager)c.getSessionHandler().getSessionManager(); + manager.setLazyLoad(false); + manager.setIdleSavePeriod(1); + try + { + File testDir = MavenTestingUtils.getTargetTestingDir("foo"); + testDir.mkdirs(); + manager.setStoreDirectory(testDir); + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + + + + @Test + public void testProxySerialization() throws Exception + { + super.testProxySerialization(); + } + +} diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java new file mode 100644 index 00000000000..3b54cad385c --- /dev/null +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/ProxySerializationTest.java @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.Test; + +/** + * ProxySerializationTest + * + * + */ +public class ProxySerializationTest extends AbstractProxySerializationTest +{ + + /** + * @see org.eclipse.jetty.server.session.AbstractProxySerializationTest#createServer(int, int, int) + */ + @Override + public AbstractTestServer createServer(int port, int max, int scavenge) + { + return new JdbcTestServer(port, max, scavenge); + } + + /** + * @see org.eclipse.jetty.server.session.AbstractProxySerializationTest#customizeContext(org.eclipse.jetty.servlet.ServletContextHandler) + */ + @Override + public void customizeContext(ServletContextHandler c) + { + } + + + + @Test + public void testProxySerialization() throws Exception + { + super.testProxySerialization(); + } + +} diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java new file mode 100644 index 00000000000..e7fe83f1f52 --- /dev/null +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractProxySerializationTest.java @@ -0,0 +1,146 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.jar.JarFile; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.JarResource; +import org.junit.Test; + +/** + * AbstractProxySerializationTest + * + * + */ +public abstract class AbstractProxySerializationTest +{ + public abstract AbstractTestServer createServer(int port, int max, int scavenge); + + public abstract void customizeContext (ServletContextHandler c); + + + + /** + * @param sec mseconds to sleep + */ + public void pause(int msec) + { + try + { + Thread.sleep(msec); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + @Test + public void testProxySerialization() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + int scavengePeriod = 10; + AbstractTestServer server = createServer(0, 20, scavengePeriod); + ServletContextHandler context = server.addContext(contextPath); + + InputStream is = this.getClass().getClassLoader().getResourceAsStream("proxy-serialization.jar"); + + File testDir = MavenTestingUtils.getTargetTestingDir("proxy-serialization"); + testDir.mkdirs(); + + File extractedJar = new File (testDir, "proxy-serialization.jar"); + extractedJar.createNewFile(); + IO.copy(is, new FileOutputStream(extractedJar)); + + + URLClassLoader loader = new URLClassLoader(new URL[] {extractedJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader()); + context.setClassLoader(loader); + context.addServlet("TestServlet", servletMapping); + customizeContext(context); + + try + { + server.start(); + int port=server.getPort(); + HttpClient client = new HttpClient(); + client.start(); + try + { + ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create"); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + String sessionCookie = response.getHeaders().getStringField("Set-Cookie"); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + //stop the context to be sure the sesssion will be passivated + context.stop(); + + //after a stop some of the volatile info is lost, so reinstate it + context.setClassLoader(loader); + context.addServlet("TestServlet", servletMapping); + + //restart the context + context.start(); + + // Make another request using the session id from before + Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=test"); + request.header("Cookie", sessionCookie); + response = request.send(); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + } + finally + { + client.stop(); + } + } + finally + { + server.stop(); + } + + } +} diff --git a/tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jar b/tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jar new file mode 100644 index 00000000000..fe3f0402dd9 Binary files /dev/null and b/tests/test-sessions/test-sessions-common/src/main/resources/proxy-serialization.jar differ