package com.voxeo.moho.remote.impl;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.rayo.core.OfferEvent;
import com.voxeo.moho.CallableEndpoint;
import com.voxeo.moho.Endpoint;
import com.voxeo.moho.Mixer;
import com.voxeo.moho.MixerEndpoint;
import com.voxeo.moho.Participant;
import com.voxeo.moho.common.event.DispatchableEventSource;
import com.voxeo.moho.common.util.Utils.DaemonThreadFactory;
import com.voxeo.moho.remote.AuthenticationCallback;
import com.voxeo.moho.remote.MohoRemote;
import com.voxeo.moho.remote.MohoRemoteException;
import com.voxeo.rayo.client.RayoClient;
import com.voxeo.rayo.client.XmppException;
import com.voxeo.rayo.client.listener.StanzaListener;
import com.voxeo.rayo.client.xmpp.stanza.IQ;
import com.voxeo.rayo.client.xmpp.stanza.Message;
import com.voxeo.rayo.client.xmpp.stanza.Presence;
@SuppressWarnings("deprecation")
public class MohoRemoteImpl extends DispatchableEventSource implements MohoRemote {
protected static final Logger LOG = Logger.getLogger(MohoRemoteImpl.class);
protected RayoClient _client;
protected ThreadPoolExecutor _executor;
protected Map<String, ParticipantImpl> _participants = new ConcurrentHashMap<String, ParticipantImpl>();
protected Lock _participanstLock = new ReentrantLock();
protected Map<String, Mixer> _mixerNameMap = new ConcurrentHashMap<String, Mixer>();
public MohoRemoteImpl() {
super();
// TODO make configurable
int eventDispatcherThreadPoolSize = 10;
_executor = new ThreadPoolExecutor(eventDispatcherThreadPoolSize, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), new DaemonThreadFactory("MohoContext"));
_executor.prestartAllCoreThreads();
_dispatcher.setExecutor(_executor, false);
}
@Override
public void disconnect() throws MohoRemoteException {
try {
Collection<ParticipantImpl> participants = _participants.values();
for (Participant participant : participants) {
participant.disconnect();
}
_participants.clear();
}
catch (Exception ex) {
LOG.warn("", ex);
}
try {
_client.disconnect();
}
catch (XmppException e) {
throw new MohoRemoteException(e);
}
finally {
_executor.shutdown();
}
}
class MohoStanzaListener implements StanzaListener {
@Override
public void onIQ(IQ iq) {
if (iq.getFrom() != null) {
// dispatch the stanza to corresponding participant.
JID fromJID = new JID(iq.getFrom());
String id = fromJID.getNode();
if (id != null) {
ParticipantImpl participant = MohoRemoteImpl.this.getParticipant(id);
if (participant != null) {
participant.onRayoCommandResult(fromJID, iq);
}
else {
LOG.error("Can't find participant for rayo event:" + iq);
}
}
else {
LOG.warn("Unprocessed IQ, No node ID:" + iq);
}
}
else {
LOG.warn("Unprocessed IQ, No from attribute:" + iq);
}
}
@Override
public void onMessage(Message message) {
LOG.error("Received message from rayo:" + message);
}
@Override
public void onPresence(Presence presence) {
if (!presence.hasExtension()) {
LOG.debug("MohoRemote Received presence without extension, discarding:" + presence);
return;
}
JID fromJID = new JID(presence.getFrom());
if (presence.getExtension().getStanzaName().equalsIgnoreCase("offer")) {
OfferEvent offerEvent = (OfferEvent) presence.getExtension().getObject();
IncomingCallImpl call = new IncomingCallImpl(MohoRemoteImpl.this, fromJID.getNode(),
(CallableEndpoint) createEndpoint(offerEvent.getFrom()),
(CallableEndpoint) createEndpoint(offerEvent.getTo()), offerEvent.getHeaders());
MohoRemoteImpl.this.dispatch(call);
}
else {
// dispatch the stanza to corresponding participant.
String callID = fromJID.getNode();
ParticipantImpl participant = MohoRemoteImpl.this.getParticipant(callID);
if (participant != null) {
participant.onRayoEvent(fromJID, presence);
}
else {
if (presence.getShow() == null) {
LOG.error("Can't find call for rayo event:" + presence);
}
}
}
}
@Override
public void onError(com.voxeo.rayo.client.xmpp.stanza.Error error) {
LOG.error("Got error" + error);
}
}
public ParticipantImpl getParticipant(final String id) {
getParticipantsLock().lock();
try {
return _participants.get(id);
}
finally {
getParticipantsLock().unlock();
}
}
protected void addParticipant(final ParticipantImpl participant) {
_participants.put(participant.getId(), participant);
if (participant instanceof Mixer) {
String name = ((Mixer) participant).getName();
if (name != null) {
_mixerNameMap.put(name, ((Mixer) participant));
}
}
}
protected void removeParticipant(final String id) {
getParticipantsLock().lock();
try {
ParticipantImpl participant = _participants.remove(id);
if (participant instanceof Mixer) {
String name = ((Mixer) participant).getName();
if (name != null) {
_mixerNameMap.remove(name);
}
}
}
finally {
getParticipantsLock().unlock();
}
}
public Lock getParticipantsLock() {
return _participanstLock;
}
// TODO connection error handling, rayo-java-client should re-try
// automatically
@Override
public void connect(AuthenticationCallback callback, String xmppServer, String rayoServer) {
connect(callback.getUserName(), callback.getPassword(), callback.getRealm(), callback.getResource(), xmppServer,
rayoServer);
}
@Override
public void connect(String userName, String passwd, String realm, String resource, String xmppServer,
String rayoServer) throws MohoRemoteException {
connect(userName, passwd, realm, resource, xmppServer, rayoServer, 5);
}
@Override
public void connect(String userName, String passwd, String realm, String resource, String xmppServer,
String rayoServer, int timeout) throws MohoRemoteException {
if (_client == null) {
_client = new RayoClient(xmppServer, rayoServer);
_client.addStanzaListener(new MohoStanzaListener());
}
try {
_client.connect(userName, passwd, resource, timeout);
}
catch (XmppException e) {
throw new MohoRemoteException("Error connecting to server", e);
}
}
@Override
public Endpoint createEndpoint(URI uri) {
return new CallableEndpointImpl(this, uri);
}
public Executor getExecutor() {
return _executor;
}
public RayoClient getRayoClient() {
return _client;
}
@Override
public MixerEndpoint createMixerEndpoint() {
return new MixerEndpointImpl(this);
}
public Mixer getMixerByName(String name) {
return _mixerNameMap.get(name);
}
}