1 | /* |
2 | * Copyright (C) Christian Schulte, 2005-206 |
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: DefaultModelProcessor.java 4613 2012-09-22 10:07:08Z schulte $ |
29 | * |
30 | */ |
31 | package org.jomc.model.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 | import org.jomc.modlet.Model; |
55 | import org.jomc.modlet.ModelContext; |
56 | import org.jomc.modlet.ModelException; |
57 | import org.jomc.modlet.ModelProcessor; |
58 | |
59 | /** |
60 | * Default object management and configuration {@code ModelProcessor} implementation. |
61 | * |
62 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
63 | * @version $JOMC: DefaultModelProcessor.java 4613 2012-09-22 10:07:08Z schulte $ |
64 | * @see ModelContext#processModel(org.jomc.modlet.Model) |
65 | */ |
66 | public class DefaultModelProcessor implements ModelProcessor |
67 | { |
68 | |
69 | /** |
70 | * Constant for the name of the model context attribute backing property {@code enabled}. |
71 | * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) |
72 | * @see ModelContext#getAttribute(java.lang.String) |
73 | * @since 1.2 |
74 | */ |
75 | public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.model.modlet.DefaultModelProcessor.enabledAttribute"; |
76 | |
77 | /** |
78 | * Constant for the name of the system property controlling property {@code defaultEnabled}. |
79 | * @see #isDefaultEnabled() |
80 | */ |
81 | private static final String DEFAULT_ENABLED_PROPERTY_NAME = |
82 | "org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled"; |
83 | |
84 | /** |
85 | * Constant for the name of the deprecated system property controlling property {@code defaultEnabled}. |
86 | * @see #isDefaultEnabled() |
87 | */ |
88 | private static final String DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME = |
89 | "org.jomc.model.DefaultModelProcessor.defaultEnabled"; |
90 | |
91 | /** |
92 | * Default value of the flag indicating the processor is enabled by default. |
93 | * @see #isDefaultEnabled() |
94 | * @since 1.2 |
95 | */ |
96 | private static final Boolean DEFAULT_ENABLED = Boolean.TRUE; |
97 | |
98 | /** Flag indicating the processor is enabled by default. */ |
99 | private static volatile Boolean defaultEnabled; |
100 | |
101 | /** Flag indicating the processor is enabled. */ |
102 | private Boolean enabled; |
103 | |
104 | /** |
105 | * Constant for the name of the model context attribute backing property {@code transformerLocation}. |
106 | * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model) |
107 | * @see ModelContext#getAttribute(java.lang.String) |
108 | * @since 1.2 |
109 | */ |
110 | public static final String TRANSFORMER_LOCATION_ATTRIBUTE_NAME = |
111 | "org.jomc.model.modlet.DefaultModelProcessor.transformerLocationAttribute"; |
112 | |
113 | /** |
114 | * Constant for the name of the system property controlling property {@code defaultTransformerLocation}. |
115 | * @see #getDefaultTransformerLocation() |
116 | */ |
117 | private static final String DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME = |
118 | "org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation"; |
119 | |
120 | /** |
121 | * Constant for the name of the deprecated system property controlling property {@code defaultTransformerLocation}. |
122 | * @see #getDefaultTransformerLocation() |
123 | */ |
124 | private static final String DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME = |
125 | "org.jomc.model.DefaultModelProcessor.defaultTransformerLocation"; |
126 | |
127 | /** |
128 | * Class path location searched for transformers by default. |
129 | * @see #getDefaultTransformerLocation() |
130 | */ |
131 | private static final String DEFAULT_TRANSFORMER_LOCATION = "META-INF/jomc.xsl"; |
132 | |
133 | /** Default transformer location. */ |
134 | private static volatile String defaultTransformerLocation; |
135 | |
136 | /** Transformer location of the instance. */ |
137 | private String transformerLocation; |
138 | |
139 | /** Creates a new {@code DefaultModelProcessor} instance. */ |
140 | public DefaultModelProcessor() |
141 | { |
142 | super(); |
143 | } |
144 | |
145 | /** |
146 | * Gets a flag indicating the processor is enabled by default. |
147 | * <p>The default enabled flag is controlled by system property |
148 | * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultEnabled} holding a value indicating the processor is |
149 | * enabled by default. If that property is not set, the {@code true} default is returned.</p> |
150 | * |
151 | * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by |
152 | * default. |
153 | * |
154 | * @see #setDefaultEnabled(java.lang.Boolean) |
155 | */ |
156 | public static boolean isDefaultEnabled() |
157 | { |
158 | if ( defaultEnabled == null ) |
159 | { |
160 | defaultEnabled = |
161 | Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME, |
162 | System.getProperty( DEPRECATED_DEFAULT_ENABLED_PROPERTY_NAME, |
163 | Boolean.toString( DEFAULT_ENABLED ) ) ) ); |
164 | |
165 | } |
166 | |
167 | return defaultEnabled; |
168 | } |
169 | |
170 | /** |
171 | * Sets the flag indicating the processor is enabled by default. |
172 | * |
173 | * @param value The new value of the flag indicating the processor is enabled by default or {@code null}. |
174 | * |
175 | * @see #isDefaultEnabled() |
176 | */ |
177 | public static void setDefaultEnabled( final Boolean value ) |
178 | { |
179 | defaultEnabled = value; |
180 | } |
181 | |
182 | /** |
183 | * Gets a flag indicating the processor is enabled. |
184 | * |
185 | * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled. |
186 | * |
187 | * @see #isDefaultEnabled() |
188 | * @see #setEnabled(java.lang.Boolean) |
189 | */ |
190 | public final boolean isEnabled() |
191 | { |
192 | if ( this.enabled == null ) |
193 | { |
194 | this.enabled = isDefaultEnabled(); |
195 | } |
196 | |
197 | return this.enabled; |
198 | } |
199 | |
200 | /** |
201 | * Sets the flag indicating the processor is enabled. |
202 | * |
203 | * @param value The new value of the flag indicating the processor is enabled or {@code null}. |
204 | * |
205 | * @see #isEnabled() |
206 | */ |
207 | public final void setEnabled( final Boolean value ) |
208 | { |
209 | this.enabled = value; |
210 | } |
211 | |
212 | /** |
213 | * Gets the default location searched for transformer resources. |
214 | * <p>The default transformer location is controlled by system property |
215 | * {@code org.jomc.model.modlet.DefaultModelProcessor.defaultTransformerLocation} holding the location to search for |
216 | * transformer resources by default. If that property is not set, the {@code META-INF/jomc.xsl} default is |
217 | * returned.</p> |
218 | * |
219 | * @return The location searched for transformer resources by default. |
220 | * |
221 | * @see #setDefaultTransformerLocation(java.lang.String) |
222 | */ |
223 | public static String getDefaultTransformerLocation() |
224 | { |
225 | if ( defaultTransformerLocation == null ) |
226 | { |
227 | defaultTransformerLocation = |
228 | System.getProperty( DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, |
229 | System.getProperty( DEPRECATED_DEFAULT_TRANSFORMER_LOCATION_PROPERTY_NAME, |
230 | DEFAULT_TRANSFORMER_LOCATION ) ); |
231 | |
232 | } |
233 | |
234 | return defaultTransformerLocation; |
235 | } |
236 | |
237 | /** |
238 | * Sets the default location searched for transformer resources. |
239 | * |
240 | * @param value The new default location to search for transformer resources or {@code null}. |
241 | * |
242 | * @see #getDefaultTransformerLocation() |
243 | */ |
244 | public static void setDefaultTransformerLocation( final String value ) |
245 | { |
246 | defaultTransformerLocation = value; |
247 | } |
248 | |
249 | /** |
250 | * Gets the location searched for transformer resources. |
251 | * |
252 | * @return The location searched for transformer resources. |
253 | * |
254 | * @see #getDefaultTransformerLocation() |
255 | * @see #setTransformerLocation(java.lang.String) |
256 | */ |
257 | public final String getTransformerLocation() |
258 | { |
259 | if ( this.transformerLocation == null ) |
260 | { |
261 | this.transformerLocation = getDefaultTransformerLocation(); |
262 | } |
263 | |
264 | return this.transformerLocation; |
265 | } |
266 | |
267 | /** |
268 | * Sets the location searched for transformer resources. |
269 | * |
270 | * @param value The new location to search for transformer resources or {@code null}. |
271 | * |
272 | * @see #getTransformerLocation() |
273 | */ |
274 | public final void setTransformerLocation( final String value ) |
275 | { |
276 | this.transformerLocation = value; |
277 | } |
278 | |
279 | /** |
280 | * Searches a given context for transformers. |
281 | * |
282 | * @param context The context to search for transformers. |
283 | * @param location The location to search at. |
284 | * |
285 | * @return The transformers found at {@code location} in {@code context} or {@code null}, if no transformers are |
286 | * found. |
287 | * |
288 | * @throws NullPointerException if {@code context} or {@code location} is {@code null}. |
289 | * @throws ModelException if getting the transformers fails. |
290 | */ |
291 | public List<Transformer> findTransformers( final ModelContext context, final String location ) throws ModelException |
292 | { |
293 | if ( context == null ) |
294 | { |
295 | throw new NullPointerException( "context" ); |
296 | } |
297 | if ( location == null ) |
298 | { |
299 | throw new NullPointerException( "location" ); |
300 | } |
301 | |
302 | try |
303 | { |
304 | final long t0 = System.currentTimeMillis(); |
305 | final List<Transformer> transformers = new LinkedList<Transformer>(); |
306 | final TransformerFactory transformerFactory = TransformerFactory.newInstance(); |
307 | final Enumeration<URL> resources = context.findResources( location ); |
308 | final ErrorListener errorListener = new ErrorListener() |
309 | { |
310 | |
311 | public void warning( final TransformerException exception ) throws TransformerException |
312 | { |
313 | if ( context.isLoggable( Level.WARNING ) ) |
314 | { |
315 | context.log( Level.WARNING, getMessage( exception ), exception ); |
316 | } |
317 | } |
318 | |
319 | public void error( final TransformerException exception ) throws TransformerException |
320 | { |
321 | if ( context.isLoggable( Level.SEVERE ) ) |
322 | { |
323 | context.log( Level.SEVERE, getMessage( exception ), exception ); |
324 | } |
325 | |
326 | throw exception; |
327 | } |
328 | |
329 | public void fatalError( final TransformerException exception ) throws TransformerException |
330 | { |
331 | if ( context.isLoggable( Level.SEVERE ) ) |
332 | { |
333 | context.log( Level.SEVERE, getMessage( exception ), exception ); |
334 | } |
335 | |
336 | throw exception; |
337 | } |
338 | |
339 | }; |
340 | |
341 | transformerFactory.setErrorListener( errorListener ); |
342 | |
343 | int count = 0; |
344 | while ( resources.hasMoreElements() ) |
345 | { |
346 | count++; |
347 | final URL url = resources.nextElement(); |
348 | |
349 | if ( context.isLoggable( Level.FINEST ) ) |
350 | { |
351 | context.log( Level.FINEST, getMessage( "processing", url.toExternalForm() ), null ); |
352 | } |
353 | |
354 | final Transformer transformer = |
355 | transformerFactory.newTransformer( new StreamSource( url.toURI().toASCIIString() ) ); |
356 | |
357 | transformer.setErrorListener( errorListener ); |
358 | |
359 | for ( Map.Entry<Object, Object> e : System.getProperties().entrySet() ) |
360 | { |
361 | transformer.setParameter( e.getKey().toString(), e.getValue() ); |
362 | } |
363 | |
364 | transformers.add( transformer ); |
365 | } |
366 | |
367 | if ( context.isLoggable( Level.FINE ) ) |
368 | { |
369 | context.log( Level.FINE, getMessage( "contextReport", count, location, |
370 | Long.valueOf( System.currentTimeMillis() - t0 ) ), null ); |
371 | |
372 | } |
373 | |
374 | return transformers.isEmpty() ? null : transformers; |
375 | } |
376 | catch ( final URISyntaxException e ) |
377 | { |
378 | throw new ModelException( getMessage( e ), e ); |
379 | } |
380 | catch ( final TransformerConfigurationException e ) |
381 | { |
382 | String message = getMessage( e ); |
383 | if ( message == null && e.getException() != null ) |
384 | { |
385 | message = getMessage( e.getException() ); |
386 | } |
387 | |
388 | throw new ModelException( message, e ); |
389 | } |
390 | } |
391 | |
392 | /** |
393 | * {@inheritDoc} |
394 | * |
395 | * @see #isEnabled() |
396 | * @see #getTransformerLocation() |
397 | * @see #findTransformers(org.jomc.modlet.ModelContext, java.lang.String) |
398 | * @see #ENABLED_ATTRIBUTE_NAME |
399 | * @see #TRANSFORMER_LOCATION_ATTRIBUTE_NAME |
400 | */ |
401 | public Model processModel( final ModelContext context, final Model model ) throws ModelException |
402 | { |
403 | if ( context == null ) |
404 | { |
405 | throw new NullPointerException( "context" ); |
406 | } |
407 | if ( model == null ) |
408 | { |
409 | throw new NullPointerException( "model" ); |
410 | } |
411 | |
412 | try |
413 | { |
414 | Model processed = model; |
415 | |
416 | boolean contextEnabled = this.isEnabled(); |
417 | if ( DEFAULT_ENABLED == contextEnabled |
418 | && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean ) |
419 | { |
420 | contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME ); |
421 | } |
422 | |
423 | String contextTransformerLocation = this.getTransformerLocation(); |
424 | if ( DEFAULT_TRANSFORMER_LOCATION.equals( contextTransformerLocation ) |
425 | && context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ) instanceof String ) |
426 | { |
427 | contextTransformerLocation = (String) context.getAttribute( TRANSFORMER_LOCATION_ATTRIBUTE_NAME ); |
428 | } |
429 | |
430 | if ( contextEnabled ) |
431 | { |
432 | final org.jomc.modlet.ObjectFactory objectFactory = new org.jomc.modlet.ObjectFactory(); |
433 | final JAXBContext jaxbContext = context.createContext( model.getIdentifier() ); |
434 | final List<Transformer> transformers = this.findTransformers( context, contextTransformerLocation ); |
435 | processed = model.clone(); |
436 | |
437 | if ( transformers != null ) |
438 | { |
439 | for ( int i = 0, s0 = transformers.size(); i < s0; i++ ) |
440 | { |
441 | final JAXBElement<Model> e = objectFactory.createModel( processed ); |
442 | final JAXBSource source = new JAXBSource( jaxbContext, e ); |
443 | final JAXBResult result = new JAXBResult( jaxbContext ); |
444 | transformers.get( i ).transform( source, result ); |
445 | |
446 | if ( result.getResult() instanceof JAXBElement<?> |
447 | && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Model ) |
448 | { |
449 | processed = (Model) ( (JAXBElement<?>) result.getResult() ).getValue(); |
450 | } |
451 | else |
452 | { |
453 | throw new ModelException( getMessage( |
454 | "illegalTransformationResult", model.getIdentifier() ) ); |
455 | |
456 | } |
457 | } |
458 | } |
459 | } |
460 | else if ( context.isLoggable( Level.FINER ) ) |
461 | { |
462 | context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(), |
463 | model.getIdentifier() ), null ); |
464 | |
465 | } |
466 | |
467 | return processed; |
468 | } |
469 | catch ( final TransformerException e ) |
470 | { |
471 | String message = getMessage( e ); |
472 | if ( message == null && e.getException() != null ) |
473 | { |
474 | message = getMessage( e.getException() ); |
475 | } |
476 | |
477 | throw new ModelException( message, e ); |
478 | } |
479 | catch ( final JAXBException e ) |
480 | { |
481 | String message = getMessage( e ); |
482 | if ( message == null && e.getLinkedException() != null ) |
483 | { |
484 | message = getMessage( e.getLinkedException() ); |
485 | } |
486 | |
487 | throw new ModelException( message, e ); |
488 | } |
489 | } |
490 | |
491 | private static String getMessage( final String key, final Object... args ) |
492 | { |
493 | return MessageFormat.format( ResourceBundle.getBundle( |
494 | DefaultModelProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args ); |
495 | |
496 | } |
497 | |
498 | private static String getMessage( final Throwable t ) |
499 | { |
500 | return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null; |
501 | } |
502 | |
503 | } |