/* Copyright (c) 2007 Health Market Science, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License. This library 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. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA You can contact Health Market Science at info@healthmarketscience.com or at the following address: Health Market Science 2700 Horizon Drive Suite 200 King of Prussia, PA 19406 */ package com.healthmarketscience.rmiio; import java.io.Closeable; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.Iterator; /** * Implementation of RemoteIteratorServer which uses java serialization to * send objects to the RemoteIteratorClient. Objects are grabbed from the * localIterator as needed and serialized to the local output stream. Note * that a RemoteIterator is accepted for the local iterator so that the * iterator may throw IOExceptions. * <p> * Note, the objects are written to the ObjectOutputStream using the * {@link java.io.ObjectOutputStream#writeUnshared} method, and the * {@link java.io.ObjectOutputStream#reset} method is called periodically on * the output stream. These measures are taken because memory can build up in * the ObjectOutputStream over time and a large data set can run the client * and/or server out of memory. In general, the objects being iterated over * most likely do not have shared references, so nothing will be lost by this * choice. However, if shared references are desired, the * {@link #serializeObject} method can be overriden by a custom subclass to * change this behavior. * <p> * In the event that a RemoteIterator is being used to return low-latency, * low-bandwidth update data to the client, the noDelay option can be enabled * for the underlying stream which will effectively disable buffering of data * on the server side. This can be very useful for implementing remote * progress monitors, for example. * <p> * Note, since it is a common idiom for the local iterator to implement * Closeable in order to close local resources, this implementation will * automatically close a Closeable local iterator after the underlying server * is shutdown. * * @see <a href="{@docRoot}/overview-summary.html#Usage_Notes">Usage Notes</a> * * @author James Ahlborn */ public class SerialRemoteIteratorServer<DataType> extends EncodingRemoteIteratorServer<DataType> { /** Default value for the setting indicating how often the ObjectOutputStream should be reset */ public static final int DEFAULT_RESET_NUM_OBJECTS = 1000; /** the output stream which does the java serialization work */ private ObjectOutputStream _objOStream; /** local iterator from which we are getting Serializable objects */ private final IOIterator<DataType> _localIterator; /** keeps track of num objects written so we can do periodic reset */ private int _numObjectsWrittenSinceLastReset; /** setting which indicates how often the ObjectOutputStream should be reset (after this many objects are written). */ private final int _resetNumObjects; public SerialRemoteIteratorServer(Iterator<DataType> localIterator) throws IOException { this(true, localIterator); } public SerialRemoteIteratorServer(boolean useCompression, Iterator<DataType> localIterator) throws IOException { this(useCompression, false, localIterator); } public SerialRemoteIteratorServer(boolean useCompression, boolean noDelay, Iterator<DataType> localIterator) throws IOException { this(useCompression, noDelay, RemoteInputStreamServer.DUMMY_MONITOR, localIterator); } public SerialRemoteIteratorServer( boolean useCompression, RemoteStreamMonitor<RemoteInputStreamServer> monitor, Iterator<DataType> localIterator) throws IOException { this(useCompression, false, monitor, localIterator); } public SerialRemoteIteratorServer( boolean useCompression, boolean noDelay, RemoteStreamMonitor<RemoteInputStreamServer> monitor, Iterator<DataType> localIterator) throws IOException { this(useCompression, noDelay, monitor, RemoteInputStreamServer.DEFAULT_CHUNK_SIZE, localIterator); } public SerialRemoteIteratorServer( boolean useCompression, boolean noDelay, RemoteStreamMonitor<RemoteInputStreamServer> monitor, int chunkSize, Iterator<DataType> localIterator) throws IOException { this(useCompression, noDelay, monitor, chunkSize, RmiioUtil.adapt(localIterator)); } public SerialRemoteIteratorServer(IOIterator<DataType> localIterator) throws IOException { this(true, localIterator); } public SerialRemoteIteratorServer(boolean useCompression, IOIterator<DataType> localIterator) throws IOException { this(useCompression, false, localIterator); } public SerialRemoteIteratorServer(boolean useCompression, boolean noDelay, IOIterator<DataType> localIterator) throws IOException { this(useCompression, noDelay, RemoteInputStreamServer.DUMMY_MONITOR, localIterator); } public SerialRemoteIteratorServer( boolean useCompression, RemoteStreamMonitor<RemoteInputStreamServer> monitor, IOIterator<DataType> localIterator) throws IOException { this(useCompression, false, monitor, localIterator); } public SerialRemoteIteratorServer( boolean useCompression, boolean noDelay, RemoteStreamMonitor<RemoteInputStreamServer> monitor, IOIterator<DataType> localIterator) throws IOException { this(useCompression, noDelay, monitor, RemoteInputStreamServer.DEFAULT_CHUNK_SIZE, localIterator); } public SerialRemoteIteratorServer( boolean useCompression, boolean noDelay, RemoteStreamMonitor<RemoteInputStreamServer> monitor, int chunkSize, IOIterator<DataType> localIterator) throws IOException { this(useCompression, noDelay, monitor, chunkSize, localIterator, DEFAULT_RESET_NUM_OBJECTS); } public SerialRemoteIteratorServer( boolean useCompression, boolean noDelay, RemoteStreamMonitor<RemoteInputStreamServer> monitor, int chunkSize, IOIterator<DataType> localIterator, int resetNumObjects) throws IOException { super(useCompression, noDelay, monitor, chunkSize); _localIterator = localIterator; _resetNumObjects = resetNumObjects; } @Override protected boolean writeNextObject() throws IOException { // have to create output stream on demand because constructor generates // output! if(_objOStream == null) { _objOStream = new ObjectOutputStream(_localOStream); } if(_localIterator.hasNext()) { // write out next object serializeObject(_objOStream, _localIterator.next()); return true; } // no more return false; } @Override protected void closeIterator() throws IOException { if(_objOStream != null) { // close (flush) object stream _objOStream.close(); } // close parent super.closeIterator(); } @Override protected void closeImpl(boolean readSuccess) throws IOException { // close our local iterator if it is Closeable. Swallow exceptions // because at this point, they do not matter. if(_localIterator instanceof Closeable) { RmiioUtil.closeQuietly((Closeable)_localIterator); } super.closeImpl(readSuccess); } /** * Writes the given object to the given output stream. The default * implementation uses {@link java.io.ObjectOutputStream#writeUnshared} as * well as periodically calls {@link java.io.ObjectOutputStream#reset} on * the output stream. Subclasses may choose to change this behavior by * overriding this method. * * @param ostream the output stream to which the object should be written * @param obj the object to write */ protected void serializeObject(ObjectOutputStream ostream, Object obj) throws IOException { ostream.writeUnshared(obj); _numObjectsWrittenSinceLastReset++; if(_numObjectsWrittenSinceLastReset >= _resetNumObjects) { ostream.reset(); _numObjectsWrittenSinceLastReset = 0; } } }