/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2008], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.transport.util;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.transporter.TransporterClient;
/**
* An input stream that maintains a local byte buffer replenished via calls to a
* remote source. The stream id is used to maintain the stream state on the
* remote source. This class is not thread safe.
*
* The invoker locator to the remote source must be set before reading from this
* stream.
*/
public class RemoteInputStream
extends InputStream implements Externalizable {
private static final long serialVersionUID = -2359924019799176338L;
private InvokerLocator _sourceInvokerLocator;
private String _streamId;
private byte[] _currentBuffer;
private int _currentBufferIdx;
private InputStreamService _streamService;
private boolean _closed;
private boolean _isEOS;
/**
* Default constructor for externalization only.
*/
public RemoteInputStream() {}
/**
* Creates an instance on the remote source to be serialized and sent to
* the remote client reading from this stream.
*
* @param streamId The stream id that uniquely identifies this
* stream on the remote source.
* @throws NullPointerException if the stream id is <code>null</code>.
*/
public RemoteInputStream(String streamId) {
if (streamId == null) {
throw new NullPointerException("stream id is null");
}
_streamId = streamId;
}
/**
* Set the invoker locator to the remote source.
*
* @param invokerLocator The invoker locator to the remote source.
* @throws NullPointerException if the invoker locator is <code>null</code>.
*/
public void setRemoteSourceInvokerLocator(InvokerLocator invokerLocator) {
if (invokerLocator == null) {
throw new NullPointerException("invoker locator is null");
}
_sourceInvokerLocator = invokerLocator;
}
/**
* @see java.io.InputStream#available()
*/
public int available() throws IOException {
if (_closed) {
throw new IOException("stream is closed");
}
if (_currentBuffer == null) {
return 0;
}
return _currentBuffer.length-_currentBufferIdx;
}
/**
* @see java.io.InputStream#read(byte[], int, int)
*/
public int read(byte b[], int off, int len) throws IOException {
if (_closed) {
throw new IOException("stream is closed");
}
_isEOS = retrieveNextBuffer();
if (_isEOS) {
return -1;
}
int avail = available();
int maxLen = len;
if (avail > 0) {
// Only retrieve the next buffer if the current one is not depleted.
maxLen = Math.min(avail, len);
}
return super.read(b, off, maxLen);
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
if (_closed) {
throw new IOException("stream is closed");
}
_isEOS = retrieveNextBuffer();
if (_isEOS) {
return -1;
}
int c = 0xff & _currentBuffer[_currentBufferIdx++];
if (_currentBufferIdx == _currentBuffer.length) {
_currentBuffer = null;
}
return c;
}
private boolean retrieveNextBuffer() throws IOException {
if (_isEOS){
return true;
}
if (_currentBuffer == null) {
StreamBuffer nextBuffer = getInputStreamService().getNextBuffer(_streamId);
if (nextBuffer.isEOS()) {
return true;
}
_currentBuffer = nextBuffer.getBuffer();
_currentBufferIdx = 0;
}
return false;
}
/**
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
if (!_closed) {
_closed = true;
if (_streamService != null) {
TransporterClient.destroyTransporterClient(_streamService);
_streamService = null;
}
_currentBuffer = null;
}
}
/**
* @return The uniquely identifying stream id.
*/
public String getStreamId() {
return _streamId;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
_streamId = in.readUTF();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(_streamId);
}
private InputStreamService getInputStreamService() throws IOException {
if (_streamService == null) {
if (_sourceInvokerLocator == null) {
throw new IOException("remote source invoker locator was not set");
}
// HQ-1638 (Use the same strategy as HQ-1572)
// InputStreamService and the remoting classes for the unidirectional transport are intentionally
// put into the ServerHandler classloader. As of this comment, that classloader is a peer
// to the context classloader, so those classes are not visible to the current thread context,
// unless we force the issue.
ClassLoader currentContext = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(InputStreamService.class.getClassLoader());
try {
_streamService = (InputStreamService)TransporterClient.
createTransporterClient(_sourceInvokerLocator, InputStreamService.class);
} catch (Exception e) {
throw new IOException("Failed to connect to input stream " +
"service on remote source: "+e);
} finally {
Thread.currentThread().setContextClassLoader(currentContext);
}
}
return _streamService;
}
}