1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-10 17:49:38 +02:00

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

@ -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();
}
}
}