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: ModelContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
31 | * |
32 | */ |
33 | package org.jomc.model; |
34 | |
35 | import java.io.IOException; |
36 | import java.lang.reflect.Constructor; |
37 | import java.lang.reflect.InvocationTargetException; |
38 | import java.net.URL; |
39 | import java.text.MessageFormat; |
40 | import java.util.Enumeration; |
41 | import java.util.LinkedList; |
42 | import java.util.List; |
43 | import java.util.Locale; |
44 | import java.util.ResourceBundle; |
45 | import java.util.logging.Level; |
46 | import javax.xml.bind.JAXBContext; |
47 | import javax.xml.bind.Marshaller; |
48 | import javax.xml.bind.Unmarshaller; |
49 | import javax.xml.transform.Source; |
50 | import javax.xml.validation.Schema; |
51 | import org.w3c.dom.ls.LSResourceResolver; |
52 | import org.xml.sax.EntityResolver; |
53 | |
54 | /** |
55 | * Object management and configuration model context interface. |
56 | * |
57 | * @author <a href="mailto:cs@jomc.org">Christian Schulte</a> |
58 | * @version $Id: ModelContext.java 1393 2010-01-27 18:34:30Z schulte2005 $ |
59 | * @see #createModelContext(java.lang.ClassLoader) |
60 | */ |
61 | public abstract class ModelContext |
62 | { |
63 | |
64 | /** Listener interface. */ |
65 | public abstract static class Listener |
66 | { |
67 | |
68 | /** |
69 | * Get called on logging. |
70 | * |
71 | * @param level The level of the event. |
72 | * @param message The message of the event or {@code null}. |
73 | * @param t The throwable of the event or {@code null}. |
74 | * |
75 | * @throws NullPointerException if {@code level} is {@code null}. |
76 | */ |
77 | public abstract void onLog( Level level, String message, Throwable t ); |
78 | |
79 | } |
80 | |
81 | /** |
82 | * Log level events are logged at by default. |
83 | * @see #getDefaultLogLevel() |
84 | */ |
85 | private static final Level DEFAULT_LOG_LEVEL = Level.WARNING; |
86 | |
87 | /** Default log level. */ |
88 | private static volatile Level defaultLogLevel; |
89 | |
90 | /** Class name of the {@code ModelContext} implementation. */ |
91 | private static volatile String modelContextClassName; |
92 | |
93 | /** The listeners of the instance. */ |
94 | private List<Listener> listeners; |
95 | |
96 | /** Log level of the instance. */ |
97 | private Level logLevel; |
98 | |
99 | /** The class loader of the context. */ |
100 | private ClassLoader classLoader; |
101 | |
102 | /** |
103 | * Creates a new {@code ModelContext} instance taking a class loader. |
104 | * |
105 | * @param classLoader The class loader of the context. |
106 | */ |
107 | public ModelContext( final ClassLoader classLoader ) |
108 | { |
109 | super(); |
110 | this.classLoader = classLoader; |
111 | } |
112 | |
113 | /** |
114 | * Gets the class loader of the context. |
115 | * |
116 | * @return The class loader of the context. |
117 | */ |
118 | public ClassLoader getClassLoader() |
119 | { |
120 | if ( this.classLoader == null ) |
121 | { |
122 | this.classLoader = new ClassLoader( null ) |
123 | { |
124 | |
125 | @Override |
126 | public String toString() |
127 | { |
128 | return ModelContext.class.getName() + ".BootstrapClassLoader@" + System.identityHashCode( this ); |
129 | } |
130 | |
131 | }; |
132 | |
133 | } |
134 | |
135 | return this.classLoader; |
136 | } |
137 | |
138 | /** |
139 | * Gets the list of registered listeners. |
140 | * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make |
141 | * to the returned list will be present inside the object. This is why there is no {@code set} method for the |
142 | * listeners property.</p> |
143 | * |
144 | * @return The list of registered listeners. |
145 | * |
146 | * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable) |
147 | */ |
148 | public List<Listener> getListeners() |
149 | { |
150 | if ( this.listeners == null ) |
151 | { |
152 | this.listeners = new LinkedList<Listener>(); |
153 | } |
154 | |
155 | return this.listeners; |
156 | } |
157 | |
158 | /** |
159 | * Gets the default log level events are logged at. |
160 | * <p>The default log level is controlled by system property |
161 | * {@code org.jomc.model.ModelContext.defaultLogLevel} holding the log level to log events at by default. |
162 | * If that property is not set, the {@code WARNING} default is returned.</p> |
163 | * |
164 | * @return The log level events are logged at by default. |
165 | * |
166 | * @see #getLogLevel() |
167 | * @see Level#parse(java.lang.String) |
168 | */ |
169 | public static Level getDefaultLogLevel() |
170 | { |
171 | if ( defaultLogLevel == null ) |
172 | { |
173 | defaultLogLevel = Level.parse( System.getProperty( |
174 | "org.jomc.model.ModelContext.defaultLogLevel", DEFAULT_LOG_LEVEL.getName() ) ); |
175 | |
176 | } |
177 | |
178 | return defaultLogLevel; |
179 | } |
180 | |
181 | /** |
182 | * Sets the default log level events are logged at. |
183 | * |
184 | * @param value The new default level events are logged at or {@code null}. |
185 | * |
186 | * @see #getDefaultLogLevel() |
187 | */ |
188 | public static void setDefaultLogLevel( final Level value ) |
189 | { |
190 | defaultLogLevel = value; |
191 | } |
192 | |
193 | /** |
194 | * Gets the log level of the instance. |
195 | * |
196 | * @return The log level of the instance. |
197 | * |
198 | * @see #getDefaultLogLevel() |
199 | * @see #setLogLevel(java.util.logging.Level) |
200 | * @see #isLoggable(java.util.logging.Level) |
201 | */ |
202 | public Level getLogLevel() |
203 | { |
204 | if ( this.logLevel == null ) |
205 | { |
206 | this.logLevel = getDefaultLogLevel(); |
207 | this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.getClass().getCanonicalName(), |
208 | this.logLevel.getLocalizedName() ), null ); |
209 | |
210 | } |
211 | |
212 | return this.logLevel; |
213 | } |
214 | |
215 | /** |
216 | * Sets the log level of the instance. |
217 | * |
218 | * @param value The new log level of the instance or {@code null}. |
219 | * |
220 | * @see #getLogLevel() |
221 | * @see #isLoggable(java.util.logging.Level) |
222 | */ |
223 | public void setLogLevel( final Level value ) |
224 | { |
225 | this.logLevel = value; |
226 | } |
227 | |
228 | /** |
229 | * Checks if a message at a given level is provided to the listeners of the instance. |
230 | * |
231 | * @param level The level to test. |
232 | * |
233 | * @return {@code true} if messages at {@code level} are provided to the listeners of the instance; |
234 | * {@code false} if messages at {@code level} are not provided to the listeners of the instance. |
235 | * |
236 | * @throws NullPointerException if {@code level} is {@code null}. |
237 | * |
238 | * @see #getLogLevel() |
239 | * @see #setLogLevel(java.util.logging.Level) |
240 | */ |
241 | public boolean isLoggable( final Level level ) |
242 | { |
243 | if ( level == null ) |
244 | { |
245 | throw new NullPointerException( "level" ); |
246 | } |
247 | |
248 | return level.intValue() >= this.getLogLevel().intValue(); |
249 | } |
250 | |
251 | /** |
252 | * Notifies registered listeners. |
253 | * |
254 | * @param level The level of the event. |
255 | * @param message The message of the event or {@code null}. |
256 | * @param throwable The throwable of the event {@code null}. |
257 | * |
258 | * @throws NullPointerException if {@code level} is {@code null}. |
259 | * |
260 | * @see #getListeners() |
261 | * @see #isLoggable(java.util.logging.Level) |
262 | */ |
263 | public void log( final Level level, final String message, final Throwable throwable ) |
264 | { |
265 | if ( level == null ) |
266 | { |
267 | throw new NullPointerException( "level" ); |
268 | } |
269 | |
270 | if ( this.isLoggable( level ) ) |
271 | { |
272 | for ( Listener l : this.getListeners() ) |
273 | { |
274 | l.onLog( level, message, throwable ); |
275 | } |
276 | } |
277 | } |
278 | |
279 | /** |
280 | * Gets the name of the class providing the {@code ModelContext} implementation. |
281 | * <p>The name of the class providing the {@code ModelContext} implementation returned by method |
282 | * {@link #createModelContext(java.lang.ClassLoader)} is controlled by system property |
283 | * {@code org.jomc.model.ModelContext.className}. If that property is not set, the name of the |
284 | * {@link DefaultModelContext} class is returned.</p> |
285 | * |
286 | * @return The name of the class providing the {@code ModelContext} implementation. |
287 | * |
288 | * @see #setModelContextClassName(java.lang.String) |
289 | */ |
290 | public static String getModelContextClassName() |
291 | { |
292 | if ( modelContextClassName == null ) |
293 | { |
294 | modelContextClassName = System.getProperty( "org.jomc.model.ModelContext.className", |
295 | DefaultModelContext.class.getName() ); |
296 | |
297 | } |
298 | |
299 | return modelContextClassName; |
300 | } |
301 | |
302 | /** |
303 | * Sets the name of the class providing the ModelContext implementation. |
304 | * |
305 | * @param value The new name of the class providing the ModelContext implementation or {@code null}. |
306 | * |
307 | * @see #getModelContextClassName() |
308 | */ |
309 | public static void setModelContextClassName( final String value ) |
310 | { |
311 | modelContextClassName = value; |
312 | } |
313 | |
314 | /** |
315 | * Searches the context for a class with a given name. |
316 | * |
317 | * @param name The name of the class to return. |
318 | * |
319 | * @return A Class object of the class with name {@code name} or {@code null} if no such class is found. |
320 | * |
321 | * @throws NullPointerException if {@code name} is {@code null}. |
322 | * @throws ModelException if searching fails. |
323 | */ |
324 | public Class findClass( final String name ) throws ModelException |
325 | { |
326 | if ( name == null ) |
327 | { |
328 | throw new NullPointerException( "name" ); |
329 | } |
330 | |
331 | try |
332 | { |
333 | return Class.forName( name, true, this.getClassLoader() ); |
334 | } |
335 | catch ( final ClassNotFoundException e ) |
336 | { |
337 | if ( this.isLoggable( Level.FINE ) ) |
338 | { |
339 | this.log( Level.FINE, getMessage( "classNotFound", name ), e ); |
340 | } |
341 | |
342 | return null; |
343 | } |
344 | } |
345 | |
346 | /** |
347 | * Searches the context for a resource with a given name. |
348 | * |
349 | * @param name The name of the resource to return. |
350 | * |
351 | * @return An URL object for reading the resource or {@code null} if no such resource is found. |
352 | * |
353 | * @throws NullPointerException if {@code name} is {@code null}. |
354 | * @throws ModelException if searching fails. |
355 | */ |
356 | public URL findResource( final String name ) throws ModelException |
357 | { |
358 | if ( name == null ) |
359 | { |
360 | throw new NullPointerException( "name" ); |
361 | } |
362 | |
363 | return this.getClassLoader().getResource( name ); |
364 | } |
365 | |
366 | /** |
367 | * Searches the context for resources with a given name. |
368 | * |
369 | * @param name The name of the resources to return. |
370 | * |
371 | * @return An enumeration of URL objects for reading the resources. If no resources are found, the enumeration will |
372 | * be empty. |
373 | * |
374 | * @throws NullPointerException if {@code name} is {@code null}. |
375 | * @throws ModelException if searching fails. |
376 | */ |
377 | public Enumeration<URL> findResources( final String name ) throws ModelException |
378 | { |
379 | if ( name == null ) |
380 | { |
381 | throw new NullPointerException( "name" ); |
382 | } |
383 | |
384 | try |
385 | { |
386 | return this.getClassLoader().getResources( name ); |
387 | } |
388 | catch ( final IOException e ) |
389 | { |
390 | throw new ModelException( e ); |
391 | } |
392 | } |
393 | |
394 | /** |
395 | * Searches the context for modules. |
396 | * |
397 | * @return The modules found in the context. |
398 | * |
399 | * @throws ModelException if searching modules fails. |
400 | */ |
401 | public abstract Modules findModules() throws ModelException; |
402 | |
403 | /** |
404 | * Processes modules. |
405 | * |
406 | * @param modules The modules to process. |
407 | * |
408 | * @return The processed modules. |
409 | * |
410 | * @throws NullPointerException if {@code modules} is {@code null}. |
411 | * @throws ModelException if processing modules fails. |
412 | */ |
413 | public abstract Modules processModules( final Modules modules ) throws ModelException; |
414 | |
415 | /** |
416 | * Validates a given model. |
417 | * |
418 | * @param model A source providing the model to validate. |
419 | * |
420 | * @return Validation report. |
421 | * |
422 | * @throws NullPointerException if {@code model} is {@code null}. |
423 | * @throws ModelException if validating the model fails. |
424 | */ |
425 | public abstract ModelValidationReport validateModel( final Source model ) throws ModelException; |
426 | |
427 | /** |
428 | * Validates a given list of modules. |
429 | * |
430 | * @param modules The list of modules to validate. |
431 | * |
432 | * @return Validation report. |
433 | * |
434 | * @throws NullPointerException if {@code modules} is {@code null}. |
435 | * @throws ModelException if validating the modules fails. |
436 | */ |
437 | public abstract ModelValidationReport validateModel( final Modules modules ) throws ModelException; |
438 | |
439 | /** |
440 | * Creates a new object management and configuration model {@code ModelContext} instance. |
441 | * |
442 | * @param classLoader The class loader to create a new object management and configuration model context instance |
443 | * with or {@code null} to create a new context using the platform's bootstrap class loader. |
444 | * |
445 | * @return A new {@code ModelContext} instance. |
446 | * |
447 | * @throws ModelException if creating a new object management and configuration model context instance fails. |
448 | * |
449 | * @see #getModelContextClassName() |
450 | */ |
451 | public static ModelContext createModelContext( final ClassLoader classLoader ) throws ModelException |
452 | { |
453 | if ( DefaultModelContext.class.getName().equals( getModelContextClassName() ) ) |
454 | { |
455 | return new DefaultModelContext( classLoader ); |
456 | } |
457 | |
458 | try |
459 | { |
460 | final Class clazz = Class.forName( getModelContextClassName(), true, classLoader ); |
461 | final Constructor ctor = clazz.getConstructor( ClassLoader.class ); |
462 | return (ModelContext) ctor.newInstance( classLoader ); |
463 | } |
464 | catch ( final ClassNotFoundException e ) |
465 | { |
466 | throw new ModelException( e ); |
467 | } |
468 | catch ( final NoSuchMethodException e ) |
469 | { |
470 | throw new ModelException( e ); |
471 | } |
472 | catch ( final InstantiationException e ) |
473 | { |
474 | throw new ModelException( e ); |
475 | } |
476 | catch ( final IllegalAccessException e ) |
477 | { |
478 | throw new ModelException( e ); |
479 | } |
480 | catch ( final InvocationTargetException e ) |
481 | { |
482 | throw new ModelException( e ); |
483 | } |
484 | catch ( final ClassCastException e ) |
485 | { |
486 | throw new ModelException( e ); |
487 | } |
488 | } |
489 | |
490 | /** |
491 | * Creates a new object management and configuration model SAX entity resolver instance. |
492 | * |
493 | * @return A new object management and configuration model SAX entity resolver instance. |
494 | * |
495 | * @throws ModelException if creating a new object management and configuration model SAX entity resolver instance |
496 | * fails. |
497 | */ |
498 | public abstract EntityResolver createEntityResolver() throws ModelException; |
499 | |
500 | /** |
501 | * Creates a new object management and configuration model L/S resource resolver instance. |
502 | * |
503 | * @return A new object management and configuration model L/S resource resolver instance. |
504 | * |
505 | * @throws ModelException if creating a new object management and configuration model L/S resource resolver instance |
506 | * fails. |
507 | */ |
508 | public abstract LSResourceResolver createResourceResolver() throws ModelException; |
509 | |
510 | /** |
511 | * Creates a new object management and configuration model JAXP schema instance. |
512 | * |
513 | * @return A new object management and configuration model JAXP schema instance. |
514 | * |
515 | * @throws ModelException if creating a new object management and configuration model JAXP schema instance fails. |
516 | */ |
517 | public abstract Schema createSchema() throws ModelException; |
518 | |
519 | /** |
520 | * Creates a new object management and configuration model JAXB context instance. |
521 | * |
522 | * @return A new object management and configuration model JAXB context instance. |
523 | * |
524 | * @throws ModelException if creating a new object management and configuration model JAXB context instance fails. |
525 | */ |
526 | public abstract JAXBContext createContext() throws ModelException; |
527 | |
528 | /** |
529 | * Creates a new object management and configuration model JAXB marshaller instance. |
530 | * |
531 | * @return A new object management and configuration model JAXB marshaller instance. |
532 | * |
533 | * @throws ModelException if creating a new object management and configuration model JAXB marshaller instance |
534 | * fails. |
535 | */ |
536 | public abstract Marshaller createMarshaller() throws ModelException; |
537 | |
538 | /** |
539 | * Creates a new object management and configuration model JAXB unmarshaller instance. |
540 | * |
541 | * @return A new object management and configuration model JAXB unmarshaller instance. |
542 | * |
543 | * @throws ModelException if creating a new object management and configuration model JAXB unmarshaller instance |
544 | * fails. |
545 | */ |
546 | public abstract Unmarshaller createUnmarshaller() throws ModelException; |
547 | |
548 | private static String getMessage( final String key, final Object... args ) |
549 | { |
550 | return MessageFormat.format( ResourceBundle.getBundle( ModelContext.class.getName().replace( '.', '/' ), |
551 | Locale.getDefault() ).getString( key ), args ); |
552 | |
553 | } |
554 | |
555 | } |