001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smack.provider;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.jivesoftware.smack.SmackConfiguration;
026import org.jivesoftware.smack.packet.ExtensionElement;
027import org.jivesoftware.smack.packet.IQ;
028import org.jivesoftware.smack.util.StringUtils;
029
030import org.jxmpp.util.XmppStringUtils;
031
032/**
033 * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
034 * providers exist:<ul>
035 *      <li>IQProvider -- parses IQ requests into Java objects.
036 *      <li>PacketExtension -- parses XML sub-documents attached to packets into
037 *          PacketExtension instances.</ul>
038 *
039 * <b>IQProvider</b><p>
040 *
041 * By default, Smack only knows how to process IQ packets with sub-packets that
042 * are in a few namespaces such as:<ul>
043 *      <li>jabber:iq:auth
044 *      <li>jabber:iq:roster
045 *      <li>jabber:iq:register</ul>
046 *
047 * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
048 * mechanism is provided. IQ providers are registered programmatically or by creating a
049 * providers file. The file is an XML
050 * document that contains one or more iqProvider entries, as in the following example:
051 *
052 * <pre>
053 * &lt;?xml version="1.0"?&gt;
054 * &lt;smackProviders&gt;
055 *     &lt;iqProvider&gt;
056 *         &lt;elementName&gt;query&lt;/elementName&gt;
057 *         &lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
058 *         &lt;className&gt;org.jivesoftware.smack.packet.Time&lt;/className&gt;
059 *     &lt;/iqProvider&gt;
060 * &lt;/smackProviders&gt;</pre>
061 *
062 * Each IQ provider is associated with an element name and a namespace. If multiple provider
063 * entries attempt to register to handle the same namespace, the first entry loaded from the
064 * classpath will take precedence. The IQ provider class can either implement the IQProvider
065 * interface, or extend the IQ class. In the former case, each IQProvider is responsible for
066 * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection
067 * is used to try to automatically set properties of the IQ instance using the values found
068 * in the IQ stanza(/packet) XML. For example, an XMPP time stanza(/packet) resembles the following:
069 * <pre>
070 * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
071 *     &lt;query xmlns='jabber:iq:time'&gt;
072 *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
073 *         &lt;tz&gt;MDT&lt;/tz&gt;
074 *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
075 *     &lt;/query&gt;
076 * &lt;/iq&gt;</pre>
077 *
078 * In order for this stanza(/packet) to be automatically mapped to the Time object listed in the
079 * providers file above, it must have the methods setUtc(String), setTz(String), and
080 * setDisplay(String). The introspection service will automatically try to convert the String
081 * value from the XML into a boolean, int, long, float, double, or Class depending on the
082 * type the IQ instance expects.<p>
083 *
084 * A pluggable system for stanza(/packet) extensions, child elements in a custom namespace for
085 * message and presence packets, also exists. Each extension provider
086 * is registered with a name space in the smack.providers file as in the following example:
087 *
088 * <pre>
089 * &lt;?xml version="1.0"?&gt;
090 * &lt;smackProviders&gt;
091 *     &lt;extensionProvider&gt;
092 *         &lt;elementName&gt;x&lt;/elementName&gt;
093 *         &lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
094 *         &lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt;/className&gt;
095 *     &lt;/extensionProvider&gt;
096 * &lt;/smackProviders&gt;</pre>
097 *
098 * If multiple provider entries attempt to register to handle the same element name and namespace,
099 * the first entry loaded from the classpath will take precedence. Whenever a stanza(/packet) extension
100 * is found in a packet, parsing will be passed to the correct provider. Each provider
101 * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In
102 * the former case, each extension provider is responsible for parsing the raw XML stream to
103 * construct an object. In the latter case, bean introspection is used to try to automatically
104 * set the properties of th class using the values in the stanza(/packet) extension sub-element. When an
105 * extension provider is not registered for an element name and namespace combination, Smack will
106 * store all top-level elements of the sub-packet in DefaultPacketExtension object and then
107 * attach it to the packet.<p>
108 *
109 * @author Matt Tucker
110 */
111public final class ProviderManager {
112
113    private static final Map<String, ExtensionElementProvider<ExtensionElement>> extensionProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>();
114    private static final Map<String, IQProvider<IQ>> iqProviders = new ConcurrentHashMap<String, IQProvider<IQ>>();
115    private static final Map<String, ExtensionElementProvider<ExtensionElement>> streamFeatureProviders = new ConcurrentHashMap<String, ExtensionElementProvider<ExtensionElement>>();
116
117    static {
118        // Ensure that Smack is initialized by calling getVersion, so that user
119        // registered providers do not get overwritten by a following Smack
120        // initialization. This guarantees that Smack is initialized before a
121        // new provider is registered
122        SmackConfiguration.getVersion();
123    }
124
125    @SuppressWarnings("unchecked")
126    public static void addLoader(ProviderLoader loader) {
127        if (loader.getIQProviderInfo() != null) {
128            for (IQProviderInfo info : loader.getIQProviderInfo()) {
129                addIQProvider(info.getElementName(), info.getNamespace(), info.getProvider());
130            }
131        }
132
133        if (loader.getExtensionProviderInfo() != null) {
134            for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) {
135                addExtensionProvider(info.getElementName(), info.getNamespace(), info.getProvider());
136            }
137        }
138
139        if (loader.getStreamFeatureProviderInfo() != null) {
140            for (StreamFeatureProviderInfo info : loader.getStreamFeatureProviderInfo()) {
141                addStreamFeatureProvider(info.getElementName(), info.getNamespace(),
142                                (ExtensionElementProvider<ExtensionElement>) info.getProvider());
143            }
144        }
145    }
146
147    /**
148     * Returns the IQ provider registered to the specified XML element name and namespace.
149     * For example, if a provider was registered to the element name "query" and the
150     * namespace "jabber:iq:time", then the following stanza(/packet) would trigger the provider:
151     *
152     * <pre>
153     * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
154     *     &lt;query xmlns='jabber:iq:time'&gt;
155     *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
156     *         &lt;tz&gt;MDT&lt;/tz&gt;
157     *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
158     *     &lt;/query&gt;
159     * &lt;/iq&gt;</pre>
160     *
161     * <p>Note: this method is generally only called by the internal Smack classes.
162     *
163     * @param elementName the XML element name.
164     * @param namespace the XML namespace.
165     * @return the IQ provider.
166     */
167    public static IQProvider<IQ> getIQProvider(String elementName, String namespace) {
168        String key = getKey(elementName, namespace);
169        return iqProviders.get(key);
170    }
171
172    /**
173     * Returns an unmodifiable collection of all IQProvider instances. Each object
174     * in the collection will either be an IQProvider instance, or a Class object
175     * that implements the IQProvider interface.
176     *
177     * @return all IQProvider instances.
178     */
179    public static List<IQProvider<IQ>> getIQProviders() {
180        List<IQProvider<IQ>> providers = new ArrayList<>(iqProviders.size());
181        providers.addAll(iqProviders.values());
182        return providers;
183    }
184
185    /**
186     * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ)
187     * with the specified element name and name space. The provider will override any providers
188     * loaded through the classpath.
189     *
190     * @param elementName the XML element name.
191     * @param namespace the XML namespace.
192     * @param provider the IQ provider.
193     */
194    @SuppressWarnings("unchecked")
195    public static void addIQProvider(String elementName, String namespace,
196            Object provider)
197    {
198        validate(elementName, namespace);
199        // First remove existing providers
200        String key = removeIQProvider(elementName, namespace);
201        if (provider instanceof IQProvider) {
202            iqProviders.put(key, (IQProvider<IQ>) provider);
203        } else {
204            throw new IllegalArgumentException("Provider must be an IQProvider");
205        }
206    }
207
208    /**
209     * Removes an IQ provider with the specified element name and namespace. This
210     * method is typically called to cleanup providers that are programmatically added
211     * using the {@link #addIQProvider(String, String, Object) addIQProvider} method.
212     *
213     * @param elementName the XML element name.
214     * @param namespace the XML namespace.
215     * @return the key of the removed IQ Provider
216     */
217    public static String removeIQProvider(String elementName, String namespace) {
218        String key = getKey(elementName, namespace);
219        iqProviders.remove(key);
220        return key;
221    }
222
223    /**
224     * Returns the stanza(/packet) extension provider registered to the specified XML element name
225     * and namespace. For example, if a provider was registered to the element name "x" and the
226     * namespace "jabber:x:event", then the following stanza(/packet) would trigger the provider:
227     *
228     * <pre>
229     * &lt;message to='romeo@montague.net' id='message_1'&gt;
230     *     &lt;body&gt;Art thou not Romeo, and a Montague?&lt;/body&gt;
231     *     &lt;x xmlns='jabber:x:event'&gt;
232     *         &lt;composing/&gt;
233     *     &lt;/x&gt;
234     * &lt;/message&gt;</pre>
235     *
236     * <p>Note: this method is generally only called by the internal Smack classes.
237     *
238     * @param elementName element name associated with extension provider.
239     * @param namespace namespace associated with extension provider.
240     * @return the extension provider.
241     */
242    public static ExtensionElementProvider<ExtensionElement> getExtensionProvider(String elementName, String namespace) {
243        String key = getKey(elementName, namespace);
244        return extensionProviders.get(key);
245    }
246
247    /**
248     * Adds an extension provider with the specified element name and name space. The provider
249     * will override any providers loaded through the classpath. The provider must be either
250     * a PacketExtensionProvider instance, or a Class object of a Javabean.
251     *
252     * @param elementName the XML element name.
253     * @param namespace the XML namespace.
254     * @param provider the extension provider.
255     */
256    @SuppressWarnings("unchecked")
257    public static void addExtensionProvider(String elementName, String namespace,
258            Object provider)
259    {
260        validate(elementName, namespace);
261        // First remove existing providers
262        String key = removeExtensionProvider(elementName, namespace);
263        if (provider instanceof ExtensionElementProvider) {
264            extensionProviders.put(key, (ExtensionElementProvider<ExtensionElement>) provider);
265        } else {
266            throw new IllegalArgumentException("Provider must be a PacketExtensionProvider");
267        }
268    }
269
270    /**
271     * Removes an extension provider with the specified element name and namespace. This
272     * method is typically called to cleanup providers that are programmatically added
273     * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method.
274     *
275     * @param elementName the XML element name.
276     * @param namespace the XML namespace.
277     * @return the key of the removed stanza(/packet) extension provider
278     */
279    public static String removeExtensionProvider(String elementName, String namespace) {
280        String key = getKey(elementName, namespace);
281        extensionProviders.remove(key);
282        return key;
283    }
284
285    /**
286     * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object
287     * in the collection will either be a PacketExtensionProvider instance, or a Class object
288     * that implements the PacketExtensionProvider interface.
289     *
290     * @return all PacketExtensionProvider instances.
291     */
292    public static List<ExtensionElementProvider<ExtensionElement>> getExtensionProviders() {
293        List<ExtensionElementProvider<ExtensionElement>> providers = new ArrayList<>(extensionProviders.size());
294        providers.addAll(extensionProviders.values());
295        return providers;
296    }
297
298    public static ExtensionElementProvider<ExtensionElement> getStreamFeatureProvider(String elementName, String namespace) {
299        String key = getKey(elementName, namespace);
300        return streamFeatureProviders.get(key);
301    }
302
303    public static void addStreamFeatureProvider(String elementName, String namespace, ExtensionElementProvider<ExtensionElement> provider) {
304        validate(elementName, namespace);
305        String key = getKey(elementName, namespace);
306        streamFeatureProviders.put(key, provider);
307    }
308
309    public static void removeStreamFeatureProvider(String elementName, String namespace) {
310        String key = getKey(elementName, namespace);
311        streamFeatureProviders.remove(key);
312    }
313
314    private static String getKey(String elementName, String namespace) {
315        return XmppStringUtils.generateKey(elementName, namespace);
316    }
317
318    private static void validate(String elementName, String namespace) {
319        if (StringUtils.isNullOrEmpty(elementName)) {
320            throw new IllegalArgumentException("elementName must not be null or empty");
321        }
322        if (StringUtils.isNullOrEmpty(namespace)) {
323            throw new IllegalArgumentException("namespace must not be null or empty");
324        }
325    }
326}