/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.protocol.simple;
import com.sun.sgs.app.Delivery;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.auth.IdentityCoordinator;
import com.sun.sgs.impl.auth.NamePasswordCredentials;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.impl.sharedutil.PropertiesWrapper;
import com.sun.sgs.impl.util.AbstractKernelRunnable;
import com.sun.sgs.impl.util.AbstractService;
import com.sun.sgs.kernel.ComponentRegistry;
import com.sun.sgs.kernel.KernelRunnable;
import com.sun.sgs.kernel.RecurringTaskHandle;
import com.sun.sgs.nio.channels.AsynchronousByteChannel;
import com.sun.sgs.protocol.ProtocolAcceptor;
import com.sun.sgs.protocol.ProtocolDescriptor;
import com.sun.sgs.protocol.ProtocolListener;
import com.sun.sgs.protocol.SessionProtocol;
import com.sun.sgs.protocol.simple.SimpleSgsProtocol;
import com.sun.sgs.service.TransactionProxy;
import com.sun.sgs.transport.ConnectionHandler;
import com.sun.sgs.transport.Transport;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.login.LoginException;
/**
* A protocol acceptor for connections that speak the {@link
* SimpleSgsProtocol}. The {@link #SimpleSgsProtocolAcceptor constructors}
* support the following properties: <p>
*
* <dl style="margin-left: 1em">
*
* <dt> <i>Property:</i> <code><b>
* {@value #TRANSPORT_PROPERTY}
* </b></code><br>
* <i>Default:</i> {@value #DEFAULT_TRANSPORT}
*
* <dd style="padding-top: .5em">Specifies the transport. The
* specified transport must support {@link Delivery#RELIABLE}.<p>
*
* <dt> <i>Property:</i> <code><b>
* {@value #PROTOCOL_VERSION_PROPERTY}
* </b></code><br>
* <i>Default:</i> {@value #DEFAULT_PROTOCOL_VERSION}
*
* <dd style="padding-top: .5em">Specifies the <code>SimpleSgsProtocol</code>
* version for this acceptor's connections. Valid values for the protocol
* version are <b><code>0x05</code></b> which supports client session
* relocation, and <b><code>0x04</code></b> (the default), which does not
* support client session relocation but is compatible with clients
* using the older protocol version. Protocol version
* <b><code>0x05</code></b> is incompatible with clients using
* protocol version <b><code>0x04</code></b>.<p>
*
* <dt> <i>Property:</i> <code><b>
* {@value #READ_BUFFER_SIZE_PROPERTY}
* </b></code><br>
* <i>Default:</i> {@value #DEFAULT_READ_BUFFER_SIZE}<br>
* <i>Minimum:</i> {@value #MIN_READ_BUFFER_SIZE}<br>
*
* <dd style="padding-top: .5em">
* Specifies the read buffer size.<p>
*
* <dt> <i>Property:</i> <code><b>
* {@value #DISCONNECT_DELAY_PROPERTY}
* </b></code><br>
* <i>Default:</i> {@value #DEFAULT_DISCONNECT_DELAY}<br>
* <i>Minimum:</i> {@value #MIN_DISCONNECT_DELAY}<br>
*
* <dd style="padding-top: .5em">
* Specifies the disconnect delay (in milliseconds) for disconnecting
* sessions.<p>
* </dl> <p>
*/
public class SimpleSgsProtocolAcceptor
extends AbstractService
implements ProtocolAcceptor
{
/** The package name. */
private static final String PKG_NAME = "com.sun.sgs.impl.protocol.simple";
/** The logger for this class. */
private static final LoggerWrapper staticLogger =
new LoggerWrapper(Logger.getLogger(PKG_NAME + ".acceptor"));
/** The name of the version key. */
private static final String VERSION_KEY = PKG_NAME + ".service.version";
/** The major version. */
private static final int MAJOR_VERSION = 1;
/** The minor version. */
private static final int MINOR_VERSION = 0;
/** The name of the read buffer size property. */
public static final String READ_BUFFER_SIZE_PROPERTY =
PKG_NAME + ".read.buffer.size";
/** The default read buffer size: {@value #DEFAULT_READ_BUFFER_SIZE}. */
public static final int DEFAULT_READ_BUFFER_SIZE = 128 * 1024;
/** The minimum read buffer size value. */
public static final int MIN_READ_BUFFER_SIZE = 8192;
/**
* The transport property. The specified transport must support
* RELIABLE delivery.
*/
public static final String TRANSPORT_PROPERTY =
PKG_NAME + ".transport";
/** The default transport. */
public static final String DEFAULT_TRANSPORT =
"com.sun.sgs.impl.transport.tcp.TcpTransport";
/** The protocol version property. Valid values are 4 and 5. */
public static final String PROTOCOL_VERSION_PROPERTY =
PKG_NAME + ".protocol.version";
/** The protocol version 4. */
public static final int PROTOCOL4 = 4;
/** The default protocol version: {@value #DEFAULT_PROTOCOL_VERSION}. */
public static final int DEFAULT_PROTOCOL_VERSION = PROTOCOL4;
/** The name of the disconnect delay property. */
public static final String DISCONNECT_DELAY_PROPERTY =
PKG_NAME + ".disconnect.delay";
/** The time (in milliseconds) that a disconnecting connection is
* allowed before this service forcibly disconnects it.
*/
public static final long DEFAULT_DISCONNECT_DELAY = 1000;
/** The minimum disconnect delay value. */
public static final long MIN_DISCONNECT_DELAY = 1000;
/** The identity manager. */
private final IdentityCoordinator identityManager;
/** The read buffer size for new connections. */
protected final int readBufferSize;
/** The transport. */
protected final Transport transport;
/** The disconnect delay (in milliseconds) for disconnecting sessions. */
private final long disconnectDelay;
/** The {@code SimpleSgsProtocol} version for the protocol impl. */
private final int protocolVersion;
/** The protocol descriptor. */
private ProtocolDescriptor protocolDesc;
/** The map of disconnecting {@code ClientSessionHandler}s, keyed by
* the time the connection should expire.
*/
private final ConcurrentSkipListMap<Long, SessionProtocol>
disconnectingHandlersMap =
new ConcurrentSkipListMap<Long, SessionProtocol>();
/** The handle for the task that monitors disconnecting client sessions. */
private final RecurringTaskHandle monitorDisconnectingSessionsTaskHandle;
/**
* Constructs an instance with the specified {@code properties},
* {@code systemRegistry}, and {@code txnProxy}.
*
* @param properties the configuration properties
* @param systemRegistry the system registry
* @param txnProxy a transaction proxy
*
* @throws Exception if a problem occurs
*/
public SimpleSgsProtocolAcceptor(Properties properties,
ComponentRegistry systemRegistry,
TransactionProxy txnProxy)
throws Exception
{
this(properties, systemRegistry, txnProxy, staticLogger);
}
/**
* Constructs an instance with the specified {@code properties},
* {@code systemRegistry}, {@code txnProxy}, and {@code logger}.
*
* @param properties the configuration properties
* @param systemRegistry the system registry
* @param txnProxy a transaction proxy
* @param logger a logger for this instance
*
* @throws Exception if a problem occurs
*/
protected SimpleSgsProtocolAcceptor(Properties properties,
ComponentRegistry systemRegistry,
TransactionProxy txnProxy,
LoggerWrapper logger)
throws Exception
{
super(properties, systemRegistry, txnProxy, logger);
logger.log(Level.CONFIG, "Creating SimpleSgsProtocolAcceptor");
PropertiesWrapper wrappedProps = new PropertiesWrapper(properties);
try {
readBufferSize = wrappedProps.getIntProperty(
READ_BUFFER_SIZE_PROPERTY, DEFAULT_READ_BUFFER_SIZE,
MIN_READ_BUFFER_SIZE, Integer.MAX_VALUE);
disconnectDelay = wrappedProps.getLongProperty(
DISCONNECT_DELAY_PROPERTY, DEFAULT_DISCONNECT_DELAY,
MIN_DISCONNECT_DELAY, Long.MAX_VALUE);
identityManager =
systemRegistry.getComponent(IdentityCoordinator.class);
transport =
wrappedProps.getClassInstanceProperty(
TRANSPORT_PROPERTY, DEFAULT_TRANSPORT, Transport.class,
new Class[] {Properties.class}, properties);
protocolVersion =
wrappedProps.getIntProperty(
PROTOCOL_VERSION_PROPERTY, DEFAULT_PROTOCOL_VERSION,
PROTOCOL4, SimpleSgsProtocol.VERSION);
if (!transport.getDelivery().equals(Delivery.RELIABLE)) {
transport.shutdown();
throw new IllegalArgumentException(
"transport must support RELIABLE delivery");
}
/*
* Set up recurring task to monitor disconnecting client sessions.
*/
monitorDisconnectingSessionsTaskHandle =
taskScheduler.scheduleRecurringTask(
new MonitorDisconnectingSessionsTask(),
taskOwner, System.currentTimeMillis(),
disconnectDelay);
monitorDisconnectingSessionsTaskHandle.start();
/*
* Check service version.
*/
transactionScheduler.runTask(
new AbstractKernelRunnable("CheckServiceVersion") {
public void run() {
checkServiceVersion(
VERSION_KEY, MAJOR_VERSION, MINOR_VERSION);
} }, taskOwner);
logger.log(Level.CONFIG,
"Created SimpleSgsProtocolAcceptor with properties:" +
"\n " + PROTOCOL_VERSION_PROPERTY + "=" +
protocolVersion +
"\n " + DISCONNECT_DELAY_PROPERTY + "=" +
disconnectDelay +
"\n " + READ_BUFFER_SIZE_PROPERTY + "=" +
readBufferSize +
"\n " + TRANSPORT_PROPERTY + "=" +
transport.getClass().getName());
} catch (RuntimeException e) {
if (logger.isLoggable(Level.CONFIG)) {
logger.logThrow(
Level.CONFIG, e,
"Failed to create SimpleSgsProtocolAcceptor");
}
throw e;
}
}
/* -- Implement AbstractService -- */
/** {@inheritDoc} */
protected void handleServiceVersionMismatch(
Version oldVersion, Version currentVersion)
{
throw new IllegalStateException(
"unable to convert version:" + oldVersion +
" to current version:" + currentVersion);
}
/** {@inheritDoc} */
public void doReady() {
}
/** {@inheritDoc} */
public void doShutdown() {
transport.shutdown();
monitorDisconnectingSessionsTaskHandle.cancel();
disconnectingHandlersMap.clear();
}
/* -- Implement ProtocolAcceptor -- */
/** {@inheritDoc} */
public synchronized ProtocolDescriptor getDescriptor() {
if (protocolDesc == null) {
protocolDesc =
new SimpleSgsProtocolDescriptor(transport.getDescriptor());
}
return protocolDesc;
}
/** {@inheritDoc} */
public void accept(ProtocolListener protocolListener) throws IOException {
transport.accept(new ConnectionHandlerImpl(protocolListener));
}
/** {@inheritDoc} */
public void close() {
shutdown();
}
/**
* Transport connection handler.
*/
private class ConnectionHandlerImpl implements ConnectionHandler {
private final ProtocolListener protocolListener;
ConnectionHandlerImpl(ProtocolListener protocolListener) {
if (protocolListener == null) {
throw new NullPointerException("null protocolListener");
}
this.protocolListener = protocolListener;
}
/** {@inheritDoc} */
public void newConnection(AsynchronousByteChannel byteChannel)
throws Exception
{
if (protocolVersion == PROTOCOL4) {
new SimpleSgsProtocolImpl(
protocolListener, SimpleSgsProtocolAcceptor.this,
byteChannel, readBufferSize);
} else /* latest SimpleSgsProtocol version */ {
new SimpleSgsRelocationProtocolImpl(
protocolListener, SimpleSgsProtocolAcceptor.this,
byteChannel, readBufferSize);
}
}
/** {@inheritDoc} */
public void shutdown() {
logger.log(Level.SEVERE, "transport unexpectly shutdown");
close();
}
}
/**
* Returns the authenticated identity for the specified {@code name} and
* {@code password}.
*
* @param name a name
* @param password a password
* @return the authenticated identity
* @throws LoginException if a problem occurs authenticating the name and
* password
*/
public Identity authenticate(String name, String password)
throws LoginException
{
return identityManager.authenticateIdentity(
new NamePasswordCredentials(name, password.toCharArray()));
}
/**
* Adds the specified {@code protocol} to the map containing {@code
* SessionProtocol}s that are disconnecting. The map is keyed by
* connection expiration time. The connection will expire after a fixed
* delay and will be forcibly terminated if the client hasn't already
* closed the connection.
*
* @param protocol a {@code SessionProtocol} that is disconnecting
*/
public void monitorDisconnection(SessionProtocol protocol) {
disconnectingHandlersMap.put(
System.currentTimeMillis() + disconnectDelay, protocol);
}
/**
* Schedules a non-durable, non-transactional {@code task}.
*
* @param task a non-durable, non-transactional task
*/
public void scheduleNonTransactionalTask(KernelRunnable task) {
taskScheduler.scheduleTask(task, taskOwner);
}
/* -- Private methods and classes -- */
/**
* A task to monitor disconnecting sessions to ensure that their
* associated connections are closed by the client in a timely manner.
* If a connection is not terminated by the expiration time, then the
* connection is forcibly closed.
*/
private class MonitorDisconnectingSessionsTask
extends AbstractKernelRunnable
{
/** Constructs and instance. */
MonitorDisconnectingSessionsTask() {
super(null);
}
/** {@inheritDoc} */
public void run() {
long now = System.currentTimeMillis();
if (!disconnectingHandlersMap.isEmpty() &&
disconnectingHandlersMap.firstKey() < now) {
Map<Long, SessionProtocol> expiredSessions =
disconnectingHandlersMap.headMap(now);
for (SessionProtocol protocol : expiredSessions.values()) {
try {
protocol.close();
} catch (IOException e) {
}
}
expiredSessions.clear();
}
}
}
}