
org.objectweb.dream.protocol.channel.MultiplexBindProtocolImpl Maven / Gradle / Ivy
/**
* Dream
* Copyright (C) 2003-2004 INRIA Rhone-Alpes
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Contact: [email protected]
*
* Initial developer(s): Matthieu Leclercq
* Contributor(s):
*/
package org.objectweb.dream.protocol.channel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.objectweb.dream.IOPushException;
import org.objectweb.dream.PushException;
import org.objectweb.dream.dreamannotation.DreamComponent;
import org.objectweb.dream.dreamannotation.DreamMonolog;
import org.objectweb.dream.message.ChunkFactoryReference;
import org.objectweb.dream.message.Message;
import org.objectweb.dream.message.MessageManagerType;
import org.objectweb.dream.protocol.BindException;
import org.objectweb.dream.protocol.ExceptionChunk;
import org.objectweb.dream.protocol.ExportException;
import org.objectweb.dream.protocol.ExportIdentifier;
import org.objectweb.dream.protocol.IncomingPush;
import org.objectweb.dream.protocol.InvalidExportIdentifierException;
import org.objectweb.dream.protocol.OutgoingPush;
import org.objectweb.dream.util.Error;
import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.fraclet.annotation.annotations.Attribute;
import org.objectweb.fractal.fraclet.annotation.annotations.Interface;
import org.objectweb.fractal.fraclet.annotation.annotations.Provides;
import org.objectweb.fractal.fraclet.annotation.annotations.Requires;
import org.objectweb.fractal.fraclet.annotation.annotations.Service;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
/**
* Implementation of a binding multiplexer protocol. This protocol allows a
* client to establish multiple bindings to the same exported channel using a
* single lower level binding.
*/
@DreamComponent(controllerDesc = "dreamUnstoppablePrimitive")
@Provides(interfaces = { @Interface(name = ChannelProtocol.ITF_NAME, signature = ChannelProtocol.class) })
public class MultiplexBindProtocolImpl implements ChannelProtocol {
private static final String BIND_REPLY_CHUNK_NAME = "multibind-bind-reply";
private Map clientBindings = new HashMap();
private Map exportedChannel = new HashMap();
private ChunkFactoryReference bindChunkFactory;
private ChunkFactoryReference multiplexChunkFactory;
private ChunkFactoryReference exceptionChunkFactory;
private ChunkFactoryReference closeChunkFactory;
// ------------------------------------------------------------------------
// ---
// Attribute fields
// ------------------------------------------------------------------------
// ---
/**
* The name of the {@link MultiplexChunk} that the protocol will add to
* outgoing messages.
*/
@Attribute(argument = "multiplexChunkName")
private String multiplexChunkName;
// ------------------------------------------------------------------------
// ---
// Client interfaces
// ------------------------------------------------------------------------
// ---
@Requires(name = ChannelProtocol.LOWER_PROTOCOL_ITF_NAME)
private ChannelProtocol lowerLevelProtocolItf;
@Requires(name = "message-manager")
private MessageManagerType messageManagerItf;
// ------------------------------------------------------------------------
// --
// Services interfaces
// ------------------------------------------------------------------------
// --
/**
* Component reference
*/
@Service
Component weaveableC;
/**
* Logger of the component
*/
@DreamMonolog()
protected Logger logger;
// ------------------------------------------------------------------------
// ---
// Implementation of the Protocol interface
// ------------------------------------------------------------------------
// ---
/**
* @see ChannelProtocol#bind(ExportIdentifier, IncomingPush, Map)
*/
public synchronized OutgoingPush bind(final ExportIdentifier exportId,
final IncomingPush toClientPush, final Map hints)
throws InvalidExportIdentifierException, BindException {
MuxDemuxImpl muxDemux = clientBindings.get(exportId);
if (muxDemux == null) {
// new lower binding
muxDemux = new MuxDemuxImpl(toClientPush, exportId);
muxDemux.lowerOutgoingPushItf = lowerLevelProtocolItf.bind(exportId, muxDemux, hints);
clientBindings.put(exportId, muxDemux);
return muxDemux.sessions.get((short) 0);
}
// lower binding already exists
try {
return muxDemux.bind(exportId, toClientPush);
} catch (IOPushException e) {
throw new BindException("Unable to send bind message on opened session", exportId, e);
}
}
/**
* @see ChannelProtocol#export(ChannelFactory, Map)
*/
public synchronized ExportIdentifier export(final ChannelFactory channel,
final Map hints) throws ExportException {
final ChannelImpl channelImpl = new ChannelImpl(channel);
final ExportIdentifier exportIdentifier = lowerLevelProtocolItf.export(channelImpl, hints);
exportedChannel.put(exportIdentifier, channelImpl);
return exportIdentifier;
}
/**
* @see ChannelProtocol#unexport(ExportIdentifier)
*/
public synchronized void unexport(final ExportIdentifier exportIdentifier)
throws InvalidExportIdentifierException {
final ChannelImpl channelImpl = (ChannelImpl) exportedChannel.remove(exportIdentifier);
if (channelImpl == null) {
throw new InvalidExportIdentifierException("Unknown export identifier");
}
channelImpl.unexport();
lowerLevelProtocolItf.unexport(exportIdentifier);
}
/**
* @see ChannelProtocol#createExportIdentifier(Map, ExportIdentifier[])
*/
public ExportIdentifier createExportIdentifier(final Map info,
final ExportIdentifier[] next) throws InvalidExportIdentifierException {
return lowerLevelProtocolItf.createExportIdentifier(info, next);
}
// ------------------------------------------------------------------------
// ---
// Utility methods
// ------------------------------------------------------------------------
// ---
private BindChunk newBindChunk() {
if (bindChunkFactory == null) {
bindChunkFactory = messageManagerItf.getChunkFactory(BindChunk.class);
}
return messageManagerItf.createChunk(bindChunkFactory);
}
private MultiplexChunk newMultiplexChunk() {
if (multiplexChunkFactory == null) {
multiplexChunkFactory = messageManagerItf.getChunkFactory(MultiplexChunk.class);
}
return messageManagerItf.createChunk(multiplexChunkFactory);
}
private ExceptionChunk newExceptionChunk() {
if (exceptionChunkFactory == null) {
exceptionChunkFactory = messageManagerItf.getChunkFactory(ExceptionChunk.class);
}
return messageManagerItf.createChunk(exceptionChunkFactory);
}
private CloseChunk newCloseChunk() {
if (closeChunkFactory == null) {
closeChunkFactory = messageManagerItf.getChunkFactory(CloseChunk.class);
}
return messageManagerItf.createChunk(closeChunkFactory);
}
// ------------------------------------------------------------------------
// ---
// Inner classes
// ------------------------------------------------------------------------
// ---
private class ChannelImpl implements ChannelFactory {
private boolean unexported = false;
private ChannelFactory upperChannelItf;
private ChannelImpl(final ChannelFactory itf) {
upperChannelItf = itf;
}
private synchronized void unexport() {
unexported = true;
}
// --------------------------------------------------------------------
// -----
// Implementation of the Channel interface
// --------------------------------------------------------------------
// -----
/**
* @see ChannelFactory#instantiate(OutgoingPush)
*/
public synchronized IncomingPush instantiate(final OutgoingPush toClientPush)
throws BindException {
return new MuxDemuxImpl(this, upperChannelItf, toClientPush);
}
}
private class MuxDemuxImpl implements IncomingPush {
private ExportIdentifier lowerId;
private ChannelImpl channel;
private short nextRouteId = 1;
private ChannelFactory upperChannelItf = null;
private OutgoingPush lowerOutgoingPushItf;
private Map sessions = new HashMap();
private Exception bindException;
// used by server side
MuxDemuxImpl(final ChannelImpl channel, final ChannelFactory upperChannelItf,
final OutgoingPush lowerOutgoingPushItf) throws BindException {
this.channel = channel;
this.upperChannelItf = upperChannelItf;
this.lowerOutgoingPushItf = lowerOutgoingPushItf;
final SessionImpl session = new SessionImpl((short) 0, this);
final IncomingPush upperIncomingPushItf = upperChannelItf.instantiate(session);
session.upperIncomingPushItf = upperIncomingPushItf;
sessions.put((short) 0, session);
}
// used by client side
MuxDemuxImpl(final IncomingPush upperIncomingPushItf, final ExportIdentifier lowerId) {
this.lowerId = lowerId;
final SessionImpl session = new SessionImpl((short) 0, this, upperIncomingPushItf,
false);
sessions.put(Short.valueOf((short) 0), session);
}
synchronized OutgoingPush bind(final ExportIdentifier exportId,
final IncomingPush upperIncomingPushItf) throws IOPushException, BindException {
short routeId = nextRouteId++;
final SessionImpl session = new SessionImpl(routeId, this, upperIncomingPushItf, true);
Short rId = Short.valueOf(routeId);
sessions.put(rId, session);
final Message message = messageManagerItf.createMessage();
final BindChunk chunk = newBindChunk();
chunk.setRouteId(routeId);
messageManagerItf.addChunk(message, BindChunk.DEFAULT_NAME, chunk);
lowerOutgoingPushItf.outgoingPush(message);
while (session.replyChunkExpected) {
try {
wait();
} catch (InterruptedException e) {
sessions.remove(rId);
throw new BindException("Interrupted while waiting for bind reply message");
}
}
if (bindException != null) {
final Exception e = bindException;
bindException = null;
throw new BindException("remote exception", exportId, e);
}
return session;
}
// --------------------------------------------------------------------
// -----
// Implementation of the IncomingPush interface
// --------------------------------------------------------------------
// -----
/**
* @see IncomingPush#incomingPush(Message)
*/
public void incomingPush(final Message message) throws PushException {
MultiplexChunk multiplexChunk = messageManagerItf.getChunk(message, multiplexChunkName);
if (multiplexChunk != null) {
multiplexChunk = messageManagerItf.removeChunk(message, multiplexChunkName);
final Short routeId = multiplexChunk.getRouteId();
messageManagerItf.deleteChunk(multiplexChunk);
// demux message
SessionImpl session;
synchronized (this) {
session = sessions.get(routeId);
if (session == null) {
throw new PushException("Unknown routeId : " + routeId);
}
if (session.replyChunkExpected) {
session.replyChunkExpected = false;
ExceptionChunk exceptionChunk = messageManagerItf.getChunk(message,
BIND_REPLY_CHUNK_NAME);
if (exceptionChunk == null) {
bindException = new BindException(
"Unable to find acknowledgment chunk in first received "
+ "message");
} else {
bindException = exceptionChunk.getException();
}
if (bindException != null) {
sessions.remove(routeId);
}
messageManagerItf.deleteMessage(message);
notify();
return;
}
}
session.upperIncomingPushItf.incomingPush(message);
return;
}
final BindChunk bindChunk = messageManagerItf.getChunk(message, BindChunk.DEFAULT_NAME);
if (bindChunk != null) {
final Message reply = messageManagerItf.createMessage();
final MultiplexChunk chunk = newMultiplexChunk();
chunk.setRouteId(bindChunk.getRouteId());
messageManagerItf.addChunk(reply, multiplexChunkName, chunk);
final ExceptionChunk exceptionChunk = newExceptionChunk();
messageManagerItf.addChunk(reply, BIND_REPLY_CHUNK_NAME, exceptionChunk);
synchronized (this) {
synchronized (channel) {
if (channel.unexported) {
// channel unexported, must return an exception.
exceptionChunk.setException(new BindException(
"Channel has been unexported"));
try {
lowerOutgoingPushItf.outgoingPush(reply);
} catch (IOPushException e) {
logger.log(BasicLevel.ERROR,
"Unable to send reply to client, close socket", e);
}
return;
}
}
// new session
final short id = bindChunk.getRouteId();
if (sessions.containsKey(id)) {
Error.error("Route already in use : " + id, logger);
}
final SessionImpl session = new SessionImpl(bindChunk.getRouteId(), this);
IncomingPush upperIncomingPushItf;
try {
upperIncomingPushItf = upperChannelItf.instantiate(session);
session.upperIncomingPushItf = upperIncomingPushItf;
sessions.put(id, session);
} catch (BindException e) {
exceptionChunk.setException(e);
}
}
messageManagerItf.deleteMessage(message);
try {
lowerOutgoingPushItf.outgoingPush(reply);
} catch (IOPushException e) {
logger.log(BasicLevel.ERROR, "Unable to send reply to client, close socket", e);
return;
}
return;
}
final CloseChunk closeChunk = messageManagerItf.getChunk(message,
CloseChunk.DEFAULT_NAME);
if (closeChunk != null) {
// close session
SessionImpl session;
synchronized (this) {
session = sessions.remove(closeChunk.getRouteId());
}
if (session == null) {
Error.error("Unknown routeId : " + closeChunk.getRouteId(), logger);
}
synchronized (session) {
session.closed = true;
}
if (session.upperIncomingPushItf != null) {
session.upperIncomingPushItf.incomingClosed(session, new IOException(
"session closed by peer"));
}
messageManagerItf.deleteMessage(message);
return;
}
// message doen't contain correct chunk.
throw new PushException("Invalid message");
}
/**
* @see IncomingPush#incomingClosed(Object, Exception)
*/
public void incomingClosed(final Object outgoingPush, final Exception exception) {
synchronized (MultiplexBindProtocolImpl.this) {
// forward to every upper sessions
final Iterator iterator = sessions.values().iterator();
while (iterator.hasNext()) {
final SessionImpl session = (SessionImpl) iterator.next();
if (session.upperIncomingPushItf != null) {
session.upperIncomingPushItf.incomingClosed(session, exception);
}
}
if (lowerId != null) {
clientBindings.remove(lowerId);
}
}
}
}
private class SessionImpl implements OutgoingPush {
private boolean replyChunkExpected;
private boolean closed = false;
private short routeId;
private MuxDemuxImpl muxDemux;
private IncomingPush upperIncomingPushItf;
// server side constructor
SessionImpl(final short id, final MuxDemuxImpl muxDemux) {
replyChunkExpected = false;
routeId = id;
this.muxDemux = muxDemux;
}
// client side constructor
SessionImpl(final short id, final MuxDemuxImpl muxDemux,
final IncomingPush upperIncomingPushItf, boolean replyChunkExpected) {
this.replyChunkExpected = replyChunkExpected;
routeId = id;
this.muxDemux = muxDemux;
this.upperIncomingPushItf = upperIncomingPushItf;
}
// --------------------------------------------------------------------
// -----
// Implementation of the OutgoingPush interface
// --------------------------------------------------------------------
// -----
/**
* @see OutgoingPush#outgoingPush(Message)
*/
public void outgoingPush(final Message message) throws IOPushException {
synchronized (this) {
if (closed) {
throw new IOPushException("Session closed");
}
}
MultiplexChunk multiplexChunk = newMultiplexChunk();
multiplexChunk.setRouteId(routeId);
messageManagerItf.addChunk(message, multiplexChunkName, multiplexChunk);
muxDemux.lowerOutgoingPushItf.outgoingPush(message);
}
/**
* @see OutgoingPush#outgoingClose(IncomingPush)
*/
public void outgoingClose(final IncomingPush incomingPush) throws IOException {
final Message message = messageManagerItf.createMessage();
final CloseChunk chunk = newCloseChunk();
chunk.setRouteId(routeId);
messageManagerItf.addChunk(message, CloseChunk.DEFAULT_NAME, chunk);
synchronized (muxDemux) {
muxDemux.sessions.remove(Short.valueOf(routeId));
synchronized (this) {
closed = true;
}
try {
muxDemux.lowerOutgoingPushItf.outgoingPush(message);
} catch (IOPushException e) {
throw (IOException) e.getCause();
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy