/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.jeri.internal.http;
import com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.jini.io.context.AcknowledgmentSource;
/**
* Superclass for OutboundRequest, InboundRequest implementation classes.
*
* @author Sun Microsystems, Inc.
*
*/
abstract class Request {
private static final Executor systemThreadPool = (Executor)
java.security.AccessController.doPrivileged(
new GetThreadPoolAction(false));
/* stream states */
private static final int UNUSED = 0;
private static final int OPEN = 1;
private static final int EOF = 2;
private static final int CLOSED = 3;
private static final int INVALID = 4;
private final Object stateLock = new Object();
private final Object outLock = new Object();
private final Object inLock = new Object();
private final ContentOutputStream out;
private final ContentInputStream in;
private boolean aborted = false;
private int outState = UNUSED;
private int inState = UNUSED;
private IOException outException;
private IOException inException;
/**
* Creates new Request, initializes content input/output streams.
*/
Request() {
out = new ContentOutputStream();
in = new ContentInputStream();
}
/**
* Returns true if any data sent.
*/
public boolean getDeliveryStatus() {
synchronized (stateLock) {
return outState > UNUSED;
}
}
/**
* Terminates request.
*/
public void abort() {
synchronized (stateLock) {
if (aborted || (outState >= CLOSED && inState >= EOF)) {
return;
}
aborted = true;
}
Runnable reaper = new Runnable() { public void run() { finish(); } };
systemThreadPool.execute(reaper, "Request reaper");
}
/**
* Finishes request, if not finished or aborted already. Returns once
* request is finished.
*/
void finish() {
try { out.close(false); } catch (Throwable th) {}
try { in.close(false); } catch (Throwable th) {}
}
/**
* Returns OutputStream used for writing outbound request/response data.
*/
OutputStream getOutputStream() {
return out;
}
/**
* Returns InputStream used for reading inbound request/response data.
*/
InputStream getInputStream() {
return in;
}
/**
* Method called internally before any outbound data is written.
*/
abstract void startOutput() throws IOException;
/**
* Method called internally to write outbound request/response data.
*/
abstract void write(byte[] b, int off, int len) throws IOException;
/**
* Method called internally to signal the end of outbound data.
*/
abstract void endOutput() throws IOException;
/**
* Method called internally before any inbound data is read. Returns true
* if inbound data is valid, false otherwise (e.g., error message content).
*/
abstract boolean startInput() throws IOException;
/**
* Method called internally to read inbound request/response data.
*/
abstract int read(byte[] b, int off, int len) throws IOException;
/**
* Method called internally to gauge available inbound data.
*/
abstract int available() throws IOException;
/**
* Method called internally when finished reading inbound data.
*/
abstract void endInput() throws IOException;
/**
* Method called internally to register acknowledgment listener.
*/
abstract void addAckListener(AcknowledgmentSource.Listener listener);
/**
* Method called internally when request is finished. If corrupt is true,
* the underlying transport channel has been left in an unknown state.
*/
abstract void done(boolean corrupt);
/**
* Method called internally to implement AcknowledgmentSource;
* checks state and delegates to addAckListener method.
*/
final boolean addAcknowledgmentListener(AcknowledgmentSource.Listener l) {
synchronized (outLock) {
synchronized (stateLock) {
if (aborted || outState >= CLOSED) {
return false;
}
}
addAckListener(l);
}
return true;
}
/**
* Stream for writing outbound request/response data.
*/
private class ContentOutputStream extends OutputStream {
public void write(int b) throws IOException {
write(new byte[] { (byte) b }, 0, 1);
}
public void write(byte[] b, int off, int len) throws IOException {
synchronized (outLock) {
checkOpen();
try {
Request.this.write(b, off, len);
} catch (Throwable th) {
invalidate(th);
}
}
}
public void flush() throws IOException {
/*
* REMIND: Upper layers will tend to invoke flush even though there
* is no need for it in RMI's protocol, so sending data in that
* event seems undesirable; also, we have no need to indicate a
* "push" to the remote endpoint. For now, we just ignore flush()
* invocations.
*/
synchronized (outLock) {
checkOpen();
}
}
public void close() throws IOException {
close(true);
}
/**
* Checks to make sure stream is open and startOutput has been called.
* Assumes caller already possesses outLock monitor.
*/
private void checkOpen() throws IOException {
synchronized (stateLock) {
if (aborted) {
throw new IOException("request aborted");
}
switch (outState) {
case OPEN:
return;
case CLOSED:
throw new IOException("stream closed");
case INVALID:
throw outException;
}
outState = OPEN;
}
try {
startOutput();
} catch (Throwable th) {
invalidate(th);
}
}
/**
* Attempts to close stream. If checkAbort is true and the request has
* been aborted, close will fail with an IOException.
*/
private void close(boolean checkAbort) throws IOException {
synchronized (outLock) {
synchronized (stateLock) {
if (checkAbort && aborted) {
throw new IOException("request aborted");
} else if (outState == CLOSED) {
return;
} else if (outState == INVALID) {
throw outException;
}
}
try {
if (outState == UNUSED) {
startOutput();
}
endOutput();
} catch (Throwable th) {
invalidate(th);
}
synchronized (stateLock) {
outState = CLOSED;
if (inState >= EOF) {
done(inState == INVALID);
}
}
}
}
/**
* Invalidates stream, saving cause. If other stream is done, finishes
* request. Assumes caller already possesses outLock monitor.
*/
private void invalidate(Throwable cause) throws IOException {
synchronized (stateLock) {
if (outState != INVALID) {
if (outState <= OPEN && inState >= EOF) {
done(true);
}
outState = INVALID;
outException = new IOException("stream invalid");
outException.initCause(cause);
}
throw outException;
}
}
}
/**
* Stream for reading inbound request/response data.
*/
private class ContentInputStream extends InputStream {
public int read() throws IOException {
byte[] b = new byte[1];
return (read(b, 0, 1) != -1) ? b[0] & 0xFF : -1;
}
public int read(byte[] b, int off, int len) throws IOException {
synchronized (inLock) {
if (!checkOpen()) {
return -1;
}
try {
int n = Request.this.read(b, off, len);
if (n == -1) {
endInput();
synchronized (stateLock) {
inState = EOF;
if (outState >= CLOSED) {
done(outState == INVALID);
}
}
}
return n;
} catch (Throwable th) {
invalidate(th);
throw new InternalError(); // unreached
}
}
}
public int available() throws IOException {
synchronized (inLock) {
if (!checkOpen()) {
return 0;
}
try {
return Request.this.available();
} catch (Throwable th) {
invalidate(th);
throw new InternalError(); // unreached
}
}
}
public void close() throws IOException {
close(true);
}
/**
* Checks to make sure stream is open and startInput has been called.
* Returns false if EOF has been reached, true otherwise. Assumes
* caller already possesses inLock monitor.
*/
private boolean checkOpen() throws IOException {
synchronized (stateLock) {
if (aborted) {
throw new IOException("request aborted");
}
switch (inState) {
case OPEN:
return true;
case EOF:
return false;
case CLOSED:
throw new IOException("stream closed");
case INVALID:
throw inException;
}
inState = OPEN;
}
try {
if (startInput()) {
return true;
} else {
endInput();
synchronized (stateLock) {
inState = EOF;
if (outState >= CLOSED) {
done(outState == INVALID);
}
}
return false;
}
} catch (Throwable th) {
invalidate(th);
throw new InternalError(); // unreached
}
}
/**
* Attempts to close stream. If checkAbort is true and the request has
* been aborted, close will fail with an IOException.
*/
private void close(boolean checkAbort) throws IOException {
synchronized (inLock) {
synchronized (stateLock) {
if (checkAbort && aborted) {
throw new IOException("request aborted");
}
switch (inState) {
case EOF:
inState = CLOSED;
case CLOSED:
return;
case INVALID:
throw inException;
}
}
try {
if (inState == UNUSED) {
startInput();
}
endInput();
} catch (Throwable th) {
invalidate(th);
}
synchronized (stateLock) {
inState = CLOSED;
if (outState >= CLOSED) {
done(outState == INVALID);
}
}
}
}
/**
* Invalidates stream, saving cause. If other stream is done, finishes
* request. Assumes caller already possesses inLock monitor.
*/
private void invalidate(Throwable cause) throws IOException {
synchronized (stateLock) {
if (inState != INVALID) {
if (inState <= OPEN && outState >= CLOSED) {
done(true);
}
inState = INVALID;
inException = new IOException("stream invalid");
inException.initCause(cause);
}
throw inException;
}
}
}
}