/*
* 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 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;
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;
/**
* 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);
}
}