Java tutorial
/** * 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"); } }