org.apache.http.impl.nio.reactor.IOSessionImpl Maven / Gradle / Ivy
/*
* ====================================================================
* 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.http.impl.nio.reactor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.SessionBufferStatus;
import org.apache.http.nio.reactor.SocketAccessor;
import org.apache.http.util.Args;
/**
* Default implementation of {@link IOSession}.
*
* @since 4.0
*/
@Contract(threading = ThreadingBehavior.SAFE)
public class IOSessionImpl implements IOSession, SocketAccessor {
private final SelectionKey key;
private final ByteChannel channel;
private final Map attributes;
private final InterestOpsCallback interestOpsCallback;
private final SessionClosedCallback sessionClosedCallback;
private volatile int status;
private volatile int currentEventMask;
private volatile SessionBufferStatus bufferStatus;
private volatile int socketTimeout;
private final long startedTime;
private volatile long lastReadTime;
private volatile long lastWriteTime;
private volatile long lastAccessTime;
/**
* Creates new instance of IOSessionImpl.
*
* @param key the selection key.
* @param interestOpsCallback interestOps callback.
* @param sessionClosedCallback session closed callback.
*
* @since 4.1
*/
public IOSessionImpl(
final SelectionKey key,
final InterestOpsCallback interestOpsCallback,
final SessionClosedCallback sessionClosedCallback) {
super();
Args.notNull(key, "Selection key");
this.key = key;
this.channel = (ByteChannel) this.key.channel();
this.interestOpsCallback = interestOpsCallback;
this.sessionClosedCallback = sessionClosedCallback;
this.attributes = Collections.synchronizedMap(new HashMap());
this.currentEventMask = key.interestOps();
this.socketTimeout = 0;
this.status = ACTIVE;
final long now = System.currentTimeMillis();
this.startedTime = now;
this.lastReadTime = now;
this.lastWriteTime = now;
this.lastAccessTime = now;
}
/**
* Creates new instance of IOSessionImpl.
*
* @param key the selection key.
* @param sessionClosedCallback session closed callback.
*/
public IOSessionImpl(
final SelectionKey key,
final SessionClosedCallback sessionClosedCallback) {
this(key, null, sessionClosedCallback);
}
@Override
public ByteChannel channel() {
return this.channel;
}
@Override
public SocketAddress getLocalAddress() {
return this.channel instanceof SocketChannel
? ((SocketChannel) this.channel).socket().getLocalSocketAddress()
: null;
}
@Override
public SocketAddress getRemoteAddress() {
return this.channel instanceof SocketChannel
? ((SocketChannel) this.channel).socket().getRemoteSocketAddress()
: null;
}
@Override
public int getEventMask() {
return this.interestOpsCallback != null ? this.currentEventMask : this.key.interestOps();
}
@Override
public synchronized void setEventMask(final int ops) {
if (this.status == CLOSED) {
return;
}
if (this.interestOpsCallback != null) {
// update the current event mask
this.currentEventMask = ops;
// local variable
final InterestOpEntry entry = new InterestOpEntry(this.key, this.currentEventMask);
// add this operation to the interestOps() queue
this.interestOpsCallback.addInterestOps(entry);
} else {
this.key.interestOps(ops);
}
this.key.selector().wakeup();
}
@Override
public synchronized void setEvent(final int op) {
if (this.status == CLOSED) {
return;
}
if (this.interestOpsCallback != null) {
// update the current event mask
this.currentEventMask |= op;
// local variable
final InterestOpEntry entry = new InterestOpEntry(this.key, this.currentEventMask);
// add this operation to the interestOps() queue
this.interestOpsCallback.addInterestOps(entry);
} else {
final int ops = this.key.interestOps();
this.key.interestOps(ops | op);
}
this.key.selector().wakeup();
}
@Override
public synchronized void clearEvent(final int op) {
if (this.status == CLOSED) {
return;
}
if (this.interestOpsCallback != null) {
// update the current event mask
this.currentEventMask &= ~op;
// local variable
final InterestOpEntry entry = new InterestOpEntry(this.key, this.currentEventMask);
// add this operation to the interestOps() queue
this.interestOpsCallback.addInterestOps(entry);
} else {
final int ops = this.key.interestOps();
this.key.interestOps(ops & ~op);
}
this.key.selector().wakeup();
}
@Override
public int getSocketTimeout() {
return this.socketTimeout;
}
@Override
public void setSocketTimeout(final int timeout) {
this.socketTimeout = timeout;
this.lastAccessTime = System.currentTimeMillis();
}
@Override
public void close() {
synchronized (this) {
if (this.status == CLOSED) {
return;
}
this.status = CLOSED;
this.key.cancel();
try {
this.key.channel().close();
} catch (final IOException ex) {
// Munching exceptions is not nice
// but in this case it is justified
}
if (this.sessionClosedCallback != null) {
this.sessionClosedCallback.sessionClosed(this);
}
if (this.key.selector().isOpen()) {
this.key.selector().wakeup();
}
}
}
@Override
public int getStatus() {
return this.status;
}
@Override
public boolean isClosed() {
return this.status == CLOSED;
}
@Override
public void shutdown() {
// For this type of session, a close() does exactly
// what we need and nothing more.
close();
}
@Override
public boolean hasBufferedInput() {
final SessionBufferStatus buffStatus = this.bufferStatus;
return buffStatus != null && buffStatus.hasBufferedInput();
}
@Override
public boolean hasBufferedOutput() {
final SessionBufferStatus buffStatus = this.bufferStatus;
return buffStatus != null && buffStatus.hasBufferedOutput();
}
@Override
public void setBufferStatus(final SessionBufferStatus bufferStatus) {
this.bufferStatus = bufferStatus;
}
@Override
public Object getAttribute(final String name) {
return this.attributes.get(name);
}
@Override
public Object removeAttribute(final String name) {
return this.attributes.remove(name);
}
@Override
public void setAttribute(final String name, final Object obj) {
this.attributes.put(name, obj);
}
public long getStartedTime() {
return this.startedTime;
}
public long getLastReadTime() {
return this.lastReadTime;
}
public long getLastWriteTime() {
return this.lastWriteTime;
}
public long getLastAccessTime() {
return this.lastAccessTime;
}
void resetLastRead() {
final long now = System.currentTimeMillis();
this.lastReadTime = now;
this.lastAccessTime = now;
}
void resetLastWrite() {
final long now = System.currentTimeMillis();
this.lastWriteTime = now;
this.lastAccessTime = now;
}
private static void formatOps(final StringBuilder buffer, final int ops) {
if ((ops & SelectionKey.OP_READ) > 0) {
buffer.append('r');
}
if ((ops & SelectionKey.OP_WRITE) > 0) {
buffer.append('w');
}
if ((ops & SelectionKey.OP_ACCEPT) > 0) {
buffer.append('a');
}
if ((ops & SelectionKey.OP_CONNECT) > 0) {
buffer.append('c');
}
}
private static void formatAddress(final StringBuilder buffer, final SocketAddress socketAddress) {
if (socketAddress instanceof InetSocketAddress) {
final InetSocketAddress addr = ((InetSocketAddress) socketAddress);
buffer.append(addr.getAddress() != null ? addr.getAddress().getHostAddress() :
addr.getAddress())
.append(':')
.append(addr.getPort());
} else {
buffer.append(socketAddress);
}
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
synchronized (this.key) {
final SocketAddress remoteAddress = getRemoteAddress();
final SocketAddress localAddress = getLocalAddress();
if (remoteAddress != null && localAddress != null) {
formatAddress(buffer, localAddress);
buffer.append("<->");
formatAddress(buffer, remoteAddress);
}
buffer.append('[');
switch (this.status) {
case ACTIVE:
buffer.append("ACTIVE");
break;
case CLOSING:
buffer.append("CLOSING");
break;
case CLOSED:
buffer.append("CLOSED");
break;
}
buffer.append("][");
if (this.key.isValid()) {
formatOps(buffer, this.interestOpsCallback != null ?
this.currentEventMask : this.key.interestOps());
buffer.append(':');
formatOps(buffer, this.key.readyOps());
}
}
buffer.append(']');
return new String(buffer);
}
@Override
public Socket getSocket() {
return this.channel instanceof SocketChannel ? ((SocketChannel) this.channel).socket() : null;
}
}