1 | /* |
2 | * Copyright (c) 2009 The JOMC Project |
3 | * Copyright (c) 2005 Christian Schulte <cs@jomc.org> |
4 | * All rights reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * |
10 | * o Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * |
13 | * o Redistributions in binary form must reproduce the above copyright |
14 | * notice, this list of conditions and the following disclaimer in |
15 | * the documentation and/or other materials provided with the |
16 | * distribution. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS" |
19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
20 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
21 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR |
22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
25 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
26 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
27 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
28 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | * |
30 | * $Id: BootstrapContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
31 | * |
32 | */ |
33 | package org.jomc.model.bootstrap; |
34 | |
35 | import java.io.BufferedReader; |
36 | import java.io.File; |
37 | import java.io.FileInputStream; |
38 | import java.io.IOException; |
39 | import java.io.InputStream; |
40 | import java.io.InputStreamReader; |
41 | import java.lang.reflect.Constructor; |
42 | import java.lang.reflect.InvocationTargetException; |
43 | import java.net.URL; |
44 | import java.util.Collection; |
45 | import java.util.Comparator; |
46 | import java.util.Enumeration; |
47 | import java.util.Map; |
48 | import java.util.TreeMap; |
49 | import javax.xml.bind.JAXBContext; |
50 | import javax.xml.bind.JAXBException; |
51 | import javax.xml.bind.Marshaller; |
52 | import javax.xml.bind.Unmarshaller; |
53 | import javax.xml.bind.util.JAXBSource; |
54 | import javax.xml.validation.Validator; |
55 | import org.xml.sax.SAXException; |
56 | |
57 | /** |
58 | * Object management and configuration model bootstrap context interface. |
59 | * |
60 | * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> |
61 | * @version $Id: BootstrapContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
62 | * @see #createBootstrapContext(java.lang.ClassLoader) |
63 | */ |
64 | public abstract class BootstrapContext |
65 | { |
66 | |
67 | /** Class name of the {@code BootstrapContext} implementation. */ |
68 | private static volatile String bootstrapContextClassName; |
69 | |
70 | /** The class loader of the context. */ |
71 | private ClassLoader classLoader; |
72 | |
73 | /** |
74 | * Creates a new {@code BootstrapContext} instance taking a class loader. |
75 | * |
76 | * @param classLoader The class loader of the context. |
77 | */ |
78 | public BootstrapContext( final ClassLoader classLoader ) |
79 | { |
80 | super(); |
81 | this.classLoader = classLoader; |
82 | } |
83 | |
84 | /** |
85 | * Gets the class loader of the context. |
86 | * |
87 | * @return The class loader of the context. |
88 | */ |
89 | public ClassLoader getClassLoader() |
90 | { |
91 | if ( this.classLoader == null ) |
92 | { |
93 | this.classLoader = new ClassLoader( null ) |
94 | { |
95 | |
96 | @Override |
97 | public String toString() |
98 | { |
99 | return BootstrapContext.class.getName() + ".BootstrapClassLoader@" + |
100 | System.identityHashCode( this ); |
101 | |
102 | } |
103 | |
104 | }; |
105 | |
106 | } |
107 | |
108 | return this.classLoader; |
109 | } |
110 | |
111 | /** |
112 | * Gets the name of the class providing the {@code BootstrapContext} implementation. |
113 | * <p>The name of the class providing the {@code BootstrapContext} implementation returned by method |
114 | * {@link #createBootstrapContext(java.lang.ClassLoader)} is controlled by system property |
115 | * {@code org.jomc.model.bootstrap.BootstrapContext.className}. If that property is not set, the name of the |
116 | * {@link DefaultBootstrapContext} class is returned.</p> |
117 | * |
118 | * @return The name of the class providing the {@code BootstrapContext} implementation. |
119 | * |
120 | * @see #setBootstrapContextClassName(java.lang.String) |
121 | */ |
122 | public static String getBootstrapContextClassName() |
123 | { |
124 | if ( bootstrapContextClassName == null ) |
125 | { |
126 | bootstrapContextClassName = System.getProperty( "org.jomc.model.bootstrap.BootstrapContext.className", |
127 | DefaultBootstrapContext.class.getName() ); |
128 | |
129 | } |
130 | |
131 | return bootstrapContextClassName; |
132 | } |
133 | |
134 | /** |
135 | * Sets the name of the class providing the {@code BootstrapContext} implementation. |
136 | * |
137 | * @param value The new name of the class providing the {@code BootstrapContext} implementation or {@code null}. |
138 | * |
139 | * @see #getBootstrapContextClassName() |
140 | */ |
141 | public static void setBootstrapContextClassName( final String value ) |
142 | { |
143 | bootstrapContextClassName = value; |
144 | } |
145 | |
146 | /** |
147 | * Searches the context for a class with a given name. |
148 | * |
149 | * @param name The name of the class to return. |
150 | * |
151 | * @return A Class object of the class with name {@code name} or {@code null} if no such class is found. |
152 | * |
153 | * @throws NullPointerException if {@code name} is {@code null}. |
154 | * @throws BootstrapException if searching fails. |
155 | */ |
156 | public Class findClass( final String name ) throws BootstrapException |
157 | { |
158 | if ( name == null ) |
159 | { |
160 | throw new NullPointerException( "name" ); |
161 | } |
162 | |
163 | try |
164 | { |
165 | return Class.forName( name, true, this.getClassLoader() ); |
166 | } |
167 | catch ( final ClassNotFoundException e ) |
168 | { |
169 | return null; |
170 | } |
171 | } |
172 | |
173 | /** |
174 | * Searches the context for a resource with a given name. |
175 | * |
176 | * @param name The name of the resource to return. |
177 | * |
178 | * @return An URL object for reading the resource or {@code null} if no such resource is found. |
179 | * |
180 | * @throws NullPointerException if {@code name} is {@code null}. |
181 | * @throws BootstrapException if searching fails. |
182 | */ |
183 | public URL findResource( final String name ) throws BootstrapException |
184 | { |
185 | if ( name == null ) |
186 | { |
187 | throw new NullPointerException( "name" ); |
188 | } |
189 | |
190 | return this.getClassLoader().getResource( name ); |
191 | } |
192 | |
193 | /** |
194 | * Searches the context for resources with a given name. |
195 | * |
196 | * @param name The name of the resources to return. |
197 | * |
198 | * @return An enumeration of URL objects for reading the resources. If no resources are found, the enumeration will |
199 | * be empty. |
200 | * |
201 | * @throws NullPointerException if {@code name} is {@code null}. |
202 | * @throws BootstrapException if searching fails. |
203 | */ |
204 | public Enumeration<URL> findResources( final String name ) throws BootstrapException |
205 | { |
206 | if ( name == null ) |
207 | { |
208 | throw new NullPointerException( "name" ); |
209 | } |
210 | |
211 | try |
212 | { |
213 | return this.getClassLoader().getResources( name ); |
214 | } |
215 | catch ( final IOException e ) |
216 | { |
217 | throw new BootstrapException( e ); |
218 | } |
219 | } |
220 | |
221 | /** |
222 | * Searches the context for schemas. |
223 | * <p>This method loads {@code SchemaProvider} classes setup via |
224 | * {@code META-INF/services/org.jomc.model.bootstrap.SchemaProvider} resources and returns a list of provided |
225 | * schemas.</p> |
226 | * |
227 | * @return The schemas found in the context. |
228 | * |
229 | * @throws BootstrapException if searching schemas fails. |
230 | * |
231 | * @see SchemaProvider#findSchemas(org.jomc.model.bootstrap.BootstrapContext) |
232 | */ |
233 | public Schemas findSchemas() throws BootstrapException |
234 | { |
235 | try |
236 | { |
237 | final Schemas schemas = new Schemas(); |
238 | |
239 | final Collection<Class<SchemaProvider>> providers = this.loadProviders( SchemaProvider.class ); |
240 | for ( Class<SchemaProvider> provider : providers ) |
241 | { |
242 | final SchemaProvider schemaProvider = provider.newInstance(); |
243 | final Schemas provided = schemaProvider.findSchemas( this ); |
244 | if ( provided != null ) |
245 | { |
246 | schemas.getSchema().addAll( provided.getSchema() ); |
247 | } |
248 | } |
249 | |
250 | final javax.xml.validation.Schema bootstrapSchema = this.createSchema(); |
251 | final Validator validator = bootstrapSchema.newValidator(); |
252 | validator.validate( new JAXBSource( this.createContext(), new ObjectFactory().createSchemas( schemas ) ) ); |
253 | |
254 | return schemas; |
255 | } |
256 | catch ( final InstantiationException e ) |
257 | { |
258 | throw new BootstrapException( e ); |
259 | } |
260 | catch ( final IllegalAccessException e ) |
261 | { |
262 | throw new BootstrapException( e ); |
263 | } |
264 | catch ( final JAXBException e ) |
265 | { |
266 | throw new BootstrapException( e ); |
267 | } |
268 | catch ( final SAXException e ) |
269 | { |
270 | throw new BootstrapException( e ); |
271 | } |
272 | catch ( final IOException e ) |
273 | { |
274 | throw new BootstrapException( e ); |
275 | } |
276 | } |
277 | |
278 | /** |
279 | * Searches the context for services. |
280 | * <p>This method loads {@code ServiceProvider} classes setup via |
281 | * {@code META-INF/services/org.jomc.model.bootstrap.ServiceProvider} resources and returns a list of provided |
282 | * services.</p> |
283 | * |
284 | * @return The services found in the context. |
285 | * |
286 | * @throws BootstrapException if searching services fails. |
287 | * |
288 | * @see ServiceProvider#findServices(org.jomc.model.bootstrap.BootstrapContext) |
289 | */ |
290 | public Services findServices() throws BootstrapException |
291 | { |
292 | try |
293 | { |
294 | final Services services = new Services(); |
295 | |
296 | final Collection<Class<ServiceProvider>> providers = this.loadProviders( ServiceProvider.class ); |
297 | for ( Class<ServiceProvider> provider : providers ) |
298 | { |
299 | final ServiceProvider serviceProvider = provider.newInstance(); |
300 | final Services provided = serviceProvider.findServices( this ); |
301 | if ( provided != null ) |
302 | { |
303 | services.getService().addAll( provided.getService() ); |
304 | } |
305 | } |
306 | |
307 | final javax.xml.validation.Schema bootstrapSchema = this.createSchema(); |
308 | final Validator validator = bootstrapSchema.newValidator(); |
309 | validator.validate( new JAXBSource( this.createContext(), new ObjectFactory().createServices( services ) ) ); |
310 | |
311 | return services; |
312 | } |
313 | catch ( final InstantiationException e ) |
314 | { |
315 | throw new BootstrapException( e ); |
316 | } |
317 | catch ( final IllegalAccessException e ) |
318 | { |
319 | throw new BootstrapException( e ); |
320 | } |
321 | catch ( final JAXBException e ) |
322 | { |
323 | throw new BootstrapException( e ); |
324 | } |
325 | catch ( final SAXException e ) |
326 | { |
327 | throw new BootstrapException( e ); |
328 | } |
329 | catch ( final IOException e ) |
330 | { |
331 | throw new BootstrapException( e ); |
332 | } |
333 | } |
334 | |
335 | /** |
336 | * Creates a new object management and configuration model {@code BootstrapContext} instance. |
337 | * |
338 | * @param classLoader The class loader to create a new object management and configuration model bootstrap context |
339 | * instance with or {@code null} to create a new bootstrap context using the platform's bootstrap class loader. |
340 | * |
341 | * @return A new {@code BootstrapContext} instance. |
342 | * |
343 | * @throws BootstrapException if creating a new object management and configuration model bootstrap context instance |
344 | * fails. |
345 | * |
346 | * @see #getBootstrapContextClassName() |
347 | */ |
348 | public static BootstrapContext createBootstrapContext( final ClassLoader classLoader ) throws BootstrapException |
349 | { |
350 | if ( DefaultBootstrapContext.class.getName().equals( getBootstrapContextClassName() ) ) |
351 | { |
352 | return new DefaultBootstrapContext( classLoader ); |
353 | } |
354 | |
355 | try |
356 | { |
357 | final Class clazz = Class.forName( getBootstrapContextClassName(), true, classLoader ); |
358 | final Constructor ctor = clazz.getConstructor( ClassLoader.class ); |
359 | return (BootstrapContext) ctor.newInstance( classLoader ); |
360 | } |
361 | catch ( final ClassNotFoundException e ) |
362 | { |
363 | throw new BootstrapException( e ); |
364 | } |
365 | catch ( final NoSuchMethodException e ) |
366 | { |
367 | throw new BootstrapException( e ); |
368 | } |
369 | catch ( final InstantiationException e ) |
370 | { |
371 | throw new BootstrapException( e ); |
372 | } |
373 | catch ( final IllegalAccessException e ) |
374 | { |
375 | throw new BootstrapException( e ); |
376 | } |
377 | catch ( final InvocationTargetException e ) |
378 | { |
379 | throw new BootstrapException( e ); |
380 | } |
381 | catch ( final ClassCastException e ) |
382 | { |
383 | throw new BootstrapException( e ); |
384 | } |
385 | } |
386 | |
387 | /** |
388 | * Creates a new object management and configuration model bootstrap JAXP schema instance. |
389 | * |
390 | * @return A new object management and configuration model bootstrap JAXP schema instance. |
391 | * |
392 | * @throws BootstrapException if creating a new object management and configuration model bootstrap JAXP schema |
393 | * instance fails. |
394 | */ |
395 | public abstract javax.xml.validation.Schema createSchema() throws BootstrapException; |
396 | |
397 | /** |
398 | * Creates a new object management and configuration model bootstrap JAXB context instance. |
399 | * |
400 | * @return A new object management and configuration model bootstrap JAXB context instance. |
401 | * |
402 | * @throws BootstrapException if creating a new object management and configuration model bootstrap JAXB context |
403 | * instance fails. |
404 | */ |
405 | public abstract JAXBContext createContext() throws BootstrapException; |
406 | |
407 | /** |
408 | * Creates a new object management and configuration model bootstrap JAXB marshaller instance. |
409 | * |
410 | * @return A new object management and configuration model bootstrap JAXB marshaller instance. |
411 | * |
412 | * @throws BootstrapException if creating a new object management and configuration model bootstrap JAXB marshaller |
413 | * instance fails. |
414 | */ |
415 | public abstract Marshaller createMarshaller() throws BootstrapException; |
416 | |
417 | /** |
418 | * Creates a new object management and configuration model bootstrap JAXB unmarshaller instance. |
419 | * |
420 | * @return A new object management and configuration model bootstrap JAXB unmarshaller instance. |
421 | * |
422 | * @throws BootstrapException if creating a new object management and configuration model bootstrap JAXB |
423 | * unmarshaller instance fails. |
424 | */ |
425 | public abstract Unmarshaller createUnmarshaller() throws BootstrapException; |
426 | |
427 | private <T> Collection<Class<T>> loadProviders( final Class<T> providerClass ) throws BootstrapException |
428 | { |
429 | try |
430 | { |
431 | final String providerNamePrefix = providerClass.getName() + "."; |
432 | final Map<String, Class<T>> providers = new TreeMap<String, Class<T>>( new Comparator<String>() |
433 | { |
434 | |
435 | public int compare( final String key1, final String key2 ) |
436 | { |
437 | return key1.compareTo( key2 ); |
438 | } |
439 | |
440 | } ); |
441 | |
442 | final File platformProviders = new File( new StringBuilder().append( System.getProperty( "java.home" ) ). |
443 | append( File.separator ).append( "jre" ).append( File.separator ).append( "lib" ). |
444 | append( File.separator ).append( "jomc.properties" ).toString() ); |
445 | |
446 | if ( platformProviders.exists() ) |
447 | { |
448 | InputStream in = null; |
449 | final java.util.Properties p = new java.util.Properties(); |
450 | |
451 | try |
452 | { |
453 | in = new FileInputStream( platformProviders ); |
454 | p.load( in ); |
455 | } |
456 | finally |
457 | { |
458 | if ( in != null ) |
459 | { |
460 | in.close(); |
461 | } |
462 | } |
463 | |
464 | for ( Map.Entry e : p.entrySet() ) |
465 | { |
466 | if ( e.getKey().toString().startsWith( providerNamePrefix ) ) |
467 | { |
468 | final Class<T> provider = this.findClass( e.getValue().toString() ); |
469 | if ( provider != null ) |
470 | { |
471 | providers.put( e.getKey().toString(), provider ); |
472 | } |
473 | } |
474 | } |
475 | } |
476 | |
477 | final Enumeration<URL> serviceProviders = |
478 | this.findResources( "META-INF/services/" + providerClass.getName() ); |
479 | |
480 | while ( serviceProviders.hasMoreElements() ) |
481 | { |
482 | final URL url = serviceProviders.nextElement(); |
483 | final BufferedReader reader = new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8" ) ); |
484 | |
485 | String line = null; |
486 | while ( ( line = reader.readLine() ) != null ) |
487 | { |
488 | if ( line.contains( "#" ) ) |
489 | { |
490 | continue; |
491 | } |
492 | |
493 | final Class<T> provider = this.findClass( line ); |
494 | if ( provider != null ) |
495 | { |
496 | providers.put( providerNamePrefix + providers.size(), provider ); |
497 | } |
498 | } |
499 | |
500 | reader.close(); |
501 | } |
502 | |
503 | return providers.values(); |
504 | } |
505 | catch ( final IOException e ) |
506 | { |
507 | throw new BootstrapException( e ); |
508 | } |
509 | } |
510 | |
511 | } |