1 | /* |
2 | * Copyright (C) Christian Schulte, 2014-005 |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * o Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * |
12 | * o Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in |
14 | * the documentation and/or other materials provided with the |
15 | * distribution. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
18 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
19 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
20 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, |
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | * |
28 | * $JOMC: DefaultModletProcessor.java 4871 2014-02-17 18:00:51Z schulte $ |
29 | * |
30 | */ |
31 | package org.jomc.modlet; |
32 | |
33 | import java.net.URISyntaxException; |
34 | import java.net.URL; |
35 | import java.text.MessageFormat; |
36 | import java.util.Enumeration; |
37 | import java.util.LinkedList; |
38 | import java.util.List; |
39 | import java.util.Locale; |
40 | import java.util.Map; |
41 | import java.util.ResourceBundle; |
42 | import java.util.logging.Level; |
43 | import javax.xml.bind.JAXBContext; |
44 | import javax.xml.bind.JAXBElement; |
45 | import javax.xml.bind.JAXBException; |
46 | import javax.xml.bind.util.JAXBResult; |
47 | import javax.xml.bind.util.JAXBSource; |
48 | import javax.xml.transform.ErrorListener; |
49 | import javax.xml.transform.Transformer; |
50 | import javax.xml.transform.TransformerConfigurationException; |
51 | import javax.xml.transform.TransformerException; |
52 | import javax.xml.transform.TransformerFactory; |
53 | import javax.xml.transform.stream.StreamSource; |
54 | |
55 | /** |
56 | * Default {@code ModletProcessor} implementation. |
57 | * |
58 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
59 | * @version $JOMC: DefaultModletProcessor.java 4871 2014-02-17 18:00:51Z schulte $ |
60 | * @see ModelContext#processModlets(org.jomc.modlet.Modlets) |
61 | * @since 1.6 |
62 | */ |
63 | public class DefaultModletProcessor implements ModletProcessor |
64 | { |
65 | |
66 | /** |
67 | * Constant for the name of the model context attribute backing property {@code enabled}. |
68 | * @see #processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets) |
69 | * @see ModelContext#getAttribute(java.lang.String) |
70 | */ |
71 | public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletProcessor.enabledAttribute"; |
72 | |
73 | /** |
74 | * Constant for the name of the system property controlling property {@code defaultEnabled}. |
75 | * @see #isDefaultEnabled() |
76 | */ |
77 | private static final String DEFAULT_ENABLED_PROPERTY_NAME = |
78 | "org.jomc.modlet.DefaultModletProcessor.defaultEnabled"; |
79 | |
80 | /** |
81 | * Default value of the flag indicating the processor is enabled by default. |
82 | * @see #isDefaultEnabled() |
83 | */ |
84 | private static final Boolean DEFAULT_ENABLED = Boolean.TRUE; |
85 | |
86 | /** Flag indicating the processor is enabled by default. */ |
87 | private static volatile Boolean defaultEnabled; |
88 | |
89 | /** Flag indicating the processor is enabled. */ |
90 | private Boolean enabled; |
91 | |
92 | /** |
93 | * Constant for the name of the system property controlling property {@code defaultOrdinal}. |
94 | * @see #getDefaultOrdinal() |
95 | */ |
96 | private static final String DEFAULT_ORDINAL_PROPERTY_NAME = |
97 | "org.jomc.modlet.DefaultModletProcessor.defaultOrdinal"; |
98 | |
99 | /** |
100 | * Default value of the ordinal number of the processor. |
101 | * @see #getDefaultOrdinal() |
102 | */ |
103 | private static final Integer DEFAULT_ORDINAL = 0; |
104 | |
105 | /** |
106 | * Default ordinal number of the processor. |
107 | */ |
108 | private static volatile Integer defaultOrdinal; |
109 | |
110 | /** |
111 | * Ordinal number of the processor. |
112 | */ |
113 | private Integer ordinal; |
114 | |
115 | /** |
116 | * Constant for the name of the model context attribute backing property {@code transformerLocation}. |
117 | * @see #processModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets) |
118 | * @see ModelContext#getAttribute(java.lang.String) |
119 | * @since 1.2 |
120 | */ |
121 | public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME = |
122 | "org.jomc.modlet.DefaultModletProcessor.transformerLocationAttribute"; |
123 | |
124 | /** |
125 | * Constant for the name of the system property controlling property {@code defaultTransformerLocation}. |
126 | * @see #getDefaultTransformerLocation() |
127 | */ |
128 | private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME = |
129 | "org.jomc.modlet.DefaultModletProcessor.defaultTransformerLocation"; |
130 | |
131 | /** |
132 | * Class path location searched for transformers by default. |
133 | * @see #getDefaultTransformerLocation() |
134 | */ |
135 | private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc-modlet.xsl"; |
136 | |
137 | /** Default transformer location. */ |
138 | private static volatile String defaultTransformerLocation; |
139 | |
140 | /** Transformer location of the instance. */ |
141 | private String transformerLocation; |
142 | |
143 | /** Creates a new {@code DefaultModletProcessor} instance. */ |
144 | public DefaultModletProcessor() |
145 | { |
146 | super(); |
147 | } |
148 | |
149 | /** |
150 | * Gets a flag indicating the processor is enabled by default. |
151 | * <p>The default enabled flag is controlled by system property |
152 | * {@code org.jomc.modlet.DefaultModletProcessor.defaultEnabled} holding a value indicating the processor is |
153 | * enabled by default. If that property is not set, the {@code true} default is returned.</p> |
154 | * |
155 | * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by |
156 | * default. |
157 | * |
158 | * @see #isEnabled() |
159 | * @see #setDefaultEnabled(java.lang.Boolean) |
160 | */ |
161 | public static boolean isDefaultEnabled() |
162 | { |
163 | if ( defaultEnabled == null ) |
164 | { |
165 | defaultEnabled = Boolean.valueOf( System.getProperty( |
166 | DEFAULT_ENABLED_PROPERTY_NAME, Boolean.toString( DEFAULT_ENABLED ) ) ); |
167 | |
168 | } |
169 | |
170 | return defaultEnabled; |
171 | } |
172 | |
173 | /** |
174 | * Sets the flag indicating the processor is enabled by default. |
175 | * |
176 | * @param value The new value of the flag indicating the processor is enabled by default or {@code null}. |
177 | * |
178 | * @see #isDefaultEnabled() |
179 | */ |
180 | public static void setDefaultEnabled( final Boolean value ) |
181 | { |
182 | defaultEnabled = value; |
183 | } |
184 | |
185 | /** |
186 | * Gets a flag indicating the processor is enabled. |
187 | * |
188 | * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled. |
189 | * |
190 | * @see #isDefaultEnabled() |
191 | * @see #setEnabled(java.lang.Boolean) |
192 | */ |
193 | public final boolean isEnabled() |
194 | { |
195 | if ( this.enabled == null ) |
196 | { |
197 | this.enabled = isDefaultEnabled(); |
198 | } |
199 | |
200 | return this.enabled; |
201 | } |
202 | |
203 | /** |
204 | * Sets the flag indicating the processor is enabled. |
205 | * |
206 | * @param value The new value of the flag indicating the processor is enabled or {@code null}. |
207 | * |
208 | * @see #isEnabled() |
209 | */ |
210 | public final void setEnabled( final Boolean value ) |
211 | { |
212 | this.enabled = value; |
213 | } |
214 | |
215 | /** |
216 | * Gets the default ordinal number of the processor. |
217 | * <p>The default ordinal number is controlled by system property |
218 | * {@code org.jomc.modlet.DefaultModletProvider.defaultOrdinal} holding the default ordinal number of the processor. |
219 | * If that property is not set, the {@code 0} default is returned.</p> |
220 | * |
221 | * @return The default ordinal number of the processor. |
222 | * |
223 | * @see #setDefaultOrdinal(java.lang.Integer) |
224 | */ |
225 | public static int getDefaultOrdinal() |
226 | { |
227 | if ( defaultOrdinal == null ) |
228 | { |
229 | defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL ); |
230 | } |
231 | |
232 | return defaultOrdinal; |
233 | } |
234 | |
235 | /** |
236 | * Sets the default ordinal number of the processor. |
237 | * |
238 | * @param value The new default ordinal number of the processor or {@code null}. |
239 | * |
240 | * @see #getDefaultOrdinal() |
241 | */ |
242 | public static void setDefaultOrdinal( final Integer value ) |
243 | { |
244 | defaultOrdinal = value; |
245 | } |
246 | |
247 | /** |
248 | * Gets the ordinal number of the processor. |
249 | * |
250 | * @return The ordinal number of the processor. |
251 | * |
252 | * @see #getDefaultOrdinal() |
253 | * @see #setOrdinal(java.lang.Integer) |
254 | */ |
255 | public final int getOrdinal() |
256 | { |
257 | if ( this.ordinal == null ) |
258 | { |
259 | this.ordinal = getDefaultOrdinal(); |
260 | } |
261 | |
262 | return this.ordinal; |
263 | } |
264 | |
265 | /** |
266 | * Sets the ordinal number of the processor. |
267 | * |
268 | * @param value The new ordinal number of the processor or {@code null}. |
269 | * |
270 | * @see #getOrdinal() |
271 | */ |
272 | public final void setOrdinal( final Integer value ) |
273 | { |
274 | this.ordinal = value; |
275 | } |
276 | |
277 | /** |
278 | * Gets the default location searched for transformer resources. |
279 | * <p>The default transformer location is controlled by system property |
280 | * {@code org.jomc.modlet.DefaultModletProcessor.defaultTransformerLocation} holding the location to search |
281 | * for transformer resources by default. If that property is not set, the {@code META-INF/jomc-modlet.xsl} default |
282 | * is returned.</p> |
283 | * |
284 | * @return The location searched for transformer resources by default. |
285 | * |
286 | * @see #setDefaultTransformerLocation(java.lang.String) |
287 | */ |
288 | public static String getDefaultTransformerLocation() |
289 | { |
290 | if ( defaultTransformerLocation == null ) |
291 | { |
292 | defaultTransformerLocation = |
293 | System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, DEFAULT_TRANSFORMER_LOCATION ); |
294 | |
295 | } |
296 | |
297 | return defaultTransformerLocation; |
298 | } |
299 | |
300 | /** |
301 | * Sets the default location searched for transformer resources. |
302 | * |
303 | * @param value The new default location to search for transformer resources or {@code null}. |
304 | * |
305 | * @see #getDefaultTransformerLocation() |
306 | */ |
307 | public static void setDefaultTransformerLocation( final String value ) |
308 | { |
309 | defaultTransformerLocation = value; |
310 | } |
311 | |
312 | /** |
313 | * Gets the location searched for transformer resources. |
314 | * |
315 | * @return The location searched for transformer resources. |
316 | * |
317 | * @see #getDefaultTransformerLocation() |
318 | * @see #setTransformerLocation(java.lang.String) |
319 | */ |
320 | public final String getTransformerLocation() |
321 | { |
322 | if ( this.transformerLocation == null ) |
323 | { |
324 | this.transformerLocation = getDefaultTransformerLocation(); |
325 | } |
326 | |
327 | return this.transformerLocation; |
328 | } |
329 | |
330 | /** |
331 | * Sets the location searched for transformer resources. |
332 | * |
333 | * @param value The new location to search for transformer resources or {@code null}. |
334 | * |
335 | * @see #getTransformerLocation() |
336 | */ |
337 | public final void setTransformerLocation( final String value ) |
338 | { |
339 | this.transformerLocation = value; |
340 | } |
341 | |
342 | /** |
343 | * Searches a given context for transformers. |
344 | * |
345 | * @param context The context to search for transformers. |
346 | * @param location The location to search at. |
347 | * |
348 | * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are |
349 | * found. |
350 | * |
351 | * @throws NullPointerException if {@code context} or {@code location} is {@code null}. |
352 | * @throws ModelException if getting the transformers fails. |
353 | */ |
354 | public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException |
355 | { |
356 | if ( context == null ) |
357 | { |
358 | throw new NullPointerException( "context" ); |
359 | } |
360 | if ( location == null ) |
361 | { |
362 | throw new NullPointerException( "location" ); |
363 | } |
364 | |
365 | try |
366 | { |
367 | final long t0 = System.currentTimeMillis(); |
368 | final List<Transformer> transformers = new LinkedList<Transformer>(); |
369 | final TransformerFactory transformerFactory = TransformerFactory.newInstance(); |
370 | final Enumeration<URL> resources = context.findResources( location ); |
371 | final ErrorListener errorListener = new ErrorListener() |
372 | { |
373 | |
374 | public void warning( final TransformerException exception ) throws TransformerException |
375 | { |
376 | if ( context.isLoggable( Level.WARNING ) ) |
377 | { |
378 | context.log( Level.WARNING, getMessage( exception ), exception ); |
379 | } |
380 | } |
381 | |
382 | public void error( final TransformerException exception ) throws TransformerException |
383 | { |
384 | if ( context.isLoggable( Level.SEVERE ) ) |
385 | { |
386 | context.log( Level.SEVERE, getMessage( exception ), exception ); |
387 | } |
388 | |
389 | throw exception; |
390 | } |
391 | |
392 | public void fatalError( final TransformerException exception ) throws TransformerException |
393 | { |
394 | if ( context.isLoggable( Level.SEVERE ) ) |
395 | { |
396 | context.log( Level.SEVERE, getMessage( exception ), exception ); |
397 | } |
398 | |
399 | throw exception; |
400 | } |
401 | |
402 | }; |
403 | |
404 | transformerFactory.setErrorListener( errorListener ); |
405 | |
406 | int count = 0; |
407 | while ( resources.hasMoreElements() ) |
408 | { |
409 | count++; |
410 | final URL url = resources.nextElement(); |
411 | |
412 | if ( context.isLoggable( Level.FINEST ) ) |
413 | { |
414 | context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null ); |
415 | } |
416 | |
417 | final Transformer transformer = |
418 | transformerFactory.newTransformer( new StreamSource( url.toURI().toASCIIString() ) ); |
419 | |
420 | transformer.setErrorListener( errorListener ); |
421 | |
422 | for ( Map.Entry<Object, Object> e : System.getProperties().entrySet() ) |
423 | { |
424 | transformer.setParameter( e.getKey().toString(), e.getValue() ); |
425 | } |
426 | |
427 | transformers.add( transformer ); |
428 | } |
429 | |
430 | if ( context.isLoggable( Level.FINE ) ) |
431 | { |
432 | context.log( Level.FINE, getMessage( "contextReport", count, location, |
433 | Long.valueOf( System.currentTimeMillis() - t0 ) ), null ); |
434 | |
435 | } |
436 | |
437 | return transformers.isEmpty() ? null : transformers; |
438 | } |
439 | catch ( final URISyntaxException e ) |
440 | { |
441 | throw new ModelException( getMessage( e ), e ); |
442 | } |
443 | catch ( final TransformerConfigurationException e ) |
444 | { |
445 | String message = getMessage( e ); |
446 | if ( message == null && e.getException() != null ) |
447 | { |
448 | message = getMessage( e.getException() ); |
449 | } |
450 | |
451 | throw new ModelException( message, e ); |
452 | } |
453 | } |
454 | |
455 | /** |
456 | * {@inheritDoc} |
457 | * |
458 | * @see #isEnabled() |
459 | * @see #getTransformerLocation() |
460 | * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String) |
461 | * @see #ENABLED_ATTRIBUTE_NAME |
462 | * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME |
463 | */ |
464 | public Modlets processModlets( final ModelContext context, final Modlets modlets ) throws ModelException |
465 | { |
466 | if ( context == null ) |
467 | { |
468 | throw new NullPointerException( "context" ); |
469 | } |
470 | if ( modlets == null ) |
471 | { |
472 | throw new NullPointerException( "modlets" ); |
473 | } |
474 | |
475 | try |
476 | { |
477 | Modlets processed = null; |
478 | |
479 | boolean contextEnabled = this.isEnabled(); |
480 | if ( DEFAULT_ENABLED == contextEnabled |
481 | && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean ) |
482 | { |
483 | contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME ); |
484 | } |
485 | |
486 | String contextTransformerLocation = this.getTransformerLocation(); |
487 | if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation ) |
488 | && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String ) |
489 | { |
490 | contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ); |
491 | } |
492 | |
493 | if ( contextEnabled ) |
494 | { |
495 | final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory(); |
496 | final JAXBContext jaxbContext = context.createContext( ModletObject.MODEL_PUBLIC_ID ); |
497 | final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation ); |
498 | |
499 | if ( transformers != null ) |
500 | { |
501 | processed = modlets.clone(); |
502 | |
503 | for ( int i = 0, s0 = transformers.size(); i < s0; i++ ) |
504 | { |
505 | final JAXBElement<Modlets> e = objectFactory.createModlets( processed ); |
506 | final JAXBSource source = new JAXBSource( jaxbContext, e ); |
507 | final JAXBResult result = new JAXBResult( jaxbContext ); |
508 | transformers.get( i ).transform( source, result ); |
509 | |
510 | if ( result.getResult() instanceof JAXBElement<?> |
511 | && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Modlets ) |
512 | { |
513 | processed = (Modlets) ( (JAXBElement<?>) result.getResult() ).getValue(); |
514 | } |
515 | else |
516 | { |
517 | throw new ModelException( getMessage( "illegalTransformationResult" ) ); |
518 | } |
519 | } |
520 | } |
521 | } |
522 | else if ( context.isLoggable( Level.FINER ) ) |
523 | { |
524 | context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName() ), null ); |
525 | } |
526 | |
527 | return processed; |
528 | } |
529 | catch ( final TransformerException e ) |
530 | { |
531 | String message = getMessage( e ); |
532 | if ( message == null && e.getException() != null ) |
533 | { |
534 | message = getMessage( e.getException() ); |
535 | } |
536 | |
537 | throw new ModelException( message, e ); |
538 | } |
539 | catch ( final JAXBException e ) |
540 | { |
541 | String message = getMessage( e ); |
542 | if ( message == null && e.getLinkedException() != null ) |
543 | { |
544 | message = getMessage( e.getLinkedException() ); |
545 | } |
546 | |
547 | throw new ModelException( message, e ); |
548 | } |
549 | } |
550 | |
551 | private static String getMessage( final String key, final Object... args ) |
552 | { |
553 | return MessageFormat.format( ResourceBundle.getBundle( |
554 | DefaultModletProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args ); |
555 | |
556 | } |
557 | |
558 | private static String getMessage( final Throwable t ) |
559 | { |
560 | return t != null |
561 | ? t.getMessage() != null && t.getMessage().trim().length() > 0 |
562 | ? t.getMessage() |
563 | : getMessage( t.getCause() ) |
564 | : null; |
565 | |
566 | } |
567 | |
568 | } |