diff options
author | Sebastien Vincent <seb@jitsi.org> | 2010-12-10 18:24:33 +0000 |
---|---|---|
committer | Sebastien Vincent <seb@jitsi.org> | 2010-12-10 18:24:33 +0000 |
commit | f5b574a71a9489a4df4e9ec0e040c62a8887087c (patch) | |
tree | c9c0abb3be1db7ea188014ce329f35c02750cc1f /src/net/java/sip | |
parent | d016f4fd6dfa2c61f38203a1ef3185b3ff6eccdb (diff) | |
download | jitsi-f5b574a71a9489a4df4e9ec0e040c62a8887087c.zip jitsi-f5b574a71a9489a4df4e9ec0e040c62a8887087c.tar.gz jitsi-f5b574a71a9489a4df4e9ec0e040c62a8887087c.tar.bz2 |
Initial support for Jingle Nodes. Please note that this feature is disabled by default.
Diffstat (limited to 'src/net/java/sip')
18 files changed, 1505 insertions, 33 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java index a37ced0..c190c1e 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java @@ -47,6 +47,9 @@ public class MainToolBar ChatSessionChangeListener, Skinnable { + /** + * Serial version UID. + */ private static final long serialVersionUID = -5572510509556499465L; /** @@ -114,7 +117,7 @@ public class MainToolBar /** * The current <tt>ChatSession</tt> made known to this instance by the last - * call to its {@link #chatChanged(ChatPanel)}. + * call to its {@link #chatChanged(ChatPanel)}. */ private ChatSession chatSession; @@ -503,7 +506,7 @@ public class MainToolBar /** * Sets the current <tt>ChatSession</tt> made known to this instance by the * last call to its {@link #chatChanged(ChatPanel)}. - * + * * @param chatSession the <tt>ChatSession</tt> to become current for this * instance */ diff --git a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf index 2f0266b..6d5e1f8 100644 --- a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf +++ b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf @@ -8,11 +8,14 @@ Import-Package: net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.packetlogging, net.java.sip.communicator.util, org.osgi.framework, + org.ice4j.stack, javax.crypto, javax.crypto.spec Export-Package: net.java.sip.communicator.service.netaddr, net.java.sip.communicator.service.netaddr.event, org.ice4j, + org.ice4j.socket, + org.ice4j.stack, org.ice4j.ice, org.ice4j.ice.harvest, org.ice4j.security diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java index d6e6721..b07f71c 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -21,6 +21,7 @@ import org.ice4j.*; import org.ice4j.ice.*; import org.ice4j.ice.harvest.*; import org.ice4j.security.*; +import org.xmpp.jnodes.smack.*; /** * A {@link TransportManagerJabberImpl} implementation that would use ICE for @@ -79,7 +80,6 @@ public class IceUdpTransportManager public IceUdpTransportManager(CallPeerJabberImpl callPeer) { super(callPeer); - iceAgent = createIceAgent(); } @@ -185,6 +185,24 @@ public class IceUdpTransportManager } } + /* Jingle nodes candidate */ + if(accID.isJingleNodesRelayEnabled()) + { + /* this method is blocking until Jingle Nodes auto-discovery (if + * enabled) finished + */ + SmackServiceNode serviceNode = + peer.getProtocolProvider().getJingleNodesServiceNode(); + + JingleNodesHarvester harvester = new JingleNodesHarvester( + serviceNode); + + if(harvester != null) + { + agent.addCandidateHarvester(harvester); + } + } + return agent; } @@ -232,7 +250,8 @@ public class IceUdpTransportManager * of the <tt>MediaStream</tt> with the specified <tt>MediaType</tt> * @throws OperationFailedException if anything goes wrong while * initializing the requested <tt>StreamConnector</tt> - * @see net.java.sip.communicator.service.protocol.media.TransportManager#getStreamConnector(MediaType) + * @see net.java.sip.communicator.service.protocol.media.TransportManager# + * getStreamConnector(MediaType) */ @Override public StreamConnector getStreamConnector(MediaType mediaType) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java index e1719d0..788231c 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java @@ -14,6 +14,7 @@ import net.java.sip.communicator.service.protocol.*; * The Jabber implementation of a sip-communicator AccountID * * @author Damian Minkov + * @author Sebastien Vincent */ public class JabberAccountID extends AccountID @@ -94,7 +95,7 @@ public class JabberAccountID } /** - * Determines whether this account's provider use the default STUN server + * Determines whether this account's provider uses the default STUN server * provided by SIP Communicator if there is no other STUN/TURN server * discovered/configured. * @@ -108,4 +109,64 @@ public class JabberAccountID ProtocolProviderFactory.USE_DEFAULT_STUN_SERVER, true); } + + /** + * Returns the list of JingleNodes trackers/relays that this account is + * currently configured to use. + * + * @return the list of JingleNodes trackers/relays that this account is + * currently configured to use. + */ + public List<JingleNodeDescriptor> getJingleNodes() + { + Map<String, String> accountProperties = getAccountProperties(); + List<JingleNodeDescriptor> serList + = new ArrayList<JingleNodeDescriptor>(); + + for (int i = 0; i < JingleNodeDescriptor.MAX_JN_RELAY_COUNT; i ++) + { + JingleNodeDescriptor node + = JingleNodeDescriptor.loadDescriptor( + accountProperties, + JingleNodeDescriptor.JN_PREFIX + i); + + // If we don't find a relay server with the given index, it means + // that there're no more servers left in the table so we've nothing + // more to do here. + if (node == null) + break; + + serList.add(node); + } + + return serList; + } + + /** + * Determines whether this account's provider is supposed to auto discover + * JingleNodes relay. + * + * @return <tt>true</tt> if this provider would need to discover JingleNodes + * relay, <tt>false</tt> otherwise + */ + public boolean isJingleNodesAutoDiscoveryEnabled() + { + return getAccountPropertyBoolean( + ProtocolProviderFactory.AUTO_DISCOVER_JINGLE_NODES, + false); + } + + /** + * Determines whether this account's provider uses JingleNodes relay (if + * available). + * + * @return <tt>true</tt> if this provider would use JingleNodes relay (if + * available), <tt>false</tt> otherwise + */ + public boolean isJingleNodesRelayEnabled() + { + return getAccountPropertyBoolean( + ProtocolProviderFactory.IS_USE_JINGLE_NODES, + false); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java new file mode 100644 index 0000000..6d439e9 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java @@ -0,0 +1,117 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.lang.reflect.*; +import java.net.*; + +import org.ice4j.*; +import org.ice4j.ice.*; +import org.ice4j.socket.*; + +/** + * Represents a <tt>Candidate</tt> obtained via Jingle Nodes. + * + * @author Sebastien Vincent + */ +public class JingleNodesCandidate + extends LocalCandidate +{ + /** + * The socket used to communicate with relay. + */ + private DatagramSocket socket = null; + + /** + * The <tt>RelayedCandidateDatagramSocket</tt> of this + * <tt>JingleNodesCandidate</tt>. + */ + private JingleNodesCandidateDatagramSocket + jingleNodesCandidateDatagramSocket = null; + + /** + * <tt>TransportAddress</tt> of the Jingle Nodes relay where we will send + * our packet. + */ + private TransportAddress localEndPoint = null; + + /** + * Creates a <tt>JingleNodesRelayedCandidate</tt> for the specified + * transport, address, and base. + * + * @param transportAddress the transport address that this candidate is + * encapsulating. + * @param parentComponent the <tt>Component</tt> that this candidate + * belongs to. + * @param localEndPoint <tt>TransportAddress</tt> of the Jingle Nodes relay + * where we will send our packet. + */ + public JingleNodesCandidate(TransportAddress transportAddress, + Component parentComponent, TransportAddress localEndPoint) + { + super(transportAddress, parentComponent, + CandidateType.RELAYED_CANDIDATE); + setBase(this); + setRelayServerAddress(localEndPoint); + this.localEndPoint = localEndPoint; + } + + /** + * Gets the <tt>JingleNodesCandidateDatagramSocket</tt> of this + * <tt>JingleNodesCandidate</tt>. + * <p> + * <b>Note</b>: The method is part of the internal API of + * <tt>RelayedCandidate</tt> and <tt>TurnCandidateHarvest</tt> and is not + * intended for public use. + * </p> + * + * @return the <tt>RelayedCandidateDatagramSocket</tt> of this + * <tt>RelayedCandidate</tt> + */ + public synchronized JingleNodesCandidateDatagramSocket + getRelayedCandidateDatagramSocket() + { + if (jingleNodesCandidateDatagramSocket == null) + { + try + { + jingleNodesCandidateDatagramSocket + = new JingleNodesCandidateDatagramSocket( + this, localEndPoint); + } + catch (SocketException sex) + { + throw new UndeclaredThrowableException(sex); + } + } + return jingleNodesCandidateDatagramSocket; + } + + /** + * Gets the <tt>DatagramSocket</tt> associated with this <tt>Candidate</tt>. + * + * @return the <tt>DatagramSocket</tt> associated with this + * <tt>Candidate</tt> + */ + public DatagramSocket getSocket() + { + if (socket == null) + { + try + { + socket + = new MultiplexingDatagramSocket( + getRelayedCandidateDatagramSocket()); + } + catch (SocketException sex) + { + throw new UndeclaredThrowableException(sex); + } + } + return socket; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidateDatagramSocket.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidateDatagramSocket.java new file mode 100644 index 0000000..45579b4 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidateDatagramSocket.java @@ -0,0 +1,136 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.io.IOException; +import java.net.*; + +import org.ice4j.*; + +/** + * Represents an application-purposed (as opposed to an ICE-specific) + * <tt>DatagramSocket</tt> for a <tt>JingleNodesCandidate</tt>. + * + * @author Sebastien Vincent + */ +public class JingleNodesCandidateDatagramSocket extends DatagramSocket +{ + /** + * <tt>TransportAddress</tt> of the Jingle Nodes relay where we will send + * our packet. + */ + private TransportAddress localEndPoint = null; + + /** + * The <tt>JingleNodesCandidate</tt>. + */ + private JingleNodesCandidate jingleNodesCandidate; + + /** + * Initializes a new <tt>JingleNodesdCandidateDatagramSocket</tt> instance + * which is to be the <tt>socket</tt> of a specific + * <tt>JingleNodesCandidate</tt>. + * + * @param jingleNodesCandidate the <tt>JingleNodesCandidate</tt> which is to + * use the new instance as the value of its <tt>socket</tt> property + * @param localEndPoint <tt>TransportAddress</tt> of the Jingle Nodes relay + * where we will send our packet. + * @throws SocketException if anything goes wrong while initializing the new + * <tt>JingleNodesCandidateDatagramSocket</tt> instance + */ + public JingleNodesCandidateDatagramSocket( + JingleNodesCandidate jingleNodesCandidate, + TransportAddress localEndPoint) + throws SocketException + { + super(/* bindaddr */ (SocketAddress) null); + this.jingleNodesCandidate = jingleNodesCandidate; + this.localEndPoint = localEndPoint; + } + + /** + * Sends a datagram packet from this socket. The <tt>DatagramPacket</tt> + * includes information indicating the data to be sent, its length, the IP + * address of the remote host, and the port number on the remote host. + * + * @param p the <tt>DatagramPacket</tt> to be sent + * @throws IOException if an I/O error occurs + * @see DatagramSocket#send(DatagramPacket) + */ + @Override + public void send(DatagramPacket p) + throws IOException + { + byte data[] = p.getData(); + int dataLen = p.getLength(); + int dataOffset = p.getOffset(); + + /* send to Jingle Nodes relay address on local port */ + DatagramPacket packet = new DatagramPacket(data, dataOffset, dataLen, + new InetSocketAddress(localEndPoint.getAddress(), + localEndPoint.getPort())); + + //XXX reuse an existing DatagramPacket ? + super.send(packet); + } + + /** + * Gets the local address to which the socket is bound. + * <tt>JingleNodesCandidateDatagramSocket</tt> returns the <tt>address</tt> + * of its <tt>localSocketAddress</tt>. + * <p> + * If there is a security manager, its <tt>checkConnect</tt> method is first + * called with the host address and <tt>-1</tt> as its arguments to see if + * the operation is allowed. + * </p> + * + * @return the local address to which the socket is bound, or an + * <tt>InetAddress</tt> representing any local address if either the socket + * is not bound, or the security manager <tt>checkConnect</tt> method does + * not allow the operation + * @see #getLocalSocketAddress() + * @see DatagramSocket#getLocalAddress() + */ + @Override + public InetAddress getLocalAddress() + { + return getLocalSocketAddress().getAddress(); + } + + /** + * Returns the port number on the local host to which this socket is bound. + * <tt>JingleNodesCandidateDatagramSocket</tt> returns the <tt>port</tt> of + * its <tt>localSocketAddress</tt>. + * + * @return the port number on the local host to which this socket is bound + * @see #getLocalSocketAddress() + * @see DatagramSocket#getLocalPort() + */ + @Override + public int getLocalPort() + { + return getLocalSocketAddress().getPort(); + } + + /** + * Returns the address of the endpoint this socket is bound to, or + * <tt>null</tt> if it is not bound yet. Since + * <tt>JingleNodesCandidateDatagramSocket</tt> represents an + * application-purposed <tt>DatagramSocket</tt> relaying data to and from a + * Jingle Nodes relay, the <tt>localSocketAddress</tt> is the + * <tt>transportAddress</tt> of respective <tt>JingleNodesCandidate</tt>. + * + * @return a <tt>SocketAddress</tt> representing the local endpoint of this + * socket, or <tt>null</tt> if it is not bound yet + * @see DatagramSocket#getLocalSocketAddress() + */ + @Override + public InetSocketAddress getLocalSocketAddress() + { + return jingleNodesCandidate.getTransportAddress(); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java new file mode 100644 index 0000000..e264395 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java @@ -0,0 +1,181 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.net.DatagramSocket; +import java.util.*; + +import org.ice4j.*; +import org.ice4j.ice.*; +import org.ice4j.ice.harvest.*; + +import org.jivesoftware.smack.*; +import org.xmpp.jnodes.smack.*; + +import net.java.sip.communicator.util.*; + +/** + * Implements a <tt>CandidateHarvester</tt> which gathers <tt>Candidate</tt>s + * for a specified {@link Component} using Jingle Nodes as defined in + * XEP 278 "Jingle Relay Nodes". + * + * @author Sebastien Vincent + */ +public class JingleNodesHarvester + implements CandidateHarvester +{ + /** + * The <tt>Logger</tt> used by the <tt>JingleNodesHarvester</tt> class and + * its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(JingleNodesHarvester.class.getName()); + + /** + * XMPP connection. + */ + private SmackServiceNode serviceNode = null; + + /** + * JingleNodes relay allocate two address/port couple for us. Due to the + * architecture of Ice4j that harvest address for each component, we store + * the second address/port couple. + */ + private TransportAddress localAddressSecond = null; + + /** + * JingleNodes relay allocate two address/port couple for us. Due to the + * architecture of Ice4j that harvest address for each component, we store + * the second address/port couple. + */ + private TransportAddress relayedAddressSecond = null; + + /** + * Constructor. + * + * @param serviceNode the <tt>SmackServiceNode</tt> + */ + public JingleNodesHarvester(SmackServiceNode serviceNode) + { + this.serviceNode = serviceNode; + } + + /** + * Gathers Jingle Nodes candidates for all host <tt>Candidate</tt>s that are + * already present in the specified <tt>component</tt>. This method relies + * on the specified <tt>component</tt> to already contain all its host + * candidates so that it would resolve them. + * + * @param component the {@link Component} that we'd like to gather candidate + * Jingle Nodes <tt>Candidate</tt>s for + * @return the <tt>LocalCandidate</tt>s gathered by this + * <tt>CandidateHarvester</tt> + */ + public synchronized Collection<LocalCandidate> harvest(Component component) + { + logger.info("harvest Jingle Nodes"); + + Collection<LocalCandidate> candidates = new HashSet<LocalCandidate>(); + String ip = null; + int port = -1; + + /* if we have already a candidate (RTCP) allocated, get it */ + if(localAddressSecond != null && relayedAddressSecond != null) + { + LocalCandidate candidate = createJingleNodesCandidate( + relayedAddressSecond, component, localAddressSecond); + candidates.add(candidate); + component.addLocalCandidate(candidate); + + localAddressSecond = null; + relayedAddressSecond = null; + return candidates; + } + + XMPPConnection conn = serviceNode.getConnection(); + JingleChannelIQ ciq = null; + + if (serviceNode != null) + { + final TrackerEntry preferred = serviceNode.getPreferedRelay(); + + if (preferred != null) + { + ciq = SmackServiceNode.getChannel(conn, preferred.getJid()); + } + } + + if (ciq != null && ciq.getRemoteport() > 0) + { + ip = ciq.getHost(); + port = ciq.getRemoteport(); + + if(logger.isInfoEnabled()) + { + logger.info("JN relay: " + ip + " remote port:" + port + + " local port: " + ciq.getLocalport()); + } + + /* RTP */ + TransportAddress relayedAddress = new TransportAddress(ip, port, + Transport.UDP); + TransportAddress localAddress = new TransportAddress(ip, + ciq.getLocalport(), Transport.UDP); + + LocalCandidate local = createJingleNodesCandidate( + relayedAddress, component, localAddress); + + /* RTCP */ + relayedAddressSecond + = new TransportAddress(ip, port + 1,Transport.UDP); + localAddressSecond + = new TransportAddress(ip, ciq.getLocalport() + 1, + Transport.UDP); + + candidates.add(local); + component.addLocalCandidate(local); + } + return candidates; + } + + /** + * Creates a new <tt>JingleNodesRelayedCandidate</tt> instance which is to + * represent a specific <tt>TransportAddress</tt>. + * + * @param transportAddress the <tt>TransportAddress</tt> allocated by the + * relay + * @param component the <tt>Component</tt> for which the candidate will be + * added + * @param localEndPoint <tt>TransportAddress</tt> of the Jingle Nodes relay + * where we will send our packet. + * @return a new <tt>JingleNodesRelayedCandidate</tt> instance which + * represents the specified <tt>TransportAddress</tt> + */ + protected JingleNodesCandidate createJingleNodesCandidate( + TransportAddress transportAddress, Component component, + TransportAddress localEndPoint) + { + JingleNodesCandidate cand = null; + + try + { + cand = new JingleNodesCandidate(transportAddress, + component, + localEndPoint); + DatagramSocket stunSocket = cand.getStunSocket(null); + cand.getStunStack().addSocket(stunSocket); + } + catch(Throwable e) + { + logger.debug( + "Exception occurred when creating JingleNodesCandidate: " + + e); + } + + return cand; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java index ea0e28a..c397a57 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java @@ -20,6 +20,10 @@ import org.osgi.framework.*; public class ProtocolProviderFactoryJabberImpl extends ProtocolProviderFactory { + /** + * Indicates if ICE should be used. + */ + public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED"; /** * Creates an instance of the ProtocolProviderFactoryJabberImpl. @@ -47,13 +51,15 @@ public class ProtocolProviderFactoryJabberImpl BundleContext context = JabberActivator.getBundleContext(); if (context == null) - throw new NullPointerException("The specified BundleContext was null"); + throw new NullPointerException( + "The specified BundleContext was null"); if (userIDStr == null) throw new NullPointerException("The specified AccountID was null"); if (accountProperties == null) - throw new NullPointerException("The specified property map was null"); + throw new NullPointerException( + "The specified property map was null"); accountProperties.put(USER_ID, userIDStr); @@ -89,7 +95,15 @@ public class ProtocolProviderFactoryJabberImpl return accountID; } - protected AccountID createAccountID(String userID, Map<String, String> accountProperties) + /** + * Create an account. + * + * @param userID the user ID + * @param accountProperties the properties associated with the user ID + * @return new <tt>AccountID</tt> + */ + protected AccountID createAccountID(String userID, + Map<String, String> accountProperties) { return new JabberAccountID(userID, accountProperties); } @@ -104,6 +118,13 @@ public class ProtocolProviderFactoryJabberImpl return service; } + /** + * Modify an existing account. + * + * @param protocolProvider the <tt>ProtocolProviderService</tt> responsible + * of the account + * @param accountProperties modified properties to be set + */ @Override public void modifyAccount( ProtocolProviderService protocolProvider, Map<String, String> accountProperties) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index a2bdae5..a41df7f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -31,6 +31,7 @@ import org.jivesoftware.smackx.*; import org.jivesoftware.smackx.packet.*; import org.osgi.framework.*; +import org.xmpp.jnodes.smack.*; /** * An implementation of the protocol provider service over the Jabber protocol @@ -92,6 +93,12 @@ public class ProtocolProviderServiceJabberImpl = IceUdpTransportPacketExtension.NAMESPACE; /** + * Jingle's Discovery Info URN for Jingle Nodes support. + */ + public static final String URN_XMPP_JINGLE_NODES + = "http://jabber.org/protocol/jinglenodes"; + + /** * Jingle's Discover Info URN for "XEP-0251: Jingle Session Transfer" * support. */ @@ -237,6 +244,16 @@ public class ProtocolProviderServiceJabberImpl private SmackPacketDebugger debugger = null; /** + * Jingle Nodes service. + */ + private SmackServiceNode jingleNodesServiceNode = null; + + /** + * Synchronization object to monitore jingle nodes auto discovery. + */ + private final Object jingleNodesSyncRoot = new Object(); + + /** * Returns the state of the registration of this protocol provider * @return the <tt>RegistrationState</tt> that this provider is * currently in or null in case it is in a unknown state. @@ -823,6 +840,8 @@ public class ProtocolProviderServiceJabberImpl logger.error("Failed to publish presence status"); } + startJingleNodesDiscovery(); + return ConnectState.STOP_TRYING; } else @@ -1184,6 +1203,17 @@ public class ProtocolProviderServiceJabberImpl supportedFeatures.add(URN_XMPP_JINGLE_RTP_VIDEO); supportedFeatures.add(URN_XMPP_JINGLE_RTP_ZRTP); + /* + * Reflect the preference of the user with respect to the use of + * Jingle Nodes. + */ + if (accountID.getAccountPropertyBoolean( + ProtocolProviderFactoryJabberImpl.IS_USE_JINGLE_NODES, + false)) + { + supportedFeatures.add(URN_XMPP_JINGLE_NODES); + } + /* add extension to support remote control */ supportedFeatures.add(InputEvtIQ.NAMESPACE); @@ -1758,6 +1788,71 @@ public class ProtocolProviderServiceJabberImpl } /** + * Start auto-discovery of JingleNodes tracker/relays. + */ + public void startJingleNodesDiscovery() + { + // Jingle Nodes Service Initialization + JabberAccountID accID = (JabberAccountID)getAccountID(); + jingleNodesServiceNode = new SmackServiceNode(connection, 60000); + + for(JingleNodeDescriptor desc : accID.getJingleNodes()) + { + TrackerEntry entry = new TrackerEntry( + desc.isRelaySupported() ? TrackerEntry.Type.relay : + TrackerEntry.Type.tracker, + TrackerEntry.Policy._public, + desc.getJID(), + JingleChannelIQ.UDP); + + jingleNodesServiceNode.addTrackerEntry(entry); + } + + final SmackServiceNode service = jingleNodesServiceNode; + final boolean autoDiscover = accID.isJingleNodesAutoDiscoveryEnabled(); + + new Thread() + { + public void run() + { + synchronized(jingleNodesSyncRoot) + { + if(logger.isInfoEnabled()) + { + logger.info("Start Jingle Nodes discovery!"); + } + + final SmackServiceNode.MappedNodes nodes = + service.searchServices( + connection, 6, 3, 20, JingleChannelIQ.UDP, + autoDiscover); + + if(logger.isInfoEnabled()) + { + logger.info("Jingle Nodes discovery terminated!"); + } + + service.addEntries(nodes); + } + } + }.start(); + } + + /** + * Get the Jingle Nodes service. Note that this method will block until + * Jingle Nodes auto discovery (if enabled) finished. + * + * @return Jingle Nodes service + */ + public SmackServiceNode getJingleNodesServiceNode() + { + synchronized(jingleNodesSyncRoot) + { + return jingleNodesServiceNode; + } + } + + /** * Logs a specific message and associated <tt>Throwable</tt> cause as an * error using the current <tt>Logger</tt> and then throws a new * <tt>OperationFailedException</tt> with the message, a specific error code diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java index 1e8cf9a..35fc3bb 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java @@ -194,8 +194,9 @@ public class RawUdpTransportManager * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests * are necessary, they should be executed in a separate thread. Candidate - * harvest would then need to be concluded in the {@link #wrapupHarvest()} - * method which would be called once we absolutely need the candidates. + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. * * @param ourOffer the content list that should tell us how many stream * connectors we actually need. @@ -232,8 +233,9 @@ public class RawUdpTransportManager * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests * are necessary, they should be executed in a separate thread. Candidate - * harvest would then need to be concluded in the {@link #wrapupHarvest()} - * method which would be called once we absolutely need the candidates. + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. * * @param theirOffer a media description offer that we've received from the * remote party and that we should use in case we need to know what diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index 20bd54b..a48b464 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -6,6 +6,8 @@ Bundle-Version: 0.0.1 System-Bundle: yes Import-Package: org.osgi.framework, org.ice4j, + org.ice4j.socket, + org.ice4j.stack, org.ice4j.ice, org.ice4j.ice.harvest, org.ice4j.security, @@ -51,4 +53,7 @@ Import-Package: org.osgi.framework, javax.xml.parsers, javax.net.ssl, javax.security.sasl, - javax.security.auth.callback + javax.security.auth.callback, + org.xmpp.jnodes, + org.xmpp.jnodes.smack, + org.xmpp.jnodes.nio
\ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java index d905923..01d6f2e 100644 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java @@ -27,6 +27,11 @@ public class FirstWizardPage extends TransparentPanel implements WizardPage { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + public static final String FIRST_PAGE_IDENTIFIER = "FirstPageIdentifier"; private final AccountPanel accountPanel; @@ -180,6 +185,20 @@ public class FirstWizardPage for (StunServerDescriptor descriptor : stunServers) registration.addStunServer(descriptor); + registration.setUseJingleNodes(iceConfigPanel.isUseJingleNodes()); + registration.setAutoDiscoverJingleNodes( + iceConfigPanel.isAutoDiscoverJingleNodes()); + + //we will be reentering all Jingle nodes so let's make sure we clear + //the servers vector in case we already did that with a "Next". + registration.getAdditionalJingleNodes().clear(); + + List<JingleNodeDescriptor> jingleNodes + = iceConfigPanel.getAdditionalJingleNodes(); + + for (JingleNodeDescriptor descriptor : jingleNodes) + registration.addJingleNodes(descriptor); + nextPageIdentifier = SUMMARY_PAGE_IDENTIFIER; this.isCommitted = true; @@ -302,10 +321,8 @@ public class FirstWizardPage = StunServerDescriptor.loadDescriptor( accountProperties, ProtocolProviderFactory.STUN_PREFIX + i); - - // If we don't find a stun server with the given index, it means - // that there're no more servers left i nthe table so we've nothing + // that there're no more servers left in the table so we've nothing // more to do here. if (stunServer == null) break; @@ -313,6 +330,38 @@ public class FirstWizardPage iceConfigPanel.addStunServer(stunServer); } + String useJN = + accountProperties.get(ProtocolProviderFactory.IS_USE_JINGLE_NODES); + boolean isUseJN = Boolean.parseBoolean( + (useJN != null && useJN.length() != 0) ? useJN : "false"); + + iceConfigPanel.setUseJingleNodes(isUseJN); + + String useAutoDiscoverJN + = accountProperties.get( + ProtocolProviderFactory.AUTO_DISCOVER_JINGLE_NODES); + boolean isUseAutoDiscoverJN = Boolean.parseBoolean( + (useAutoDiscoverJN != null && + useAutoDiscoverJN.length() != 0) ? + useAutoDiscoverJN : "false"); + + iceConfigPanel.setAutoDiscoverJingleNodes(isUseAutoDiscoverJN); + + for (int i = 0; i < JingleNodeDescriptor.MAX_JN_RELAY_COUNT ; i ++) + { + JingleNodeDescriptor jn + = JingleNodeDescriptor.loadDescriptor( + accountProperties, JingleNodeDescriptor.JN_PREFIX + i); + + // If we don't find a stun server with the given index, it means + // that there're no more servers left in the table so we've nothing + // more to do here. + if (jn == null) + break; + + iceConfigPanel.addJingleNodes(jn); + } + this.isServerOverridden = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.IS_SERVER_OVERRIDDEN, diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java index d238766..7b8c64b 100644 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java @@ -54,7 +54,7 @@ public class IceConfigPanel /** * The table model for our additional stun servers table. */ - private final StunServerTableModel tableModel = new StunServerTableModel(); + private final ServerTableModel tableModel = new ServerTableModel(); /** * The stun server table. @@ -62,6 +62,30 @@ public class IceConfigPanel private final JTable table = new JTable(tableModel); /** + * The check box allowing the user to choose to use JingleNodes. + */ + private final JCheckBox jnBox = new SIPCommCheckBox( + Resources.getString("plugin.jabberaccregwizz.USE_JINGLE_NODES")); + + /** + * The check box allowing the user to choose to automatically discover + * JingleNodes relays. + */ + private final JCheckBox jnAutoDiscoverBox = new SIPCommCheckBox( + Resources.getString("plugin.jabberaccregwizz.AUTO_DISCOVER_JN")); + + /** + * The table model for our additional stun servers table. + */ + private final ServerTableModel jnTableModel = + new ServerTableModel(); + + /** + * The JingleNodes server table. + */ + private final JTable jnTable = new JTable(jnTableModel); + + /** * Creates an instance of <tt>IceConfigPanel</tt>. */ public IceConfigPanel() @@ -80,6 +104,9 @@ public class IceConfigPanel autoDiscoverBox.setSelected(true); defaultStunBox.setSelected(true); + //jnBox.setSelected(true); + //jnAutoDiscoverBox.setSelected(true); + JPanel checkBoxPanel = new TransparentPanel(new GridLayout(0, 1)); checkBoxPanel.add(iceBox); checkBoxPanel.add(autoDiscoverBox); @@ -88,6 +115,14 @@ public class IceConfigPanel add(checkBoxPanel); add(Box.createVerticalStrut(10)); add(createAdditionalServersComponent()); + + checkBoxPanel = new TransparentPanel(new GridLayout(0, 1)); + checkBoxPanel.add(jnBox); + checkBoxPanel.add(jnAutoDiscoverBox); + + add(checkBoxPanel); + add(Box.createVerticalStrut(10)); + add(createAdditionalJingleNodesComponent()); } /** @@ -106,7 +141,7 @@ public class IceConfigPanel Resources.getString("plugin.jabberaccregwizz.SUPPORT_TURN")); table.setDefaultRenderer( StunServerDescriptor.class, - new StunServerCellRenderer()); + new ServerCellRenderer()); //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(table); @@ -480,7 +515,7 @@ public class IceConfigPanel * A custom cell renderer used in the cell containing the * <tt>StunServer</tt> instance. */ - private static class StunServerCellRenderer + private static class ServerCellRenderer extends DefaultTableCellRenderer { /** @@ -564,6 +599,27 @@ public class IceConfigPanel : table.getBackground()); } } + else if(value instanceof JingleNodeDescriptor) + { + JingleNodeDescriptor jn = (JingleNodeDescriptor) value; + + this.setText(jn.getJID()); + + if (isSelected) + { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + else + { + super.setForeground((unselectedForeground != null) + ? unselectedForeground + : table.getForeground()); + super.setBackground((unselectedBackground != null) + ? unselectedBackground + : table.getBackground()); + } + } else return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); @@ -576,7 +632,7 @@ public class IceConfigPanel * A custom table model, with a non editable cells and a custom class column * objects. */ - private class StunServerTableModel + private class ServerTableModel extends DefaultTableModel { /** @@ -616,7 +672,7 @@ public class IceConfigPanel * @return <tt>true</tt> if ICE should be used for this account, otherwise * returns <tt>false</tt> */ - boolean isUseIce() + protected boolean isUseIce() { return iceBox.isSelected(); } @@ -626,7 +682,7 @@ public class IceConfigPanel * @param isUseIce <tt>true</tt> to indicate that ICE should be used for * this account, <tt>false</tt> - otherwise. */ - void setUseIce(boolean isUseIce) + protected void setUseIce(boolean isUseIce) { iceBox.setSelected(isUseIce); } @@ -636,7 +692,7 @@ public class IceConfigPanel * @return <tt>true</tt> if the stun server should be automatically * discovered, otherwise returns <tt>false</tt>. */ - boolean isAutoDiscoverStun() + protected boolean isAutoDiscoverStun() { return autoDiscoverBox.isSelected(); } @@ -646,7 +702,7 @@ public class IceConfigPanel * @param isAutoDiscover <tt>true</tt> to indicate that stun server should * be auto-discovered, <tt>false</tt> - otherwise. */ - void setAutoDiscoverStun(boolean isAutoDiscover) + protected void setAutoDiscoverStun(boolean isAutoDiscover) { autoDiscoverBox.setSelected(isAutoDiscover); } @@ -656,7 +712,7 @@ public class IceConfigPanel * @return <tt>true</tt> if the default stun server should be used, * otherwise returns <tt>false</tt>. */ - boolean isUseDefaultStunServer() + protected boolean isUseDefaultStunServer() { return defaultStunBox.isSelected(); } @@ -666,7 +722,7 @@ public class IceConfigPanel * @param isDefaultStun <tt>true</tt> to indicate that the default stun * server should be used, <tt>false</tt> otherwise. */ - void setUseDefaultStunServer(boolean isDefaultStun) + protected void setUseDefaultStunServer(boolean isDefaultStun) { defaultStunBox.setSelected(isDefaultStun); } @@ -676,8 +732,8 @@ public class IceConfigPanel * * @return the list of additional stun servers entered by the user */ - @SuppressWarnings("unchecked")//getDataVector() is simply not parametrized - List<StunServerDescriptor> getAdditionalStunServers() + @SuppressWarnings("unchecked")//getDataVector() is simply not parameterized + protected List<StunServerDescriptor> getAdditionalStunServers() { LinkedList<StunServerDescriptor> serversList = new LinkedList<StunServerDescriptor>(); @@ -696,7 +752,7 @@ public class IceConfigPanel * servers. * @param stunServer the stun server to add */ - void addStunServer(StunServerDescriptor stunServer) + protected void addStunServer(StunServerDescriptor stunServer) { tableModel.addRow(new Object[]{stunServer, stunServer.isTurnSupported()}); @@ -707,7 +763,7 @@ public class IceConfigPanel * * @param stunServer the stun server to modify */ - void modifyStunServer(StunServerDescriptor stunServer) + protected void modifyStunServer(StunServerDescriptor stunServer) { for (int i = 0; i < tableModel.getRowCount(); i++) { @@ -734,7 +790,7 @@ public class IceConfigPanel * <tt>address</tt> and <tt>port</tt> already exists in the table, otherwise * returns <tt>null</tt> */ - StunServerDescriptor getStunServer(String address, int port) + protected StunServerDescriptor getStunServer(String address, int port) { for (int i = 0; i < tableModel.getRowCount(); i++) { @@ -791,4 +847,405 @@ public class IceConfigPanel return NetworkUtils.isValidPortNumber(port); } } + + /** + * Creates the list of additional JingleNodes that are added by the user. + * + * @return the created component + */ + private Component createAdditionalJingleNodesComponent() + { + jnTable.setPreferredScrollableViewportSize(new Dimension(450, 60)); + + jnTableModel.addColumn( + Resources.getString("plugin.jabberaccregwizz.JID_ADDRESS")); + jnTableModel.addColumn( + Resources.getString("plugin.jabberaccregwizz.RELAY_SUPPORT")); + + jnTable.setDefaultRenderer(JingleNodeDescriptor.class, + new ServerCellRenderer()); + + //Create the scroll pane and add the table to it. + JScrollPane scrollPane = new JScrollPane(jnTable); + + JButton addButton + = new JButton(Resources.getString("service.gui.ADD")); + addButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + JNConfigDialog jnDialog = new JNConfigDialog(false); + + jnDialog.setVisible(true); + } + }); + + JButton editButton + = new JButton(Resources.getString("service.gui.EDIT")); + editButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + if(jnTable.getSelectedRow() < 0) + return; + + JingleNodeDescriptor jn + = (JingleNodeDescriptor) jnTableModel.getValueAt( + jnTable.getSelectedRow(), 0); + + if (jn != null) + { + JNConfigDialog dialog = new JNConfigDialog( + jn.getJID(), jn.isRelaySupported()); + + dialog.setVisible(true); + } + } + }); + + JButton deleteButton + = new JButton(Resources.getString("service.gui.DELETE")); + deleteButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + jnTableModel.removeRow(jnTable.getSelectedRow()); + } + }); + + TransparentPanel buttonsPanel + = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); + + buttonsPanel.add(addButton); + buttonsPanel.add(editButton); + buttonsPanel.add(deleteButton); + + TransparentPanel mainPanel = new TransparentPanel(new BorderLayout()); + mainPanel.setBorder(BorderFactory.createTitledBorder( + Resources.getString( + "plugin.jabberaccregwizz.ADDITIONAL_JINGLE_NODES"))); + mainPanel.add(scrollPane); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); + + return mainPanel; + } + + /** + * The JingleNodes configuration window. + */ + private class JNConfigDialog extends SIPCommDialog + { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The main panel + */ + private final JPanel mainPanel + = new TransparentPanel(new BorderLayout()); + + /** + * The address of the stun server. + */ + private final JTextField addressField = new JTextField(); + + /** + * The check box where user would indicate whether a STUN server is also + * a TURN server. + */ + private final JCheckBox supportRelayCheckBox = new JCheckBox( + Resources.getString("plugin.jabberaccregwizz.RELAY_SUPPORT")); + + /** + * The pane where we show errors. + */ + private JEditorPane errorMessagePane; + + /** + * If the dialog is open via "edit" button. + */ + private final boolean isEditMode; + + /** + * Creates a new JNConfigDialog with filled in values. + * + * @param address the IP or FQDN of the server + * @param isRelaySupport a <tt>boolean</tt> indicating whether the node + * supports relay + */ + public JNConfigDialog(String address, boolean isRelaySupport) + { + this(true); + + addressField.setText(address); + supportRelayCheckBox.setSelected(isRelaySupport); + } + + /** + * Creates an empty dialog. + * + * @param editMode true if the dialog is in "edit" state, false means + * "add" state + */ + public JNConfigDialog(boolean editMode) + { + super(false); + + this.isEditMode = editMode; + + setTitle(Resources.getString( + "plugin.jabberaccregwizz.ADD_JINGLE_NODE")); + + JLabel addressLabel = new JLabel( + Resources.getString("plugin.jabberaccregwizz.JID_ADDRESS")); + + TransparentPanel labelsPanel + = new TransparentPanel(new GridLayout(0, 1)); + + labelsPanel.add(addressLabel); + labelsPanel.add(new JLabel()); + + TransparentPanel valuesPanel + = new TransparentPanel(new GridLayout(0, 1)); + + valuesPanel.add(addressField); + valuesPanel.add(supportRelayCheckBox); + + JButton addButton + = new JButton(Resources.getString(isEditMode ? + "service.gui.EDIT" : "service.gui.ADD")); + JButton cancelButton + = new JButton(Resources.getString("service.gui.CANCEL")); + + addButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + String address = addressField.getText(); + JingleNodeDescriptor jnServer = null; + + String errorMessage = null; + if (address == null || address.length() <= 0) + errorMessage = Resources.getString( + "plugin.jabberaccregwizz.NO_STUN_ADDRESS"); + + jnServer = getJingleNodes(address); + + if(jnServer != null && !isEditMode) + { + errorMessage = Resources.getString( + "plugin.jabberaccregwizz.STUN_ALREADY_EXIST"); + } + + if (errorMessage != null) + { + loadErrorMessage(errorMessage); + return; + } + + if(!isEditMode) + { + jnServer = new JingleNodeDescriptor( + address, supportRelayCheckBox.isSelected()); + + addJingleNodes(jnServer); + } + else + { + /* edit an existing Jingle Node */ + jnServer.setAddress(address); + + jnServer.setRelay(supportRelayCheckBox.isSelected()); + modifyJingleNodes(jnServer); + } + dispose(); + } + }); + + cancelButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + dispose(); + } + }); + + TransparentPanel buttonsPanel + = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonsPanel.add(addButton); + buttonsPanel.add(cancelButton); + + mainPanel.add(labelsPanel, BorderLayout.WEST); + mainPanel.add(valuesPanel, BorderLayout.CENTER); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); + + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, + 20)); + getContentPane().add(mainPanel, BorderLayout.NORTH); + pack(); + } + + /** + * Loads the given error message in the current dialog, by re-validating + * the content. + * + * @param errorMessage The error message to load. + */ + private void loadErrorMessage(String errorMessage) + { + if (errorMessagePane == null) + { + errorMessagePane = new JEditorPane(); + + errorMessagePane.setOpaque(false); + errorMessagePane.setForeground(Color.RED); + + mainPanel.add(errorMessagePane, BorderLayout.NORTH); + } + + errorMessagePane.setText(errorMessage); + mainPanel.revalidate(); + mainPanel.repaint(); + + this.pack(); + + //WORKAROUND: there's something wrong happening in this pack and + //components get cluttered, partially hiding the password text field. + //I am under the impression that this has something to do with the + //message pane preferred size being ignored (or being 0) which is + //why I am adding it's height to the dialog. It's quite ugly so + //please fix if you have something better in mind. + this.setSize(getWidth(), getHeight() + + errorMessagePane.getHeight()); + } + + /** + * Dummy implementation that we are not using. + * + * @param escaped unused + */ + @Override + protected void close(boolean escaped) {} + } + + /** + * Indicates if Jingle Nodes should be used for this account. + * + * @return <tt>true</tt> if Jingle Nodes should be used for this account, + * otherwise returns <tt>false</tt> + */ + protected boolean isUseJingleNodes() + { + return jnBox.isSelected(); + } + + /** + * Sets the <tt>useJingleNodes</tt> property. + * + * @param isUseJN <tt>true</tt> to indicate that Jingle Nodes should be + * used for this account, <tt>false</tt> - otherwise. + */ + protected void setUseJingleNodes(boolean isUseJN) + { + jnBox.setSelected(isUseJN); + } + + /** + * Indicates if the Jingle Nodes relays should be automatically discovered. + * + * @return <tt>true</tt> if the Jingle Nodes relays should be automatically + * discovered, otherwise returns <tt>false</tt>. + */ + protected boolean isAutoDiscoverJingleNodes() + { + return jnAutoDiscoverBox.isSelected(); + } + + /** + * Sets the <tt>autoDiscoverJingleNodes</tt> property. + * + * @param isAutoDiscover <tt>true</tt> to indicate that Jingle Nodes relays + * should be auto-discovered, <tt>false</tt> - otherwise. + */ + protected void setAutoDiscoverJingleNodes(boolean isAutoDiscover) + { + jnAutoDiscoverBox.setSelected(isAutoDiscover); + } + + /** + * Returns the list of additional Jingle Nodes entered by the user. + * + * @return the list of additional Jingle Nodes entered by the user + */ + @SuppressWarnings("unchecked")//getDataVector() is simply not parameterized + protected List<JingleNodeDescriptor> getAdditionalJingleNodes() + { + LinkedList<JingleNodeDescriptor> serversList + = new LinkedList<JingleNodeDescriptor>(); + + Vector<Vector<JingleNodeDescriptor>> serverRows + = jnTableModel.getDataVector(); + + for(Vector<JingleNodeDescriptor> row : serverRows) + serversList.add(row.elementAt(0)); + + return serversList; + } + + /** + * Indicates if a JingleNodes with the given <tt>address</tt> already exists + * in the additional stun servers table. + * + * @param address the JingleNodes address to check + * + * @return <tt>JingleNodesDescriptor</tt> if a Jingle Node with the given + * <tt>address</tt> already exists in the table, otherwise returns + * <tt>null</tt> + */ + protected JingleNodeDescriptor getJingleNodes(String address) + { + for (int i = 0; i < jnTableModel.getRowCount(); i++) + { + JingleNodeDescriptor jn + = (JingleNodeDescriptor) jnTableModel.getValueAt(i, 0); + + if (jn.getJID().equalsIgnoreCase(address)) + return jn; + } + return null; + } + + /** + * Adds the given <tt>jingleNode</tt> to the list of additional JingleNodes + * + * @param jingleNode the Jingle Node server to add + */ + protected void addJingleNodes(JingleNodeDescriptor jingleNode) + { + jnTableModel.addRow(new Object[]{jingleNode, + jingleNode.isRelaySupported()}); + } + + /** + * Modify the given <tt>jingleNode</tt> from the list of Jingle Nodes. + * + * @param jingleNode the Jingle Node to modify + */ + protected void modifyJingleNodes(JingleNodeDescriptor jingleNode) + { + for (int i = 0; i < jnTableModel.getRowCount(); i++) + { + JingleNodeDescriptor node + = (JingleNodeDescriptor) jnTableModel.getValueAt(i, 0); + + if(jingleNode == node) + { + jnTableModel.setValueAt(jingleNode, i, 0); + jnTableModel.setValueAt(jingleNode.isRelaySupported(), i, 1); + return; + } + } + } } diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java index 33185b9..dcb37ce 100755 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java @@ -105,6 +105,22 @@ public class JabberAccountRegistration = new ArrayList<StunServerDescriptor>(); /** + * Indicates if JingleNodes relays should be used. + */ + private boolean isUseJingleNodes = false; + + /** + * Indicates if JingleNodes relay server should be automatically discovered. + */ + private boolean isAutoDiscoverJingleNodes = false; + + /** + * The list of additional JingleNodes (tracker or relay) entered by user. + */ + private List<JingleNodeDescriptor> additionalJingleNodes + = new ArrayList<JingleNodeDescriptor>(); + + /** * Returns the password of the jabber registration account. * @return the password of the jabber registration account. */ @@ -363,4 +379,82 @@ public class JabberAccountRegistration { return additionalStunServers; } + + /** + * Sets the <tt>autoDiscoverJingleNodes</tt> property. + * + * @param isAutoDiscover <tt>true</tt> to indicate that relay server should + * be auto-discovered, <tt>false</tt> - otherwise. + */ + public void setAutoDiscoverJingleNodes(boolean isAutoDiscover) + { + this.isAutoDiscoverJingleNodes = isAutoDiscover; + } + + /** + * Indicates if the JingleNodes relay server should be automatically + * discovered. + * + * @return <tt>true</tt> if the relay server should be automatically + * discovered, otherwise returns <tt>false</tt>. + */ + public boolean isAutoDiscoverJingleNodes() + { + return isAutoDiscoverJingleNodes; + } + + /** + * Sets the <tt>useJingleNodes/tt> property. + * + * @param isUseJingleNodes <tt>true</tt> to indicate that Jingle Nodes + * should be used for this account, <tt>false</tt> - otherwise. + */ + public void setUseJingleNodes(boolean isUseJingleNodes) + { + this.isUseJingleNodes = isUseJingleNodes; + } + + /** + * Sets the <tt>useJingleNodes</tt> property. + * + * @param isUseJingleNodes <tt>true</tt> to indicate that JingleNodes relays + * should be used for this account, <tt>false</tt> - otherwise. + */ + public void isUseJingleNodes(boolean isUseJingleNodes) + { + this.isUseJingleNodes = isUseJingleNodes; + } + + /** + * Indicates if JingleNodes relay should be used. + * + * @return <tt>true</tt> if JingleNodes should be used, <tt>false</tt> + * otherwise + */ + public boolean isUseJingleNodes() + { + return isUseJingleNodes; + } + + /** + * Adds the given <tt>node</tt> to the list of additional JingleNodes. + * + * @param node the <tt>node</tt> to add + */ + public void addJingleNodes(JingleNodeDescriptor node) + { + additionalJingleNodes.add(node); + } + + /** + * Returns the <tt>List</tt> of all additional stun servers entered by the + * user. The list is guaranteed not to be <tt>null</tt>. + * + * @return the <tt>List</tt> of all additional stun servers entered by the + * user. + */ + public List<JingleNodeDescriptor> getAdditionalJingleNodes() + { + return additionalJingleNodes; + } } diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java index 9eaf734..b8abbee 100644 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java @@ -26,11 +26,20 @@ import org.osgi.framework.*; public class JabberAccountRegistrationWizard implements AccountRegistrationWizard { + /** + * The logger. + */ private static final Logger logger = Logger.getLogger(JabberAccountRegistrationWizard.class); + /** + * Account suffix for Google service. + */ private static final String GOOGLE_USER_SUFFIX = "gmail.com"; + /** + * XMPP server for Google service. + */ private static final String GOOGLE_CONNECT_SRV = "talk.google.com"; private FirstWizardPage firstWizardPage; @@ -40,8 +49,14 @@ public class JabberAccountRegistrationWizard private final WizardContainer wizardContainer; + /** + * The <tt>ProtocolProviderService</tt> of this account. + */ private ProtocolProviderService protocolProvider; + /** + * If the account has been modified. + */ private boolean isModification; /** @@ -243,6 +258,7 @@ public class JabberAccountRegistrationWizard { serverName = getServerFromUserName(userName); } + accountProperties.put(ProtocolProviderFactory.SERVER_ADDRESS, serverName); @@ -277,6 +293,25 @@ public class JabberAccountRegistrationWizard ProtocolProviderFactory.STUN_PREFIX + serverIndex); } + accountProperties.put(ProtocolProviderFactory.IS_USE_JINGLE_NODES, + String.valueOf(registration.isUseJingleNodes())); + + accountProperties.put( + ProtocolProviderFactory.AUTO_DISCOVER_JINGLE_NODES, + String.valueOf(registration.isAutoDiscoverJingleNodes())); + + serverIndex = -1; + List<JingleNodeDescriptor> jnRelays + = registration.getAdditionalJingleNodes(); + + for(JingleNodeDescriptor jnRelay : jnRelays) + { + serverIndex ++; + + jnRelay.storeDescriptor(accountProperties, + JingleNodeDescriptor.JN_PREFIX + serverIndex); + } + if (isModification) { providerFactory.modifyAccount( protocolProvider, diff --git a/src/net/java/sip/communicator/service/protocol/JingleNodeDescriptor.java b/src/net/java/sip/communicator/service/protocol/JingleNodeDescriptor.java new file mode 100644 index 0000000..7e7d4c5 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/JingleNodeDescriptor.java @@ -0,0 +1,185 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol; + +import java.util.*; + +/** + * A <tt>JingleNodesDescriptor</tt> stores information necessary to create a + * JingleNodes tracker or relay candidate harvester that we could use with + * ICE4J. Descriptors are normally initialized by protocol wizards. They are + * then used to convert the data into a {@link String} form suitable for storage + * in an accounts properties Map. + * + * @author Yana Stamcheva + * @author Emil Ivov + * @author Sebastien Vincent + */ +public class JingleNodeDescriptor +{ + /** + * JingleNodes prefix to store configuration. + */ + public static final String JN_PREFIX = "JINGLENODES"; + + /** + * JingleNodes prefix to store server address in configuration. + */ + public static final String JN_ADDRESS = "ADDRESS"; + + /** + * JingleNodes prefix to store the relay capabilities in configuration. + */ + public static final String JN_IS_RELAY_SUPPORTED = "IS_RELAY_SUPPORTED"; + + /** + * The maximum number of stun servers that we would allow. + */ + public static final int MAX_JN_RELAY_COUNT = 100; + + /** + * The address of the JingleNodes (JID). + */ + private String address; + + /** + * If the relay is supported by this JingleNodes. + */ + private boolean relaySupported; + + /** + * Creates an instance of <tt>JingleNodes</tt> by specifying all + * parameters. + * + * @param address address of the JingleNodes + * @param relaySupported if the JingleNodes supports relay + */ + public JingleNodeDescriptor(String address, + boolean relaySupported) + { + this.address = address; + this.relaySupported = relaySupported; + } + + /** + * Returns the address of the JingleNodes + * + * @return the address of the JingleNodes + */ + public String getJID() + { + return address; + } + + /** + * Sets the address of the JingleNodes. + * + * @param address the JID of the JingleNodes + */ + public void setAddress(String address) + { + this.address = address; + } + + /** + * Returns if the JID has relay support. + * + * @return <tt>true</tt> if relay is supported, <tt>false</tt> otherwise + */ + public boolean isRelaySupported() + { + return relaySupported; + } + + /** + * Sets the relay support corresponding to this JID. + * + * @param relaySupported relay value to set + */ + public void setRelay(boolean relaySupported) + { + this.relaySupported = relaySupported; + } + + /** + * Stores this descriptor into the specified {@link Map}.The method is meant + * for use with account property maps. It also allows prepending an account + * prefix to all property names so that multiple descriptors can be stored + * in a single {@link Map}. + * + * @param props the account properties {@link Map} that we'd like to store + * this descriptor in. + * @param namePrefix the prefix that we should prepend to every property + * name. + */ + public void storeDescriptor(Map<String, String> props, String namePrefix) + { + if(namePrefix == null) + namePrefix = ""; + + props.put(namePrefix + JN_ADDRESS, getJID()); + + + props.put(namePrefix + JN_IS_RELAY_SUPPORTED, + Boolean.toString(isRelaySupported())); + } + + /** + * Loads this descriptor from the specified {@link Map}.The method is meant + * for use with account property maps. It also allows prepending an account + * prefix to all property names so that multiple descriptors can be read + * in a single {@link Map}. + * + * @param props the account properties {@link Map} that we'd like to load + * this descriptor from. + * @param namePrefix the prefix that we should prepend to every property + * name. + * + * @return the newly created descriptor or null if no descriptor was found. + */ + public static JingleNodeDescriptor loadDescriptor( + Map<String, String> props, + String namePrefix) + { + if(namePrefix == null) + namePrefix = ""; + + String relayAddress = props.get(namePrefix + JN_ADDRESS); + + if (relayAddress == null) + return null; + + String relayStr = props.get(namePrefix + JN_IS_RELAY_SUPPORTED); + + boolean relay = false; + + try + { + relay = Boolean.parseBoolean(relayStr); + } + catch(Throwable t) + { + } + + JingleNodeDescriptor relayServer = + new JingleNodeDescriptor(relayAddress, + relay); + + return relayServer; + } + + /** + * Returns a <tt>String</tt> representation of this descriptor + * + * @return a <tt>String</tt> representation of this descriptor. + */ + public String toString() + { + return "JingleNodesDesc: " + getJID() + " relay:" + + isRelaySupported(); + } +} diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java index 33c2004..6402a3f 100644 --- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java +++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java @@ -160,7 +160,6 @@ public abstract class ProtocolProviderFactory */ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT"; - /** * The name of the property under which we store the user preference for a * transport protocol to use (i.e. tcp or udp). @@ -315,6 +314,17 @@ public abstract class ProtocolProviderFactory public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED"; /** + * Indicates if JingleNodes should be used with ICE. + */ + public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED"; + + /** + * Indicates if JingleNodes should be used with ICE. + */ + public static final String AUTO_DISCOVER_JINGLE_NODES + = "AUTO_DISCOVER_JINGLE_NODES"; + + /** * Address used to reach voicemail box, by services able to * subscribe for voicemail new messages notifications. */ diff --git a/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java b/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java index 5e5df32..6a43bd2 100644 --- a/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java +++ b/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java @@ -6,7 +6,6 @@ */ package net.java.sip.communicator.service.protocol; -import java.io.*; import java.util.*; import net.java.sip.communicator.util.*; |