/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Caucho Technology (http://www.caucho.com/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Hessian", "Resin", and "Caucho" must not be used to
* endorse or promote products derived from this software without prior
* written permission. For written permission, please contact
* info@caucho.com.
*
* 5. Products derived from this software may not be called "Resin"
* nor may "Resin" appear in their names without prior written
* permission of Caucho Technology.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Scott Ferguson
*/
package com.caucho.hessian.mux;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Hessian Mux, a peer-to-peer protocol.
*/
public class MuxServer {
private Object READ_LOCK = new Object();
private Object WRITE_LOCK = new Object();
private InputStream is;
private OutputStream os;
private boolean isClient;
private transient boolean isClosed;
// channels that have data ready.
private boolean inputReady[] = new boolean[4];
// true if there's a thread already reading
private boolean isReadLocked;
// true if there's a thread already writing
private boolean isWriteLocked;
/**
* Null argument constructor.
*/
public MuxServer()
{
}
/**
* Create a new multiplexor with input and output streams.
*
* @param is the underlying input stream
* @param os the underlying output stream
* @param isClient true if this is the connection client.
*/
public MuxServer(InputStream is, OutputStream os, boolean isClient)
{
init(is, os, isClient);
}
/**
* Initialize the multiplexor with input and output streams.
*
* @param is the underlying input stream
* @param os the underlying output stream
* @param isClient true if this is the connection client.
*/
public void init(InputStream is, OutputStream os, boolean isClient)
{
this.is = is;
this.os = os;
this.isClient = isClient;
}
/**
* Gets the raw input stream. Clients will normally not call
* this.
*/
public InputStream getInputStream()
{
return is;
}
/**
* Gets the raw output stream. Clients will normally not call
* this.
*/
public OutputStream getOutputStream()
{
return os;
}
/**
* Starts a client call.
*/
public boolean startCall(MuxInputStream in, MuxOutputStream out)
throws IOException
{
int channel = isClient ? 2 : 3;
return startCall(channel, in, out);
}
/**
* Starts a client call.
*/
public boolean startCall(int channel, MuxInputStream in, MuxOutputStream out)
throws IOException
{
// XXX: Eventually need to check to see if the channel is used.
// It's not clear whether this should cause a wait or an exception.
in.init(this, channel);
out.init(this, channel);
return true;
}
/**
* Reads a server request.
*/
public boolean readRequest(MuxInputStream in, MuxOutputStream out)
throws IOException
{
int channel = isClient ? 3 : 2;
in.init(this, channel);
out.init(this, channel);
if (readChannel(channel) != null) {
in.setInputStream(is);
in.readToData(false);
return true;
}
else
return false;
}
/**
* Grabs the channel for writing.
*
* @param channel the channel
*
* @return true if the channel has permission to write.
*/
OutputStream writeChannel(int channel)
throws IOException
{
while (os != null) {
boolean canWrite = false;
synchronized (WRITE_LOCK) {
if (! isWriteLocked) {
isWriteLocked = true;
canWrite = true;
}
else {
try {
WRITE_LOCK.wait(5000);
} catch (Exception e) {
}
}
}
if (canWrite) {
os.write('C');
os.write(channel >> 8);
os.write(channel);
return os;
}
}
return null;
}
void yield(int channel)
throws IOException
{
os.write('Y');
freeWriteLock();
}
void flush(int channel)
throws IOException
{
os.write('Y');
os.flush();
freeWriteLock();
}
void close(int channel)
throws IOException
{
if (os != null) {
os.write('Q');
os.flush();
freeWriteLock();
}
}
/**
* Frees the channel for writing.
*/
void freeWriteLock()
{
synchronized (WRITE_LOCK) {
isWriteLocked = false;
WRITE_LOCK.notifyAll();
}
}
/**
* Reads data from a channel.
*
* @param channel the channel
*
* @return true if the channel is valid.
*/
InputStream readChannel(int channel)
throws IOException
{
while (! isClosed) {
if (inputReady[channel]) {
inputReady[channel] = false;
return is;
}
boolean canRead = false;
synchronized (READ_LOCK) {
if (! isReadLocked) {
isReadLocked = true;
canRead = true;
}
else {
try {
READ_LOCK.wait(5000);
} catch (Exception e) {
}
}
}
if (canRead) {
try {
readData();
} catch (IOException e) {
close();
}
}
}
return null;
}
boolean getReadLock()
{
synchronized (READ_LOCK) {
if (! isReadLocked) {
isReadLocked = true;
return true;
}
else {
try {
READ_LOCK.wait(5000);
} catch (Exception e) {
}
}
}
return false;
}
/**
* Frees the channel for reading.
*/
void freeReadLock()
{
synchronized (READ_LOCK) {
isReadLocked = false;
READ_LOCK.notifyAll();
}
}
/**
* Reads data until a channel packet 'C' or error 'E' is received.
*/
private void readData()
throws IOException
{
while (! isClosed) {
int code = is.read();
switch (code) {
case ' ':
case '\t':
case '\n':
case '\r':
break;
case 'C': {
int channel = (is.read() << 8) + is.read();
inputReady[channel] = true;
return;
}
case 'E': {
int channel = (is.read() << 8) + is.read();
int status = (is.read() << 8) + is.read();
inputReady[channel] = true;
return;
}
case -1:
close();
return;
default:
// An error in the protocol. Kill the connection.
close();
return;
}
}
return;
}
/**
* Close the mux
*/
public void close()
throws IOException
{
isClosed = true;
OutputStream os = this.os;
this.os = null;
InputStream is = this.is;
this.is = null;
if (os != null)
os.close();
if (is != null)
is.close();
}
}