Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 64be3f5cf2f0525d1e557176b170f82630258003 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
/*
 * 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
	}
}

Back to the top