/*
* 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 org.apache.coyote.http11.upgrade;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import org.apache.coyote.ContainerThreadMarker;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.net.DispatchType;
import org.apache.tomcat.util.net.SocketWrapperBase;
import org.apache.tomcat.util.res.StringManager;
public class UpgradeServletOutputStream extends ServletOutputStream {
private static final Log log = LogFactory.getLog(UpgradeServletOutputStream.class);
private static final StringManager sm =
StringManager.getManager(UpgradeServletOutputStream.class);
private final UpgradeProcessorBase processor;
private final SocketWrapperBase<?> socketWrapper;
// Used to ensure that isReady() and onWritePossible() have a consistent
// view of buffer and registered.
private final Object registeredLock = new Object();
// Used to ensure that only one thread writes to the socket at a time and
// that buffer is consistently updated with any unwritten data after the
// write. Note it is not necessary to hold this lock when checking if buffer
// contains data but, depending on how the result is used, some form of
// synchronization may be required (see fireListenerLock for an example).
private final Object writeLock = new Object();
private volatile boolean flushing = false;
private volatile boolean closed = false;
// Start in blocking-mode
private volatile WriteListener listener = null;
// Guarded by registeredLock
private boolean registered = false;
public UpgradeServletOutputStream(UpgradeProcessorBase processor,
SocketWrapperBase<?> socketWrapper) {
this.processor = processor;
this.socketWrapper = socketWrapper;
}
@Override
public final boolean isReady() {
if (listener == null) {
throw new IllegalStateException(
sm.getString("upgrade.sos.canWrite.ise"));
}
if (closed) {
return false;
}
// Make sure isReady() and onWritePossible() have a consistent view of
// fireListener when determining if the listener should fire
synchronized (registeredLock) {
if (flushing) {
// Since flushing is true the socket must already be registered
// for write and multiple registrations will cause problems.
registered = true;
return false;
} else if (registered){
// The socket is already registered for write and multiple
// registrations will cause problems.
return false;
} else {
boolean result = socketWrapper.isReadyForWrite();
registered = !result;
return result;
}
}
}
@Override
public final void setWriteListener(WriteListener listener) {
if (listener == null) {
throw new IllegalArgumentException(
sm.getString("upgrade.sos.writeListener.null"));
}
if (this.listener != null) {
throw new IllegalArgumentException(
sm.getString("upgrade.sos.writeListener.set"));
}
if (closed) {
throw new IllegalStateException(sm.getString("upgrade.sos.write.closed"));
}
// Container is responsible for first call to onWritePossible().
synchronized (registeredLock) {
registered = true;
// Container is responsible for first call to onDataAvailable().
if (ContainerThreadMarker.isContainerThread()) {
processor.addDispatch(DispatchType.NON_BLOCKING_WRITE);
} else {
socketWrapper.registerWriteInterest();
}
}
this.listener = listener;
}
final boolean isClosed() {
return closed;
}
@Override
public void write(int b) throws IOException {
synchronized (writeLock) {
preWriteChecks();
writeInternal(new byte[] { (byte) b }, 0, 1);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
synchronized (writeLock) {
preWriteChecks();
writeInternal(b, off, len);
}
}
@Override
public void flush() throws IOException {
preWriteChecks();
flushInternal(listener == null, true);
}
private void flushInternal(boolean block, boolean updateFlushing) throws IOException {
try {
synchronized (writeLock) {
if (updateFlushing) {
flushing = socketWrapper.flush(block);
if (flushing) {
socketWrapper.registerWriteInterest();
}
} else {
socketWrapper.flush(block);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
onError(t);
if (t instanceof IOException) {
throw (IOException) t;
} else {
throw new IOException(t);
}
}
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
flushInternal((listener == null), false);
}
private void preWriteChecks() {
if (listener != null && !socketWrapper.canWrite()) {
throw new IllegalStateException(sm.getString("upgrade.sos.write.ise"));
}
if (closed) {
throw new IllegalStateException(sm.getString("upgrade.sos.write.closed"));
}
}
/**
* Must hold writeLock to call this method.
*/
private void writeInternal(byte[] b, int off, int len) throws IOException {
if (listener == null) {
// Simple case - blocking IO
socketWrapper.write(true, b, off, len);
} else {
socketWrapper.write(false, b, off, len);
}
}
final void onWritePossible() {
try {
if (flushing) {
flushInternal(false, true);
if (flushing) {
return;
}
} else {
// This may fill the write buffer in which case the
// isReadyForWrite() call below will re-register the socket for
// write
flushInternal(false, false);
}
} catch (IOException ioe) {
onError(ioe);
return;
}
// Make sure isReady() and onWritePossible() have a consistent view
// of buffer and fireListener when determining if the listener
// should fire
boolean fire = false;
synchronized (registeredLock) {
if (socketWrapper.isReadyForWrite()) {
registered = false;
fire = true;
} else {
registered = true;
}
}
if (fire) {
ClassLoader oldCL = processor.getUpgradeToken().getContextBind().bind(false, null);
try {
listener.onWritePossible();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
onError(t);
} finally {
processor.getUpgradeToken().getContextBind().unbind(false, oldCL);
}
}
}
private final void onError(Throwable t) {
if (listener == null) {
return;
}
ClassLoader oldCL = processor.getUpgradeToken().getContextBind().bind(false, null);
try {
listener.onError(t);
} catch (Throwable t2) {
ExceptionUtils.handleThrowable(t2);
log.warn(sm.getString("upgrade.sos.onErrorFail"), t2);
} finally {
processor.getUpgradeToken().getContextBind().unbind(false, oldCL);
}
try {
close();
} catch (IOException ioe) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("upgrade.sos.errorCloseFail"), ioe);
}
}
}
}