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