SMACK-286 Made ProviderManager much more configurable.

Separated the reading of provider files from the ProviderManager.  Manager now only manages.  Added ability to add collections of providers to the manager via a ProviderLoader, of which there is one default implementation which loads from the default file format.  Now provider files can be programmatically added at any time.  Also updated the configuration abilities so that a provider file can also be set via VM arg, as well as the smack configuration itself. Introduced Java Util Logging as well.

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/branches/smack_3_4_0@13861 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
rcollier 2014-01-16 05:14:39 +00:00
parent 7e3d4186bb
commit f155cb4d07
25 changed files with 1709 additions and 1000 deletions

View File

@ -29,7 +29,7 @@
<property name="version.minor" value="3" />
<property name="version.revision" value="1" />
<property name="version.extra" value="" />
<if>
<equals arg1="${version.extra}" arg2=""/>
<then>
@ -170,54 +170,63 @@
<!-- jar -->
<!-- ======================================================================================= -->
<target name="jar" depends="compile" unless="jar.uptodate" description="Produces smack.jar">
<copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/jul.properties" />
<copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/smack-config.xml" />
<jar destfile="${jar.dest.dir}/smack.jar"
<copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/core.providers" />
<property name="smack.jar.name" value="${jar.dest.dir}/smack.jar" />
<jar destfile="${smack.jar.name}"
basedir="${compile.dir}"
includes="org/jivesoftware/smack/**/*.class, **/smack-config.xml">
includes="org/jivesoftware/smack/**/*.class, **/smack-config.xml, **/core.providers, **/jul.properties">
<zipfileset src="${merge.lib.dir}/xpp.jar"/>
</jar>
<taskdef resource="aQute/bnd/ant/taskdef.properties" classpath="${basedir}/build/build/biz.aQute.bnd.jar"/>
<bndwrap jars="${jar.dest.dir}/smack.jar" output="${jar.dest.dir}/smack.jar" />
<jar file="${jar.dest.dir}/smack.jar" update="true">
<bndwrap jars="${smack.jar.name}" output="${smack.jar.name}" />
<jar file="${smack.jar.name}" update="true">
<manifest>
<attribute name="Bundle-SymbolicName" value="org.igniterealtime.smack" />
</manifest>
</jar>
<copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/smack.providers" />
<jar destfile="${jar.dest.dir}/smackx.jar"
<property name="smackx.jar.name" value="${jar.dest.dir}/smackx.jar" />
<copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/extension.providers" />
<jar destfile="${smackx.jar.name}"
basedir="${compile.dir}"
includes="org/jivesoftware/smackx/**/*.class, **/*.providers"
includes="org/jivesoftware/smackx/**/*.class, **/extension.providers"
excludes="org/jivesoftware/smackx/debugger/*.class">
<manifest>
<attribute name="Class-Path" value="smack.jar" />
</manifest>
<zipfileset src="${merge.lib.dir}/jzlib.jar"/>
</jar>
<bndwrap jars="${jar.dest.dir}/smackx.jar" output="${jar.dest.dir}/smackx.jar" />
<jar file="${jar.dest.dir}/smackx.jar" update="true">
<bndwrap jars="${smackx.jar.name}" output="${smackx.jar.name}" />
<jar file="${smackx.jar.name}" update="true">
<manifest>
<attribute name="Bundle-SymbolicName" value="org.igniterealtime.smack-ext" />
</manifest>
</jar>
<copy todir="${compile.dir}/images">
<property name="debug.jar.name" value="${jar.dest.dir}/smack-debug.jar" />
<copy todir="${compile.dir}/images">
<fileset dir="${basedir}/build/resources/images">
<include name="*.png"/>
</fileset>
</copy>
<jar destfile="${jar.dest.dir}/smackx-debug.jar"
<jar destfile="${debug.jar.name}"
basedir="${compile.dir}"
includes="org/jivesoftware/smackx/debugger/*.class, **/*.png">
<manifest>
<attribute name="Class-Path" value="smack.jar" />
</manifest>
</jar>
<bndwrap jars="${jar.dest.dir}/smackx-debug.jar" output="${jar.dest.dir}/smackx-debug.jar" />
<jar file="${jar.dest.dir}/smackx-debug.jar" update="true">
<bndwrap jars="${debug.jar.name}" output="${debug.jar.name}" />
<jar file="${debug.jar.name}" update="true">
<manifest>
<attribute name="Bundle-SymbolicName" value="org.igniterealtime.smack-ext-debug" />
</manifest>
</jar>
<jar destfile="${jar.dest.dir}/smackx-jingle.jar"
<property name="jingle.jar.name" value="${jar.dest.dir}/smack-jingle.jar" />
<jar destfile="${jingle.jar.name}"
basedir="${compile.dir}/jingle/extension"
includes="org/jivesoftware/smackx/**/*.class">
<manifest>
@ -225,14 +234,14 @@
</manifest>
<zipfileset src="${jingle.extension.merge.lib.dir}/jstun.jar"/>
</jar>
<bndwrap jars="${jar.dest.dir}/smackx-jingle.jar" output="${jar.dest.dir}/smackx-jingle.jar" />
<jar file="${jar.dest.dir}/smackx-jingle.jar" update="true">
<bndwrap jars="${jingle.jar.name}" output="${jingle.jar.name}" />
<jar file="${jingle.jar.name}" update="true">
<manifest>
<attribute name="Bundle-SymbolicName" value="org.igniterealtime.smack-ext-jingle" />
</manifest>
</jar>
<delete file="${compile.dir}/META-INF/smack-config.xml" />
<delete file="${compile.dir}/META-INF/smack.providers" />
<delete file="${compile.dir}/META-INF/*.providers" />
<delete>
<fileset dir="${compile.dir}/images">
<include name="*.png"/>

View File

@ -43,10 +43,14 @@
</else>
</if>
<property name="release.dir" value="${basedir}/target/release/${release.name}" />
<property name="release.samples.dir" value="${release.dir}/samples" />
<property name="release-dev.dir" value="${basedir}/target/release/${release-dev.name}" />
<property name="release-dev.samples.dir" value="${release-dev.name}/samples" />
<!-- create release dirs -->
<mkdir dir="${release.dir}" />
<mkdir dir="${release.samples.dir}" />
<mkdir dir="${release-dev.dir}" />
<mkdir dir="${release-dev.dir.samples}/samples" />
<!-- Copy smack.jar -->
<copy todir="${release.dir}">
<fileset dir="${jar.dest.dir}" includes="smack.jar" />
@ -54,12 +58,20 @@
<fileset dir="${jar.dest.dir}" includes="smackx-debug.jar" />
<fileset dir="${jar.dest.dir}" includes="smackx-jingle.jar" />
</copy>
<copy todir="${release.samples.dir}">
<fileset dir="${basedir}/build/resources/META-INF" includes="sample.providers" />
<fileset dir="${basedir}/build/resources/META-INF" includes="smack-config.xml" />
</copy>
<copy todir="${release-dev.dir}">
<fileset dir="${jar.dest.dir}" includes="smack.jar" />
<fileset dir="${jar.dest.dir}" includes="smackx.jar" />
<fileset dir="${jar.dest.dir}" includes="smackx-debug.jar" />
<fileset dir="${jar.dest.dir}" includes="smackx-jingle.jar" />
</copy>
<copy todir="${release-dev.samples.dir}">
<fileset dir="${basedir}/build/resources/META-INF" includes="sample.providers" />
<fileset dir="${basedir}/build/resources/META-INF" includes="smack-config" />
</copy>
<!-- Copy build dir -->
<copy todir="${release-dev.dir}/build">
<fileset dir="${basedir}/build">
@ -110,7 +122,10 @@
</copy>
<!-- Copy resources -->
<copy todir="${release-dev.dir}/build/resources">
<fileset dir="${basedir}/build/resources" includes="META-INF/smack.providers" />
<fileset dir="${basedir}/build/resources" includes="META-INF/core.providers" />
<fileset dir="${basedir}/build/resources" includes="META-INF/extension.providers" />
<fileset dir="${basedir}/build/resources" includes="META-INF/sample.providers" />
<fileset dir="${basedir}/build/resources" includes="META-INF/jul.properties" />
<fileset dir="${basedir}/build/resources" includes="META-INF/smack-config.xml" />
</copy>
<copy todir="${release-dev.dir}/build/resources/images">

View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!-- Providers file for default Smack extensions -->
<smackProviders>
<!-- Privacy -->
<iqProvider>
<elementName>query</elementName>
<namespace>jabber:iq:privacy</namespace>
<className>org.jivesoftware.smack.provider.PrivacyProvider</className>
</iqProvider>
<!-- Ping (XEP-199) Manager -->
<iqProvider>
<elementName>ping</elementName>
<namespace>urn:xmpp:ping</namespace>
<className>org.jivesoftware.smack.ping.provider.PingProvider</className>
</iqProvider>
</smackProviders>

View File

@ -0,0 +1,3 @@
# Java Util Logging configuration for Smack.
handlers = java.util.logging.ConsoleHandler
.level = WARNING

View File

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<!-- Sample providers file -->
<smackProviders>
<iqProvider>
<elementName>element</elementName>
<namespace>ns</namespace>
<className>com.myco.MyIQProvider</className>
</iqProvider>
<extensionProvider>
<elementName>elem</elementName>
<namespace>http://jabber.org/protocol/whoknows</namespace>
<className>com.myco.MyExtProvider</className>
</extensionProvider>
</smackProviders>

View File

@ -22,9 +22,13 @@
<!-- Classes that will be loaded when Smack starts -->
<startupClasses>
<className>org.jivesoftware.smackx.ServiceDiscoveryManager</className>
<className>org.jivesoftware.smack.LoggingInitializer</className>
<className>org.jivesoftware.smack.provider.CoreInitializer</className>
<className>org.jivesoftware.smack.provider.VmArgInitializer</className>
<className>org.jivesoftware.smack.PrivacyListManager</className>
<className>org.jivesoftware.smack.keepalive.KeepAliveManager</className>
<className>org.jivesoftware.smackx.provider.ExtensionInitializer</className>
<className>org.jivesoftware.smackx.ServiceDiscoveryManager</className>
<className>org.jivesoftware.smackx.XHTMLManager</className>
<className>org.jivesoftware.smackx.muc.MultiUserChat</className>
<className>org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager</className>

View File

@ -40,6 +40,37 @@ over which features applications require:
</ul>
<p class="subheader">Configuration</p>
Smack has an initialization process that involves 2 phases.
<ul>
<li>Initializing system properties - Initializing all the system properties accessible through the class
<b>SmackConfiguration</b>. These properties are retrieve by the <i>getXXX</i> methods on that class.
<li>Initializing startup classes - Initializing any classes meant to be active at startup by instantiating
the class, and then calling the <i>initialize</i> method on that class if it extends <b>SmackInitializer</b>.
If it does not extend this interface, then initialization will have to take place in a static block of code
which is automatically executed when the class is loaded.
</ul>
<p>
Initialization is accomplished via a configuration file. By default, Smack will load the one embedded in
the Smack jar at <i>META-INF/smack-config.xml</i>. This particular configuration contains all the default
property values as well as a list of initializer classes to load. All manager type classes are contained
in this list of initializers. If your application does not use all the features provided by Smack via the
aforementioned managers, you may want to 'turn them off' by providing a custom config file that does not
include that feature.
<p>
If you want to change the configuration file used, you have two options:
<ul>
<li>Programmatically - Call the <i>setConfigFileUrl</i> method of <b>SmackConfiguration</b> with the location
of a new config file.
<pre>SmackConfiguration.setConfigFileUrl("classpath:test/smack-config.xml", null)</pre>
<li>VM Argument - Set the VM argument <i>smack.config.file</i> to the location of a new config file.
<pre>-Dsmack.config.file=file:///c:/com/myco/provider/myco_custom_config.xml</pre>
</ul>
<p>
Please note, there is a copy of the <b>smack-config.xml</b> in the <i>samples</i> directory of the deployment
archive file (zip or tar).
<p class="subheader">
Establishing a Connection
</p>

View File

@ -15,44 +15,68 @@ Provider Architecture: Packet Extensions and Custom IQ's
</div>
<p>
<p class="subheader">Introduction</p>
The Smack provider architecture is a system for plugging in
custom XML parsing of packet extensions and IQ packets. The
standard <a href="extensions/index.html">Smack Extensions</a>
are built using the provider architecture. Two types of
providers exist:<ul>
are built using the provider architecture. There are two types of
providers:<ul>
<li><tt>IQProvider</tt> -- parses IQ requests into Java objects.
<li><tt>PacketExtension</tt> -- parses XML sub-documents attached to
<li><tt>Extension Provider</tt> -- parses XML sub-documents attached to
packets into PacketExtension instances.</ul>
<p class="subheader">IQProvider</p>
By default, Smack only knows how to process IQ packets with sub-packets that
are in a few namespaces such as:<ul>
By default, Smack only knows how to process a few standard packets and sub-packets
that are in a few namespaces such as:<ul>
<li>jabber:iq:auth
<li>jabber:iq:roster
<li>jabber:iq:register</ul>
Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
mechanism is provided. IQ providers are registered programatically or by creating a
smack.providers file in the META-INF directory of your JAR file. The file is an XML
document that contains one or more iqProvider entries, as in the following example:
There are many more IQ types and extensions that are part of XMPP standards, and of
course an endless number that can be added as custom extensions. To support this, an
extensible parsing mechanism is provided via Smack and user build providers.
<p>
Whenever a packet extension is found in a packet, parsing will
be passed to the correct provider. Each provider can either implement the
PacketExtensionProvider interface or be a standard Java Bean. In the
former case, each extension provider is responsible for parsing the raw
XML stream, via the <a href="http://www.xmlpull.org/">XML Pull Parser</a>, to contruct an object. In the latter case, bean introspection
is used to try to automatically set the properties of the class using
the values in the packet extension sub-element.
<p>
When no extension provider is registered for an element name and
namespace combination, Smack will store all top-level elements of the
sub-packet in the DefaultPacketExtension object and then attach it to the packet.
<p>
Management of these providers is accomplished via the <a href="">ProviderManager</a>
class. There are multiple ways to add providers to the manager.<ul>
<li>Call addXXProvider methods - You can call the appropriate add methods directly.
<pre>
ProviderManager.getInstance().addIQProvider("element", "namespace", new MyIQProvider());
ProviderManager.getInstance().addExtensionProvider("element", "namespace", new MyExtProvider());
</pre>
<li>Add a loader - You can add a ProviderLoader which will inject a means of loading multiple
providers (both types) into the manager. This is the mechanism used by Smack to load from the
Smack specific file format (via ProviderFileLoader). Implementers can provide the means to load
providers from any source they wish, or simply reuse the ProviderFileLoader to load from
their own provider files.
<pre>
ProviderManager.getInstance().addLoader(new ProviderFileLoader(FileUtils.getStreamForUrl("classpath:com/myco/provider/myco_custom.providers", null)));
</pre>
<li>VM Argument - You can add a provider file via the VM argument <i>smack.provider.file</i>.
This will load the file at the specified URL during startup when Smack initializes.
This also assumes the default configuration, since it requires that the <b>VmArgInitializer</b> was
part of the startup configuration.
<pre>
&lt;?xml version="1.0"?&gt;
&lt;smackProviders&gt;
&lt;iqProvider&gt;
&lt;elementName&gt;query&lt;/elementName&gt;
&lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
&lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
&lt;/iqProvider&gt;
&lt;/smackProviders&gt;</pre>
-Dsmack.provider.file=classpath:com/myco/provider/myco_custom.providers
or
-Dsmack.provider.file=file:///c:/myco/provider/myco_custom.providers
</pre>
</ul>
Each IQ provider is associated with an element name and a namespace. In the
example above, the element name is <tt>query</tt> and the namespace is
<tt>abber:iq:time</tt>. If multiple provider entries attempt to register to
handle the same namespace, the first entry loaded from the classpath will
take precedence. <p>
<p class="subheader">IQ Providers</p>
The IQ provider class can either implement the IQProvider
interface, or extend the IQ class. In the former case, each IQProvider is
@ -61,54 +85,191 @@ the latter case, bean introspection is used to try to automatically set
properties of the IQ instance using the values found in the IQ packet XML.
For example, an XMPP time packet resembles the following:
<p>
<i>Introspection</i>
<p>
<u>Time Packet</u>
<pre>
&lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
&lt;query xmlns='jabber:iq:time'&gt;
&lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
&lt;tz&gt;MDT&lt;/tz&gt;
&lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
&lt;/query&gt;
&lt;/iq&gt;</pre>
&lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
&lt;query xmlns='jabber:iq:time'&gt;
&lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
&lt;tz&gt;MDT&lt;/tz&gt;
&lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
&lt;/query&gt;
&lt;/iq&gt;
</pre>
In order for this packet to be automatically mapped to the Time object listed in the
providers file above, it must have the methods setUtc(String), setTz(String), and
setDisplay(String). The introspection service will automatically try to convert the String
<p>
<u>Time IQ Class</u>
<pre>
class Time extends IQ {
private Date utc;
private TimeZone timeZone;
private String display;
@Override
public String getChildElementXML() {
return null;
}
public void setUtc(String utcString) {
try {
utc = StringUtils.parseDate(utcString);
} catch (ParseException e) {
}
}
public void setTimeZone(String zone) {
timeZone = TimeZone.getTimeZone(zone);
}
public void setDisplay(String timeDisplay) {
display = timeDisplay;
}
}
</pre>
The introspection service will automatically try to convert the String
value from the XML into a boolean, int, long, float, double, or Class depending on the
type the IQ instance expects.<p>
type the IQ instance expects.
<p class="subheader">PacketExtensionProvider</p>
<p>
<i>IQProvider Implementation</i>
<p>
<u>Disco Items Packet</u>
<pre>
&lt;iq type='result' from='shakespeare.lit' to='romeo@montague.net/orchard' id='items1'&gt;
&lt;query xmlns='http://jabber.org/protocol/disco#items'&gt;
&lt;item jid='people.shakespeare.lit' name='Directory of Characters'/&gt;
&lt;item jid='plays.shakespeare.lit' name='Play-Specific Chatrooms'/&gt;
&lt;item jid='mim.shakespeare.lit' name='Gateway to Marlowe IM'/&gt;
&lt;item jid='words.shakespeare.lit' name='Shakespearean Lexicon'/&gt;
&lt;item jid='globe.shakespeare.lit' name='Calendar of Performances'/&gt;
&lt;item jid='headlines.shakespeare.lit' name='Latest Shakespearean News'/&gt;
&lt;item jid='catalog.shakespeare.lit' name='Buy Shakespeare Stuff!'/&gt;
&lt;item jid='en2fr.shakespeare.lit' name='French Translation Service'/&gt;
&lt;/query&gt;
&lt;/iq&gt;
</pre>
Packet extension providers provide a pluggable system for
packet extensions, which are child elements in a custom namespace
of IQ, message and presence packets.
Each extension provider is registered with an element name and namespace
in the smack.providers file as in the following example:
<p>
<u>Disco Items IQProvider</u>
<pre>
public class DiscoverItemsProvider implements IQProvider {
public IQ parseIQ(XmlPullParser parser) throws Exception {
DiscoverItems discoverItems = new DiscoverItems();
boolean done = false;
DiscoverItems.Item item;
String jid = "";
String name = "";
String action = "";
String node = "";
discoverItems.setNode(parser.getAttributeValue("", "node"));
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG && "item".equals(parser.getName())) {
// Initialize the variables from the parsed XML
jid = parser.getAttributeValue("", "jid");
name = parser.getAttributeValue("", "name");
node = parser.getAttributeValue("", "node");
action = parser.getAttributeValue("", "action");
}
else if (eventType == XmlPullParser.END_TAG && "item".equals(parser.getName())) {
// Create a new Item and add it to DiscoverItems.
item = new DiscoverItems.Item(jid);
item.setName(name);
item.setNode(node);
item.setAction(action);
discoverItems.addItem(item);
}
else if (eventType == XmlPullParser.END_TAG && "query".equals(parser.getName())) {
done = true;
}
}
return discoverItems;
}
}
</pre>
<p class="subheader">Extension Providers</p>
Packet extension providers are responsible for parsing packet extensions, which are
child elements in a custom namespace of IQ, message and presence packets.
<p>
<u>Pubsub Subscription Packet</u>
<pre>
&lt;?xml version="1.0"?&gt;
&lt;smackProviders&gt;
&lt;iq type='result' from='pubsub.shakespeare.lit' to='francisco@denmark.lit/barracks' id='sub1'&gt;
&lt;pubsub xmlns='http://jabber.org/protocol/pubsub'&gt;
&lt;subscription node='princely_musings' jid='francisco@denmark.lit' subscription='unconfigured'&gt;
&lt;subscribe-options&gt;
&lt;required/&gt;
&lt;/subscribe-options&gt;
&lt;/subscription&gt;
&lt;/pubsub&gt;
&lt;/iq&gt;
</pre>
<p>
<u>Subscription PacketExtensionProvider Implementation</u>
<pre>
public class SubscriptionProvider implements PacketExtensionProvider {
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
String jid = parser.getAttributeValue(null, "jid");
String nodeId = parser.getAttributeValue(null, "node");
String subId = parser.getAttributeValue(null, "subid");
String state = parser.getAttributeValue(null, "subscription");
boolean isRequired = false;
int tag = parser.next();
if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("subscribe-options")) {
tag = parser.next();
if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("required"))
isRequired = true;
while (parser.next() != XmlPullParser.END_TAG && parser.getName() != "subscribe-options");
}
while (parser.getEventType() != XmlPullParser.END_TAG) parser.next();
return new Subscription(jid, nodeId, subId, (state == null ? null : Subscription.State.valueOf(state)), isRequired);
}
}
</pre>
<p class="subheader">Provider file format</p>
This is the format for a provider file which can be parsed by the <b>ProviderFileLoader</b>.
<pre>
&lt;?xml version="1.0"?&gt;
&lt;smackProviders&gt;
&lt;iqProvider&gt;
&lt;elementName&gt;query&lt;/elementName&gt;
&lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
&lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
&lt;/iqProvider&gt;
&lt;iqProvider&gt;
&lt;elementName&gt;query&lt;/elementName&gt;
&lt;namespace&gt;http://jabber.org/protocol/disco#items&lt;/namespace&gt;
&lt;className&gt;org.jivesoftware.smackx.provider.DiscoverItemsProvider&lt/className&gt;
&lt;/iqProvider&gt;
&lt;extensionProvider&gt;
&lt;elementName&gt;x&lt;/elementName&gt;
&lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
&lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt/className&gt;
&lt;elementName&gt;subscription&lt;/elementName&gt;
&lt;namespace&gt;http://jabber.org/protocol/pubsub&lt;/namespace&gt;
&lt;className&gt;org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider&lt/className&gt;
&lt;/extensionProvider&gt;
&lt;/smackProviders&gt;</pre>
&lt;/smackProviders&gt;</pre>
If multiple provider entries attempt to register to handle the same element
name and namespace, the first entry loaded from the classpath will take
precedence.<p>
Each provider is associated with an element name and a namespace. If multiple provider entries attempt to register to
handle the same namespace, the last entry added to the <b>ProviderManager</b> will overwrite any other that was loaded
before it.
<p>
Whenever a packet extension is found in a packet, parsing will
be passed to the correct provider. Each provider can either implement the
PacketExtensionProvider interface or be a standard Java Bean. In the
former case, each extension provider is responsible for parsing the raw
XML stream to contruct an object. In the latter case, bean introspection
is used to try to automatically set the properties of the class using
the values in the packet extension sub-element.<p>
When an extension provider is not registered for an element name and
namespace combination, Smack will store all top-level elements of the
sub-packet in DefaultPacketExtension object and then attach it to the packet.
<br clear="all" /><br><br>

View File

@ -0,0 +1,28 @@
package org.jivesoftware.smack;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.jivesoftware.smack.util.FileUtils;
/**
* Initializes the Java logging system.
*
* @author Robin Collier
*
*/
public class LoggingInitializer implements SmackInitializer {
private static Logger log = Logger.getLogger(LoggingInitializer.class.getName());
@Override
public void initialize() {
try {
LogManager.getLogManager().readConfiguration(FileUtils.getStreamForUrl("classpath:META-INF/jul.properties", null));
}
catch (Exception e) {
log .log(Level.WARNING, "Could not initialize Java Logging from default file.", e);
}
}
}

View File

@ -20,16 +20,18 @@
package org.jivesoftware.smack;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.parsing.ExceptionThrowingCallback;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.util.FileUtils;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
@ -49,16 +51,22 @@ import org.xmlpull.v1.XmlPullParser;
* @author Gaston Dombiak
*/
public final class SmackConfiguration {
private static final String SMACK_VERSION = "3.3.1";
private static final String DEFAULT_CONFIG_FILE = "classpath:META-INF/smack-config.xml";
private static final Logger log = Logger.getLogger(SmackConfiguration.class.getName());
private static InputStream configFileStream;
private static int packetReplyTimeout = 5000;
private static int keepAliveInterval = 30000;
private static Vector<String> defaultMechs = new Vector<String>();
private static List<String> defaultMechs = new ArrayList<String>();
private static boolean localSocks5ProxyEnabled = true;
private static int localSocks5ProxyPort = 7777;
private static int packetCollectorSize = 5000;
private static boolean initialized = false;
/**
* The default parsing exception callback is {@link ExceptionThrowingCallback} which will
@ -81,72 +89,39 @@ public final class SmackConfiguration {
* 1) a set of classes will be loaded in order to execute their static init block
* 2) retrieve and set the current Smack release
*/
static {
/**
* Sets the location of the config file on the classpath. Only required if changing from the default location of <i>classpath:META-INF/smack-config.xml</i>.
*
* <p>
* This method must be called before accessing any other class in Smack.
*
* @param configFileUrl The location of the config file.
* @param loader The classloader to use if the URL has a protocol of <b>classpath</> and the file is not located on the default classpath.
* This can be set to null to use defaults and is ignored for all other protocols.
* @throws IllegalArgumentException If the config URL is invalid in that it cannot open an {@link InputStream}
*/
public static void setConfigFileUrl(String configFileUrl, ClassLoader loader) {
try {
// Get an array of class loaders to try loading the providers files from.
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
Enumeration<URL> configEnum = classLoader.getResources("META-INF/smack-config.xml");
while (configEnum.hasMoreElements()) {
URL url = configEnum.nextElement();
InputStream systemStream = null;
try {
systemStream = url.openStream();
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(systemStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("className")) {
// Attempt to load the class so that the class can get initialized
parseClassToLoad(parser);
}
else if (parser.getName().equals("packetReplyTimeout")) {
packetReplyTimeout = parseIntProperty(parser, packetReplyTimeout);
}
else if (parser.getName().equals("keepAliveInterval")) {
keepAliveInterval = parseIntProperty(parser, keepAliveInterval);
}
else if (parser.getName().equals("mechName")) {
defaultMechs.add(parser.nextText());
}
else if (parser.getName().equals("localSocks5ProxyEnabled")) {
localSocks5ProxyEnabled = Boolean.parseBoolean(parser.nextText());
}
else if (parser.getName().equals("localSocks5ProxyPort")) {
localSocks5ProxyPort = parseIntProperty(parser, localSocks5ProxyPort);
}
else if (parser.getName().equals("packetCollectorSize")) {
packetCollectorSize = parseIntProperty(parser, packetCollectorSize);
}
else if (parser.getName().equals("autoEnableEntityCaps")) {
autoEnableEntityCaps = Boolean.parseBoolean(parser.nextText());
}
}
eventType = parser.next();
}
while (eventType != XmlPullParser.END_DOCUMENT);
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
systemStream.close();
}
catch (Exception e) {
// Ignore.
}
}
}
}
}
configFileStream = FileUtils.getStreamForUrl(configFileUrl, loader);
}
catch (Exception e) {
e.printStackTrace();
}
throw new IllegalArgumentException("Failed to create input stream from specified file URL ["+ configFileUrl + "]", e);
}
initialize();
}
/**
* Sets the {@link InputStream} representing the smack configuration file. This can be used to override the default with something that is not on the classpath.
* <p>
* This method must be called before accessing any other class in Smack.
* @param configFile
*/
public static void setConfigFileStream(InputStream configFile) {
configFileStream = configFile;
initialize();
}
/**
* Returns the Smack version information, eg "1.3.0".
*
@ -163,6 +138,8 @@ public final class SmackConfiguration {
* @return the milliseconds to wait for a response from the server
*/
public static int getPacketReplyTimeout() {
initialize();
// The timeout value must be greater than 0 otherwise we will answer the default value
if (packetReplyTimeout <= 0) {
packetReplyTimeout = 5000;
@ -177,6 +154,8 @@ public final class SmackConfiguration {
* @param timeout the milliseconds to wait for a response from the server
*/
public static void setPacketReplyTimeout(int timeout) {
initialize();
if (timeout <= 0) {
throw new IllegalArgumentException();
}
@ -192,6 +171,7 @@ public final class SmackConfiguration {
* no keep-alive should be sent.
*/
public static int getKeepAliveInterval() {
initialize();
return keepAliveInterval;
}
@ -204,6 +184,7 @@ public final class SmackConfiguration {
* or -1 if no keep-alive should be sent.
*/
public static void setKeepAliveInterval(int interval) {
initialize();
keepAliveInterval = interval;
}
@ -214,6 +195,7 @@ public final class SmackConfiguration {
* @return The number of packets to queue before deleting older packets.
*/
public static int getPacketCollectorSize() {
initialize();
return packetCollectorSize;
}
@ -224,6 +206,7 @@ public final class SmackConfiguration {
* @param The number of packets to queue before deleting older packets.
*/
public static void setPacketCollectorSize(int collectorSize) {
initialize();
packetCollectorSize = collectorSize;
}
@ -233,6 +216,8 @@ public final class SmackConfiguration {
* @param mech the SASL mechanism to be added
*/
public static void addSaslMech(String mech) {
initialize();
if(! defaultMechs.contains(mech) ) {
defaultMechs.add(mech);
}
@ -244,6 +229,8 @@ public final class SmackConfiguration {
* @param mechs the Collection of SASL mechanisms to be added
*/
public static void addSaslMechs(Collection<String> mechs) {
initialize();
for(String mech : mechs) {
addSaslMech(mech);
}
@ -255,9 +242,8 @@ public final class SmackConfiguration {
* @param mech the SASL mechanism to be removed
*/
public static void removeSaslMech(String mech) {
if( defaultMechs.contains(mech) ) {
defaultMechs.remove(mech);
}
initialize();
defaultMechs.remove(mech);
}
/**
@ -266,9 +252,8 @@ public final class SmackConfiguration {
* @param mechs the Collection of SASL mechanisms to be removed
*/
public static void removeSaslMechs(Collection<String> mechs) {
for(String mech : mechs) {
removeSaslMech(mech);
}
initialize();
defaultMechs.removeAll(mechs);
}
/**
@ -279,7 +264,7 @@ public final class SmackConfiguration {
* @return the list of SASL mechanisms to be used.
*/
public static List<String> getSaslMechs() {
return defaultMechs;
return Collections.unmodifiableList(defaultMechs);
}
/**
@ -288,6 +273,7 @@ public final class SmackConfiguration {
* @return if the local Socks5 proxy should be started
*/
public static boolean isLocalSocks5ProxyEnabled() {
initialize();
return localSocks5ProxyEnabled;
}
@ -297,6 +283,7 @@ public final class SmackConfiguration {
* @param localSocks5ProxyEnabled if the local Socks5 proxy should be started
*/
public static void setLocalSocks5ProxyEnabled(boolean localSocks5ProxyEnabled) {
initialize();
SmackConfiguration.localSocks5ProxyEnabled = localSocks5ProxyEnabled;
}
@ -306,6 +293,7 @@ public final class SmackConfiguration {
* @return the port of the local Socks5 proxy
*/
public static int getLocalSocks5ProxyPort() {
initialize();
return localSocks5ProxyPort;
}
@ -316,6 +304,7 @@ public final class SmackConfiguration {
* @param localSocks5ProxyPort the port of the local Socks5 proxy to set
*/
public static void setLocalSocks5ProxyPort(int localSocks5ProxyPort) {
initialize();
SmackConfiguration.localSocks5ProxyPort = localSocks5ProxyPort;
}
@ -324,6 +313,7 @@ public final class SmackConfiguration {
* @return
*/
public static boolean autoEnableEntityCaps() {
initialize();
return autoEnableEntityCaps;
}
@ -333,6 +323,7 @@ public final class SmackConfiguration {
* @param true if Entity Caps should be auto enabled, false if not
*/
public static void setAutoEnableEntityCaps(boolean b) {
initialize();
autoEnableEntityCaps = b;
}
@ -343,6 +334,7 @@ public final class SmackConfiguration {
* @see ParsingExceptionCallback
*/
public static void setDefaultParsingExceptionCallback(ParsingExceptionCallback callback) {
initialize();
defaultCallback = callback;
}
@ -353,6 +345,7 @@ public final class SmackConfiguration {
* @see ParsingExceptionCallback
*/
public static ParsingExceptionCallback getDefaultParsingExceptionCallback() {
initialize();
return defaultCallback;
}
@ -360,11 +353,15 @@ public final class SmackConfiguration {
String className = parser.nextText();
// Attempt to load the class so that the class can get initialized
try {
Class.forName(className);
Class<?> initClass = Class.forName(className);
if (SmackInitializer.class.isAssignableFrom(initClass)) {
SmackInitializer initializer = (SmackInitializer) initClass.newInstance();
initializer.initialize();
}
}
catch (ClassNotFoundException cnfe) {
System.err.println("Error! A startup class specified in smack-config.xml could " +
"not be loaded: " + className);
log.log(Level.WARNING, "A startup class [" + className + "] specified in smack-config.xml could not be loaded: ");
}
}
@ -375,27 +372,93 @@ public final class SmackConfiguration {
return Integer.parseInt(parser.nextText());
}
catch (NumberFormatException nfe) {
nfe.printStackTrace();
log.log(Level.SEVERE, "Could not parse integer", nfe);
return defaultValue;
}
}
/**
* Returns an array of class loaders to load resources from.
*
* @return an array of ClassLoader instances.
/*
* Order of precedence for config file is VM arg, setConfigXXX methods and embedded default file location.
*/
private static ClassLoader[] getClassLoaders() {
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = SmackConfiguration.class.getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
// Clean up possible null values. Note that #getClassLoader may return a null value.
List<ClassLoader> loaders = new ArrayList<ClassLoader>();
for (ClassLoader classLoader : classLoaders) {
if (classLoader != null) {
loaders.add(classLoader);
private static void initialize() {
if (initialized) {
return;
}
initialized = true;
String configFileLocation = System.getProperty("smack.config.file");
if (configFileLocation != null) {
try {
configFileStream = FileUtils.getStreamForUrl(configFileLocation, null);
}
catch (Exception e) {
log.log(Level.SEVERE, "Error creating input stream for config file [" + configFileLocation + "] from VM argument", e);
}
}
if (configFileStream == null) {
try {
configFileStream = FileUtils.getStreamForUrl(DEFAULT_CONFIG_FILE, null);
}
catch (Exception e) {
log.log(Level.INFO, "Could not create input stream for default config file [" + DEFAULT_CONFIG_FILE + "]", e);
}
}
if (configFileStream != null) {
readFile(configFileStream);
}
else {
log.log(Level.INFO, "No configuration file found");
}
}
private static void readFile(InputStream cfgFileStream) {
XmlPullParser parser = new MXParser();
try {
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(cfgFileStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("className")) {
// Attempt to load the class so that the class can get initialized
parseClassToLoad(parser);
}
else if (parser.getName().equals("packetReplyTimeout")) {
packetReplyTimeout = parseIntProperty(parser, packetReplyTimeout);
}
else if (parser.getName().equals("keepAliveInterval")) {
keepAliveInterval = parseIntProperty(parser, keepAliveInterval);
}
else if (parser.getName().equals("mechName")) {
defaultMechs.add(parser.nextText());
}
else if (parser.getName().equals("localSocks5ProxyEnabled")) {
localSocks5ProxyEnabled = Boolean.parseBoolean(parser.nextText());
}
else if (parser.getName().equals("localSocks5ProxyPort")) {
localSocks5ProxyPort = parseIntProperty(parser, localSocks5ProxyPort);
}
else if (parser.getName().equals("packetCollectorSize")) {
packetCollectorSize = parseIntProperty(parser, packetCollectorSize);
}
else if (parser.getName().equals("autoEnableEntityCaps")) {
autoEnableEntityCaps = Boolean.parseBoolean(parser.nextText());
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
} catch (Exception e) {
log.log(Level.SEVERE, "Error occurred while reading config file", e);
}
finally {
try {
cfgFileStream.close();
} catch (IOException e) {
log.log(Level.INFO, "Error while closing config file input stream", e);
}
}
return loaders.toArray(new ClassLoader[loaders.size()]);
}
}

View File

@ -0,0 +1,14 @@
package org.jivesoftware.smack;
/**
* Defines an initialization class that will be instantiated and invoked by the {@link SmackConfiguration} class during initialization.
*
* <p>
* Any implementation of this class MUST have a default constructor.
*
* @author Robin Collier
*
*/
public interface SmackInitializer {
void initialize();
}

View File

@ -0,0 +1,26 @@
package org.jivesoftware.smack.provider;
abstract class AbstractProviderInfo {
private String element;
private String ns;
private Object provider;
AbstractProviderInfo(String elementName, String namespace, Object iqOrExtProvider) {
element = elementName;
ns = namespace;
provider = iqOrExtProvider;
}
public String getElementName() {
return element;
}
public String getNamespace() {
return ns;
}
Object getProvider() {
return provider;
}
}

View File

@ -0,0 +1,15 @@
package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.SmackInitializer;
/**
* Loads the default provider file for the Smack core on initialization.
*
* @author Robin Collier
*
*/
public class CoreInitializer extends UrlProviderFileInitializer implements SmackInitializer {
protected String getFilePath() {
return "classpath:META-INF/core.providers";
}
}

View File

@ -0,0 +1,33 @@
package org.jivesoftware.smack.provider;
/**
* Defines the information required to register a packet extension Provider with the {@link ProviderManager} when using the
* {@link ProviderLoader}.
*
* @author Robin Collier
*
*/
public final class ExtensionProviderInfo extends AbstractProviderInfo {
/**
* Defines an extension provider which implements the <code>PacketExtensionProvider</code> interface.
*
* @param elementName Element that provider parses.
* @param namespace Namespace that provider parses.
* @param extProvider The provider implementation.
*/
public ExtensionProviderInfo(String elementName, String namespace, PacketExtensionProvider extProvider) {
super(elementName, namespace, extProvider);
}
/**
* Defines an extension provider which is adheres to the JavaBean spec for parsing the extension.
*
* @param elementName Element that provider parses.
* @param namespace Namespace that provider parses.
* @param beanClass The provider bean class.
*/
public ExtensionProviderInfo(String elementName, String namespace, Class<?> beanClass) {
super(elementName, namespace, beanClass);
}
}

View File

@ -0,0 +1,35 @@
package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.IQ;
/**
* Defines the information required to register an IQ Provider with the {@link ProviderManager} when using the
* {@link ProviderLoader}.
*
* @author Robin Collier
*
*/
public final class IQProviderInfo extends AbstractProviderInfo {
/**
* Defines an IQ provider which implements the <code>IQProvider</code> interface.
*
* @param elementName Element that provider parses.
* @param namespace Namespace that provider parses.
* @param iqProvider The provider implementation.
*/
public IQProviderInfo(String elementName, String namespace, IQProvider iqProvider) {
super(elementName, namespace, iqProvider);
}
/**
* Defines an IQ class which can be used as a provider via introspection.
*
* @param elementName Element that provider parses.
* @param namespace Namespace that provider parses.
* @param iqProviderClass The IQ class being parsed.
*/
public IQProviderInfo(String elementName, String namespace, Class<? extends IQ> iqProviderClass) {
super(elementName, namespace, iqProviderClass);
}
}

View File

@ -0,0 +1,146 @@
package org.jivesoftware.smack.provider;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.PacketExtension;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
/**
* Loads the {@link IQProvider} and {@link PacketExtensionProvider} information from a standard provider file in preparation
* for loading into the {@link ProviderManager}.
*
* @author Robin Collier
*
*/
public class ProviderFileLoader implements ProviderLoader {
private final static Logger log = Logger.getLogger(ProviderFileLoader.class.getName());
private Collection<IQProviderInfo> iqProviders;
private Collection<ExtensionProviderInfo> extProviders;
private InputStream providerStream;
public ProviderFileLoader(InputStream providerFileInputStream) {
setInputStream(providerFileInputStream);
}
public ProviderFileLoader() {
}
@Override
public Collection<IQProviderInfo> getIQProviderInfo() {
initialize();
return iqProviders;
}
@Override
public Collection<ExtensionProviderInfo> getExtensionProviderInfo() {
initialize();
return extProviders;
}
@SuppressWarnings("unchecked")
private synchronized void initialize() {
// Check to see if already initialized
if (iqProviders != null) {
return;
}
if (providerStream == null) {
throw new IllegalArgumentException("No input stream set for loader");
}
iqProviders = new ArrayList<IQProviderInfo>();
extProviders = new ArrayList<ExtensionProviderInfo>();
// Load processing providers.
try {
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(providerStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
String typeName = parser.getName();
try {
if (!"smackProviders".equals(typeName)) {
parser.next();
parser.next();
String elementName = parser.nextText();
parser.next();
parser.next();
String namespace = parser.nextText();
parser.next();
parser.next();
String className = parser.nextText();
try {
// Attempt to load the provider class and then create
// a new instance if it's an IQProvider. Otherwise, if it's
// an IQ class, add the class object itself, then we'll use
// reflection later to create instances of the class.
if ("iqProvider".equals(typeName)) {
// Add the provider to the map.
Class<?> provider = Class.forName(className);
if (IQProvider.class.isAssignableFrom(provider)) {
iqProviders.add(new IQProviderInfo(elementName, namespace, (IQProvider) provider.newInstance()));
}
else if (IQ.class.isAssignableFrom(provider)) {
iqProviders.add(new IQProviderInfo(elementName, namespace, (Class<? extends IQ>)provider));
}
}
else {
// Attempt to load the provider class and then create
// a new instance if it's an ExtensionProvider. Otherwise, if it's
// a PacketExtension, add the class object itself and
// then we'll use reflection later to create instances
// of the class.
Class<?> provider = Class.forName(className);
if (PacketExtensionProvider.class.isAssignableFrom(provider)) {
extProviders.add(new ExtensionProviderInfo(elementName, namespace, (PacketExtensionProvider) provider.newInstance()));
}
else if (PacketExtension.class.isAssignableFrom(provider)) {
extProviders.add(new ExtensionProviderInfo(elementName, namespace, provider));
}
}
}
catch (ClassNotFoundException cnfe) {
log.log(Level.SEVERE, "Could not find provider class", cnfe);
}
}
}
catch (IllegalArgumentException illExc) {
log.log(Level.SEVERE, "Invalid provider type found [" + typeName + "] when expecting iqProvider or extensionProvider", illExc);
}
}
eventType = parser.next();
}
while (eventType != XmlPullParser.END_DOCUMENT);
}
catch (Exception e){
log.log(Level.SEVERE, "Unknown error occurred while parsing provider file", e);
}
finally {
try {
providerStream.close();
}
catch (Exception e) {
// Ignore.
}
}
}
public void setInputStream(InputStream providerFileInput) {
if (providerFileInput == null) {
throw new IllegalArgumentException("InputStream cannot be null");
}
providerStream = providerFileInput;
initialize();
}
}

View File

@ -0,0 +1,23 @@
package org.jivesoftware.smack.provider;
import java.util.Collection;
/**
* Used to load providers into the {@link ProviderManager}.
*
* @author Robin Collier
*/
public interface ProviderLoader {
/**
* Provides the IQ provider info for the creation of IQ providers to be added to the <code>ProviderManager</code>.
* @return The IQ provider info to load.
*/
Collection<IQProviderInfo> getIQProviderInfo();
/**
* Provides the extension providers for the creation of extension providers to be added to the <code>ProviderManager</code>.
* @return The extension provider info to load.
*/
Collection<ExtensionProviderInfo> getExtensionProviderInfo();
}

View File

@ -20,16 +20,13 @@
package org.jivesoftware.smack.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.PacketExtension;
import org.xmlpull.mxp1.MXParser;
import org.xmlpull.v1.XmlPullParser;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.packet.IQ;
/**
* Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
* providers exist:<ul>
@ -102,20 +99,14 @@ import java.util.concurrent.ConcurrentHashMap;
* can either implement the PacketExtensionProvider interface or be a standard Java Bean. In
* the former case, each extension provider is responsible for parsing the raw XML stream to
* contruct an object. In the latter case, bean introspection is used to try to automatically
* set the properties of the class using the values in the packet extension sub-element. When an
* set the properties of th class using the values in the packet extension sub-element. When an
* extension provider is not registered for an element name and namespace combination, Smack will
* store all top-level elements of the sub-packet in DefaultPacketExtension object and then
* attach it to the packet.<p>
*
* It is possible to provide a custom provider manager instead of the default implementation
* provided by Smack. If you want to provide your own provider manager then you need to do it
* before creating any {@link org.jivesoftware.smack.Connection} by sending the static
* {@link #setInstance(ProviderManager)} message. Trying to change the provider manager after
* an Connection was created will result in an {@link IllegalStateException} error.
*
* @author Matt Tucker
*/
public class ProviderManager {
public final class ProviderManager {
private static ProviderManager instance;
@ -123,9 +114,7 @@ public class ProviderManager {
private Map<String, Object> iqProviders = new ConcurrentHashMap<String, Object>();
/**
* Returns the only ProviderManager valid instance. Use {@link #setInstance(ProviderManager)}
* to configure your own provider manager. If non was provided then an instance of this
* class will be used.
* Returns the ProviderManager instance.
*
* @return the only ProviderManager valid instance.
*/
@ -136,130 +125,28 @@ public class ProviderManager {
return instance;
}
/**
* Sets the only ProviderManager valid instance to be used by all Connections. If you
* want to provide your own provider manager then you need to do it before creating
* any Connection. Otherwise an IllegalStateException will be thrown.
*
* @param providerManager the only ProviderManager valid instance to be used.
* @throws IllegalStateException if a provider manager was already configued.
*/
public static synchronized void setInstance(ProviderManager providerManager) {
if (instance != null) {
throw new IllegalStateException("ProviderManager singleton already set");
}
instance = providerManager;
private ProviderManager() {
super();
}
protected void initialize() {
// Load IQ processing providers.
try {
// Get an array of class loaders to try loading the providers files from.
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
Enumeration<URL> providerEnum = classLoader.getResources(
"META-INF/smack.providers");
while (providerEnum.hasMoreElements()) {
URL url = providerEnum.nextElement();
InputStream providerStream = null;
try {
providerStream = url.openStream();
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(providerStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("iqProvider")) {
parser.next();
parser.next();
String elementName = parser.nextText();
parser.next();
parser.next();
String namespace = parser.nextText();
parser.next();
parser.next();
String className = parser.nextText();
// Only add the provider for the namespace if one isn't
// already registered.
String key = getProviderKey(elementName, namespace);
if (!iqProviders.containsKey(key)) {
// Attempt to load the provider class and then create
// a new instance if it's an IQProvider. Otherwise, if it's
// an IQ class, add the class object itself, then we'll use
// reflection later to create instances of the class.
try {
// Add the provider to the map.
Class<?> provider = Class.forName(className);
if (IQProvider.class.isAssignableFrom(provider)) {
iqProviders.put(key, provider.newInstance());
}
else if (IQ.class.isAssignableFrom(provider)) {
iqProviders.put(key, provider);
}
}
catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
}
else if (parser.getName().equals("extensionProvider")) {
parser.next();
parser.next();
String elementName = parser.nextText();
parser.next();
parser.next();
String namespace = parser.nextText();
parser.next();
parser.next();
String className = parser.nextText();
// Only add the provider for the namespace if one isn't
// already registered.
String key = getProviderKey(elementName, namespace);
if (!extensionProviders.containsKey(key)) {
// Attempt to load the provider class and then create
// a new instance if it's a Provider. Otherwise, if it's
// a PacketExtension, add the class object itself and
// then we'll use reflection later to create instances
// of the class.
try {
// Add the provider to the map.
Class<?> provider = Class.forName(className);
if (PacketExtensionProvider.class.isAssignableFrom(
provider)) {
extensionProviders.put(key, provider.newInstance());
}
else if (PacketExtension.class.isAssignableFrom(
provider)) {
extensionProviders.put(key, provider);
}
}
catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
}
}
eventType = parser.next();
}
while (eventType != XmlPullParser.END_DOCUMENT);
}
finally {
try {
providerStream.close();
}
catch (Exception e) {
// Ignore.
}
}
}
public void addLoader(ProviderLoader loader) {
if (loader == null) {
throw new IllegalArgumentException("loader cannot be null");
}
if (loader.getIQProviderInfo() != null) {
for (IQProviderInfo info : loader.getIQProviderInfo()) {
iqProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider());
}
}
catch (Exception e) {
e.printStackTrace();
if (loader.getExtensionProviderInfo() != null) {
for (ExtensionProviderInfo info : loader.getExtensionProviderInfo()) {
extensionProviders.put(getProviderKey(info.getElementName(), info.getNamespace()), info.getProvider());
}
}
}
/**
* Returns the IQ provider registered to the specified XML element name and namespace.
* For example, if a provider was registered to the element name "query" and the
@ -411,28 +298,4 @@ public class ProviderManager {
buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
return buf.toString();
}
/**
* Returns an array of class loaders to load resources from.
*
* @return an array of ClassLoader instances.
*/
private ClassLoader[] getClassLoaders() {
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = ProviderManager.class.getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
// Clean up possible null values. Note that #getClassLoader may return a null value.
List<ClassLoader> loaders = new ArrayList<ClassLoader>();
for (ClassLoader classLoader : classLoaders) {
if (classLoader != null) {
loaders.add(classLoader);
}
}
return loaders.toArray(new ClassLoader[loaders.size()]);
}
private ProviderManager() {
super();
initialize();
}
}

View File

@ -0,0 +1,54 @@
package org.jivesoftware.smack.provider;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackInitializer;
import org.jivesoftware.smack.util.FileUtils;
/**
* Loads the provider file defined by the URL returned by {@link #getFilePath()}. This file will be loaded on Smack initialization.
*
* @author Robin Collier
*
*/
public abstract class UrlProviderFileInitializer implements SmackInitializer {
private static final Logger log = Logger.getLogger(UrlProviderFileInitializer.class.getName());
@Override
public void initialize() {
String filePath = getFilePath();
try {
InputStream is = FileUtils.getStreamForUrl(filePath, getClassLoader());
if (is != null) {
log.log(Level.INFO, "Loading providers for file [" + filePath + "]");
ProviderManager.getInstance().addLoader(new ProviderFileLoader(is));
}
else {
log.log(Level.WARNING, "No input stream created for " + filePath);
}
}
catch (Exception e) {
log.log(Level.SEVERE, "Error trying to load provider file " + filePath, e);
}
}
protected abstract String getFilePath();
/**
* Returns an array of class loaders to load resources from.
*
* @return an array of ClassLoader instances.
*/
protected ClassLoader getClassLoader() {
return null;
}
}

View File

@ -0,0 +1,23 @@
package org.jivesoftware.smack.provider;
/**
* Looks for a provider file location based on the VM argument <i>smack.provider.file</>. If it is supplied, its value will
* be used as a file location for a providers file and loaded into the {@link ProviderManager} on Smack initialization.
*
* @author Robin Collier
*
*/
public class VmArgInitializer extends UrlProviderFileInitializer {
protected String getFilePath() {
return System.getProperty("smack.provider.file");
}
@Override
public void initialize() {
if (getFilePath() != null) {
super.initialize();
}
}
}

View File

@ -0,0 +1,59 @@
package org.jivesoftware.smack.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
public final class FileUtils {
private FileUtils() {
}
public static InputStream getStreamForUrl(String url, ClassLoader loader) throws MalformedURLException, IOException {
URI fileUri = URI.create(url);
if (fileUri.getScheme() == null) {
throw new MalformedURLException("No protocol found in file URL: " + url);
}
if (fileUri.getScheme().equals("classpath")) {
// Get an array of class loaders to try loading the providers files from.
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
InputStream is = classLoader.getResourceAsStream(fileUri.getSchemeSpecificPart());
if (is != null) {
return is;
}
}
}
else {
return fileUri.toURL().openStream();
}
return null;
}
/**
* Returns default classloaders.
*
* @return an array of ClassLoader instances.
*/
public static ClassLoader[] getClassLoaders() {
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = FileUtils.class.getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
// Clean up possible null values. Note that #getClassLoader may return a null value.
List<ClassLoader> loaders = new ArrayList<ClassLoader>();
for (ClassLoader classLoader : classLoaders) {
if (classLoader != null) {
loaders.add(classLoader);
}
}
return loaders.toArray(new ClassLoader[loaders.size()]);
}
}

View File

@ -0,0 +1,16 @@
package org.jivesoftware.smackx.provider;
import org.jivesoftware.smack.provider.UrlProviderFileInitializer;
/**
* Loads the default provider file for the Smack extensions on initialization.
*
* @author Robin Collier
*
*/
public class ExtensionInitializer extends UrlProviderFileInitializer {
@Override
protected String getFilePath() {
return "classpath:META-INF/extension.providers";
}
}

View File

@ -0,0 +1,55 @@
package org.jivesoftware.smackx.provider;
import java.util.ArrayList;
import java.util.Collection;
import junit.framework.Assert;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.ExtensionProviderInfo;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.IQProviderInfo;
import org.jivesoftware.smack.provider.ProviderFileLoader;
import org.jivesoftware.smack.provider.ProviderLoader;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.FileUtils;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
public class ProviderConfigTest {
@Test
public void addGenericLoaderProvider() {
ProviderManager.getInstance().addLoader(new ProviderLoader() {
@Override
public Collection<IQProviderInfo> getIQProviderInfo() {
ArrayList<IQProviderInfo> l = new ArrayList<IQProviderInfo>(1);
l.add(new IQProviderInfo("provider", "test:provider", new TestIQProvider()));
return l;
}
@Override
public Collection<ExtensionProviderInfo> getExtensionProviderInfo() {
return null;
}
});
Assert.assertNotNull(ProviderManager.getInstance().getIQProvider("provider", "test:provider"));
}
@Test
public void addClasspathFileLoaderProvider() throws Exception{
ProviderManager.getInstance().addLoader(new ProviderFileLoader(FileUtils.getStreamForUrl("classpath:org/jivesoftware/smackx/provider/test.providers", null)));
Assert.assertNotNull(ProviderManager.getInstance().getIQProvider("provider", "test:file_provider"));
}
public static class TestIQProvider implements IQProvider {
@Override
public IQ parseIQ(XmlPullParser parser) throws Exception {
return null;
}
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- Providers file for default Smack extensions -->
<smackProviders>
<iqProvider>
<elementName>provider</elementName>
<namespace>test:file_provider</namespace>
<className>org.jivesoftware.smackx.provider.ProviderConfigTest$TestIQProvider</className>
</iqProvider>
</smackProviders>