All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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