org.commonreality.participant.impl.AbstractParticipant.java Source code

Java tutorial

Introduction

Here is the source code for org.commonreality.participant.impl.AbstractParticipant.java

Source

/*
 * Created on Feb 23, 2007 Copyright (C) 2001-6, Anthony Harrison
 * amharrison@gmail.com (jactr.org) 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.1 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
 */
package org.commonreality.participant.impl;

import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.commonreality.executor.GeneralThreadFactory;
import org.commonreality.identifier.IIdentifier;
import org.commonreality.net.handler.IMessageHandler;
import org.commonreality.net.message.IAcknowledgement;
import org.commonreality.net.message.IMessage;
import org.commonreality.net.message.command.control.ControlAcknowledgement;
import org.commonreality.net.message.notification.NotificationMessage;
import org.commonreality.net.message.request.IRequest;
import org.commonreality.net.message.request.connect.ConnectionRequest;
import org.commonreality.net.message.request.object.IObjectDataRequest;
import org.commonreality.net.message.request.object.NewIdentifierRequest;
import org.commonreality.net.message.request.time.RequestTime;
import org.commonreality.net.protocol.IProtocolConfiguration;
import org.commonreality.net.service.IClientService;
import org.commonreality.net.service.INetworkService;
import org.commonreality.net.service.IServerService;
import org.commonreality.net.session.ISessionInfo;
import org.commonreality.net.session.ISessionListener;
import org.commonreality.net.transport.ITransportProvider;
import org.commonreality.participant.IParticipant;
import org.commonreality.participant.addressing.IAddressingInformation;
import org.commonreality.participant.addressing.impl.BasicAddressingInformation;
import org.commonreality.participant.impl.ack.SessionAcknowledgements;
import org.commonreality.participant.impl.handlers.DefaultHandlers;
import org.commonreality.participant.impl.handlers.GeneralObjectHandler;
import org.commonreality.reality.CommonReality;
import org.commonreality.reality.impl.StateAndConnectionManager;

/**
 * Skeleton participant that handles the majority of tasks. A participants life
 * cycle works like this: Sometime after instantiation, connect will be called
 * which will establish the connection to CommonReality. When common reality is
 * ready to start, it will signal that all sensors and agents should initialize.
 * afetr initializing, start will be signaled. suspend and resume may be called
 * while running The simulation will run for some amount of time, until stop
 * will be called. then either reset or shutdown may be called. shutdown should
 * then disconnect.
 * 
 * <p>Exposes system property "participant.ioMaxThreads" (default 1) to permit
 * tuning of threading behavior. Plus "participant.useSharedThreads" (default
 * false)</p>
 * 
 * @author developer
 */
public abstract class AbstractParticipant extends ThinParticipant implements IParticipant {
    /**
     * logger definition
     */
    static final Log LOGGER = LogFactory.getLog(AbstractParticipant.class);

    static private ScheduledExecutorService _periodicExecutor;

    /**
     * return a shared periodic executor that can be useful in many circumstances
     * for periodic events
     * 
     * @return
     */
    static public ScheduledExecutorService getPeriodicExecutor() {
        synchronized (AbstractParticipant.class) {
            if (_periodicExecutor == null || _periodicExecutor.isShutdown() || _periodicExecutor.isTerminated())
                _periodicExecutor = Executors.newScheduledThreadPool(1,
                        new GeneralThreadFactory("IParticipant-Periodic"));
            return _periodicExecutor;
        }
    }

    private IIdentifier _commonRealityIdentifier;

    private volatile ISessionInfo<?> _crSession; // if
                                                 // connected
                                                 // to
                                                 // CR.

    private Map<INetworkService, SocketAddress> _services;

    private GeneralThreadFactory _centralThreadFactory;

    private GeneralThreadFactory _ioThreadFactory;

    private GeneralObjectHandler _generalObjectHandler;

    public AbstractParticipant(CommonReality cr, IIdentifier.Type type) {
        super(cr, type);
        _services = new HashMap<INetworkService, SocketAddress>();

        _generalObjectHandler = new GeneralObjectHandler(this);
    }

    public GeneralObjectHandler getGeneralObjectHandler() {
        return _generalObjectHandler;
    }

    public void setCommonRealityIdentifier(IIdentifier crId) {
        if (_commonRealityIdentifier != null)
            throw new IllegalStateException("CommonReality identifier has already been set");
        _commonRealityIdentifier = crId;
    }

    public IIdentifier getCommonRealityIdentifier() {
        return _commonRealityIdentifier;
    }

    abstract public IAddressingInformation getAddressingInformation();

    abstract public String getName();

    synchronized protected GeneralThreadFactory getCentralThreadFactory() {
        if (_centralThreadFactory == null)
            _centralThreadFactory = new GeneralThreadFactory(getName());
        return _centralThreadFactory;
    }

    synchronized protected GeneralThreadFactory getIOThreadFactory() {
        if (_ioThreadFactory == null)
            _ioThreadFactory = new GeneralThreadFactory(getName() + "-IOProcessor",
                    getCentralThreadFactory().getThreadGroup());
        return _ioThreadFactory;
    }

    protected void setSession(ISessionInfo<?> session) {
        _crSession = session;
    }

    protected ISessionInfo<?> getSession() {
        return _crSession;
    }

    @Override
    public void setIdentifier(IIdentifier identifier) {
        super.setIdentifier(identifier);

        ISessionInfo<?> session = getSession();
        if (session != null) {
            session.setAttribute(StateAndConnectionManager.IDENTIFIER, identifier);

            Thread.currentThread().setName(identifier.getName() + "-io");
        }
    }

    /**
     * creates and returns the normal message handles required by this
     * participant.
     * 
     * @return
     */
    protected Map<Class<?>, IMessageHandler<?>> createDefaultHandlers() {
        return new DefaultHandlers().createHandlers(this);
    }

    /**
     * by default, returns null.. as server side for an abstract participant is a
     * little less clear.
     * 
     * @return
     */
    protected ISessionListener createDefaultServerListener() {
        return new ISessionListener() {

            @Override
            public void opened(ISessionInfo<?> session) {
                session.addExceptionHandler((s, t) -> {
                    try {
                        LOGGER.error(String.format("Exception caught from %s, closing ", s), t);
                        if (s.isConnected() && !s.isClosing())
                            s.close();
                    } catch (Exception e) {
                        LOGGER.error(String.format("Exception from %s, closing. ", s), e);
                    }
                    return true;
                });
            }

            @Override
            public void closed(ISessionInfo<?> session) {

            }

            @Override
            public void created(ISessionInfo<?> session) {

            }

            @Override
            public void destroyed(ISessionInfo<?> session) {

            }

        };
    }

    protected ISessionListener createDefaultClientListener() {
        return new ISessionListener() {

            @Override
            public void opened(ISessionInfo<?> session) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug(String.format("Connection opened to %s", session));

                setSession(session);

                try {
                    new SessionAcknowledgements(session); // self installs

                    session.addExceptionHandler((s, t) -> {
                        try {
                            LOGGER.error(String.format("Exception caught from %s, closing ", s), t);
                            if (s.isConnected() && !s.isClosing())
                                s.close();
                        } catch (Exception e) {
                            LOGGER.error(String.format("Exception from %s, closing. ", s), e);
                        }
                        return true;
                    });
                } catch (Exception e) {
                    LOGGER.error("FAiled in connect properly? ", e);
                }

                /*
                 * great big fat hack, it is possible for the connection message to be
                 * sent before CR has actually received the opened callback, in which
                 * case the message will be dropped. so we delay the sending of this
                 * message.
                 */
                AbstractParticipant.getPeriodicExecutor().schedule(new Runnable() {
                    public void run() {
                        send(new ConnectionRequest(getName(), _type, getCredentials(), getAddressingInformation()));

                        if (LOGGER.isDebugEnabled())
                            LOGGER.debug("Sent connection request");
                    }
                }, 50, TimeUnit.MILLISECONDS);
            }

            @Override
            public void destroyed(ISessionInfo<?> session) {

            }

            @Override
            public void created(ISessionInfo<?> session) {

            }

            @Override
            public void closed(ISessionInfo<?> session) {
                if (session == getSession())
                    try {
                        if (LOGGER.isDebugEnabled())
                            LOGGER.debug(
                                    String.format("[%s] Session closed, stopping and shuting down", getName()));

                        if (stateMatches(State.STARTED, State.SUSPENDED))
                            stop();

                        setSession(null);

                        if (stateMatches(State.CONNECTED, State.INITIALIZED, State.UNKNOWN, State.STOPPED))
                            shutdown(false);

                    } catch (Exception e) {
                        LOGGER.error("Failed to cleanly disconnect from CR", e);
                    }
                else if (LOGGER.isDebugEnabled())
                    LOGGER.debug(
                            String.format("[%s] Spurious closed from another session? %s", getName(), session));
            }
        };
    }

    /**
     * specify what transport, protocol and address we can accessed on
     * 
     * @param service
     * @param address
     */
    public void addServerService(IServerService service, ITransportProvider transport,
            IProtocolConfiguration configuration, SocketAddress address) {
        try {
            service.configure(transport, configuration, createDefaultHandlers(), createDefaultServerListener(),
                    getIOThreadFactory());
            // if state is anything but unknown, we need to start the service
            if (!stateMatches(State.UNKNOWN))
                startService(service, address);
            _services.put(service, address);
        } catch (Exception e) {
            LOGGER.error("Could not start server service ", e);
        }
    }

    /**
     * specify what transport, protocol and address we can use to connect to
     * another participant (usually, just common reality)
     * 
     * @param service
     * @param address
     */
    public void addClientService(IClientService service, ITransportProvider transport,
            IProtocolConfiguration configuration, SocketAddress address) {
        try {
            service.configure(transport, configuration, createDefaultHandlers(), createDefaultClientListener(),
                    getIOThreadFactory());
            // if state is anything but unknown, we need to start the service
            if (!stateMatches(State.UNKNOWN))
                startService(service, address);
            _services.put(service, address);
        } catch (Exception e) {
            LOGGER.error("Could not start server service ", e);
        }
    }

    private void startService(INetworkService service, SocketAddress address) throws Exception {
        if (LOGGER.isDebugEnabled())
            LOGGER.debug(getName() + " Starting " + service.getClass().getSimpleName() + " on " + address);
        service.start(address);
    }

    private void stopService(INetworkService service, SocketAddress address) throws Exception {
        if (LOGGER.isDebugEnabled())
            LOGGER.debug("Stopping " + service.getClass().getSimpleName() + " on " + address,
                    new RuntimeException());
        service.stop(address);
    }

    @Override
    protected void setState(State state) {
        super.setState(state);
        // and send a message, if unknown, we aren't currently connected.
        // just to avoid the message.
        // if (!state.equals(State.UNKNOWN))
        send(new ControlAcknowledgement(getIdentifier(), -1, state));
    }

    /**
     * TODO: (BUG) this starts all services in a random order. it should be server first
     *      then clients to avoid deadlock in complex configurations
     * @see org.commonreality.participant.IParticipant#connect()
     */
    @Override
    public void connect() throws Exception {
        checkState(State.UNKNOWN);

        Exception differed = null;
        for (Map.Entry<INetworkService, SocketAddress> entry : _services.entrySet())
            try {
                startService(entry.getKey(), entry.getValue());
            } catch (Exception e) {
                LOGGER.error("Could not start service ", e);
                differed = e;
            }

        if (differed != null)
            throw differed;

        // moved to setIdentifier()
        // setState(State.CONNECTED);
    }

    @Override
    public void disconnect() throws Exception {
        disconnect(false);
    }

    @Override
    public void disconnect(boolean force) throws Exception {
        if (!force)
            checkState(State.CONNECTED, State.INITIALIZED, State.STOPPED, State.UNKNOWN);

        Exception differed = null;
        for (Map.Entry<INetworkService, SocketAddress> entry : _services.entrySet())
            try {
                stopService(entry.getKey(), entry.getValue());
            } catch (Exception e) {
                LOGGER.error("Could not stop service ", e);
                differed = e;
            }
        _services.clear();

        setState(State.UNKNOWN);

        if (differed != null)
            throw differed;
    }

    protected Collection<IAddressingInformation> getServerAddressInformation() {
        ArrayList<IAddressingInformation> rtn = new ArrayList<IAddressingInformation>();
        for (Map.Entry<INetworkService, SocketAddress> entry : _services.entrySet())
            if (entry.getKey() instanceof IServerService)
                rtn.add(new BasicAddressingInformation(entry.getValue()));
        return rtn;
    }

    /**
     * send a message to common reality. We also cache any data requests going out
     * and store them temporarily. they will not be applied to the respective
     * object manager until we get confirmation from CR
     * 
     * @param message
     */
    @Override
    public Future<IAcknowledgement> send(IMessage message) {
        // BasicParticipantIOHandler handler = (BasicParticipantIOHandler)
        // getIOHandler();

        /*
         * anytime we send data out, we store it because we wont actually set the
         * data until we get confirmation back from CR in the form of the
         * IObjectCommand. However, if the data request is going out to everyone (in
         * the case of a RealObject, AgentObject or SensorObject) we will get the
         * data sent to us, so we dont bother storing it (otherwise, we'd get double
         * data)
         */
        if (message instanceof IObjectDataRequest
                && !IIdentifier.ALL.equals(((IObjectDataRequest) message).getDestination()))
            getGeneralObjectHandler().storeObjectData(((IObjectDataRequest) message).getData(), message);

        Future<IAcknowledgement> rtn = null;

        ISessionInfo<?> session = getSession();
        if (session != null) {
            // this could be dangerous.. where else do we sync on session?
            synchronized (session) {
                if (message instanceof IRequest) {
                    SessionAcknowledgements sa = SessionAcknowledgements.getSessionAcks(session);
                    if (sa != null)
                        rtn = sa.newAckFuture(message);
                }
            }

            try {
                // not too efficient..

                session.write(message);

                // flush when we send a time request?
                if (shouldFlush(message))
                    session.flush();
            } catch (Exception e) {
                LOGGER.error("AbstractParticipant.send threw Exception : ", e);
            }
        } else if (LOGGER.isDebugEnabled())
            LOGGER.debug(String.format("[%s] Null session, could not send %s ", getName(), message),
                    new RuntimeException());

        if (rtn == null)
            rtn = EMPTY_ACK;

        return rtn;
    }

    protected boolean shouldFlush(Object message) {
        boolean flush = message instanceof RequestTime || message instanceof ConnectionRequest
                || message instanceof NewIdentifierRequest || message instanceof NotificationMessage
                || message instanceof IRequest || message instanceof IAcknowledgement;
        return flush;
    }
}