* Issue #3597 Fix session persistence classloading for deep structures with system classes.
This commit is contained in:
parent
c9164b0cf5
commit
ab174d1015
|
@ -162,6 +162,10 @@ You can do so directly to the API via a context XML file such as the following:
|
|||
|
||||
If none of the alternatives already described meet your needs, you can always provide a custom classloader for your webapp.
|
||||
We recommend, but do not require, that your custom loader subclasses link:{JDURL}/org/eclipse/jetty/webapp/WebAppClassLoader.html[WebAppClassLoader].
|
||||
|
||||
If you do not subclass WebAppClassLoader, we recommend that you implement the link:{JDURL}/org/eclipse/jetty/util/ClassVisibilityChecker.html[ClassVisibilityChecker] interface.
|
||||
Without this interface, session persistence will be slower.
|
||||
|
||||
You configure the classloader for the webapp like so:
|
||||
|
||||
[source, java, subs="{sub-order}"]
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
|
||||
import org.eclipse.jetty.util.ClassVisibilityChecker;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
@ -78,22 +79,47 @@ public class SessionData implements Serializable
|
|||
out.writeObject(entries);
|
||||
for (Entry<String,Object> entry: data._attributes.entrySet())
|
||||
{
|
||||
out.writeUTF(entry.getKey());
|
||||
ClassLoader loader = entry.getValue().getClass().getClassLoader();
|
||||
boolean isServerLoader = false;
|
||||
|
||||
if (loader == Thread.currentThread().getContextClassLoader()) //is it the webapp classloader?
|
||||
isServerLoader = false;
|
||||
else if (loader == Thread.currentThread().getContextClassLoader().getParent() || loader == SessionData.class.getClassLoader() || loader == null) // is it the container loader?
|
||||
isServerLoader = true;
|
||||
else
|
||||
throw new IOException ("Unknown loader"); // we don't know what loader to use
|
||||
out.writeUTF(entry.getKey());
|
||||
|
||||
out.writeBoolean(isServerLoader);
|
||||
Class<?> clazz = entry.getValue().getClass();
|
||||
ClassLoader loader = clazz.getClassLoader();
|
||||
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||
boolean isContextLoader;
|
||||
|
||||
if (loader == contextLoader) //is it the context classloader?
|
||||
isContextLoader = true;
|
||||
else if (contextLoader == null) //not context classloader
|
||||
isContextLoader = false;
|
||||
else if (contextLoader instanceof ClassVisibilityChecker)
|
||||
{
|
||||
//Clazz not loaded by context classloader, but ask if loadable by context classloader,
|
||||
//because preferable to use context classloader if possible (eg for deep structures).
|
||||
ClassVisibilityChecker checker = (ClassVisibilityChecker)(contextLoader);
|
||||
isContextLoader = (checker.isSystemClass(clazz) && !(checker.isServerClass(clazz)));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Class wasn't loaded by context classloader, but try loading from context loader,
|
||||
//because preferable to use context classloader if possible (eg for deep structures).
|
||||
try
|
||||
{
|
||||
Class<?> result = contextLoader.loadClass(clazz.getName());
|
||||
isContextLoader = (result == clazz); //only if TTCL loaded this instance of the class
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
isContextLoader = false; //TCCL can't see the class
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Attribute {} class={} isServerLoader={}", entry.getKey(),clazz.getName(),(!isContextLoader));
|
||||
out.writeBoolean(!isContextLoader);
|
||||
out.writeObject(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* De-serialize the attribute map of a session.
|
||||
*
|
||||
|
@ -118,12 +144,15 @@ public class SessionData implements Serializable
|
|||
|
||||
data._attributes = new ConcurrentHashMap<>();
|
||||
int entries = ((Integer)o).intValue();
|
||||
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
|
||||
ClassLoader serverLoader = SessionData.class.getClassLoader();
|
||||
for (int i=0; i < entries; i++)
|
||||
{
|
||||
String name = in.readUTF(); //attribute name
|
||||
boolean isServerClassLoader = in.readBoolean(); //use server or webapp classloader to load
|
||||
|
||||
Object value = ((ClassLoadingObjectInputStream)in).readObject(isServerClassLoader?SessionData.class.getClassLoader():Thread.currentThread().getContextClassLoader());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Deserialize {} isServerLoader={} serverLoader={} tccl={}", name, isServerClassLoader,serverLoader, contextLoader);
|
||||
Object value = ((ClassLoadingObjectInputStream)in).readObject(isServerClassLoader?serverLoader:contextLoader);
|
||||
data._attributes.put(name, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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;
|
||||
|
||||
/**
|
||||
* ClassVisibilityChecker
|
||||
*
|
||||
* Interface to be implemented by classes capable of checking class visibility
|
||||
* for a context.
|
||||
*
|
||||
*/
|
||||
public interface ClassVisibilityChecker
|
||||
{
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Is the class a System Class.
|
||||
* A System class is a class that is visible to a webapplication,
|
||||
* but that cannot be overridden by the contents of WEB-INF/lib or
|
||||
* WEB-INF/classes
|
||||
* @param clazz The fully qualified name of the class.
|
||||
* @return True if the class is a system class.
|
||||
*/
|
||||
boolean isSystemClass(Class<?> clazz);
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Is the class a Server Class.
|
||||
* A Server class is a class that is part of the implementation of
|
||||
* the server and is NIT visible to a webapplication. The web
|
||||
* application may provide it's own implementation of the class,
|
||||
* to be loaded from WEB-INF/lib or WEB-INF/classes
|
||||
* @param clazz The fully qualified name of the class.
|
||||
* @return True if the class is a server class.
|
||||
*/
|
||||
boolean isServerClass(Class<?> clazz);
|
||||
}
|
|
@ -39,6 +39,7 @@ import java.util.Set;
|
|||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.eclipse.jetty.util.ClassVisibilityChecker;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -65,7 +66,7 @@ import org.eclipse.jetty.util.resource.ResourceCollection;
|
|||
* context classloader will be used. If that is null then the
|
||||
* classloader that loaded this class is used as the parent.
|
||||
*/
|
||||
public class WebAppClassLoader extends URLClassLoader
|
||||
public class WebAppClassLoader extends URLClassLoader implements ClassVisibilityChecker
|
||||
{
|
||||
static
|
||||
{
|
||||
|
@ -85,7 +86,7 @@ public class WebAppClassLoader extends URLClassLoader
|
|||
/* ------------------------------------------------------------ */
|
||||
/** The Context in which the classloader operates.
|
||||
*/
|
||||
public interface Context
|
||||
public interface Context extends ClassVisibilityChecker
|
||||
{
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Convert a URL or path to a Resource.
|
||||
|
@ -103,26 +104,6 @@ public class WebAppClassLoader extends URLClassLoader
|
|||
*/
|
||||
PermissionCollection getPermissions();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Is the class a System Class.
|
||||
* A System class is a class that is visible to a webapplication,
|
||||
* but that cannot be overridden by the contents of WEB-INF/lib or
|
||||
* WEB-INF/classes
|
||||
* @param clazz The fully qualified name of the class.
|
||||
* @return True if the class is a system class.
|
||||
*/
|
||||
boolean isSystemClass(Class<?> clazz);
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Is the class a Server Class.
|
||||
* A Server class is a class that is part of the implementation of
|
||||
* the server and is NIT visible to a webapplication. The web
|
||||
* application may provide it's own implementation of the class,
|
||||
* to be loaded from WEB-INF/lib or WEB-INF/classes
|
||||
* @param clazz The fully qualified name of the class.
|
||||
* @return True if the class is a server class.
|
||||
*/
|
||||
boolean isServerClass(Class<?> clazz);
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -744,5 +725,17 @@ public class WebAppClassLoader extends URLClassLoader
|
|||
{
|
||||
return "WebAppClassLoader=" + _name+"@"+Long.toHexString(hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSystemClass(Class<?> clazz)
|
||||
{
|
||||
return _context.isSystemClass(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServerClass(Class<?> clazz)
|
||||
{
|
||||
return _context.isServerClass(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,10 +31,12 @@ import static org.junit.jupiter.api.Assertions.fail;
|
|||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
@ -238,6 +240,11 @@ public abstract class AbstractSessionDataStoreTest
|
|||
File factoryClass = new File (proxyabledir, "ProxyableFactory.class");
|
||||
IO.copy(is, new FileOutputStream(factoryClass));
|
||||
is.close();
|
||||
|
||||
is = Thread.currentThread().getContextClassLoader().getResourceAsStream("Foo.clazz");
|
||||
File fooClass = new File (proxyabledir, "Foo.class");
|
||||
IO.copy(is, new FileOutputStream(fooClass));
|
||||
is.close();
|
||||
|
||||
URL[] proxyabledirUrls = new URL[]{proxyabledir.toURI().toURL()};
|
||||
_contextClassLoader = new URLClassLoader(proxyabledirUrls, Thread.currentThread().getContextClassLoader());
|
||||
|
@ -272,6 +279,15 @@ public abstract class AbstractSessionDataStoreTest
|
|||
|
||||
//Make an attribute that uses the proxy only known to the webapp classloader
|
||||
data.setAttribute("a", proxy);
|
||||
|
||||
//Now make an attribute that uses a system class to store a webapp classes
|
||||
//see issue #3597
|
||||
Class fooclazz = Class.forName("Foo", true, _contextClassLoader);
|
||||
Constructor constructor = fooclazz.getConstructor(null);
|
||||
Object foo = constructor.newInstance(null);
|
||||
ArrayList list = new ArrayList();
|
||||
list.add(foo);
|
||||
data.setAttribute("foo", list);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue