Author: stliu
Date: 2010-05-18 13:04:38 -0400 (Tue, 18 May 2010)
New Revision: 19542
Modified:
core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SerializationHelper.java
Log:
JBPAPP-4326 HHH-4786 Hibernate SerializableType + Custom Serializable Class Causes CL
Problems
Modified:
core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SerializationHelper.java
===================================================================
---
core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SerializationHelper.java 2010-05-18
16:58:33 UTC (rev 19541)
+++
core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SerializationHelper.java 2010-05-18
17:04:38 UTC (rev 19542)
@@ -1,55 +1,26 @@
-/* ====================================================================
- * The Apache Software License, Version 1.1
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
*
- * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
- * reserved.
+ * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors. All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
*
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
*
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
*
- * 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.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301 USA
*
- * 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 apache(a)apache.org.
- *
- * 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.hibernate.util;
@@ -63,11 +34,12 @@
import java.io.ObjectStreamClass;
import java.io.ObjectInputStream;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.hibernate.type.SerializationException;
import org.hibernate.Hibernate;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+
/**
* <p>Assists with the serialization process and performs additional functionality
based
* on serialization.</p>
@@ -87,148 +59,187 @@
* @author Stephen Colebourne
* @author Jeff Varszegi
* @author Gary Gregory
+ * @version $Id$
* @since 1.0
- * @version $Id$
*/
public final class SerializationHelper {
- private static final Log log = LogFactory.getLog(SerializationHelper.class);
+ private static final Log log = LogFactory.getLog( SerializationHelper.class );
- private SerializationHelper() {}
+ private SerializationHelper() {
+ }
- // 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) throws SerializationException {
- log.trace("Starting clone through serialization");
+ // 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) throws SerializationException {
+ log.trace( "Starting clone through serialization" );
if ( object == null ) {
return null;
}
- return deserialize( serialize( object ), object.getClass().getClassLoader() );
- }
+ return deserialize( serialize( object ), object.getClass().getClassLoader() );
+ }
- // 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) throws
SerializationException {
- if (outputStream == null) {
- throw new IllegalArgumentException("The OutputStream must not be
null");
- }
+ // Serialize
+ //-----------------------------------------------------------------------
- if ( log.isTraceEnabled() ) {
- if ( Hibernate.isInitialized( obj ) ) {
- log.trace( "Starting serialization of object [" + obj +
"]" );
- }
- else {
- log.trace( "Starting serialization of [uninitialized proxy]" );
- }
- }
+ /**
+ * <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) throws
SerializationException {
+ 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);
+ if ( log.isTraceEnabled() ) {
+ if ( Hibernate.isInitialized( obj ) ) {
+ log.trace( "Starting serialization of object [" + obj + "]" );
+ }
+ else {
+ log.trace( "Starting serialization of [uninitialized proxy]" );
+ }
+ }
- }
- catch (IOException ex) {
- throw new SerializationException("could not serialize", ex);
- }
- finally {
- try {
- if (out != null) out.close();
- }
- catch (IOException ignored) {}
- }
- }
+ ObjectOutputStream out = null;
+ try {
+ // stream closed in the finally
+ out = new ObjectOutputStream( outputStream );
+ out.writeObject( obj );
- /**
- * <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) throws SerializationException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
- serialize(obj, baos);
- return baos.toByteArray();
- }
+ }
+ catch ( IOException ex ) {
+ throw new SerializationException( "could not serialize", ex );
+ }
+ finally {
+ try {
+ if ( out != null ) {
+ out.close();
+ }
+ }
+ catch ( IOException ignored ) {
+ }
+ }
+ }
- // Deserialize
- //-----------------------------------------------------------------------
- /**
- * Deserializes an object from the specified stream using the Thread Context
- * ClassLoader (TCCL). If there is no TCCL set, the classloader of the calling
- * class is used.
- * <p/>
- * Delegates to {@link #deserialize(java.io.InputStream, ClassLoader)}
- *
- * @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) throws
SerializationException {
- return deserialize( inputStream, Thread.currentThread().getContextClassLoader() );
- }
+ /**
+ * <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) throws SerializationException {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( 512 );
+ serialize( obj, byteArrayOutputStream );
+ return byteArrayOutputStream.toByteArray();
+ }
- /**
- * Deserializes an object from the specified stream using the Thread Context
+ // Deserialize
+ //-----------------------------------------------------------------------
+
+ /**
+ * Deserializes an object from the specified stream using the Thread Context
+ * ClassLoader (TCCL).
+ * <p/>
+ * Delegates to {@link #doDeserialize}
+ *
+ * @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) throws SerializationException
{
+ return doDeserialize( inputStream, defaultClassLoader(), hibernateClassLoader(), null
);
+ }
+
+ /**
+ * Returns the Thread Context ClassLoader (TCCL).
+ *
+ * @return The current TCCL
+ */
+ public static ClassLoader defaultClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ public static ClassLoader hibernateClassLoader() {
+ return SerializationHelper.class.getClassLoader();
+ }
+
+ /**
+ * Deserializes an object from the specified stream using the Thread Context
* ClassLoader (TCCL). If there is no TCCL set, the classloader of the calling
* class is used.
- * <p/>
- * The stream will be closed once the object is read. This avoids the need
+ * <p/>
+ * The stream will be closed once the object is read. This avoids the need
* for a finally clause, and maybe also exception handling, in the application
* code.
- * <p/>
- * The stream passed in is not buffered internally within this method. This is
+ * <p/>
+ * The stream passed in is not buffered internally within this method. This is
* the responsibility of the caller, if desired.
- *
- * @param inputStream the serialized object input stream, must not be null
+ *
+ * @param inputStream the serialized object input stream, must not be null
* @param loader The classloader to use
*
- * @return the deserialized object
+ * @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, ClassLoader loader) throws
SerializationException {
- if (inputStream == null) {
- throw new IllegalArgumentException( "The InputStream must not be
null" );
- }
+ * @throws IllegalArgumentException if <code>inputStream</code> is
<code>null</code>
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ public static Object deserialize(InputStream inputStream, ClassLoader loader) throws
SerializationException {
+ return doDeserialize( inputStream, loader, defaultClassLoader(), hibernateClassLoader()
);
+ }
+ public static Object doDeserialize(
+ InputStream inputStream,
+ ClassLoader loader,
+ ClassLoader fallbackLoader1,
+ ClassLoader fallbackLoader2) throws SerializationException {
+ if ( inputStream == null ) {
+ throw new IllegalArgumentException( "The InputStream must not be null" );
+ }
+
log.trace( "Starting deserialization of object" );
try {
- CustomObjectInputStream in = new CustomObjectInputStream( inputStream, loader );
+ CustomObjectInputStream in = new CustomObjectInputStream(
+ inputStream,
+ loader,
+ fallbackLoader1,
+ fallbackLoader2
+ );
try {
return in.readObject();
}
@@ -242,7 +253,7 @@
try {
in.close();
}
- catch (IOException ignore) {
+ catch ( IOException ignore ) {
// ignore
}
}
@@ -252,70 +263,118 @@
}
}
- /**
- * Deserializes an Object from an array of bytes.
+ /**
+ * Deserializes an object from an array of bytes using the Thread Context
+ * ClassLoader (TCCL). If there is no TCCL set, the classloader of the calling
+ * class is used.
* <p/>
* Delegates to {@link #deserialize(byte[], ClassLoader)}
- *
- * @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) throws SerializationException {
- return deserialize( objectData, Thread.currentThread().getContextClassLoader() );
- }
+ *
+ * @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) throws SerializationException {
+ return doDeserialize( wrap( objectData ), defaultClassLoader(), hibernateClassLoader(),
null );
+ }
- /**
- * Deserializes an Object from an array of bytes.
+ private static InputStream wrap(byte[] objectData) {
+ if ( objectData == null ) {
+ throw new IllegalArgumentException( "The byte[] must not be null" );
+ }
+ return new ByteArrayInputStream( objectData );
+ }
+
+ /**
+ * Deserializes an object from an array of bytes.
* <p/>
* Delegates to {@link #deserialize(java.io.InputStream, ClassLoader)} using a
- * {@link ByteArrayInputStream} to wrap the array.
- *
- * @param objectData the serialized object, must not be null
+ * {@link ByteArrayInputStream} to wrap the array.
+ *
+ * @param objectData the serialized object, must not be null
* @param loader The classloader to use
*
- * @return the deserialized object
+ * @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, ClassLoader loader) throws
SerializationException {
- if ( objectData == null ) {
- throw new IllegalArgumentException( "The byte[] must not be null"
);
- }
- ByteArrayInputStream bais = new ByteArrayInputStream( objectData );
- return deserialize( bais, loader );
- }
+ * @throws IllegalArgumentException if <code>objectData</code> is
<code>null</code>
+ * @throws SerializationException (runtime) if the serialization fails
+ */
+ public static Object deserialize(byte[] objectData, ClassLoader loader) throws
SerializationException {
+ return doDeserialize( wrap( objectData ), loader, defaultClassLoader(),
hibernateClassLoader() );
+ }
/**
- * Custom ObjectInputStream implementation to more appropriately handle classloading
- * within app servers (mainly jboss - hence this class inspired by jboss's class of
- * the same purpose).
+ * By default, to resolve the classes being deserialized JDK serialization uses the
+ * classes loader which loaded the class which initiated the deserialization call.
Here
+ * that would be hibernate classes. However, there are cases where that is not the
correct
+ * class loader to use; mainly here we are worried about deserializing user classes in
+ * environments (app servers, etc) where Hibernate is on a parent classes loader. To
+ * facilitate for that we allow passing in the class loader we should use.
*/
private static final class CustomObjectInputStream extends ObjectInputStream {
- private final ClassLoader loader;
+ private final ClassLoader loader1;
+ private final ClassLoader loader2;
+ private final ClassLoader loader3;
- private CustomObjectInputStream(InputStream in, ClassLoader loader) throws IOException
{
+ private CustomObjectInputStream(
+ InputStream in,
+ ClassLoader loader1,
+ ClassLoader loader2,
+ ClassLoader loader3) throws IOException {
super( in );
- this.loader = loader;
+ this.loader1 = loader1;
+ this.loader2 = loader2;
+ this.loader3 = loader3;
}
+ /**
+ * {@inheritDoc}
+ */
protected Class resolveClass(ObjectStreamClass v) throws IOException,
ClassNotFoundException {
String className = v.getName();
- log.trace("Attempting to locate class [" + className + "]");
+ log.trace( "Attempting to locate class [" + className + "]" );
- if ( loader != null ) {
+ try {
+ return Class.forName( className, false, loader1 );
+ }
+ catch ( ClassNotFoundException e ) {
+ log.trace( "Unable to locate class using given classloader" );
+ }
+
+ if ( different( loader1, loader2 ) ) {
try {
- return Class.forName( className, false, loader );
+ return Class.forName( className, false, loader2 );
}
- catch (ClassNotFoundException e) {
+ catch ( ClassNotFoundException e ) {
log.trace( "Unable to locate class using given classloader" );
}
}
+ if ( different( loader1, loader3 ) && different( loader2, loader3 ) ) {
+ try {
+ return Class.forName( className, false, loader2 );
+ }
+ catch ( ClassNotFoundException e ) {
+ log.trace( "Unable to locate class using given classloader" );
+ }
+ }
+
+ // By default delegate to normal JDK deserialization which will use the class loader
+ // of the class which is calling this deserialization.
return super.resolveClass( v );
}
+
+ private boolean different(ClassLoader one, ClassLoader other) {
+ if ( one == null ) {
+ return other != null;
+ }
+ else {
+ return !one.equals( other );
+ }
+ }
}
}