blob: 64be3f5cf2f0525d1e557176b170f82630258003 [file] [log] [blame]
/*
* Copyright (c) OSGi Alliance (2002, 2015). All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.osgi.util.xml;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.SAXParserFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
/**
* A BundleActivator class that allows any JAXP compliant XML Parser to register
* itself as an OSGi parser service.
*
* Multiple JAXP compliant parsers can concurrently register by using this
* BundleActivator class. Bundles who wish to use an XML parser can then use the
* framework's service registry to locate available XML Parsers with the desired
* characteristics such as validating and namespace-aware.
*
* <p>
* The services that this bundle activator enables a bundle to provide are:
* <ul>
* <li>{@code javax.xml.parsers.SAXParserFactory}({@link #SAXFACTORYNAME})</li>
* <li>{@code javax.xml.parsers.DocumentBuilderFactory}( {@link #DOMFACTORYNAME}
* )</li>
* </ul>
*
* <p>
* The algorithm to find the implementations of the abstract parsers is derived
* from the JAR file specifications, specifically the Services API.
* <p>
* An XMLParserActivator assumes that it can find the class file names of the
* factory classes in the following files:
* <ul>
* <li>{@code /META-INF/services/javax.xml.parsers.SAXParserFactory} is a file
* contained in a jar available to the runtime which contains the implementation
* class name(s) of the SAXParserFactory.</li>
* <li>{@code /META-INF/services/javax.xml.parsers.DocumentBuilderFactory} is a
* file contained in a jar available to the runtime which contains the
* implementation class name(s) of the {@code DocumentBuilderFactory}</li>
* </ul>
* <p>
* If either of the files does not exist, {@code XMLParserActivator} assumes
* that the parser does not support that parser type.
*
* <p>
* {@code XMLParserActivator} attempts to instantiate both the
* {@code SAXParserFactory} and the {@code DocumentBuilderFactory}. It registers
* each factory with the framework along with service properties:
* <ul>
* <li>{@link #PARSER_VALIDATING}- indicates if this factory supports validating
* parsers. It's value is a {@code Boolean}.</li>
* <li>{@link #PARSER_NAMESPACEAWARE}- indicates if this factory supports
* namespace aware parsers It's value is a {@code Boolean}.</li>
* </ul>
* <p>
* Individual parser implementations may have additional features, properties,
* or attributes which could be used to select a parser with a filter. These can
* be added by extending this class and overriding the {@code setSAXProperties}
* and {@code setDOMProperties} methods.
*
* @ThreadSafe
* @author $Id$
*/
public class XMLParserActivator implements BundleActivator, ServiceFactory<Object> {
/** Context of this bundle */
private volatile BundleContext bundleContext;
/**
* Filename containing the SAX Parser Factory Class name. Also used as the
* basis for the {@code SERVICE_PID} registration property.
*/
public static final String SAXFACTORYNAME = "javax.xml.parsers.SAXParserFactory";
/**
* Filename containing the DOM Parser Factory Class name. Also used as the
* basis for the {@code SERVICE_PID} registration property.
*/
public static final String DOMFACTORYNAME = "javax.xml.parsers.DocumentBuilderFactory";
/** Path to the factory class name files */
private static final String PARSERCLASSFILEPATH = "/META-INF/services/";
/** Fully qualified path name of SAX Parser Factory Class Name file */
public static final String SAXCLASSFILE = PARSERCLASSFILEPATH + SAXFACTORYNAME;
/** Fully qualified path name of DOM Parser Factory Class Name file */
public static final String DOMCLASSFILE = PARSERCLASSFILEPATH + DOMFACTORYNAME;
/** SAX Factory Service Description */
private static final String SAXFACTORYDESCRIPTION = "A JAXP Compliant SAX Parser";
/** DOM Factory Service Description */
private static final String DOMFACTORYDESCRIPTION = "A JAXP Compliant DOM Parser";
/**
* Service property specifying if factory is configured to support
* validating parsers. The value is of type {@code Boolean}.
*/
public static final String PARSER_VALIDATING = "parser.validating";
/**
* Service property specifying if factory is configured to support namespace
* aware parsers. The value is of type {@code Boolean}.
*/
public static final String PARSER_NAMESPACEAWARE = "parser.namespaceAware";
/**
* Key for parser factory name property - this must be saved in the parsers
* properties hashtable so that the parser factory can be instantiated from
* a ServiceReference
*/
private static final String FACTORYNAMEKEY = "parser.factoryname";
/**
* Called when this bundle is started so the Framework can perform the
* bundle-specific activities necessary to start this bundle. This method
* can be used to register services or to allocate any resources that this
* bundle needs.
*
* <p>
* This method must complete and return to its caller in a timely manner.
*
* <p>
* This method attempts to register a SAX and DOM parser with the
* Framework's service registry.
*
* @param context The execution context of the bundle being started.
* @throws java.lang.Exception If this method throws an exception, this
* bundle is marked as stopped and the Framework will remove this
* bundle's listeners, unregister all services registered by this
* bundle, and release all services used by this bundle.
*/
@Override
public void start(BundleContext context) throws Exception {
this.bundleContext = context;
Bundle parserBundle = context.getBundle();
// check for sax parsers
registerSAXParsers(getParserFactoryClassNames(parserBundle.getResource(SAXCLASSFILE)));
// check for dom parsers
registerDOMParsers(getParserFactoryClassNames(parserBundle.getResource(DOMCLASSFILE)));
}
/**
* This method has nothing to do as all active service registrations will
* automatically get unregistered when the bundle stops.
*
* @param context The execution context of the bundle being stopped.
* @throws java.lang.Exception If this method throws an exception, the
* bundle is still marked as stopped, and the Framework will remove
* the bundle's listeners, unregister all services registered by the
* bundle, and release all services used by the bundle.
*/
@Override
public void stop(BundleContext context) throws Exception {
// framework will automatically unregister the parser services
}
/**
* Given the URL for a file, reads and returns the parser class names. There
* may be multiple classes specified in this file, one per line. There may
* also be comment lines in the file, which begin with "#".
*
* @param parserUrl The URL of the service file containing the parser class
* names
* @return A List of strings containing the parser class names.
* @throws IOException if there is a problem reading the URL input stream
*/
private List<String> getParserFactoryClassNames(URL parserUrl) throws IOException {
if (parserUrl == null) {
return Collections.emptyList();
}
List<String> v = new ArrayList<String>(1);
String parserFactoryClassName = null;
InputStream is = parserUrl.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while (true) {
parserFactoryClassName = br.readLine();
if (parserFactoryClassName == null) {
break; // end of file reached
}
String pfcName = parserFactoryClassName.trim();
if (pfcName.length() == 0) {
continue; // blank line
}
int commentIdx = pfcName.indexOf("#");
if (commentIdx == 0) { // comment line
continue;
} else
if (commentIdx < 0) { // no comment on this line
v.add(pfcName);
} else {
v.add(pfcName.substring(0, commentIdx).trim());
}
}
return v;
}
/**
* Register SAX Parser Factory Services with the framework.
*
* @param parserFactoryClassNames - a {@code List} of {@code String} objects
* containing the names of the parser Factory Classes
* @throws FactoryConfigurationError if thrown from {@code getFactory}
*/
private void registerSAXParsers(List<String> parserFactoryClassNames) throws FactoryConfigurationError {
Iterator<String> e = parserFactoryClassNames.iterator();
int index = 0;
while (e.hasNext()) {
String parserFactoryClassName = e.next();
// create a sax parser factory just to get it's default
// properties. It will never be used since
// this class will operate as a service factory and give each
// service requestor it's own SaxParserFactory
SAXParserFactory factory = (SAXParserFactory) getFactory(parserFactoryClassName);
Hashtable<String, Object> properties = new Hashtable<String, Object>(7);
// figure out the default properties of the parser
setDefaultSAXProperties(factory, properties, index);
// store the parser factory class name in the properties so that
// it can be retrieved when getService is called
// to return a parser factory
properties.put(FACTORYNAMEKEY, parserFactoryClassName);
// register the factory as a service
bundleContext.registerService(SAXFACTORYNAME, this, properties);
index++;
}
}
/**
* <p>
* Set the SAX Parser Service Properties. By default, the following
* properties are set:
* <ul>
* <li>{@code SERVICE_DESCRIPTION}</li>
* <li>{@code SERVICE_PID}</li>
* <li>{@code PARSER_VALIDATING}- instantiates a parser and queries it to
* find out whether it is validating or not</li>
* <li>{@code PARSER_NAMESPACEAWARE}- instantiates a parser and queries it
* to find out whether it is namespace aware or not</li>
* <ul>
*
* @param factory The {@code SAXParserFactory} object
* @param props {@code Hashtable} of service properties.
*/
private void setDefaultSAXProperties(SAXParserFactory factory, Hashtable<String, Object> props, int index) {
props.put(Constants.SERVICE_DESCRIPTION, SAXFACTORYDESCRIPTION);
props.put(Constants.SERVICE_PID, SAXFACTORYNAME + "." + bundleContext.getBundle().getBundleId() + "." + index);
setSAXProperties(factory, props);
}
/**
* <p>
* Set the customizable SAX Parser Service Properties.
*
* <p>
* This method attempts to instantiate a validating parser and a namespace
* aware parser to determine if the parser can support those features. The
* appropriate properties are then set in the specified properties object.
*
* <p>
* This method can be overridden to add additional SAX2 features and
* properties. If you want to be able to filter searches of the OSGi service
* registry, this method must put a key, value pair into the properties
* object for each feature or property. For example,
*
* properties.put("http://www.acme.com/features/foo", Boolean.TRUE);
*
* @param factory - the SAXParserFactory object
* @param properties - the properties object for the service
*/
public void setSAXProperties(SAXParserFactory factory, Hashtable<String, Object> properties) {
// check if this parser can be configured to validate
boolean validating = true;
factory.setValidating(true);
factory.setNamespaceAware(false);
try {
factory.newSAXParser();
} catch (Exception pce_val) {
validating = false;
}
// check if this parser can be configured to be namespaceaware
boolean namespaceaware = true;
factory.setValidating(false);
factory.setNamespaceAware(true);
try {
factory.newSAXParser();
} catch (Exception pce_nsa) {
namespaceaware = false;
}
// set the factory values
factory.setValidating(validating);
factory.setNamespaceAware(namespaceaware);
// set the OSGi service properties
properties.put(PARSER_NAMESPACEAWARE, Boolean.valueOf(namespaceaware));
properties.put(PARSER_VALIDATING, Boolean.valueOf(validating));
}
/**
* Register DOM Parser Factory Services with the framework.
*
* @param parserFactoryClassNames - a {@code List} of {@code String} objects
* containing the names of the parser Factory Classes
* @throws FactoryConfigurationError if thrown from {@code getFactory}
*/
private void registerDOMParsers(List<String> parserFactoryClassNames) throws FactoryConfigurationError {
Iterator<String> e = parserFactoryClassNames.iterator();
int index = 0;
while (e.hasNext()) {
String parserFactoryClassName = e.next();
// create a dom parser factory just to get it's default
// properties. It will never be used since
// this class will operate as a service factory and give each
// service requestor it's own DocumentBuilderFactory
DocumentBuilderFactory factory = (DocumentBuilderFactory) getFactory(parserFactoryClassName);
Hashtable<String, Object> properties = new Hashtable<String, Object>(7);
// figure out the default properties of the parser
setDefaultDOMProperties(factory, properties, index);
// store the parser factory class name in the properties so that
// it can be retrieved when getService is called
// to return a parser factory
properties.put(FACTORYNAMEKEY, parserFactoryClassName);
// register the factory as a service
bundleContext.registerService(DOMFACTORYNAME, this, properties);
index++;
}
}
/**
* Set the DOM parser service properties.
*
* By default, the following properties are set:
* <ul>
* <li>{@code SERVICE_DESCRIPTION}</li>
* <li>{@code SERVICE_PID}</li>
* <li>{@code PARSER_VALIDATING}</li>
* <li>{@code PARSER_NAMESPACEAWARE}</li>
* <ul>
*
* @param factory The {@code DocumentBuilderFactory} object
* @param props {@code Hashtable} of service properties.
*/
private void setDefaultDOMProperties(DocumentBuilderFactory factory, Hashtable<String, Object> props, int index) {
props.put(Constants.SERVICE_DESCRIPTION, DOMFACTORYDESCRIPTION);
props.put(Constants.SERVICE_PID, DOMFACTORYNAME + "." + bundleContext.getBundle().getBundleId() + "." + index);
setDOMProperties(factory, props);
}
/**
* <p>
* Set the customizable DOM Parser Service Properties.
*
* <p>
* This method attempts to instantiate a validating parser and a namespace
* aware parser to determine if the parser can support those features. The
* appropriate properties are then set in the specified props object.
*
* <p>
* This method can be overridden to add additional DOM2 features and
* properties. If you want to be able to filter searches of the OSGi service
* registry, this method must put a key, value pair into the properties
* object for each feature or property. For example,
*
* properties.put("http://www.acme.com/features/foo", Boolean.TRUE);
*
* @param factory - the DocumentBuilderFactory object
* @param props - Hashtable of service properties.
*/
public void setDOMProperties(DocumentBuilderFactory factory, Hashtable<String, Object> props) {
// check if this parser can be configured to validate
boolean validating = true;
factory.setValidating(true);
factory.setNamespaceAware(false);
try {
factory.newDocumentBuilder();
} catch (Exception pce_val) {
validating = false;
}
// check if this parser can be configured to be namespaceaware
boolean namespaceaware = true;
factory.setValidating(false);
factory.setNamespaceAware(true);
try {
factory.newDocumentBuilder();
} catch (Exception pce_nsa) {
namespaceaware = false;
}
// set the factory values
factory.setValidating(validating);
factory.setNamespaceAware(namespaceaware);
// set the OSGi service properties
props.put(PARSER_VALIDATING, Boolean.valueOf(validating));
props.put(PARSER_NAMESPACEAWARE, Boolean.valueOf(namespaceaware));
}
/**
* Given a parser factory class name, instantiate that class.
*
* @param parserFactoryClassName A {@code String} object containing the name
* of the parser factory class
* @return a parserFactoryClass Object
* @pre parserFactoryClassName!=null
*/
private Object getFactory(String parserFactoryClassName) throws FactoryConfigurationError {
try {
return bundleContext.getBundle().loadClass(parserFactoryClassName).newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new FactoryConfigurationError(e);
}
}
/**
* Creates a new XML Parser Factory object.
*
* <p>
* A unique XML Parser Factory object is returned for each call to this
* method.
*
* <p>
* The returned XML Parser Factory object will be configured for validating
* and namespace aware support as specified in the service properties of the
* specified ServiceRegistration object.
*
* This method can be overridden to configure additional features in the
* returned XML Parser Factory object.
*
* @param bundle The bundle using the service.
* @param registration The {@code ServiceRegistration} object for the
* service.
* @return A new, configured XML Parser Factory object or null if a
* configuration error was encountered
*/
@Override
public Object getService(Bundle bundle, ServiceRegistration<Object> registration) {
ServiceReference<Object> sref = registration.getReference();
String parserFactoryClassName = (String) sref.getProperty(FACTORYNAMEKEY);
// need to set factory properties
Object factory = getFactory(parserFactoryClassName);
if (factory instanceof SAXParserFactory) {
((SAXParserFactory) factory).setValidating(((Boolean) sref.getProperty(PARSER_VALIDATING)).booleanValue());
((SAXParserFactory) factory).setNamespaceAware(((Boolean) sref.getProperty(PARSER_NAMESPACEAWARE)).booleanValue());
} else {
if (factory instanceof DocumentBuilderFactory) {
((DocumentBuilderFactory) factory).setValidating(((Boolean) sref.getProperty(PARSER_VALIDATING)).booleanValue());
((DocumentBuilderFactory) factory).setNamespaceAware(((Boolean) sref.getProperty(PARSER_NAMESPACEAWARE)).booleanValue());
}
}
return factory;
}
/**
* Releases a XML Parser Factory object.
*
* @param bundle The bundle releasing the service.
* @param registration The {@code ServiceRegistration} object for the
* service.
* @param service The XML Parser Factory object returned by a previous call
* to the {@code getService} method.
*/
@Override
public void ungetService(Bundle bundle, ServiceRegistration<Object> registration, Object service) {
// nothing to do
}
}