/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.libraries.serializer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
/**
* The SerializeHelper is used to make implementing custom serialization handlers easier. Handlers for certain object
* types need to be added to this helper before this implementation is usable.
*
* @author Thomas Morgner
*/
public class SerializerHelper {
private static final Log logger = LogFactory.getLog( SerializerHelper.class );
/**
* The singleton instance of the serialize helper.
*/
private static SerializerHelper singleton;
/**
* Returns or creates a new SerializerHelper. When a new instance is created by this method, all known
* SerializeMethods are registered.
*
* @return the SerializerHelper singleton instance.
*/
public static synchronized SerializerHelper getInstance() {
if ( singleton == null ) {
singleton = LibSerializerBoot.getInstance().getObjectFactory().get( SerializerHelper.class );
singleton.registerMethods();
}
return singleton;
}
/**
* A collection of the serializer methods.
*/
private final HashMap<Class, SerializeMethod> methods;
/**
* A class comparator for searching the super class of an certain class.
*/
private final ClassComparator comparator;
/**
* Creates a new SerializerHelper.
*/
public SerializerHelper() {
this.comparator = new ClassComparator();
this.methods = new HashMap<Class, SerializeMethod>();
}
/**
* Registers a new SerializeMethod with this SerializerHelper.
*
* @param method the method that should be registered.
*/
public synchronized void registerMethod( final SerializeMethod method ) {
this.methods.put( method.getObjectClass(), method );
}
/**
* Traverses the configuration and registers all serialization handlers in this factory.
*/
protected void registerMethods() {
final Configuration config = LibSerializerBoot.getInstance().getGlobalConfig();
final Iterator sit = config.findPropertyKeys( "org.pentaho.reporting.libraries.serializer.handler." );
while ( sit.hasNext() ) {
final String configkey = (String) sit.next();
final String c = config.getConfigProperty( configkey );
final SerializeMethod maybeModule = ObjectUtilities.loadAndInstantiate
( c, SerializerHelper.class, SerializeMethod.class );
if ( maybeModule != null ) {
registerMethod( maybeModule );
} else {
logger.warn( "Invalid SerializeMethod implementation: " + c );
}
}
}
/**
* Deregisters a new SerializeMethod with this SerializerHelper.
*
* @param method the method that should be deregistered.
*/
public synchronized void unregisterMethod( final SerializeMethod method ) {
this.methods.remove( method.getObjectClass() );
}
/**
* Returns the collection of all registered serialize methods.
*
* @return a collection of the registered serialize methods.
*/
protected HashMap getMethods() {
return methods;
}
/**
* Returns the class comparator instance used to find correct super classes.
*
* @return the class comparator.
*/
protected ClassComparator getComparator() {
return comparator;
}
/**
* Looks up the SerializeMethod for the given class or null if there is no SerializeMethod for the given class.
*
* @param c the class for which we want to lookup a serialize method.
* @return the method or null, if there is no registered method for the class.
*/
protected SerializeMethod getSerializer( final Class c ) {
final SerializeMethod sm = methods.get( c );
if ( sm != null ) {
return sm;
}
return getSuperClassObjectDescription( c );
}
/**
* Looks up the SerializeMethod for the given class or null if there is no SerializeMethod for the given class. This
* method searches all superclasses.
*
* @param d the class for which we want to lookup a serialize method.
* @return the method or null, if there is no registered method for the class.
*/
@SuppressWarnings( "unchecked" )
protected SerializeMethod getSuperClassObjectDescription
( final Class d ) {
SerializeMethod knownSuperClass = null;
final Iterator<Class> keys = methods.keySet().iterator();
while ( keys.hasNext() ) {
final Class keyClass = keys.next();
if ( keyClass.isAssignableFrom( d ) ) {
final SerializeMethod od = methods.get( keyClass );
if ( knownSuperClass == null ) {
knownSuperClass = od;
} else {
if ( comparator.isComparable
( knownSuperClass.getObjectClass(), od.getObjectClass() ) ) {
if ( comparator.compare
( knownSuperClass.getObjectClass(), od.getObjectClass() ) < 0 ) {
knownSuperClass = od;
}
}
}
}
}
return knownSuperClass;
}
/**
* Writes a serializable object description to the given object output stream. This method selects the best serialize
* helper method for the given object.
*
* @param o the to be serialized object.
* @param out the outputstream that should receive the object.
* @throws IOException if an I/O error occured.
*/
public synchronized void writeObject( final Object o,
final ObjectOutputStream out )
throws IOException {
try {
if ( o == null ) {
out.writeByte( 0 );
return;
}
if ( o instanceof Serializable ) {
out.writeByte( 1 );
out.writeObject( o );
return;
}
final SerializeMethod m = getSerializer( o.getClass() );
if ( m == null ) {
throw new NotSerializableException( o.getClass().getName() );
}
out.writeByte( 2 );
out.writeObject( m.getObjectClass() );
m.writeObject( o, out );
} catch ( NotSerializableException nse ) {
logger.warn( "Unable to serialize object: " + o );
throw nse;
}
}
public synchronized boolean isSerializable( final Object o ) {
if ( o == null ) {
return true;
}
if ( o instanceof Serializable ) {
return true;
}
final SerializeMethod m = getSerializer( o.getClass() );
return m != null;
}
/**
* Reads the object from the object input stream. This object selects the best serializer to read the object.
* <p/>
* Make sure, that you use the same configuration (library and class versions, registered methods in the
* SerializerHelper) for reading as you used for writing.
*
* @param in the object input stream from where to read the serialized data.
* @return the generated object.
* @throws IOException if reading the stream failed.
* @throws ClassNotFoundException if serialized object class cannot be found.
*/
public synchronized Object readObject( final ObjectInputStream in )
throws IOException, ClassNotFoundException {
final int type = in.readByte();
if ( type == 0 ) {
return null;
}
if ( type == 1 ) {
return in.readObject();
}
final Class c = (Class) in.readObject();
final SerializeMethod m = getSerializer( c );
if ( m == null ) {
throw new NotSerializableException( c.getName() );
}
return m.readObject( in );
}
}