1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-26 16:22:06 +01:00

Added DNS SRV support (SMACK-29).

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@2832 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Matt Tucker 2005-09-17 22:01:24 +00:00 committed by matt
parent 7f9129ee9b
commit ab474ea063
6 changed files with 993 additions and 7 deletions

View file

@ -74,6 +74,15 @@
<SOURCES /> <SOURCES />
</library> </library>
</orderEntry> </orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/../resources" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntryProperties /> <orderEntryProperties />
</component> </component>
</module> </module>

View file

@ -33,6 +33,17 @@
<entry name=".+\.(properties|xml|html|dtd|tld)" /> <entry name=".+\.(properties|xml|html|dtd|tld)" />
<entry name=".+\.(gif|png|jpeg|jpg)" /> <entry name=".+\.(gif|png|jpeg|jpg)" />
</resourceExtensions> </resourceExtensions>
<wildcardResourcePatterns>
<entry name="?*.properties" />
<entry name="?*.xml" />
<entry name="?*.html" />
<entry name="?*.dtd" />
<entry name="?*.tld" />
<entry name="?*.gif" />
<entry name="?*.png" />
<entry name="?*.jpeg" />
<entry name="?*.jpg" />
</wildcardResourcePatterns>
</component> </component>
<component name="DataSourceManagerImpl" /> <component name="DataSourceManagerImpl" />
<component name="DependenciesAnalyzeManager"> <component name="DependenciesAnalyzeManager">
@ -188,7 +199,7 @@
<module fileurl="file://$PROJECT_DIR$/Smack.iml" filepath="$PROJECT_DIR$/Smack.iml" /> <module fileurl="file://$PROJECT_DIR$/Smack.iml" filepath="$PROJECT_DIR$/Smack.iml" />
</modules> </modules>
</component> </component>
<component name="ProjectRootManager" version="2" assert-keyword="false" jdk-15="false" project-jdk-name="JDK 1.4.2" /> <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="false" project-jdk-name="JDK 1.4.2" />
<component name="RmicSettings"> <component name="RmicSettings">
<option name="IS_EANABLED" value="false" /> <option name="IS_EANABLED" value="false" />
<option name="DEBUGGING_INFO" value="true" /> <option name="DEBUGGING_INFO" value="true" />

View file

@ -28,6 +28,7 @@ import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.DNSUtil;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@ -132,8 +133,11 @@ public class XMPPConnection {
Map chats = new HashMap(); Map chats = new HashMap();
/** /**
* Creates a new connection to the specified XMPP server. The default port of 5222 will * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be
* be used. The IP address of the server is assumed to match the service name. * performed to try to determine the IP address and port corresponding to the
* serviceName; if that lookup fails, it's assumed that server resides at serviceName
* with the default port of 5222. This is the preferred constructor for connecting
* to an XMPP server.
* *
* @param serviceName the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>. * @param serviceName the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
* @throws XMPPException if an error occurs while trying to establish the connection. * @throws XMPPException if an error occurs while trying to establish the connection.
@ -143,7 +147,27 @@ public class XMPPConnection {
* appropiate error messages to end-users. * appropiate error messages to end-users.
*/ */
public XMPPConnection(String serviceName) throws XMPPException { public XMPPConnection(String serviceName) throws XMPPException {
this(serviceName, 5222, serviceName); DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName);
this.host = address.getHost();
this.port = address.getPort();
try {
this.socket = new Socket(host, port);
}
catch (UnknownHostException uhe) {
throw new XMPPException(
"Could not connect to " + host + ":" + port + ".",
new XMPPError(504),
uhe);
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError connecting to " + host + ":" + port + ".",
new XMPPError(502),
ioe);
}
this.serviceName = serviceName;
init();
} }
/** /**
@ -159,7 +183,25 @@ public class XMPPConnection {
* appropiate error messages to end-users. * appropiate error messages to end-users.
*/ */
public XMPPConnection(String host, int port) throws XMPPException { public XMPPConnection(String host, int port) throws XMPPException {
this(host, port, host); this.host = host;
this.port = port;
try {
this.socket = new Socket(host, port);
}
catch (UnknownHostException uhe) {
throw new XMPPException(
"Could not connect to " + host + ":" + port + ".",
new XMPPError(504),
uhe);
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError connecting to " + host + ":" + port + ".",
new XMPPError(502),
ioe);
}
this.serviceName = host;
init();
} }
/** /**
@ -216,7 +258,8 @@ public class XMPPConnection {
* appropiate error messages to end-users. * appropiate error messages to end-users.
*/ */
public XMPPConnection(String host, int port, String serviceName, SocketFactory socketFactory) public XMPPConnection(String host, int port, String serviceName, SocketFactory socketFactory)
throws XMPPException { throws XMPPException
{
this.host = host; this.host = host;
this.port = port; this.port = port;
try { try {
@ -994,5 +1037,4 @@ public class XMPPConnection {
// Send a new opening stream to the server // Send a new opening stream to the server
packetWriter.openStream(); packetWriter.openStream();
} }
} }

View file

@ -0,0 +1,628 @@
/**
* $Revision: 1456 $
* $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $
*
* Copyright 2003-2005 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.smack.util;
import java.util.*;
/**
* A specialized Map that is size-limited (using an LRU algorithm) and
* has an optional expiration time for cache items. The Map is thread-safe.<p>
*
* The algorithm for cache is as follows: a HashMap is maintained for fast
* object lookup. Two linked lists are maintained: one keeps objects in the
* order they are accessed from cache, the other keeps objects in the order
* they were originally added to cache. When objects are added to cache, they
* are first wrapped by a CacheObject which maintains the following pieces
* of information:<ul>
* <li> A pointer to the node in the linked list that maintains accessed
* order for the object. Keeping a reference to the node lets us avoid
* linear scans of the linked list.
* <li> A pointer to the node in the linked list that maintains the age
* of the object in cache. Keeping a reference to the node lets us avoid
* linear scans of the linked list.</ul>
* <p/>
* To get an object from cache, a hash lookup is performed to get a reference
* to the CacheObject that wraps the real object we are looking for.
* The object is subsequently moved to the front of the accessed linked list
* and any necessary cache cleanups are performed. Cache deletion and expiration
* is performed as needed.
*
* @author Matt Tucker
*/
class Cache implements Map {
/**
* The map the keys and values are stored in.
*/
protected Map map;
/**
* Linked list to maintain order that cache objects are accessed
* in, most used to least used.
*/
protected LinkedList lastAccessedList;
/**
* Linked list to maintain time that cache objects were initially added
* to the cache, most recently added to oldest added.
*/
protected LinkedList ageList;
/**
* Maximum number of items the cache will hold.
*/
protected int maxCacheSize;
/**
* Maximum length of time objects can exist in cache before expiring.
*/
protected long maxLifetime;
/**
* Maintain the number of cache hits and misses. A cache hit occurs every
* time the get method is called and the cache contains the requested
* object. A cache miss represents the opposite occurence.<p>
*
* Keeping track of cache hits and misses lets one measure how efficient
* the cache is; the higher the percentage of hits, the more efficient.
*/
protected long cacheHits, cacheMisses = 0L;
/**
* Create a new cache and specify the maximum size of for the cache in
* bytes, and the maximum lifetime of objects.
*
* @param maxSize the maximum number of objects the cache will hold. -1
* means the cache has no max size.
* @param maxLifetime the maximum amount of time (in ms) objects can exist in
* cache before being deleted. -1 means objects never expire.
*/
public Cache(int maxSize, long maxLifetime) {
if (maxSize == 0) {
throw new IllegalArgumentException("Max cache size cannot be 0.");
}
this.maxCacheSize = maxSize;
this.maxLifetime = maxLifetime;
// Our primary data structure is a hash map. The default capacity of 11
// is too small in almost all cases, so we set it bigger.
map = new HashMap(103);
lastAccessedList = new LinkedList();
ageList = new LinkedList();
}
public synchronized Object put(Object key, Object value) {
Object oldValue = null;
// Delete an old entry if it exists.
if (map.containsKey(key)) {
oldValue = remove(key, true);
}
CacheObject cacheObject = new CacheObject(value);
map.put(key, cacheObject);
// Make an entry into the cache order list.
// Store the cache order list entry so that we can get back to it
// during later lookups.
cacheObject.lastAccessedListNode = lastAccessedList.addFirst(key);
// Add the object to the age list
LinkedListNode ageNode = ageList.addFirst(key);
ageNode.timestamp = System.currentTimeMillis();
cacheObject.ageListNode = ageNode;
// If cache is too full, remove least used cache entries until it is not too full.
cullCache();
return oldValue;
}
public synchronized Object get(Object key) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
CacheObject cacheObject = (CacheObject) map.get(key);
if (cacheObject == null) {
// The object didn't exist in cache, so increment cache misses.
cacheMisses++;
return null;
}
// Remove the object from it's current place in the cache order list,
// and re-insert it at the front of the list.
cacheObject.lastAccessedListNode.remove();
lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
// The object exists in cache, so increment cache hits. Also, increment
// the object's read count.
cacheHits++;
cacheObject.readCount++;
return cacheObject.object;
}
public synchronized Object remove(Object key) {
return remove(key, false);
}
/*
* Remove operation with a flag so we can tell coherence if the remove was
* caused by cache internal processing such as eviction or loading
*/
public synchronized Object remove(Object key, boolean internal) {
CacheObject cacheObject = (CacheObject) map.remove(key);
// If the object is not in cache, stop trying to remove it.
if (cacheObject == null) {
return null;
}
// Remove from the cache order list
cacheObject.lastAccessedListNode.remove();
cacheObject.ageListNode.remove();
// Remove references to linked list nodes
cacheObject.ageListNode = null;
cacheObject.lastAccessedListNode = null;
return cacheObject.object;
}
public synchronized void clear() {
Object[] keys = map.keySet().toArray();
for (int i = 0; i < keys.length; i++) {
remove(keys[i]);
}
// Now, reset all containers.
map.clear();
lastAccessedList.clear();
ageList.clear();
cacheHits = 0;
cacheMisses = 0;
}
public synchronized int size() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.size();
}
public synchronized boolean isEmpty() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.isEmpty();
}
public synchronized Collection values() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
Object[] cacheObjects = map.values().toArray();
Object[] values = new Object[cacheObjects.length];
for (int i = 0; i < cacheObjects.length; i++) {
values[i] = ((CacheObject) cacheObjects[i]).object;
}
return Collections.unmodifiableList(Arrays.asList(values));
}
public synchronized boolean containsKey(Object key) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return map.containsKey(key);
}
public void putAll(Map map) {
for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
Object value = entry.getValue();
// If the map is another DefaultCache instance than the
// entry values will be CacheObject instances that need
// to be converted to the normal object form.
if (value instanceof CacheObject) {
value = ((CacheObject) value).object;
}
put(entry.getKey(), value);
}
}
public synchronized boolean containsValue(Object value) {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
CacheObject cacheObject = new CacheObject(value);
return map.containsValue(cacheObject);
}
public synchronized Set entrySet() {
// Warning -- this method returns CacheObject instances and not Objects
// in the same form they were put into cache.
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return Collections.unmodifiableSet(map.entrySet());
}
public synchronized Set keySet() {
// First, clear all entries that have been in cache longer than the
// maximum defined age.
deleteExpiredEntries();
return Collections.unmodifiableSet(map.keySet());
}
public long getCacheHits() {
return cacheHits;
}
public long getCacheMisses() {
return cacheMisses;
}
public int getMaxCacheSize() {
return maxCacheSize;
}
public synchronized void setMaxCacheSize(int maxCacheSize) {
this.maxCacheSize = maxCacheSize;
// It's possible that the new max size is smaller than our current cache
// size. If so, we need to delete infrequently used items.
cullCache();
}
public long getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(long maxLifetime) {
this.maxLifetime = maxLifetime;
}
/**
* Clears all entries out of cache where the entries are older than the
* maximum defined age.
*/
protected synchronized void deleteExpiredEntries() {
// Check if expiration is turned on.
if (maxLifetime <= 0) {
return;
}
// Remove all old entries. To do this, we remove objects from the end
// of the linked list until they are no longer too old. We get to avoid
// any hash lookups or looking at any more objects than is strictly
// neccessary.
LinkedListNode node = ageList.getLast();
// If there are no entries in the age list, return.
if (node == null) {
return;
}
// Determine the expireTime, which is the moment in time that elements
// should expire from cache. Then, we can do an easy check to see
// if the expire time is greater than the expire time.
long expireTime = System.currentTimeMillis() - maxLifetime;
while (expireTime > node.timestamp) {
if (remove(node.object, true) == null) {
System.err.println("Error attempting to remove(" + node.object.toString() +
") - cacheObject not found in cache!");
// remove from the ageList
node.remove();
}
// Get the next node.
node = ageList.getLast();
// If there are no more entries in the age list, return.
if (node == null) {
return;
}
}
}
/**
* Removes the least recently used elements if the cache size is greater than
* or equal to the maximum allowed size until the cache is at least 10% empty.
*/
protected synchronized void cullCache() {
// Check if a max cache size is defined.
if (maxCacheSize < 0) {
return;
}
// See if the cache is too big. If so, clean out cache until it's 10% free.
if (map.size() > maxCacheSize) {
// First, delete any old entries to see how much memory that frees.
deleteExpiredEntries();
// Next, delete the least recently used elements until 10% of the cache
// has been freed.
int desiredSize = (int) (maxCacheSize * .90);
for (int i=map.size(); i>desiredSize; i--) {
// Get the key and invoke the remove method on it.
if (remove(lastAccessedList.getLast().object, true) == null) {
System.err.println("Error attempting to cullCache with remove(" +
lastAccessedList.getLast().object.toString() + ") - " +
"cacheObject not found in cache!");
lastAccessedList.getLast().remove();
}
}
}
}
/**
* Wrapper for all objects put into cache. It's primary purpose is to maintain
* references to the linked lists that maintain the creation time of the object
* and the ordering of the most used objects.
*
* This class is optimized for speed rather than strictly correct encapsulation.
*/
private static class CacheObject {
/**
* Underlying object wrapped by the CacheObject.
*/
public Object object;
/**
* A reference to the node in the cache order list. We keep the reference
* here to avoid linear scans of the list. Every time the object is
* accessed, the node is removed from its current spot in the list and
* moved to the front.
*/
public LinkedListNode lastAccessedListNode;
/**
* A reference to the node in the age order list. We keep the reference
* here to avoid linear scans of the list. The reference is used if the
* object has to be deleted from the list.
*/
public LinkedListNode ageListNode;
/**
* A count of the number of times the object has been read from cache.
*/
public int readCount = 0;
/**
* Creates a new cache object wrapper.
*
* @param object the underlying Object to wrap.
*/
public CacheObject(Object object) {
this.object = object;
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CacheObject)) {
return false;
}
final CacheObject cacheObject = (CacheObject) o;
if (!object.equals(cacheObject.object)) {
return false;
}
return true;
}
public int hashCode() {
return object.hashCode();
}
}
/**
* Simple LinkedList implementation. The main feature is that list nodes
* are public, which allows very fast delete operations when one has a
* reference to the node that is to be deleted.<p>
*/
private static class LinkedList {
/**
* The root of the list keeps a reference to both the first and last
* elements of the list.
*/
private LinkedListNode head = new LinkedListNode("head", null, null);
/**
* Creates a new linked list.
*/
public LinkedList() {
head.next = head.previous = head;
}
/**
* Returns the first linked list node in the list.
*
* @return the first element of the list.
*/
public LinkedListNode getFirst() {
LinkedListNode node = head.next;
if (node == head) {
return null;
}
return node;
}
/**
* Returns the last linked list node in the list.
*
* @return the last element of the list.
*/
public LinkedListNode getLast() {
LinkedListNode node = head.previous;
if (node == head) {
return null;
}
return node;
}
/**
* Adds a node to the beginning of the list.
*
* @param node the node to add to the beginning of the list.
*/
public LinkedListNode addFirst(LinkedListNode node) {
node.next = head.next;
node.previous = head;
node.previous.next = node;
node.next.previous = node;
return node;
}
/**
* Adds an object to the beginning of the list by automatically creating a
* a new node and adding it to the beginning of the list.
*
* @param object the object to add to the beginning of the list.
* @return the node created to wrap the object.
*/
public LinkedListNode addFirst(Object object) {
LinkedListNode node = new LinkedListNode(object, head.next, head);
node.previous.next = node;
node.next.previous = node;
return node;
}
/**
* Adds an object to the end of the list by automatically creating a
* a new node and adding it to the end of the list.
*
* @param object the object to add to the end of the list.
* @return the node created to wrap the object.
*/
public LinkedListNode addLast(Object object) {
LinkedListNode node = new LinkedListNode(object, head, head.previous);
node.previous.next = node;
node.next.previous = node;
return node;
}
/**
* Erases all elements in the list and re-initializes it.
*/
public void clear() {
//Remove all references in the list.
LinkedListNode node = getLast();
while (node != null) {
node.remove();
node = getLast();
}
//Re-initialize.
head.next = head.previous = head;
}
/**
* Returns a String representation of the linked list with a comma
* delimited list of all the elements in the list.
*
* @return a String representation of the LinkedList.
*/
public String toString() {
LinkedListNode node = head.next;
StringBuffer buf = new StringBuffer();
while (node != head) {
buf.append(node.toString()).append(", ");
node = node.next;
}
return buf.toString();
}
}
/**
* Doubly linked node in a LinkedList. Most LinkedList implementations keep the
* equivalent of this class private. We make it public so that references
* to each node in the list can be maintained externally.
*
* Exposing this class lets us make remove operations very fast. Remove is
* built into this class and only requires two reference reassignments. If
* remove existed in the main LinkedList class, a linear scan would have to
* be performed to find the correct node to delete.
*
* The linked list implementation was specifically written for the Jive
* cache system. While it can be used as a general purpose linked list, for
* most applications, it is more suitable to use the linked list that is part
* of the Java Collections package.
*/
private static class LinkedListNode {
public LinkedListNode previous;
public LinkedListNode next;
public Object object;
/**
* This class is further customized for the Jive cache system. It
* maintains a timestamp of when a Cacheable object was first added to
* cache. Timestamps are stored as long values and represent the number
* of milliseconds passed since January 1, 1970 00:00:00.000 GMT.<p>
*
* The creation timestamp is used in the case that the cache has a
* maximum lifetime set. In that case, when
* [current time] - [creation time] > [max lifetime], the object will be
* deleted from cache.
*/
public long timestamp;
/**
* Constructs a new linked list node.
*
* @param object the Object that the node represents.
* @param next a reference to the next LinkedListNode in the list.
* @param previous a reference to the previous LinkedListNode in the list.
*/
public LinkedListNode(Object object, LinkedListNode next,
LinkedListNode previous)
{
this.object = object;
this.next = next;
this.previous = previous;
}
/**
* Removes this node from the linked list that it is a part of.
*/
public void remove() {
previous.next = next;
next.previous = previous;
}
/**
* Returns a String representation of the linked list node by calling the
* toString method of the node's object.
*
* @return a String representation of the LinkedListNode.
*/
public String toString() {
return object.toString();
}
}
}

View file

@ -0,0 +1,219 @@
/**
* $Revision: 1456 $
* $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $
*
* Copyright 2003-2005 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.smack.util;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.Attributes;
import java.util.Hashtable;
import java.util.Map;
/**
* Utilty class to perform DNS lookups for XMPP services.
*
* @author Matt Tucker
*/
public class DNSUtil {
/**
* Create a cache to hold the 100 most recently accessed DNS lookups for a period of
* 10 minutes.
*/
private static Map cache = new Cache(100, 1000*60*10);
private static DirContext context;
static {
try {
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
context = new InitialDirContext(env);
}
catch (Exception e) {
// Ignore.
}
}
/**
* Returns the host name and port that the specified XMPP server can be
* reached at for client-to-server communication. A DNS lookup for a SRV
* record in the form "_xmpp-client._tcp.example.com" is attempted, according
* to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form
* of "_jabber._tcp.example.com" is attempted since servers that implement an
* older version of the protocol may be listed using that notation. If that
* lookup fails as well, it's assumed that the XMPP server lives at the
* host resolved by a DNS lookup at the specified domain on the default port
* of 5222.<p>
*
* As an example, a lookup for "example.com" may return "im.example.com:5269".
*
* @param domain the domain.
* @return a HostAddress, which encompasses the hostname and port that the XMPP
* server can be reached at for the specified domain.
*/
public static HostAddress resolveXMPPDomain(String domain) {
if (context == null) {
return new HostAddress(domain, 5222);
}
String key = "c" + domain;
// Return item from cache if it exists.
if (cache.containsKey(key)) {
HostAddress address = (HostAddress)cache.get(key);
if (address != null) {
return address;
}
}
String host = domain;
int port = 5222;
try {
Attributes dnsLookup = context.getAttributes("_xmpp-client._tcp." + domain);
String srvRecord = (String)dnsLookup.get("SRV").get();
String [] srvRecordEntries = srvRecord.split(" ");
port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);
host = srvRecordEntries[srvRecordEntries.length-1];
}
catch (Exception e) {
// Ignore.
}
// Host entries in DNS should end with a ".".
if (host.endsWith(".")) {
host = host.substring(0, host.length()-1);
}
HostAddress address = new HostAddress(host, port);
// Add item to cache.
cache.put(key, address);
return address;
}
/**
* Returns the host name and port that the specified XMPP server can be
* reached at for server-to-server communication. A DNS lookup for a SRV
* record in the form "_xmpp-server._tcp.example.com" is attempted, according
* to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form
* of "_jabber._tcp.example.com" is attempted since servers that implement an
* older version of the protocol may be listed using that notation. If that
* lookup fails as well, it's assumed that the XMPP server lives at the
* host resolved by a DNS lookup at the specified domain on the default port
* of 5269.<p>
*
* As an example, a lookup for "example.com" may return "im.example.com:5269".
*
* @param domain the domain.
* @return a HostAddress, which encompasses the hostname and port that the XMPP
* server can be reached at for the specified domain.
*/
public static HostAddress resolveXMPPServerDomain(String domain) {
if (context == null) {
return new HostAddress(domain, 5269);
}
String key = "s" + domain;
// Return item from cache if it exists.
if (cache.containsKey(key)) {
HostAddress address = (HostAddress)cache.get(key);
if (address != null) {
return address;
}
}
String host = domain;
int port = 5269;
try {
Attributes dnsLookup = context.getAttributes("_xmpp-server._tcp." + domain);
String srvRecord = (String)dnsLookup.get("SRV").get();
String [] srvRecordEntries = srvRecord.split(" ");
port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);
host = srvRecordEntries[srvRecordEntries.length-1];
}
catch (Exception e) {
// Attempt lookup with older "jabber" name.
try {
Attributes dnsLookup = context.getAttributes("_jabber._tcp." + domain);
String srvRecord = (String)dnsLookup.get("SRV").get();
String [] srvRecordEntries = srvRecord.split(" ");
port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);
host = srvRecordEntries[srvRecordEntries.length-1];
}
catch (Exception e2) { }
}
// Host entries in DNS should end with a ".".
if (host.endsWith(".")) {
host = host.substring(0, host.length()-1);
}
HostAddress address = new HostAddress(host, port);
// Add item to cache.
cache.put(key, address);
return address;
}
/**
* Encapsulates a hostname and port.
*/
public static class HostAddress {
private String host;
private int port;
private HostAddress(String host, int port) {
this.host = host;
this.port = port;
}
/**
* Returns the hostname.
*
* @return the hostname.
*/
public String getHost() {
return host;
}
/**
* Returns the port.
*
* @return the port.
*/
public int getPort() {
return port;
}
public String toString() {
return host + ":" + port;
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof HostAddress)) {
return false;
}
final HostAddress address = (HostAddress) o;
if (!host.equals(address.host)) {
return false;
}
if (port != address.port) {
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,77 @@
/**
* $RCSfile$
* $Revision: 2485 $
* $Date: 2005-04-14 17:56:37 -0700 (Thu, 14 Apr 2005) $
*
* Copyright (C) 2002-2003 Jive Software. All rights reserved.
* ====================================================================
* The Jive Software License (based on Apache Software License, Version 1.1)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by
* Jive Software (http://www.jivesoftware.com)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Smack" and "Jive Software" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact webmaster@jivesoftware.com.
*
* 5. Products derived from this software may not be called "Smack",
* nor may "Smack" appear in their name, without prior written
* permission of Jive Software.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jivesoftware.smack.util;
import junit.framework.TestCase;
/**
* A test case for the Cache class.
*/
public class CacheTest extends TestCase {
public void testMaxSize() {
Cache cache = new Cache(100, -1);
for (int i=0; i<1000; i++) {
cache.put(new Integer(i), "value");
assertTrue("Cache size must never be larger than 100.", cache.size() <= 100);
}
}
public void testLRU() {
Cache cache = new Cache(100, -1);
for (int i=0; i<1000; i++) {
cache.put(new Integer(i), "value");
assertTrue("LRU algorithm for cache key of '0' failed.", cache.get(new Integer(0)) != null);
}
}
}