mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-12-27 14:47:59 +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:
parent
7f9129ee9b
commit
ab474ea063
6 changed files with 993 additions and 7 deletions
|
@ -74,6 +74,15 @@
|
|||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="file://$MODULE_DIR$/../resources" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntryProperties />
|
||||
</component>
|
||||
</module>
|
||||
|
|
|
@ -33,6 +33,17 @@
|
|||
<entry name=".+\.(properties|xml|html|dtd|tld)" />
|
||||
<entry name=".+\.(gif|png|jpeg|jpg)" />
|
||||
</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 name="DataSourceManagerImpl" />
|
||||
<component name="DependenciesAnalyzeManager">
|
||||
|
@ -188,7 +199,7 @@
|
|||
<module fileurl="file://$PROJECT_DIR$/Smack.iml" filepath="$PROJECT_DIR$/Smack.iml" />
|
||||
</modules>
|
||||
</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">
|
||||
<option name="IS_EANABLED" value="false" />
|
||||
<option name="DEBUGGING_INFO" value="true" />
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.jivesoftware.smack.packet.Packet;
|
|||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.packet.XMPPError;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.DNSUtil;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
@ -132,8 +133,11 @@ public class XMPPConnection {
|
|||
Map chats = new HashMap();
|
||||
|
||||
/**
|
||||
* Creates a new connection to the specified XMPP server. The default port of 5222 will
|
||||
* be used. The IP address of the server is assumed to match the service name.
|
||||
* Creates a new connection to the specified XMPP server. A DNS SRV lookup will be
|
||||
* 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>.
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public XMPPConnection(String host, int port, String serviceName, SocketFactory socketFactory)
|
||||
throws XMPPException {
|
||||
throws XMPPException
|
||||
{
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
try {
|
||||
|
@ -994,5 +1037,4 @@ public class XMPPConnection {
|
|||
// Send a new opening stream to the server
|
||||
packetWriter.openStream();
|
||||
}
|
||||
|
||||
}
|
628
source/org/jivesoftware/smack/util/Cache.java
Normal file
628
source/org/jivesoftware/smack/util/Cache.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
219
source/org/jivesoftware/smack/util/DNSUtil.java
Normal file
219
source/org/jivesoftware/smack/util/DNSUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
77
test/org/jivesoftware/smack/util/CacheTest.java
Normal file
77
test/org/jivesoftware/smack/util/CacheTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue