/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 Jive Software. All rights reserved.
*
* 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 org.jivesoftware.sparkplugin;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jingle.JingleManager;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.JingleSessionRequest;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.jmf.JmfMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.jspeex.SpeexMediaManager;
import org.jivesoftware.smackx.jingle.nat.BridgedTransportManager;
import org.jivesoftware.smackx.jingle.nat.ICETransportManager;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.STUN;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.spark.PresenceManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.phone.Phone;
import org.jivesoftware.spark.phone.PhoneManager;
import org.jivesoftware.spark.plugin.Plugin;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.TranscriptWindow;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.gateways.transports.TransportUtils;
import org.jivesoftware.sparkimpl.settings.local.LocalPreferences;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
/**
* A simple Jingle Plugin for Spark that uses server Media Proxy for the transport and NAT Traversal
*/
public class JinglePlugin implements Plugin, Phone, ConnectionListener {
private JingleManager jingleManager;
private static final String JINGLE_NAMESPACE = "http://www.xmpp.org/extensions/xep-0166.html#ns";
private String stunServer = "";
private int stunPort = 0;
private boolean readyToConnect = false;
private Map<String, Boolean> jingleFeature = new HashMap<String, Boolean>();
private boolean fallbackStunEnabled = false;
public void initialize() {
// Add Jingle to discovered items list.
SparkManager.addFeature(JINGLE_NAMESPACE);
final LocalPreferences localPref = SettingsManager.getLocalPreferences();
//If there is a server entered in spark.properties use it as fallback
if (!localPref.getStunFallbackHost().equals("")) {
fallbackStunEnabled = true;
}
// Get the default port
stunPort = localPref.getStunFallbackPort();
// Set Jingle Enabled
JingleManager.setJingleServiceEnabled();
JingleManager.setServiceEnabled(SparkManager.getConnection(), true);
// Add to PhoneManager
PhoneManager.getInstance().addPhone(this);
// Adds a tab handler.
SparkManager.getChatManager()
.addSparkTabHandler(new JingleTabHandler());
final SwingWorker jingleLoadingThread = new SwingWorker() {
public Object construct() {
if (fallbackStunEnabled) {
stunServer = localPref.getStunFallbackHost();
readyToConnect = true;
}
if (STUN.serviceAvailable(SparkManager.getConnection())) {
STUN stun = STUN
.getSTUNServer(SparkManager.getConnection());
if (stun != null) {
List<STUN.StunServerAddress> servers = stun
.getServers();
if (servers.size() > 0) {
stunServer = servers.get(0).getServer();
stunPort = Integer.parseInt(servers.get(0)
.getPort());
readyToConnect = true;
}
}
}
if (readyToConnect)
{
JingleTransportManager transportManager = new ICETransportManager(SparkManager.getConnection(), stunServer, stunPort);
List<JingleMediaManager> mediaManagers = new ArrayList<JingleMediaManager>();
// Get the Locator from the Settings
String locator = SettingsManager.getLocalPreferences().getAudioDevice();
mediaManagers.add(new JmfMediaManager(locator, transportManager));
mediaManagers.add(new SpeexMediaManager(transportManager));
//mediaManagers.add(new ScreenShareMediaManager(transportManager));
jingleManager = new JingleManager(SparkManager.getConnection(), mediaManagers);
if (transportManager instanceof BridgedTransportManager) {
jingleManager.addCreationListener((BridgedTransportManager)transportManager);
}
else if (transportManager instanceof ICETransportManager) {
jingleManager.addCreationListener((ICETransportManager)transportManager);
}
}
return true;
}
public void finished() {
addListeners();
}
};
jingleLoadingThread.start();
// Add Presence listener for better service discovery.
addPresenceListener();
SparkManager.getConnection().addConnectionListener(this);
}
/**
* Adds Jingle and ChatRoom listeners.
*/
private void addListeners() {
if (jingleManager == null) {
if (readyToConnect)
{
Log.error("Unable to resolve Jingle Connection (Host: "+stunServer+" Port: "+stunPort+")");
}
return;
}
// Listen in for new incoming Jingle requests.
jingleManager.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
incomingJingleSession(request);
}
});
}
});
}
public Collection<Action> getPhoneActions(final String jid) {
// Do not even disco gateway clients.
if (TransportUtils.isFromGateway(jid) || jingleManager == null) {
return Collections.emptyList();
}
Boolean supportsJingle = jingleFeature.get(StringUtils.parseBareAddress(jid));
if (supportsJingle == null) {
// Disco for event.
// Obtain the ServiceDiscoveryManager associated with my XMPPConnection
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection());
String fullJID = PresenceManager.getFullyQualifiedJID(jid);
// Get the items of a given XMPP entity
DiscoverInfo discoverInfo = null;
try {
discoverInfo = discoManager.discoverInfo(fullJID);
}
catch (XMPPException e) {
Log.debug("Unable to disco " + fullJID);
}
if (discoverInfo != null) {
// Get the discovered items of the queried XMPP entity
supportsJingle = discoverInfo.containsFeature(JINGLE_NAMESPACE);
jingleFeature.put(jid, supportsJingle);
}
else {
jingleFeature.put(jid, false);
supportsJingle = false;
}
}
if (!supportsJingle) {
return Collections.emptyList();
}
final List<Action> actions = new ArrayList<Action>();
Action action = new AbstractAction() {
private static final long serialVersionUID = 1467355627829748086L;
public void actionPerformed(ActionEvent e) {
placeCall(jid);
}
};
action.putValue(Action.NAME, "<html><b>" + JingleResources.getString("label.computer.to.computer") + "</b></html>");
action.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.COMPUTER_IMAGE_16x16));
actions.add(action);
return actions;
}
public void placeCall(String jid) {
// cancel call request if no Media Locator available
if (PhoneManager.isUseStaticLocator() && PhoneManager.isUsingMediaLocator()) {
return;
}
PhoneManager.setUsingMediaLocator(true);
jid = SparkManager.getUserManager().getFullJID(jid);
ChatRoom room = SparkManager.getChatManager().getChatRoom(StringUtils.parseBareAddress(jid));
if (JingleStateManager.getInstance().getJingleRoomState(room) != null) {
return;
}
SparkManager.getChatManager().getChatContainer().activateChatRoom(room);
// Create a new Jingle Call with a full JID
JingleSession session = null;
try {
session = jingleManager.createOutgoingJingleSession(jid);
}
catch (XMPPException e) {
Log.error(e);
}
TranscriptWindow transcriptWindow = room.getTranscriptWindow();
StyledDocument doc = (StyledDocument)transcriptWindow.getDocument();
Style style = doc.addStyle("StyleName", null);
OutgoingCall outgoingCall = new OutgoingCall();
outgoingCall.handleOutgoingCall(session, room, jid);
StyleConstants.setComponent(style, outgoingCall);
// Insert the image at the end of the text
try {
doc.insertString(doc.getLength(), "ignored text", style);
doc.insertString(doc.getLength(), "\n", null);
}
catch (BadLocationException e) {
Log.error(e);
}
room.scrollToBottom();
}
public void shutdown() {
}
public boolean canShutDown() {
return false;
}
public void uninstall() {
}
/**
* Notify user that a new incoming jingle request has been receieved.
*
* @param request the <code>JingleSessionRequest</code>.
*/
private void incomingJingleSession(JingleSessionRequest request) {
if (PhoneManager.isUseStaticLocator() && PhoneManager.isUsingMediaLocator()) {
request.reject();
}
else {
PhoneManager.setUsingMediaLocator(true);
new IncomingCall(request);
}
}
/**
* Adds a presence listener to remove offline users from discovered features.
*/
private void addPresenceListener() {
// Check presence changes
SparkManager.getConnection().addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence)packet;
if (!presence.isAvailable()) {
String from = presence.getFrom();
if (ModelUtil.hasLength(from)) {
// Remove from
jingleFeature.remove(from);
}
}
}
}, new PacketTypeFilter(Presence.class));
}
public void connectionClosed() {
}
public void connectionClosedOnError(Exception e) {
}
public void reconnectingIn(int seconds) {
}
public void reconnectionSuccessful() {
// Add Jingle to discovered items list.
SparkManager.addFeature(JINGLE_NAMESPACE);
}
public void reconnectionFailed(Exception e) {
}
}