/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.internal.util;
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.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
import org.hibernate.Hibernate;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.type.SerializationException;
Assists with the serialization process and performs additional functionality based
on serialization.
- Deep clone using serialization
- Serialize managing finally and IOException
- Deserialize managing finally and IOException
This class throws exceptions for invalid null
inputs.
Each method documents its behaviour in more detail.
Author: Nissim Karpenstein, Janek Bogucki, Daniel Rall, Stephen Colebourne, Jeff Varszegi, Gary Gregory Since: 1.0
/**
* <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/>
* <p>This class throws exceptions for invalid <code>null</code> inputs.
* Each method documents its behaviour in more detail.</p>
*
* @author <a href="mailto:nissim@nksystems.com">Nissim Karpenstein</a>
* @author <a href="mailto:janekdb@yahoo.co.uk">Janek Bogucki</a>
* @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
* @author Stephen Colebourne
* @author Jeff Varszegi
* @author Gary Gregory
*
* @since 1.0
*/
public final class SerializationHelper {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( SerializationHelper.class );
private SerializationHelper() {
}
// Clone
//-----------------------------------------------------------------------
Deep clone an Object
using serialization.
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 Serializable
.
Params: - object – the
Serializable
object to clone
Throws: - SerializationException – (runtime) if the serialization fails
Returns: the cloned object
/**
* <p>Deep clone an <code>Object</code> using serialization.</p>
* <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() );
}
// Serialize
//-----------------------------------------------------------------------
Serializes an Object
to the specified stream.
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.
The stream passed in is not buffered internally within this method.
This is the responsibility of your application if desired.
Params: - obj – the object to serialize to bytes, may be null
- outputStream – the stream to write to, must not be null
Throws: - IllegalArgumentException – if
outputStream
is null
- SerializationException – (runtime) if the serialization fails
/**
* <p>Serializes an <code>Object</code> to the specified stream.</p>
* <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/>
* <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" );
}
if ( LOG.isTraceEnabled() ) {
if ( Hibernate.isInitialized( obj ) ) {
LOG.tracev( "Starting serialization of object [{0}]", obj );
}
else {
LOG.trace( "Starting serialization of [uninitialized proxy]" );
}
}
ObjectOutputStream out = null;
try {
// stream closed in the finally
out = new ObjectOutputStream( outputStream );
out.writeObject( obj );
}
catch (IOException ex) {
throw new SerializationException( "could not serialize", ex );
}
finally {
try {
if ( out != null ) {
out.close();
}
}
catch (IOException ignored) {
}
}
}
Serializes an Object
to a byte array for
storage/serialization.
Params: - obj – the object to serialize to bytes
Throws: - SerializationException – (runtime) if the serialization fails
Returns: a byte[] with the converted Serializable
/**
* <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();
}
// Deserialize
//-----------------------------------------------------------------------
Deserializes an object from the specified stream using the Thread Context
ClassLoader (TCCL).
Delegates to doDeserialize
Params: - inputStream – the serialized object input stream, must not be null
Throws: - IllegalArgumentException – if
inputStream
is null
- SerializationException – (runtime) if the serialization fails
Returns: the deserialized object
/**
* 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 <T> T deserialize(InputStream inputStream) throws SerializationException {
return doDeserialize( inputStream, defaultClassLoader(), hibernateClassLoader(), null );
}
Returns the Thread Context ClassLoader (TCCL).
Returns: The current TCCL
/**
* 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.
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.
The stream passed in is not buffered internally within this method. This is
the responsibility of the caller, if desired.
Params: - inputStream – the serialized object input stream, must not be null
- loader – The classloader to use
Throws: - IllegalArgumentException – if
inputStream
is null
- SerializationException – (runtime) if the serialization fails
Returns: the deserialized object
/**
* 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
* 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
* the responsibility of the caller, if desired.
*
* @param inputStream the serialized object input stream, must not be null
* @param loader The classloader to use
*
* @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 {
return doDeserialize( inputStream, loader, defaultClassLoader(), hibernateClassLoader() );
}
@SuppressWarnings("unchecked")
public static <T> T 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,
fallbackLoader1,
fallbackLoader2
);
try {
return (T) in.readObject();
}
catch (ClassNotFoundException e) {
throw new SerializationException( "could not deserialize", e );
}
catch (IOException e) {
throw new SerializationException( "could not deserialize", e );
}
finally {
try {
in.close();
}
catch (IOException ignore) {
// ignore
}
}
}
catch (IOException e) {
throw new SerializationException( "could not deserialize", e );
}
}
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.
Delegates to deserialize(byte[], ClassLoader)
Params: - objectData – the serialized object, must not be null
Throws: - IllegalArgumentException – if
objectData
is null
- SerializationException – (runtime) if the serialization fails
Returns: the deserialized object
/**
* 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 doDeserialize( wrap( objectData ), defaultClassLoader(), hibernateClassLoader(), null );
}
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.
Delegates to deserialize(InputStream, ClassLoader)
using a ByteArrayInputStream
to wrap the array. Params: - objectData – the serialized object, must not be null
- loader – The classloader to use
Throws: - IllegalArgumentException – if
objectData
is null
- SerializationException – (runtime) if the serialization fails
Returns: the deserialized object
/**
* 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
* @param loader The classloader to use
*
* @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 {
return doDeserialize( wrap( objectData ), loader, defaultClassLoader(), hibernateClassLoader() );
}
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.
/**
* 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 loader1;
private final ClassLoader loader2;
private final ClassLoader loader3;
private CustomObjectInputStream(
InputStream in,
ClassLoader loader1,
ClassLoader loader2,
ClassLoader loader3) throws IOException {
super( in );
this.loader1 = loader1;
this.loader2 = loader2;
this.loader3 = loader3;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override
protected Class resolveClass(ObjectStreamClass v) throws IOException, ClassNotFoundException {
final String className = v.getName();
LOG.tracev( "Attempting to locate class [{0}]", className );
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, loader2 );
}
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, loader3 );
}
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;
}
return !one.equals( other );
}
}
}