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