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 XML. For example, an XMPP time stanza 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 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 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 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 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 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        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 programmatically 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 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 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 extension 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        validate(elementName, namespace);
259        // First remove existing providers
260        String key = removeExtensionProvider(elementName, namespace);
261        if (provider instanceof ExtensionElementProvider) {
262            extensionProviders.put(key, (ExtensionElementProvider<ExtensionElement>) provider);
263        } else {
264            throw new IllegalArgumentException("Provider must be a PacketExtensionProvider");
265        }
266    }
267
268    /**
269     * Removes an extension provider with the specified element name and namespace. This
270     * method is typically called to cleanup providers that are programmatically added
271     * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method.
272     *
273     * @param elementName the XML element name.
274     * @param namespace the XML namespace.
275     * @return the key of the removed stanza extension provider
276     */
277    public static String removeExtensionProvider(String elementName, String namespace) {
278        String key = getKey(elementName, namespace);
279        extensionProviders.remove(key);
280        return key;
281    }
282
283    /**
284     * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object
285     * in the collection will either be a PacketExtensionProvider instance, or a Class object
286     * that implements the PacketExtensionProvider interface.
287     *
288     * @return all PacketExtensionProvider instances.
289     */
290    public static List<ExtensionElementProvider<ExtensionElement>> getExtensionProviders() {
291        List<ExtensionElementProvider<ExtensionElement>> providers = new ArrayList<>(extensionProviders.size());
292        providers.addAll(extensionProviders.values());
293        return providers;
294    }
295
296    public static ExtensionElementProvider<ExtensionElement> getStreamFeatureProvider(String elementName, String namespace) {
297        String key = getKey(elementName, namespace);
298        return streamFeatureProviders.get(key);
299    }
300
301    public static void addStreamFeatureProvider(String elementName, String namespace, ExtensionElementProvider<ExtensionElement> provider) {
302        validate(elementName, namespace);
303        String key = getKey(elementName, namespace);
304        streamFeatureProviders.put(key, provider);
305    }
306
307    public static void removeStreamFeatureProvider(String elementName, String namespace) {
308        String key = getKey(elementName, namespace);
309        streamFeatureProviders.remove(key);
310    }
311
312    private static String getKey(String elementName, String namespace) {
313        return XmppStringUtils.generateKey(elementName, namespace);
314    }
315
316    private static void validate(String elementName, String namespace) {
317        if (StringUtils.isNullOrEmpty(elementName)) {
318            throw new IllegalArgumentException("elementName must not be null or empty");
319        }
320        if (StringUtils.isNullOrEmpty(namespace)) {
321            throw new IllegalArgumentException("namespace must not be null or empty");
322        }
323    }
324}