com.voxeo.moho.remote.impl.CallImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.voxeo.moho.remote.impl.CallImpl.java

Source

/**
 * Copyright 2010-2011 Voxeo Corporation Licensed 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.
 */

package com.voxeo.moho.remote.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.media.mscontrol.join.Joinable;
import javax.media.mscontrol.join.Joinable.Direction;
import javax.media.mscontrol.join.JoinableStream;
import javax.media.mscontrol.join.JoinableStream.StreamType;

import org.apache.commons.collections.CollectionUtils;

import com.rayo.core.CallRejectReason;
import com.rayo.core.DtmfEvent;
import com.rayo.core.EndEvent;
import com.rayo.core.HangupCommand;
import com.rayo.core.JoinCommand;
import com.rayo.core.JoinDestinationType;
import com.rayo.core.JoinedEvent;
import com.rayo.core.UnjoinedEvent;
import com.rayo.core.verb.OffHoldEvent;
import com.rayo.core.verb.OnHoldEvent;
import com.voxeo.moho.Call;
import com.voxeo.moho.CallableEndpoint;
import com.voxeo.moho.Endpoint;
import com.voxeo.moho.Joint;
import com.voxeo.moho.Mixer;
import com.voxeo.moho.Participant;
import com.voxeo.moho.SignalException;
import com.voxeo.moho.Unjoint;
import com.voxeo.moho.common.event.MohoCallCompleteEvent;
import com.voxeo.moho.common.event.MohoEarlyMediaEvent;
import com.voxeo.moho.common.event.MohoInputDetectedEvent;
import com.voxeo.moho.common.event.MohoJoinCompleteEvent;
import com.voxeo.moho.common.event.MohoUnjoinCompleteEvent;
import com.voxeo.moho.event.AcceptableEvent;
import com.voxeo.moho.event.CallCompleteEvent;
import com.voxeo.moho.event.CallEvent;
import com.voxeo.moho.event.EarlyMediaEvent;
import com.voxeo.moho.event.Event;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.event.JoinCompleteEvent;
import com.voxeo.moho.event.RequestEvent;
import com.voxeo.moho.event.ResponseEvent;
import com.voxeo.moho.event.UnjoinCompleteEvent;
import com.voxeo.moho.remote.MohoRemoteException;
import com.voxeo.moho.remote.impl.event.MohoHangupEventImpl;
import com.voxeo.rayo.client.XmppException;
import com.voxeo.rayo.client.xmpp.stanza.IQ;
import com.voxeo.rayo.client.xmpp.stanza.Presence;

// TODO if we join two call in DIRECT mode, then one side hangup, we want join
// another side to media again, for example say something. how to do that with
// Rayo protocol?
public abstract class CallImpl extends MediaServiceSupport<Call> implements Call, RayoListener {

    protected CallableEndpoint _caller;

    protected CallableEndpoint _callee;

    protected List<Call> _peers = new ArrayList<Call>(0);

    protected State _state;

    protected Map<String, String> _headers;

    protected Boolean _isMuted = false;

    protected Boolean _isHold = false;

    protected Lock _stateLock = new ReentrantLock();

    protected CallImpl(MohoRemoteImpl mohoRemote, String callID, CallableEndpoint caller, CallableEndpoint callee,
            Map<String, String> headers) {
        super(mohoRemote);
        _caller = caller;
        _callee = callee;
        _id = callID;
        if (_id != null) {
            _mohoRemote.addParticipant(this);
        }
        _headers = headers;
    }

    @Override
    public <S extends EventSource, T extends Event<S>> Future<T> dispatch(final T event) {
        Future<T> retval = null;
        if (!(event instanceof CallEvent) && !(event instanceof RequestEvent)
                && !(event instanceof ResponseEvent)) {
            retval = super.dispatch(event);
        } else {
            final Runnable acceptor = new Runnable() {
                @Override
                public void run() {
                    if (event instanceof EarlyMediaEvent) {
                        if (!((MohoEarlyMediaEvent) event).isProcessed()) {
                            try {
                                ((EarlyMediaEvent) event).reject(null);
                            } catch (final SignalException e) {
                                LOG.warn(e);
                            }
                        }
                    }

                    else if (event instanceof AcceptableEvent) {
                        if (!((AcceptableEvent) event).isAccepted() && !((AcceptableEvent) event).isRejected()) {
                            try {
                                ((AcceptableEvent) event).accept();
                            } catch (final SignalException e) {
                                LOG.warn(e);
                            }
                        }
                    }

                }
            };
            retval = super.dispatch(event, acceptor);
        }
        return retval;
    }

    @Override
    public Call[] getPeers() {
        return _peers.toArray(new CallImpl[_peers.size()]);
    }

    @Override
    public Joint join() {
        return join(Joinable.Direction.DUPLEX);
    }

    @Override
    public Joint join(final CallableEndpoint other, final JoinType type, final Direction direction) {
        return join(other, type, direction, null);
    }

    @Override
    public Joint join(CallableEndpoint other, JoinType type, Direction direction, Map<String, String> headers) {
        Call outboundCall = other.createCall(getAddress(), headers);
        return this.join(outboundCall, type, direction);
    }

    @Override
    public Joint join(Participant other, JoinType type, Direction direction) {
        return join(other, type, Boolean.TRUE, direction);
    }

    @Override
    public Unjoint unjoin(Participant other) {
        if (!_joinees.contains(other)) {
            throw new IllegalStateException("Not joined.");
        }
        UnJointImpl unJoint = null;
        try {
            JoinDestinationType type = null;
            if (other instanceof Call) {
                type = JoinDestinationType.CALL;
            } else {
                type = JoinDestinationType.MIXER;
            }
            unJoint = new UnJointImpl(this);
            _unjoints.put(other.getId(), unJoint);
            IQ iq = _mohoRemote.getRayoClient().unjoin(other.getId(), type, this.getId());

            if (iq.isError()) {
                _unjoints.remove(other.getId());
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            }
        } catch (XmppException e) {
            _unjoints.remove(other.getId());
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }
        return unJoint;
    }

    @Override
    public Endpoint getInvitor() {
        return _caller;
    }

    @Override
    public CallableEndpoint getInvitee() {
        return _callee;
    }

    @Override
    public void mute() {
        checkIsConnected();
        if (_isMuted) {
            return;
        }
        try {
            IQ iq = _mohoRemote.getRayoClient().mute(this.getId());
            if (iq.isError()) {
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            } else {
                _isMuted = true;
            }
        } catch (XmppException e) {
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }
    }

    @Override
    public void unmute() {
        checkIsConnected();
        if (!_isMuted) {
            throw new IllegalStateException("This call hasn't been muted");
        }
        try {
            IQ iq = _mohoRemote.getRayoClient().unmute(this.getId());
            if (iq.isError()) {
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            } else {
                _isMuted = false;
            }
        } catch (XmppException e) {
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }
    }

    @Override
    public void hold() {
        checkIsConnected();
        if (_isHold) {
            return;
        }
        try {
            IQ iq = _mohoRemote.getRayoClient().hold(this.getId());
            if (iq.isError()) {
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            } else {
                _isHold = true;
            }
        } catch (XmppException e) {
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }
    }

    @Override
    public void unhold() {
        checkIsConnected();
        if (!_isHold) {
            throw new IllegalStateException("This call hasn't been hold");
        }
        try {
            IQ iq = _mohoRemote.getRayoClient().unhold(this.getId());
            if (iq.isError()) {
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            } else {
                _isHold = false;
            }
        } catch (XmppException e) {
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }
    }

    @Override
    public boolean isHold() {
        return _isHold;
    }

    @Override
    public boolean isMute() {
        return _isMuted;
    }

    public void hangup() {
        hangup(null);
    }

    @Override
    public void hangup(Map<String, String> headers) {
        // if (_state == Call.State.INPROGRESS || _state == Call.State.INITIALIZED
        // || _state == Call.State.ACCEPTED
        // || _state == Call.State.CONNECTED || _state == Call.State.INPROGRESS) {
        try {
            HangupCommand command = new HangupCommand();
            command.setCallId(this.getId());
            command.setHeaders(headers);

            IQ iq = _mohoRemote.getRayoClient().command(command, this.getId());

            if (iq.isError()) {
                cleanUp();
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            }
        } catch (XmppException e) {
            cleanUp();
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }
        // }
    }

    @Override
    public JoinableStream getJoinableStream(StreamType value) {
        throw new UnsupportedOperationException(Constants.unsupported_operation);
    }

    @Override
    public JoinableStream[] getJoinableStreams() {
        throw new UnsupportedOperationException(Constants.unsupported_operation);
    }

    @Override
    public Participant[] getParticipants() {
        return _joinees.getJoinees();
    }

    @Override
    public Participant[] getParticipants(Direction direction) {
        return _joinees.getJoinees(direction);
    }

    @Override
    public void disconnect() {
        this.hangup();
    }

    protected void cleanUp() {
        _mohoRemote.removeParticipant(_id);

        _peers.clear();
        _joinees.clear();

        // TODO
        // Commenting this as otherwise complete events for active verbs will not
        // make it to the client
        // _componentListeners.clear();

        Collection<JointImpl> joints = _joints.values();
        for (JointImpl joint : joints) {
            joint.done(new SignalException("Call end."));
        }
        _joints.clear();

        Collection<UnJointImpl> unjoints = _unjoints.values();
        for (UnJointImpl unjoint : unjoints) {
            unjoint.done(new SignalException("Call end."));
        }
        _unjoints.clear();
    }

    @Override
    public String getHeader(String name) {
        return _headers.get(name);
    }

    @Override
    public ListIterator<String> getHeaders(String name) {
        List<String> list = new ArrayList<String>();
        String value = _headers.get(name);
        list.add(value);
        return list.listIterator();
    }

    @Override
    public Iterator<String> getHeaderNames() {
        return CollectionUtils.unmodifiableCollection(_headers.values()).iterator();
    }

    @Override
    public void onRayoEvent(JID from, Presence presence) {
        if (from.getResource() != null) {
            super.onRayoEvent(from, presence);
        } else {
            LOG.debug("CallImpl Recived presence, processing:" + presence);
            Object object = presence.getExtension().getObject();
            if (object instanceof EndEvent) {
                EndEvent event = (EndEvent) object;
                EndEvent.Reason rayoReason = event.getReason();
                if (rayoReason == EndEvent.Reason.HANGUP) {
                    MohoHangupEventImpl mohoEvent = new MohoHangupEventImpl(this);
                    this.dispatch(mohoEvent);
                }
                MohoCallCompleteEvent mohoEvent = new MohoCallCompleteEvent(this,
                        getMohoReasonByRayoEndEventReason(event.getReason()), null, event.getHeaders());

                if (getMohoReasonByRayoEndEventReason(event.getReason()) == CallCompleteEvent.Cause.DISCONNECT) {
                    this.setCallState(State.DISCONNECTED);
                } else {
                    this.setCallState(State.FAILED);
                }

                this.dispatch(mohoEvent);
                cleanUp();
            } else if (object instanceof DtmfEvent) {
                DtmfEvent event = (DtmfEvent) object;
                MohoInputDetectedEvent<Call> mohoEvent = new MohoInputDetectedEvent<Call>(this, event.getSignal());
                this.dispatch(mohoEvent);
            } else if (object instanceof JoinedEvent) {
                JoinedEvent event = (JoinedEvent) object;
                MohoJoinCompleteEvent mohoEvent = null;
                String id = event.getTo();
                JoinDestinationType type = event.getType();
                JointImpl joint = _joints.remove(id);
                if (type == JoinDestinationType.CALL) {
                    Call peer = (Call) this.getMohoRemote().getParticipant(id);
                    _joinees.add(peer, joint.getType(), joint.getDirection());
                    _peers.add(peer);
                    mohoEvent = new MohoJoinCompleteEvent(this, peer, JoinCompleteEvent.Cause.JOINED, true);
                } else {
                    Mixer peer = (Mixer) this.getMohoRemote().getParticipant(id);
                    _joinees.add(peer, joint.getType(), joint.getDirection());
                    mohoEvent = new MohoJoinCompleteEvent(this, peer, JoinCompleteEvent.Cause.JOINED, true);
                }
                this.dispatch(mohoEvent);
                joint.done(mohoEvent);
            } else if (object instanceof UnjoinedEvent) {
                UnjoinedEvent event = (UnjoinedEvent) object;
                MohoUnjoinCompleteEvent mohoEvent = null;
                String id = event.getFrom();
                JoinDestinationType type = event.getType();
                UnJointImpl unjoint = _unjoints.remove(id);
                if (type == JoinDestinationType.CALL) {
                    Call peer = (Call) _mohoRemote.getParticipant(id);
                    _joinees.remove(peer);
                    _peers.remove(peer);
                    mohoEvent = new MohoUnjoinCompleteEvent(this, peer, UnjoinCompleteEvent.Cause.SUCCESS_UNJOIN,
                            true);
                } else {
                    Mixer peer = (Mixer) this.getMohoRemote().getParticipant(id);
                    _joinees.remove(peer);
                    mohoEvent = new MohoUnjoinCompleteEvent(this, peer, UnjoinCompleteEvent.Cause.SUCCESS_UNJOIN,
                            true);
                }
                this.dispatch(mohoEvent);
                if (unjoint != null) {
                    unjoint.done(mohoEvent);
                }
            } else if (object instanceof OffHoldEvent) {
                // TODO for conference
            } else if (object instanceof OnHoldEvent) {
                // TODO for conference
            } else {
                LOG.error("CallImpl Can't process presence:" + presence);
            }
        }
    }

    protected CallRejectReason getRayoCallRejectReasonByMohoReason(AcceptableEvent.Reason reason) {
        switch (reason) {
        case DECLINE:
            return CallRejectReason.DECLINE;
        case BUSY:
            return CallRejectReason.BUSY;
        case ERROR:
            return CallRejectReason.ERROR;
        default:
            return CallRejectReason.ERROR;
        }
    }

    protected CallCompleteEvent.Cause getMohoReasonByRayoEndEventReason(EndEvent.Reason reason) {
        switch (reason) {
        case HANGUP:
            return CallCompleteEvent.Cause.DISCONNECT;
        case TIMEOUT:
            return CallCompleteEvent.Cause.TIMEOUT;
        case BUSY:
            return CallCompleteEvent.Cause.BUSY;
        case REJECT:
            return CallCompleteEvent.Cause.DECLINE;
        case REDIRECT:
            return CallCompleteEvent.Cause.REDIRECT;
        case ERROR:
            return CallCompleteEvent.Cause.ERROR;
        default:
            return CallCompleteEvent.Cause.ERROR;
        }
    }

    @Override
    public Joint join(Participant other, JoinType type, boolean force, Direction direction) {
        return this.join(other, type, force, direction, true);
    }

    // TOD rayo support dtmfpassThrough?
    @Override
    public Joint join(Participant other, JoinType type, boolean force, Direction direction,
            boolean dtmfPassThrough) {
        JointImpl joint = null;
        try {
            joint = new JointImpl(this, type, direction, false);
            String thisID = startJoin();
            String otherID = ((ParticipantImpl) other).startJoin();

            _joints.put(other.getId(), joint);
            ((MediaServiceSupport<?>) other)._joints.put(this.getId(), joint);

            JoinCommand command = new JoinCommand();
            command.setCallId(thisID);
            command.setTo(otherID);
            command.setMedia(type);
            command.setDirection(direction);
            command.setForce(force);
            JoinDestinationType destinationType = null;
            if (other instanceof Call) {
                destinationType = JoinDestinationType.CALL;
            } else {
                destinationType = JoinDestinationType.MIXER;
            }
            command.setType(destinationType);

            IQ iq = _mohoRemote.getRayoClient().join(command, this.getId());
            if (iq.isError()) {
                _joints.remove(other.getId());
                ((MediaServiceSupport<?>) other)._joints.remove(this.getId());
                com.voxeo.rayo.client.xmpp.stanza.Error error = iq.getError();
                throw new SignalException(error.getCondition() + error.getText());
            }
        } catch (XmppException e) {
            _joints.remove(other.getId());
            ((MediaServiceSupport<?>) other)._joints.remove(this.getId());
            LOG.error("", e);
            throw new MohoRemoteException(e);
        }

        return joint;
    }

    public boolean compareAndsetState(List<State> oldStates, State newValue) {
        _stateLock.lock();
        try {
            if (oldStates.contains(_state)) {
                _state = newValue;
                return true;
            }
            return false;
        } finally {
            _stateLock.unlock();
        }
    }

    public boolean compareAndsetState(State oldState, State newValue) {
        _stateLock.lock();
        try {
            if (oldState == _state) {
                _state = newValue;
                return true;
            }
            return false;
        } finally {
            _stateLock.unlock();
        }
    }

    @Override
    public State getCallState() {
        _stateLock.lock();
        try {
            return _state;
        } finally {
            _stateLock.unlock();
        }
    }

    public void setCallState(State state) {
        _stateLock.lock();
        try {
            _state = state;
        } finally {
            _stateLock.unlock();
        }
    }

    public void checkIsConnected() {
        if (_state != State.CONNECTED) {
            throw new IllegalStateException("Isn't connected");
        }
    }

    public void update() {

    }

    @Override
    public Joint join(JoinType type, boolean force, Direction direction, Map<String, String> headers,
            boolean dtmfPassThrough, CallableEndpoint... others) {
        throw new UnsupportedOperationException("Moho remote doens't support this operation now");
    }

    @Override
    public Joint join(JoinType type, boolean force, Direction direction, boolean dtmfPassThrough, Call... others) {
        throw new UnsupportedOperationException("Moho remote doens't support this operation now");
    }
}