/* 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.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import com.healthmarketscience.rmiio.util.EncodingInputStream; /** * Utility which provides a wrapper InputStream for the client of a * RemoteInputStream. The wrapper will automagically handle any compression * needs of the remote stream. RemoteException's will be retried using the * given RemoteRetry implementation. Note that the InputStreams will not * support mark()/reset() stream functionality. Users should generally not * need to wrap the returned stream with a BufferedInputStream as buffering * will be done by the returned implementation (unless *large* amounts of * buffering are desired). * * @author James Ahlborn */ public class RemoteInputStreamClient { protected static final Logger LOG = LoggerFactory.getLogger(RemoteInputStreamClient.class); private RemoteInputStreamClient() {} /** * Wraps a RemoteInputStream as an InputStream using the * {@link RemoteClient#DEFAULT_RETRY} retry policy. * * @param remoteIn a remote input stream interface * @return an InputStream which will read from the given RemoteInputStream */ public static InputStream wrap(RemoteInputStream remoteIn) throws IOException { return wrap(remoteIn, RemoteClient.DEFAULT_RETRY); } /** * Wraps a RemoteInputStream as an InputStream using the given retry * strategy. * * @param remoteIn a remote input stream interface * @param retry RemoteException retry policy to use, if <code>null</code>, * {@link RemoteClient#DEFAULT_RETRY} will be used. * @return an InputStream which will read from the given RemoteInputStream */ public static InputStream wrap(RemoteInputStream remoteIn, RemoteRetry retry) throws IOException { if(retry == null) { retry = RemoteClient.DEFAULT_RETRY; } InputStream retStream = new RemoteInputStreamImpl(remoteIn, retry); // determine if using compression (use wrapped _remoteIn with retry // builtin) if(((RemoteInputStreamImpl)retStream)._remoteIn.usingGZIPCompression()) { // handle compression in the data retStream = new SaferGZIPInputStream(retStream, RemoteInputStreamServer.DEFAULT_CHUNK_SIZE); } return retStream; } /** * InputStream implementation which reads data from a RemoteInputStream * server. */ private static final class RemoteInputStreamImpl extends EncodingInputStream { /** handle to the RemoteInputStream server */ private final RemoteInputStream _remoteIn; /** output stream to which we write the bytes from the remote server */ private final PacketOutputStream _ostream; /** the next sequence id to use for a remote call */ private int nextActionId = RemoteStreamServer.INITIAL_VALID_SEQUENCE_ID; /** keep track of successful remote close calls, so that double closing the stream does not cause spurious errors (in the normal case) */ private volatile boolean _remoteCloseSuccessful; /** keep track of whether any over-the-wire read calls failed */ private volatile boolean _readSuccess = true; public RemoteInputStreamImpl(RemoteInputStream remoteIn, RemoteRetry retry) { super(RemoteInputStreamServer.DEFAULT_CHUNK_SIZE); // wrap the remote stub with automatic retry facility using given retry // policy _remoteIn = new RemoteInputStreamWrapper(remoteIn, retry, LOG); // note, we call this here because this subclass is final, otherwise we // would not want to call this in the constructor _ostream = createOutputStream(); } @Override public synchronized int available() throws IOException { return super.available(); } @Override public synchronized int read() throws IOException { return super.read(); } @Override public synchronized int read(byte[] b) throws IOException { return super.read(b); } @Override public synchronized int read(byte[] buf, int pos, int len) throws IOException { return super.read(buf, pos, len); } @Override public synchronized long skip(long len) throws IOException { return super.skip(len); } @Override public synchronized byte[] readPacket(boolean readPartial) throws IOException { return super.readPacket(readPartial); } @Override public synchronized int packetsAvailable() throws IOException { return super.packetsAvailable(); } @Override public void close() throws IOException { if(_remoteCloseSuccessful) { // we've already successfully called close on the remote stream, // calling it again would result in an exception because the remote // server will be gone return; } // close the remote stream _remoteIn.close(_readSuccess); super.close(); // only set this if the close call is successful (does not throw) _remoteCloseSuccessful = true; } @Override protected void encode(int suggestedLength) throws IOException { // grab more data from remote server boolean success = false; byte[] packet = null; try { packet = _remoteIn.readPacket(nextActionId++); success = true; } finally { if(!success) { _readSuccess = false; } } if(packet != null) { _ostream.writePacket(packet); } else { _ostream.close(); } } @Override protected long encodeSkip(long len) throws IOException { boolean success = false; try { long result = _remoteIn.skip(len, nextActionId++); success = true; return result; } finally { if(!success) { _readSuccess = false; } } } } /** * Subclass of GZIPInputStream which makes a better attempt at closing the * underlying RemoteInputStream, even if the data has not been successfully * read. */ private static class SaferGZIPInputStream extends GZIPInputStream { private SaferGZIPInputStream(InputStream in, int size) throws IOException { super(in, size); } @Override public void close() throws IOException { // GZIPInputStream will not close underlying stream if it fails on final // read, but that means remote stream won't get closed. we want to // force remote stream close regardless of success Exception closeFailure = null; try { super.close(); } catch(Exception e) { closeFailure = e; } finally { in.close(); } if(closeFailure != null) { if(closeFailure instanceof IOException) { throw (IOException)closeFailure; } throw (RuntimeException)closeFailure; } } } }