Hi Guys,

Here's a tiny patch if you want it.  It's for a very limited situation that 
probably only is ever going to affect a small number of developers, however 
I've implemented it in a very non intrusive manner, so it won't impact the 
majority of developers.

The method in question is 
org.apache.commons.lang.SerializationUtils.deserialize(InputStream 
inputStream)

while this is pretty common code using the ObjectInputStream to deserialize 
and object...there is one small drawback with using 
java.io.ObjectInputStream.  When loading the Class for the object you're 
trying to deserialize, java.io.ObjectInputStream gets the classloader from a 
native method called latestUserDefinedLoader().

Now, as far as I can make out...this travels up the stack to get the class 
loader, however, if your code is below the commons-lang code in a classloader 
heirarchy, and exists on it's own classloader, the standard ObjectInputStream 
will throw a class not found exception.

An example.  Say you add common-lang to the tomcat startup classpath...and 
then you deploy a web-app that uses common-lang....and in that web app you 
create a classloader and load an object....and then you try to use the 
SerializationUtils to clone that object (serialize and deserialize)....the 
class loader that will be used will be the tomcat base classloader, and your 
custom loaded code won't be visible.

Now, you would probably never want to do this, I just use it as an example 
because everyone know and understands how tomcat works.  Where you would run 
into this, is if you were writing some kind of container system (with it's 
own classloading components), and had common-lang in the base of it, yet used 
common-lang from within it (down the classloading heirarchy somewhere).

The core change is that ObjectInputStream needs to use 
Thread.getCurrentThread().getContextClassLoader() to resolve the class 
instead of Class.forName(...,..., latestUserDefinedLoader())

Now I know a lot of you are saying..."who the h-e-2*hockey-sticks is going to 
need to do that"...well, me...and I would imagine at least one other person 
has hit a similar situation.

So, not to cause any fuss (this is the non intrusive bit), I created an 
abstract factory to do the creation of ObjectInputStream and, if you don't 
specify a particular env variable, it just gives you the standard 
ObjectInputStream...if you do, it gives you one with resolveClass(...) 
overloaded.

For more detail, see the javadoc.

Sorry no diff...simple change.

Cheers
Adam
/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.commons.lang;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

import org.apache.commons.lang.io.ObjectInputStreamFactory;

/**
 * <p>Assists with the serialization process and performs additional functionality based 
 * on serialization.</p>
 * <p>
 * <ul>
 * <li>Deep clone using serialization
 * <li>Serialize managing finally and IOException
 * <li>Deserialize managing finally and IOException
 * </ul>
 *
 * <p>This class throws exceptions for invalid <code>null</code> inputs.
 * Each method documents its behaviour in more detail.</p>
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Nissim Karpenstein</a>
 * @author <a href="mailto:[EMAIL PROTECTED]">Janek Bogucki</a>
 * @author <a href="mailto:[EMAIL PROTECTED]">Daniel Rall</a>
 * @author Stephen Colebourne
 * @author Jeff Varszegi
 * @author Gary Gregory
 * @since 1.0
 * @version $Id: SerializationUtils.java,v 1.12 2003/08/22 17:25:33 ggregory Exp $
 */
public class SerializationUtils {
    
    /**
     * <p>SerializationUtils instances should NOT be constructed in standard programming.
     * Instead, the class should be used as <code>SerializationUtils.clone(object)</code>.</p>
     *
     * <p>This constructor is public to permit tools that require a JavaBean instance
     * to operate.</p>
     * @since 2.0
     */
    public SerializationUtils() {
        super();
    }

    // Clone
    //-----------------------------------------------------------------------
    /**
     * <p>Deep clone an <code>Object</code> using serialization.</p>
     *
     * <p>This is many times slower than writing clone methods by hand
     * on all objects in your object graph. However, for complex object
     * graphs, or for those that don't support deep cloning this can
     * be a simple alternative implementation. Of course all the objects
     * must be <code>Serializable</code>.</p>
     * 
     * @param object  the <code>Serializable</code> object to clone
     * @return the cloned object
     * @throws SerializationException (runtime) if the serialization fails
     */
    public static Object clone(Serializable object) {
        return deserialize(serialize(object));
    }
    
    // Serialize
    //-----------------------------------------------------------------------
    /**
     * <p>Serializes an <code>Object</code> to the specified stream.</p>
     *
     * <p>The stream will be closed once the object is written.
     * This avoids the need for a finally clause, and maybe also exception
     * handling, in the application code.</p>
     * 
     * <p>The stream passed in is not buffered internally within this method.
     * This is the responsibility of your application if desired.</p>
     *
     * @param obj  the object to serialize to bytes, may be null
     * @param outputStream  the stream to write to, must not be null
     * @throws IllegalArgumentException if <code>outputStream</code> is <code>null</code>
     * @throws SerializationException (runtime) if the serialization fails
     */
    public static void serialize(Serializable obj, OutputStream outputStream) {
        if (outputStream == null) {
            throw new IllegalArgumentException("The OutputStream must not be null");
        }
        ObjectOutputStream out = null;
        try {
            // stream closed in the finally
            out = new ObjectOutputStream(outputStream);
            out.writeObject(obj);
            
        } catch (IOException ex) {
            throw new SerializationException(ex);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException ex) {
                // ignore;
            }
        }
    }

    /**
     * <p>Serializes an <code>Object</code> to a byte array for
     * storage/serialization.</p>
     *
     * @param obj  the object to serialize to bytes
     * @return a byte[] with the converted Serializable
     * @throws SerializationException (runtime) if the serialization fails
     */
    public static byte[] serialize(Serializable obj) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        serialize(obj, baos);
        return baos.toByteArray();
    }

    // Deserialize
    //-----------------------------------------------------------------------
    /**
     * <p>Deserializes an <code>Object</code> from the specified stream.</p>
     *
     * <p>The stream will be closed once the object is written. This
     * avoids the need for a finally clause, and maybe also exception
     * handling, in the application code.</p>
     * 
     * <p>The stream passed in is not buffered internally within this method.
     * This is the responsibility of your application if desired.</p>
     *
     * @param inputStream  the serialized object input stream, must not be null
     * @return the deserialized object
     * @throws IllegalArgumentException if <code>inputStream</code> is <code>null</code>
     * @throws SerializationException (runtime) if the serialization fails
     */
    public static Object deserialize(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The InputStream must not be null");
        }
        ObjectInputStream in = null;
        try {
            // stream closed in the finally
            in = ObjectInputStreamFactory.newFactory().newObjectInputStream(inputStream);
            return in.readObject();
            
        } catch (ClassNotFoundException ex) {
            throw new SerializationException(ex);
        } catch (IOException ex) {
            throw new SerializationException(ex);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                // ignore
            }
        }
    }

    /**
     * <p>Deserializes a single <code>Object</code> from an array of bytes.</p>
     *
     * @param objectData  the serialized object, must not be null
     * @return the deserialized object
     * @throws IllegalArgumentException if <code>objectData</code> is <code>null</code>
     * @throws SerializationException (runtime) if the serialization fails
     */
    public static Object deserialize(byte[] objectData) {
        if (objectData == null) {
            throw new IllegalArgumentException("The byte[] must not be null");
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
        return deserialize(bais);
    }
    
}
/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.commons.lang.io;

import java.io.InputStream;
import java.io.ObjectInputStream;

/**
 * <code>ObjectInputStreamFactory</code> is resposible for supplying an implementation of
 * <code>java.io.ObjectInputStream</code> for use when deserializing.  This was inspired by an issue
 * (bug?) with java SDK code in which <code>java.io.ObjectInputStream.resolveClass(...)</code> calls
 * <code>java.lang.Class.forName(...)</code> to load the required class of the object being read.  This
 * causes an issue in a container type situation where the current <code>java.lang.ClassLoader</code> differs
 * from the system <code>ClassLoader</code>, and the system classloader does not have access to the class
 * definition of the object being deserialized.
 * 
 * For Example:  
 * 
 * If we have a class <code>Foo</code> that is loaded by a custom classloader during runtime (say a network classloader
 * that always reads the latest jar from www.foo.com/latest), and the serialization utils are loaded by either the system class loader 
 * or a classloader <b>above</b> our classloader in the classloader stack and we try to serialize an instance of <code>Foo</code> then
 * read it back (deserialize it), then the standard <code>java.io.ObjectInputStream</code> will look on the system
 * classloader (using Class.forName(java.lang.String, .., ..)) for the definition of <code>Foo</code>, rather than looking on the current
 * classloader (using Thread.getContextClassLoader().loadClass(java.lang.String)).  In this situation, a <code>ClassNotFoundException</code>
 * would be thrown as the system classloader does not know about class <code>Foo</code>
 * 
 * The default behaviour of this factory is to return <code>java.io.ObjectInputStream</code>, however if the property
 * -Dorg.apache.commons.lang.io.overrideJDKObjectInputStream is set to 'true', then <code>org.apache.commons.lang.io.ThreadClassloaderObjectInputStream</code> 
 * will be used.
 * 
 * In short, if our classloader is below (declared after) the classloader that declared the <code>SerializationUtils</code> then 
 * our classes won't be visible during deserialization, as the native method <code>latestUserDefinedLoader()</code> in <code>java.io.ObjectInputStream</code>
 * appears to only look up the stack from the calling class, and not down or at the current thread.
 * 
 * @author	Adam Jenkins
 * @since	2.0
 */
public abstract class ObjectInputStreamFactory {
	
	//look up system property	
	private static final String SYSTEM_PROPERTY = "org.apache.commons.lang.io.overrideJDKObjectInputStream"; 
		
	/**
	 * Singleton access method.
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0
	 * @return	The currently configured <code>ObjectInputStreamFactory</code>
	 */
	public static ObjectInputStreamFactory newFactory(){
		System.out.println("ObjectInputStreamFactory.newFactory()");
		if(useOverride()){
			System.out.println("Using override");
			return new OverrideObjectInputStreamFactory();
		}else{
			System.out.println("Not using override");
			return new StandardObjectInputStreamFactory();
		}
	}
	
	/**
	 * Creates a new <code>ObjectInputStream</code> around the supplied <code>InputStream</code>
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0
	 * @return	The <code>ObjectInputStream</code>
	 * @throws 	StreamCorruptedException - if the stream header is incorrect 
     * @throws 	IOException - if an I/O error occurs while reading stream header 
     * @throws 	SecurityException - if untrusted subclass illegally overrides security-sensitive methods 
     * @throws 	NullPointerException - if in is null
	 * @see		java.io.ObjectInputStream
	 */
	public abstract ObjectInputStream newObjectInputStream(InputStream in) throws java.io.IOException;
	
	/**
	 * Should we use the standard object input stream, or override it with the one that loads using the context classloader?
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0	 
	 * @return	true if the system property is set to use the custom <code>ObjectInputStream</code>, false otherwise.
	 */
	private static boolean useOverride(){		
		String conditional = System.getProperty(SYSTEM_PROPERTY);
		System.out.println("Override property: " + conditional);
		if(conditional == null) return false;
		if(conditional.toUpperCase().equals("TRUE")) return true;
		return false;
	}
	
	
	
	//------------BEGIN INNER CLASSES------------------//
	
	/**
	 * Factory class responsible for creating <code>ObjectInputStream</code>s that use the context classloader.
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0
	 */
	private static class OverrideObjectInputStreamFactory extends ObjectInputStreamFactory{
		
		/**
		 * Creates a new <code>ObjectInputStream</code> around the supplied <code>InputStream</code>
		 * 
		 * @author	Adam Jenkins
		 * @since	2.0
		 * @return	The <code>ObjectInputStream</code>
		 * @throws 	StreamCorruptedException - if the stream header is incorrect 
	     * @throws 	IOException - if an I/O error occurs while reading stream header 
	     * @throws 	SecurityException - if untrusted subclass illegally overrides security-sensitive methods 
	     * @throws 	NullPointerException - if in is null
		 * @see		java.io.ObjectInputStream
		 */
		public ObjectInputStream newObjectInputStream(InputStream in) throws java.io.IOException {
			return new org.apache.commons.lang.io.ThreadClassloaderObjectInputStream(in);
		};		
		
	}
	
	/**
	 * Factory responsible for creating standard <code>java.io.ObjectInputStream</code>s.
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0
	 */
	private static class StandardObjectInputStreamFactory extends ObjectInputStreamFactory{
		
		/**
		 * Creates a new <code>ObjectInputStream</code> around the supplied <code>InputStream</code>
		 * 
		 * @author	Adam Jenkins
		 * @since	2.0
		 * @return	The <code>ObjectInputStream</code>
		 * @throws 	StreamCorruptedException - if the stream header is incorrect 
	     * @throws 	IOException - if an I/O error occurs while reading stream header 
	     * @throws 	SecurityException - if untrusted subclass illegally overrides security-sensitive methods 
	     * @throws 	NullPointerException - if in is null
		 * @see		java.io.ObjectInputStream
		 */
		public ObjectInputStream newObjectInputStream(InputStream in) throws java.io.IOException{
			return new java.io.ObjectInputStream(in);
		}		
	}
	
	
	//-------------END INNER CLASSES-------------------//

}
/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.commons.lang.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;

/**
 * <code>ThreadClassloaderObjectInputStream</code> performs the same duties as <code>java.io.ObjectInputStream</code>, 
 * however it uses the context classload to resolve classes instead of the system classloader.
 * 
 * @author	Adam Jenkins
 * @since	2.0
 */
public class ThreadClassloaderObjectInputStream extends ObjectInputStream {

	/**
	 * Creates a ThreadClassloaderObjectInputStream that reads from the specified InputStream.
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0
	 * @param 	in	InputStream to read from
	 * @throws StreamCorruptedException - if the stream header is incorrect 
     * @throws IOException - if an I/O error occurs while reading stream header 
     * @throws SecurityException - if untrusted subclass illegally overrides security-sensitive methods 
     * @throws NullPointerException - if in is null
	 */
	public ThreadClassloaderObjectInputStream(InputStream in) throws java.io.IOException{
		super(in);
	}


	/**
	 * Resolves the class using the current threads context classloader, or, if an error occurs doing that, 
	 * this method delegates up to the super class method.
	 * 
	 * @author	Adam Jenkins
	 * @since	2.0
	 * @throws	java.io.IOException,
	 * @throws	java.lang.ClassNotFoundException
	 * @see java.io.ObjectInputStream#resolveClass(java.io.ObjectStreamClass)
	 */
	protected Class resolveClass(ObjectStreamClass desc)
		throws IOException, ClassNotFoundException {
		System.out.println("resolving class " + desc.getName());
		try{
			System.out.println("loading class from context class loader");
			return Thread.currentThread().getContextClassLoader().loadClass(desc.getName());
		}catch(Throwable t){
			t.printStackTrace();
			System.out.println("delegating to super");
			return super.resolveClass(desc);
		}
	}

}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to