/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.protocol.jabber;
import java.util.*;
import java.util.concurrent.*;
import net.java.sip.communicator.util.Logger;
import org.jitsi.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.*;
import org.xmpp.jnodes.smack.*;
/**
* Search for jingle nodes.
*
* @author Damian Minkov
*/
public class JingleNodesServiceDiscovery
implements Runnable
{
/**
* Logger of this class
*/
private static final Logger logger =
Logger.getLogger(JingleNodesServiceDiscovery.class);
/**
* Property containing jingle nodes prefix to search for.
*/
private static final String JINGLE_NODES_SEARCH_PREFIX_PROP =
"net.java.sip.communicator.impl.protocol.jabber.JINGLE_NODES_SEARCH_PREFIXES";
/**
* Property containing jingle nodes prefix to search for.
*/
private static final String JINGLE_NODES_SEARCH_PREFIXES_STOP_ON_FIRST_PROP =
"net.java.sip.communicator.impl.protocol.jabber.JINGLE_NODES_SEARCH_PREFIXES_STOP_ON_FIRST";
/**
* Synchronization object to monitor auto discovery.
*/
private final Object jingleNodesSyncRoot;
/**
* The service.
*/
private final SmackServiceNode service;
/**
* The connection, must be connected.
*/
private final XMPPConnection connection;
/**
* Our account.
*/
private final JabberAccountIDImpl accountID;
/**
* Creates discovery
* @param service the service.
* @param connection the connected connection.
* @param accountID our account.
* @param syncRoot the synchronization object while discovering.
*/
JingleNodesServiceDiscovery(SmackServiceNode service,
XMPPConnection connection,
JabberAccountIDImpl accountID,
Object syncRoot)
{
this.jingleNodesSyncRoot = syncRoot;
this.service = service;
this.connection = connection;
this.accountID = accountID;
}
/**
* The actual discovery.
*/
public void run()
{
synchronized(jingleNodesSyncRoot)
{
long start = System.currentTimeMillis();
if(logger.isInfoEnabled())
{
logger.info("Start Jingle Nodes discovery!");
}
SmackServiceNode.MappedNodes nodes;
String searchNodesWithPrefix =
JabberActivator.getResources()
.getSettingsString(JINGLE_NODES_SEARCH_PREFIX_PROP);
if(searchNodesWithPrefix == null
|| searchNodesWithPrefix.length() == 0)
searchNodesWithPrefix =
JabberActivator.getConfigurationService()
.getString(JINGLE_NODES_SEARCH_PREFIX_PROP);
// if there are no default prefix settings or
// this option is turned off, just process with default
// service discovery making list empty.
if( searchNodesWithPrefix == null
|| searchNodesWithPrefix.length() == 0
|| searchNodesWithPrefix.equalsIgnoreCase("off"))
{
searchNodesWithPrefix = "";
}
nodes = searchServicesWithPrefix(
service,
connection, 6, 3, 20, JingleChannelIQ.UDP,
accountID.isJingleNodesSearchBuddiesEnabled(),
accountID.isJingleNodesAutoDiscoveryEnabled(),
searchNodesWithPrefix);
if(logger.isInfoEnabled())
{
logger.info("Jingle Nodes discovery terminated! ");
logger.info("Found " + (nodes != null ?
nodes.getRelayEntries().size() : "0") +
" Jingle Nodes relay for account: " +
accountID.getAccountAddress()
+ " in " + (System.currentTimeMillis() - start) + " ms.");
}
if(nodes != null)
service.addEntries(nodes);
}
}
/**
* Searches for services as the prefix list has priority. If it is set
* return after first found service.
*
* @param service the service.
* @param xmppConnection the connection.
* @param maxEntries maximum entries to be searched.
* @param maxDepth the depth while recursively searching.
* @param maxSearchNodes number of nodes to query
* @param protocol the protocol
* @param searchBuddies should we search our buddies in contactlist.
* @param autoDiscover is auto discover turned on
* @param prefix the coma separated list of prefixes to be searched first.
* @return
*/
private SmackServiceNode.MappedNodes searchServicesWithPrefix(
SmackServiceNode service,
XMPPConnection xmppConnection,
int maxEntries,
int maxDepth,
int maxSearchNodes,
String protocol,
boolean searchBuddies,
boolean autoDiscover,
String prefix)
{
if (xmppConnection == null || !xmppConnection.isConnected())
{
return null;
}
SmackServiceNode.MappedNodes mappedNodes =
new SmackServiceNode.MappedNodes();
ConcurrentHashMap<String, String> visited
= new ConcurrentHashMap<String, String>();
// Request to our pre-configured trackerEntries
for(Map.Entry<String, TrackerEntry> entry
: service.getTrackerEntries().entrySet())
{
SmackServiceNode.deepSearch(
xmppConnection,
maxEntries,
entry.getValue().getJid(),
mappedNodes,
maxDepth - 1,
maxSearchNodes,
protocol,
visited);
}
if(autoDiscover)
{
boolean continueSearch =
searchDiscoItems(
service,
xmppConnection,
maxEntries,
xmppConnection.getServiceName(),
mappedNodes,
maxDepth - 1,
maxSearchNodes,
protocol,
visited,
prefix);
// option to stop after first found is turned on, lets exit
if(!continueSearch)
return mappedNodes;
// Request to Server
SmackServiceNode.deepSearch(
xmppConnection,
maxEntries,
xmppConnection.getHost(),
mappedNodes,
maxDepth - 1,
maxSearchNodes,
protocol,
visited);
// Request to Buddies
if (xmppConnection.getRoster() != null && searchBuddies)
{
for (final RosterEntry re : xmppConnection.getRoster().getEntries())
{
for (final Iterator<Presence> i
= xmppConnection.getRoster()
.getPresences(re.getUser());
i.hasNext();)
{
final Presence presence = i.next();
if (presence.isAvailable())
{
SmackServiceNode.deepSearch(
xmppConnection,
maxEntries,
presence.getFrom(),
mappedNodes,
maxDepth - 1,
maxSearchNodes,
protocol,
visited);
}
}
}
}
}
return null;
}
/**
* Discover services and query them.
* @param service the service.
* @param xmppConnection the connection.
* @param maxEntries maximum entries to be searched.
* @param startPoint the start point to search recursively
* @param mappedNodes nodes found
* @param maxDepth the depth while recursively searching.
* @param maxSearchNodes number of nodes to query
* @param protocol the protocol
* @param visited nodes already visited
* @param prefix the coma separated list of prefixes to be searched first.
* @return
*/
private static boolean searchDiscoItems(
SmackServiceNode service,
XMPPConnection xmppConnection,
int maxEntries,
String startPoint,
SmackServiceNode.MappedNodes mappedNodes,
int maxDepth,
int maxSearchNodes,
String protocol,
ConcurrentHashMap<String, String> visited,
String prefix)
{
String[] prefixes = prefix.split(",");
// default is to stop when first one is found
boolean stopOnFirst = true;
String stopOnFirstDefaultValue =
JabberActivator.getResources().getSettingsString(
JINGLE_NODES_SEARCH_PREFIXES_STOP_ON_FIRST_PROP);
if(stopOnFirstDefaultValue != null)
{
stopOnFirst = Boolean.parseBoolean(stopOnFirstDefaultValue);
}
stopOnFirst = JabberActivator.getConfigurationService().getBoolean(
JINGLE_NODES_SEARCH_PREFIXES_STOP_ON_FIRST_PROP,
stopOnFirst);
final DiscoverItems items = new DiscoverItems();
items.setTo(startPoint);
PacketCollector collector =
xmppConnection.createPacketCollector(
new PacketIDFilter(items.getPacketID()));
xmppConnection.sendPacket(items);
DiscoverItems result = (DiscoverItems) collector.nextResult(
Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
if (result != null)
{
// first search priority items
Iterator<DiscoverItems.Item> i = result.getItems();
for (DiscoverItems.Item item = i.hasNext() ? i.next() : null;
item != null;
item = i.hasNext() ? i.next() : null)
{
for(String pref : prefixes)
{
if( !StringUtils.isNullOrEmpty(pref)
&& item.getEntityID().startsWith(pref.trim()))
{
SmackServiceNode.deepSearch(
xmppConnection,
maxEntries,
item.getEntityID(),
mappedNodes,
maxDepth,
maxSearchNodes,
protocol,
visited);
if(stopOnFirst)
return false;// stop and don't continue
}
}
}
// now search rest
i = result.getItems();
for (DiscoverItems.Item item = i.hasNext() ? i.next() : null;
item != null;
item = i.hasNext() ? i.next() : null)
{
// we may searched already this node if it starts
// with some of the prefixes
if(!visited.containsKey(item.getEntityID()))
SmackServiceNode.deepSearch(
xmppConnection,
maxEntries,
item.getEntityID(),
mappedNodes,
maxDepth,
maxSearchNodes,
protocol,
visited);
if(stopOnFirst)
return false;// stop and don't continue
}
}
collector.cancel();
// true we should continue searching
return true;
}
}