1d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen/**
2d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * Copyright 2009 Jonas Ådahl.
3d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * Copyright 2011-2013 Florian Schmaus
4d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
5d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
6d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * you may not use this file except in compliance with the License.
7d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * You may obtain a copy of the License at
8d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
9d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *     http://www.apache.org/licenses/LICENSE-2.0
10d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
11d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * Unless required by applicable law or agreed to in writing, software
12d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * distributed under the License is distributed on an "AS IS" BASIS,
13d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * See the License for the specific language governing permissions and
15d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * limitations under the License.
16d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen */
17d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
18d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenpackage org.jivesoftware.smackx.entitycaps;
19d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
20d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.Connection;
21d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.ConnectionCreationListener;
22d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.ConnectionListener;
23d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.PacketInterceptor;
24d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.PacketListener;
25d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.SmackConfiguration;
26d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.XMPPConnection;
27d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.XMPPException;
28d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.packet.IQ;
29d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.packet.Packet;
30d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.packet.PacketExtension;
31d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.packet.Presence;
32d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.filter.NotFilter;
33d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.filter.PacketFilter;
34d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.filter.AndFilter;
35d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.filter.PacketTypeFilter;
36d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.filter.PacketExtensionFilter;
37d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.util.Base64;
38d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smack.util.Cache;
39d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.Form;
40d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.FormField;
41d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.NodeInformationProvider;
42d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.ServiceDiscoveryManager;
43d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.entitycaps.cache.EntityCapsPersistentCache;
44d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.entitycaps.packet.CapsExtension;
45d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.packet.DiscoverInfo;
46d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.packet.DataForm;
47d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.packet.DiscoverInfo.Feature;
48d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
49d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport org.jivesoftware.smackx.packet.DiscoverItems.Item;
50d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
51d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.Collections;
52d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.Comparator;
53d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.HashMap;
54d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.Iterator;
55d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.LinkedList;
56d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.List;
57d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.Map;
58d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.Queue;
59d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.SortedSet;
60d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.TreeSet;
61d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.WeakHashMap;
62d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.util.concurrent.ConcurrentLinkedQueue;
63d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.io.IOException;
64d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.lang.ref.WeakReference;
65d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.security.MessageDigest;
66d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenimport java.security.NoSuchAlgorithmException;
67d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
68d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen/**
69d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * Keeps track of entity capabilities.
70d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen *
71d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen * @author Florian Schmaus
72d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen */
73d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chenpublic class EntityCapsManager {
74d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
75d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static final String NAMESPACE = "http://jabber.org/protocol/caps";
76d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static final String ELEMENT = "c";
77d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
78d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private static final String ENTITY_NODE = "http://www.igniterealtime.org/projects/smack";
79d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>();
80d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
81d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected static EntityCapsPersistentCache persistentCache;
82d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
83d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private static Map<Connection, EntityCapsManager> instances = Collections
84d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            .synchronizedMap(new WeakHashMap<Connection, EntityCapsManager>());
85d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
86d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
87d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Map of (node + '#" + hash algorithm) to DiscoverInfo data
88d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
89d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected static Map<String, DiscoverInfo> caps = new Cache<String, DiscoverInfo>(1000, -1);
90d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
91d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
92d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Map of Full JID -&gt; DiscoverInfo/null. In case of c2s connection the
93d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * key is formed as user@server/resource (resource is required) In case of
94d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * link-local connection the key is formed as user@host (no resource) In
95d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * case of a server or component the key is formed as domain
96d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
97d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected static Map<String, NodeVerHash> jidCaps = new Cache<String, NodeVerHash>(10000, -1);
98d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
99d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    static {
100d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        Connection.addConnectionCreationListener(new ConnectionCreationListener() {
101d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void connectionCreated(Connection connection) {
102d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (connection instanceof XMPPConnection)
103d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    new EntityCapsManager(connection);
104d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
105d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        });
106d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
107d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        try {
108d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            MessageDigest sha1MessageDigest = MessageDigest.getInstance("SHA-1");
109d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            SUPPORTED_HASHES.put("sha-1", sha1MessageDigest);
110d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        } catch (NoSuchAlgorithmException e) {
111d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            // Ignore
112d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
113d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
114d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
115d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private WeakReference<Connection> weakRefConnection;
116d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private ServiceDiscoveryManager sdm;
117d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private boolean entityCapsEnabled;
118d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private String currentCapsVersion;
119d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private boolean presenceSend = false;
120d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private Queue<String> lastLocalCapsVersions = new ConcurrentLinkedQueue<String>();
121d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
122d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
123d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Add DiscoverInfo to the database.
124d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
125d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param nodeVer
126d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            The node and verification String (e.g.
127d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=").
128d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param info
129d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            DiscoverInfo for the specified node.
130d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
131d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static void addDiscoverInfoByNode(String nodeVer, DiscoverInfo info) {
132d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        caps.put(nodeVer, info);
133d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
134d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (persistentCache != null)
135d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            persistentCache.addDiscoverInfoByNodePersistent(nodeVer, info);
136d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
137d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
138d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
139d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Get the Node version (node#ver) of a JID. Returns a String or null if
140d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * EntiyCapsManager does not have any information.
141d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
142d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param user
143d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the user (Full JID)
144d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return the node version (node#ver) or null
145d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
146d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static String getNodeVersionByJid(String jid) {
147d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        NodeVerHash nvh = jidCaps.get(jid);
148d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (nvh != null) {
149d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return nvh.nodeVer;
150d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        } else {
151d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return null;
152d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
153d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
154d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
155d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static NodeVerHash getNodeVerHashByJid(String jid) {
156d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return jidCaps.get(jid);
157d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
158d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
159d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
160d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Get the discover info given a user name. The discover info is returned if
161d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * the user has a node#ver associated with it and the node#ver has a
162d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * discover info associated with it.
163d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
164d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param user
165d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            user name (Full JID)
166d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return the discovered info
167d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
168d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static DiscoverInfo getDiscoverInfoByUser(String user) {
169d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        NodeVerHash nvh = jidCaps.get(user);
170d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (nvh == null)
171d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return null;
172d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
173d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return getDiscoveryInfoByNodeVer(nvh.nodeVer);
174d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
175d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
176d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
177d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Retrieve DiscoverInfo for a specific node.
178d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
179d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param nodeVer
180d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            The node name (e.g.
181d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=").
182d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return The corresponding DiscoverInfo or null if none is known.
183d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
184d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static DiscoverInfo getDiscoveryInfoByNodeVer(String nodeVer) {
185d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        DiscoverInfo info = caps.get(nodeVer);
186d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (info != null)
187d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            info = new DiscoverInfo(info);
188d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
189d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return info;
190d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
191d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
192d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
193d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Set the persistent cache implementation
194d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
195d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param cache
196d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @throws IOException
197d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
198d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static void setPersistentCache(EntityCapsPersistentCache cache) throws IOException {
199d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (persistentCache != null)
200d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            throw new IllegalStateException("Entity Caps Persistent Cache was already set");
201d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        persistentCache = cache;
202d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        persistentCache.replay();
203d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
204d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
205d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
206d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Sets the maximum Cache size for the JID to nodeVer Cache
207d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
208d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param maxCacheSize
209d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
210d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    @SuppressWarnings("rawtypes")
211d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static void setJidCapsMaxCacheSize(int maxCacheSize) {
212d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        ((Cache) jidCaps).setMaxCacheSize(maxCacheSize);
213d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
214d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
215d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
216d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Sets the maximum Cache size for the nodeVer to DiscoverInfo Cache
217d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
218d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param maxCacheSize
219d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
220d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    @SuppressWarnings("rawtypes")
221d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static void setCapsMaxCacheSize(int maxCacheSize) {
222d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        ((Cache) caps).setMaxCacheSize(maxCacheSize);
223d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
224d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
225d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private EntityCapsManager(Connection connection) {
226d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        this.weakRefConnection = new WeakReference<Connection>(connection);
227d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        this.sdm = ServiceDiscoveryManager.getInstanceFor(connection);
228d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        init();
229d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
230d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
231d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private void init() {
232d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        Connection connection = weakRefConnection.get();
233d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        instances.put(connection, this);
234d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
235d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        connection.addConnectionListener(new ConnectionListener() {
236d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void connectionClosed() {
237d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // Unregister this instance since the connection has been closed
238d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                presenceSend = false;
239d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                instances.remove(weakRefConnection.get());
240d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
241d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
242d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void connectionClosedOnError(Exception e) {
243d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                presenceSend = false;
244d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
245d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
246d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void reconnectionFailed(Exception e) {
247d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // ignore
248d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
249d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
250d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void reconnectingIn(int seconds) {
251d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // ignore
252d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
253d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
254d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void reconnectionSuccessful() {
255d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // ignore
256d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
257d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        });
258d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
259d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // This calculates the local entity caps version
260d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        updateLocalEntityCaps();
261d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
262d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (SmackConfiguration.autoEnableEntityCaps())
263d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            enableEntityCaps();
264d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
265d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        PacketFilter packetFilter = new AndFilter(new PacketTypeFilter(Presence.class), new PacketExtensionFilter(
266d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                ELEMENT, NAMESPACE));
267d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        connection.addPacketListener(new PacketListener() {
268d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            // Listen for remote presence stanzas with the caps extension
269d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            // If we receive such a stanza, record the JID and nodeVer
270d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
271d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void processPacket(Packet packet) {
272d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (!entityCapsEnabled())
273d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    return;
274d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
275d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                CapsExtension ext = (CapsExtension) packet.getExtension(EntityCapsManager.ELEMENT,
276d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        EntityCapsManager.NAMESPACE);
277d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
278d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                String hash = ext.getHash().toLowerCase();
279d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (!SUPPORTED_HASHES.containsKey(hash))
280d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    return;
281d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
282d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                String from = packet.getFrom();
283d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                String node = ext.getNode();
284d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                String ver = ext.getVer();
285d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
286d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                jidCaps.put(from, new NodeVerHash(node, ver, hash));
287d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
288d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
289d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }, packetFilter);
290d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
291d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        packetFilter = new AndFilter(new PacketTypeFilter(Presence.class), new NotFilter(new PacketExtensionFilter(
292d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                ELEMENT, NAMESPACE)));
293d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        connection.addPacketListener(new PacketListener() {
294d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
295d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void processPacket(Packet packet) {
296d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // always remove the JID from the map, even if entityCaps are
297d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // disabled
298d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                String from = packet.getFrom();
299d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                jidCaps.remove(from);
300d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
301d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }, packetFilter);
302d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
303d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        packetFilter = new PacketTypeFilter(Presence.class);
304d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        connection.addPacketSendingListener(new PacketListener() {
305d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
306d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void processPacket(Packet packet) {
307d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                presenceSend = true;
308d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
309d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }, packetFilter);
310d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
311d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // Intercept presence packages and add caps data when intended.
312d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // XEP-0115 specifies that a client SHOULD include entity capabilities
313d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // with every presence notification it sends.
314d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class);
315d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        PacketInterceptor packetInterceptor = new PacketInterceptor() {
316d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public void interceptPacket(Packet packet) {
317d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (!entityCapsEnabled)
318d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    return;
319d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
320d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                CapsExtension caps = new CapsExtension(ENTITY_NODE, getCapsVersion(), "sha-1");
321d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                packet.addExtension(caps);
322d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
323d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        };
324d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        connection.addPacketInterceptor(packetInterceptor, capsPacketFilter);
325d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // It's important to do this as last action. Since it changes the
326d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // behavior of the SDM in some ways
327d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        sdm.setEntityCapsManager(this);
328d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
329d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
330d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static synchronized EntityCapsManager getInstanceFor(Connection connection) {
331d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // For testing purposed forbid EntityCaps for non XMPPConnections
332d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // it may work on BOSH connections too
333d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (!(connection instanceof XMPPConnection))
334d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return null;
335d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
336d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (SUPPORTED_HASHES.size() <= 0)
337d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return null;
338d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
339d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        EntityCapsManager entityCapsManager = instances.get(connection);
340d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
341d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (entityCapsManager == null) {
342d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            entityCapsManager = new EntityCapsManager(connection);
343d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
344d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
345d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return entityCapsManager;
346d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
347d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
348d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public void enableEntityCaps() {
349d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // Add Entity Capabilities (XEP-0115) feature node.
350d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        sdm.addFeature(NAMESPACE);
351d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        updateLocalEntityCaps();
352d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        entityCapsEnabled = true;
353d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
354d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
355d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public void disableEntityCaps() {
356d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        entityCapsEnabled = false;
357d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        sdm.removeFeature(NAMESPACE);
358d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
359d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
360d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public boolean entityCapsEnabled() {
361d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return entityCapsEnabled;
362d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
363d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
364d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
365d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Remove a record telling what entity caps node a user has.
366d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
367d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param user
368d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the user (Full JID)
369d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
370d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public void removeUserCapsNode(String user) {
371d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        jidCaps.remove(user);
372d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
373d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
374d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
375d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Get our own caps version. The version depends on the enabled features. A
376d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * caps version looks like '66/0NaeaBKkwk85efJTGmU47vXI='
377d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
378d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return our own caps version
379d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
380d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public String getCapsVersion() {
381d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return currentCapsVersion;
382d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
383d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
384d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
385d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Returns the local entity's NodeVer (e.g.
386d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * "http://www.igniterealtime.org/projects/smack/#66/0NaeaBKkwk85efJTGmU47vXI=
387d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * )
388d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
389d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return
390d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
391d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public String getLocalNodeVer() {
392d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return ENTITY_NODE + '#' + getCapsVersion();
393d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
394d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
395d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
396d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Returns true if Entity Caps are supported by a given JID
397d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
398d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param jid
399d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return
400d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
401d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public boolean areEntityCapsSupported(String jid) {
402d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (jid == null)
403d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
404d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
405d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        try {
406d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            DiscoverInfo result = sdm.discoverInfo(jid);
407d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return result.containsFeature(NAMESPACE);
408d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        } catch (XMPPException e) {
409d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
410d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
411d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
412d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
413d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
414d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Returns true if Entity Caps are supported by the local service/server
415d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
416d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return
417d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
418d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public boolean areEntityCapsSupportedByServer() {
419d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return areEntityCapsSupported(weakRefConnection.get().getServiceName());
420d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
421d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
422d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
423d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Updates the local user Entity Caps information with the data provided
424d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
425d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * If we are connected and there was already a presence send, another
426d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * presence is send to inform others about your new Entity Caps node string.
427d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
428d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param discoverInfo
429d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the local users discover info (mostly the service discovery
430d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            features)
431d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param identityType
432d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the local users identity type
433d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param identityName
434d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the local users identity name
435d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param extendedInfo
436d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the local users extended info
437d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
438d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public void updateLocalEntityCaps() {
439d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        Connection connection = weakRefConnection.get();
440d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
441d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        DiscoverInfo discoverInfo = new DiscoverInfo();
442d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        discoverInfo.setType(IQ.Type.RESULT);
443d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        discoverInfo.setNode(getLocalNodeVer());
444d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (connection != null)
445d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            discoverInfo.setFrom(connection.getUser());
446d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        sdm.addDiscoverInfoTo(discoverInfo);
447d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
448d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        currentCapsVersion = generateVerificationString(discoverInfo, "sha-1");
449d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        addDiscoverInfoByNode(ENTITY_NODE + '#' + currentCapsVersion, discoverInfo);
450d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (lastLocalCapsVersions.size() > 10) {
451d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            String oldCapsVersion = lastLocalCapsVersions.poll();
452d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sdm.removeNodeInformationProvider(ENTITY_NODE + '#' + oldCapsVersion);
453d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
454d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        lastLocalCapsVersions.add(currentCapsVersion);
455d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
456d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        caps.put(currentCapsVersion, discoverInfo);
457d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (connection != null)
458d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            jidCaps.put(connection.getUser(), new NodeVerHash(ENTITY_NODE, currentCapsVersion, "sha-1"));
459d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
460d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        sdm.setNodeInformationProvider(ENTITY_NODE + '#' + currentCapsVersion, new NodeInformationProvider() {
461d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            List<String> features = sdm.getFeaturesList();
462d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            List<Identity> identities = new LinkedList<Identity>(ServiceDiscoveryManager.getIdentities());
463d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            List<PacketExtension> packetExtensions = sdm.getExtendedInfoAsList();
464d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
465d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
466d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public List<Item> getNodeItems() {
467d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                return null;
468d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
469d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
470d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
471d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public List<String> getNodeFeatures() {
472d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                return features;
473d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
474d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
475d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
476d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public List<Identity> getNodeIdentities() {
477d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                return identities;
478d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
479d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
480d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            @Override
481d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            public List<PacketExtension> getNodePacketExtensions() {
482d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                return packetExtensions;
483d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
484d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        });
485d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
486d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // Send an empty presence, and let the packet intercepter
487d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // add a <c/> node to it.
488d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // See http://xmpp.org/extensions/xep-0115.html#advertise
489d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // We only send a presence packet if there was already one send
490d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // to respect ConnectionConfiguration.isSendPresence()
491d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (connection != null && connection.isAuthenticated() && presenceSend) {
492d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            Presence presence = new Presence(Presence.Type.available);
493d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            connection.sendPacket(presence);
494d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
495d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
496d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
497d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
498d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Verify DisoverInfo and Caps Node as defined in XEP-0115 5.4 Processing
499d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Method
500d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
501d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @see <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0115
502d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *      5.4 Processing Method</a>
503d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
504d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param capsNode
505d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the caps node (i.e. node#ver)
506d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param info
507d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return true if it's valid and should be cache, false if not
508d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
509d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static boolean verifyDiscoverInfoVersion(String ver, String hash, DiscoverInfo info) {
510d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // step 3.3 check for duplicate identities
511d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (info.containsDuplicateIdentities())
512d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
513d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
514d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // step 3.4 check for duplicate features
515d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (info.containsDuplicateFeatures())
516d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
517d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
518d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // step 3.5 check for well-formed packet extensions
519d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (verifyPacketExtensions(info))
520d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
521d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
522d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        String calculatedVer = generateVerificationString(info, hash);
523d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
524d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (!ver.equals(calculatedVer))
525d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return false;
526d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
527d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return true;
528d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
529d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
530d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
531d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
532d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param info
533d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return true if the packet extensions is ill-formed
534d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
535d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected static boolean verifyPacketExtensions(DiscoverInfo info) {
536d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        List<FormField> foundFormTypes = new LinkedList<FormField>();
537d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        for (Iterator<PacketExtension> i = info.getExtensions().iterator(); i.hasNext();) {
538d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            PacketExtension pe = i.next();
539d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            if (pe.getNamespace().equals(Form.NAMESPACE)) {
540d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                DataForm df = (DataForm) pe;
541d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                for (Iterator<FormField> it = df.getFields(); it.hasNext();) {
542d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    FormField f = it.next();
543d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    if (f.getVariable().equals("FORM_TYPE")) {
544d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        for (FormField fft : foundFormTypes) {
545d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                            if (f.equals(fft))
546d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                                return true;
547d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        }
548d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        foundFormTypes.add(f);
549d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    }
550d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
551d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
552d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
553d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return false;
554d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
555d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
556d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    /**
557d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * Generates a XEP-115 Verification String
558d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
559d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @see <a href="http://xmpp.org/extensions/xep-0115.html#ver">XEP-115
560d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *      Verification String</a>
561d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *
562d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param discoverInfo
563d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @param hash
564d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *            the used hash function
565d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     * @return The generated verification String or null if the hash is not
566d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     *         supported
567d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen     */
568d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    protected static String generateVerificationString(DiscoverInfo discoverInfo, String hash) {
569d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        MessageDigest md = SUPPORTED_HASHES.get(hash.toLowerCase());
570d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (md == null)
571d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return null;
572d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
573d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        DataForm extendedInfo = (DataForm) discoverInfo.getExtension(Form.ELEMENT, Form.NAMESPACE);
574d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
575d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 1. Initialize an empty string S ('sb' in this method).
576d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        StringBuilder sb = new StringBuilder(); // Use StringBuilder as we don't
577d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                                                // need thread-safe StringBuffer
578d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
579d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 2. Sort the service discovery identities by category and then by
580d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // type and then by xml:lang
581d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // (if it exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/'
582d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // [NAME]. Note that each slash is included even if the LANG or
583d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // NAME is not included (in accordance with XEP-0030, the category and
584d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // type MUST be included.
585d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        SortedSet<DiscoverInfo.Identity> sortedIdentities = new TreeSet<DiscoverInfo.Identity>();
586d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
587d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        for (Iterator<DiscoverInfo.Identity> it = discoverInfo.getIdentities(); it.hasNext();)
588d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sortedIdentities.add(it.next());
589d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
590d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 3. For each identity, append the 'category/type/lang/name' to S,
591d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // followed by the '<' character.
592d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        for (Iterator<DiscoverInfo.Identity> it = sortedIdentities.iterator(); it.hasNext();) {
593d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            DiscoverInfo.Identity identity = it.next();
594d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append(identity.getCategory());
595d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append("/");
596d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append(identity.getType());
597d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append("/");
598d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append(identity.getLanguage() == null ? "" : identity.getLanguage());
599d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append("/");
600d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append(identity.getName() == null ? "" : identity.getName());
601d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append("<");
602d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
603d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
604d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 4. Sort the supported service discovery features.
605d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        SortedSet<String> features = new TreeSet<String>();
606d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        for (Iterator<Feature> it = discoverInfo.getFeatures(); it.hasNext();)
607d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            features.add(it.next().getVar());
608d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
609d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 5. For each feature, append the feature to S, followed by the '<'
610d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // character
611d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        for (String f : features) {
612d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append(f);
613d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append("<");
614d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
615d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
616d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // only use the data form for calculation is it has a hidden FORM_TYPE
617d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // field
618d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // see XEP-0115 5.4 step 3.6
619d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        if (extendedInfo != null && extendedInfo.hasHiddenFormTypeField()) {
620d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            synchronized (extendedInfo) {
621d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // 6. If the service discovery information response includes
622d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
623d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // by the XML character data of the <value/> element).
624d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                SortedSet<FormField> fs = new TreeSet<FormField>(new Comparator<FormField>() {
625d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    public int compare(FormField f1, FormField f2) {
626d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        return f1.getVariable().compareTo(f2.getVariable());
627d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    }
628d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                });
629d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
630d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                FormField ft = null;
631d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
632d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                for (Iterator<FormField> i = extendedInfo.getFields(); i.hasNext();) {
633d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    FormField f = i.next();
634d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    if (!f.getVariable().equals("FORM_TYPE")) {
635d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        fs.add(f);
636d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    } else {
637d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                        ft = f;
638d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    }
639d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
640d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
641d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // Add FORM_TYPE values
642d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                if (ft != null) {
643d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    formFieldValuesToCaps(ft.getValues(), sb);
644d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
645d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
646d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // 7. 3. For each field other than FORM_TYPE:
647d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // 1. Append the value of the "var" attribute, followed by the
648d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // '<' character.
649d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // 2. Sort values by the XML character data of the <value/>
650d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // element.
651d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // 3. For each <value/> element, append the XML character data,
652d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                // followed by the '<' character.
653d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                for (FormField f : fs) {
654d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    sb.append(f.getVariable());
655d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    sb.append("<");
656d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                    formFieldValuesToCaps(f.getValues(), sb);
657d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen                }
658d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            }
659d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
660d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 8. Ensure that S is encoded according to the UTF-8 encoding (RFC
661d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 3269).
662d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 9. Compute the verification string by hashing S using the algorithm
663d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // specified in the 'hash' attribute (e.g., SHA-1 as defined in RFC
664d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // 3174).
665d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // The hashed data MUST be generated with binary output and
666d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // encoded using Base64 as specified in Section 4 of RFC 4648
667d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // (note: the Base64 output MUST NOT include whitespace and MUST set
668d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        // padding bits to zero).
669d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        byte[] digest = md.digest(sb.toString().getBytes());
670d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        return Base64.encodeBytes(digest);
671d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
672d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
673d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    private static void formFieldValuesToCaps(Iterator<String> i, StringBuilder sb) {
674d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        SortedSet<String> fvs = new TreeSet<String>();
675d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        while (i.hasNext()) {
676d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            fvs.add(i.next());
677d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
678d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        for (String fv : fvs) {
679d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append(fv);
680d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            sb.append("<");
681d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
682d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
683d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
684d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    public static class NodeVerHash {
685d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        private String node;
686d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        private String hash;
687d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        private String ver;
688d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        private String nodeVer;
689d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
690d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        NodeVerHash(String node, String ver, String hash) {
691d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            this.node = node;
692d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            this.ver = ver;
693d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            this.hash = hash;
694d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            nodeVer = node + "#" + ver;
695d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
696d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
697d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        public String getNodeVer() {
698d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return nodeVer;
699d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
700d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
701d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        public String getNode() {
702d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return node;
703d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
704d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
705d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        public String getHash() {
706d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return hash;
707d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
708d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen
709d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        public String getVer() {
710d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen            return ver;
711d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen        }
712d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen    }
713d7955ce24d294fb2014c59d11fca184471056f44Shuyi Chen}
714