| /* |
| * 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 |
| } |
| } |