View Javadoc
1   /*
2    *   Copyright (C) Christian Schulte <cs@schulte.it>, 2015-006
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: DefaultModletValidator.java 5043 2015-05-27 07:03:39Z schulte $
29   *
30   */
31  package org.jomc.modlet;
32  
33  import java.io.IOException;
34  import java.text.MessageFormat;
35  import java.util.HashMap;
36  import java.util.Map;
37  import java.util.ResourceBundle;
38  import java.util.logging.Level;
39  import javax.xml.bind.JAXBException;
40  import javax.xml.bind.util.JAXBSource;
41  import javax.xml.validation.Validator;
42  import org.xml.sax.SAXException;
43  
44  /**
45   * Default {@code ModletValidator} implementation.
46   *
47   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
48   * @version $JOMC: DefaultModletValidator.java 5043 2015-05-27 07:03:39Z schulte $
49   * @see ModelContext#validateModlets(org.jomc.modlet.Modlets)
50   * @since 1.9
51   */
52  public class DefaultModletValidator implements ModletValidator
53  {
54  
55      /**
56       * Constant for the name of the model context attribute backing property {@code enabled}.
57       *
58       * @see #validateModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets)
59       * @see ModelContext#getAttribute(java.lang.String)
60       */
61      public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletValidator.enabledAttribute";
62  
63      /**
64       * Constant for the name of the system property controlling property {@code defaultEnabled}.
65       *
66       * @see #isDefaultEnabled()
67       */
68      private static final String DEFAULT_ENABLED_PROPERTY_NAME =
69          "org.jomc.modlet.DefaultModletValidator.defaultEnabled";
70  
71      /**
72       * Default value of the flag indicating the validator is enabled by default.
73       *
74       * @see #isDefaultEnabled()
75       */
76      private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
77  
78      /**
79       * Flag indicating the validator is enabled by default.
80       */
81      private static volatile Boolean defaultEnabled;
82  
83      /**
84       * Flag indicating the validator is enabled.
85       */
86      private Boolean enabled;
87  
88      /**
89       * Constant for the name of the system property controlling property {@code defaultOrdinal}.
90       *
91       * @see #getDefaultOrdinal()
92       */
93      private static final String DEFAULT_ORDINAL_PROPERTY_NAME =
94          "org.jomc.modlet.DefaultModletValidator.defaultOrdinal";
95  
96      /**
97       * Default value of the ordinal number of the validator.
98       *
99       * @see #getDefaultOrdinal()
100      */
101     private static final Integer DEFAULT_ORDINAL = 0;
102 
103     /**
104      * Default ordinal number of the validator.
105      */
106     private static volatile Integer defaultOrdinal;
107 
108     /**
109      * Ordinal number of the validator.
110      */
111     private Integer ordinal;
112 
113     /**
114      * Creates a new {@code DefaultModletValidator} instance.
115      */
116     public DefaultModletValidator()
117     {
118         super();
119     }
120 
121     /**
122      * Gets a flag indicating the validator is enabled by default.
123      * <p>
124      * The default enabled flag is controlled by system property
125      * {@code org.jomc.modlet.DefaultModletValidator.defaultEnabled} holding a value indicating the validator is
126      * enabled by default. If that property is not set, the {@code true} default is returned.
127      * </p>
128      *
129      * @return {@code true}, if the validator is enabled by default; {@code false}, if the validator is disabled by
130      * default.
131      *
132      * @see #isEnabled()
133      * @see #setDefaultEnabled(java.lang.Boolean)
134      */
135     public static boolean isDefaultEnabled()
136     {
137         if ( defaultEnabled == null )
138         {
139             defaultEnabled = Boolean.valueOf( System.getProperty(
140                 DEFAULT_ENABLED_PROPERTY_NAME, Boolean.toString( DEFAULT_ENABLED ) ) );
141 
142         }
143 
144         return defaultEnabled;
145     }
146 
147     /**
148      * Sets the flag indicating the validator is enabled by default.
149      *
150      * @param value The new value of the flag indicating the validator is enabled by default or {@code null}.
151      *
152      * @see #isDefaultEnabled()
153      */
154     public static void setDefaultEnabled( final Boolean value )
155     {
156         defaultEnabled = value;
157     }
158 
159     /**
160      * Gets a flag indicating the validator is enabled.
161      *
162      * @return {@code true}, if the validator is enabled; {@code false}, if the validator is disabled.
163      *
164      * @see #isDefaultEnabled()
165      * @see #setEnabled(java.lang.Boolean)
166      */
167     public final boolean isEnabled()
168     {
169         if ( this.enabled == null )
170         {
171             this.enabled = isDefaultEnabled();
172         }
173 
174         return this.enabled;
175     }
176 
177     /**
178      * Sets the flag indicating the validator is enabled.
179      *
180      * @param value The new value of the flag indicating the validator is enabled or {@code null}.
181      *
182      * @see #isEnabled()
183      */
184     public final void setEnabled( final Boolean value )
185     {
186         this.enabled = value;
187     }
188 
189     /**
190      * Gets the default ordinal number of the validator.
191      * <p>
192      * The default ordinal number is controlled by system property
193      * {@code org.jomc.modlet.DefaultModletValidator.defaultOrdinal} holding the default ordinal number of the
194      * validator. If that property is not set, the {@code 0} default is returned.
195      * </p>
196      *
197      * @return The default ordinal number of the validator.
198      *
199      * @see #setDefaultOrdinal(java.lang.Integer)
200      */
201     public static int getDefaultOrdinal()
202     {
203         if ( defaultOrdinal == null )
204         {
205             defaultOrdinal = Integer.getInteger( DEFAULT_ORDINAL_PROPERTY_NAME, DEFAULT_ORDINAL );
206         }
207 
208         return defaultOrdinal;
209     }
210 
211     /**
212      * Sets the default ordinal number of the validator.
213      *
214      * @param value The new default ordinal number of the validator or {@code null}.
215      *
216      * @see #getDefaultOrdinal()
217      */
218     public static void setDefaultOrdinal( final Integer value )
219     {
220         defaultOrdinal = value;
221     }
222 
223     /**
224      * Gets the ordinal number of the validator.
225      *
226      * @return The ordinal number of the validator.
227      *
228      * @see #getDefaultOrdinal()
229      * @see #setOrdinal(java.lang.Integer)
230      */
231     public final int getOrdinal()
232     {
233         if ( this.ordinal == null )
234         {
235             this.ordinal = getDefaultOrdinal();
236         }
237 
238         return this.ordinal;
239     }
240 
241     /**
242      * Sets the ordinal number of the validator.
243      *
244      * @param value The new ordinal number of the validator or {@code null}.
245      *
246      * @see #getOrdinal()
247      */
248     public final void setOrdinal( final Integer value )
249     {
250         this.ordinal = value;
251     }
252 
253     @Override
254     public ModelValidationReport validateModlets( final ModelContext context, final Modlets modlets )
255         throws NullPointerException, ModelException
256     {
257         if ( context == null )
258         {
259             throw new NullPointerException( "context" );
260         }
261         if ( modlets == null )
262         {
263             throw new NullPointerException( "modlets" );
264         }
265 
266         try
267         {
268             boolean contextEnabled = this.isEnabled();
269             if ( DEFAULT_ENABLED == contextEnabled
270                      && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
271             {
272                 contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
273             }
274 
275             final ModelValidationReport report = new ModelValidationReport();
276 
277             if ( contextEnabled )
278             {
279                 final javax.xml.validation.Schema modletSchema = context.createSchema( ModletObject.MODEL_PUBLIC_ID );
280                 final Validator validator = modletSchema.newValidator();
281                 validator.setErrorHandler( new ModelErrorHandler( context, report ) );
282                 validator.validate( new JAXBSource( context.createContext( ModletObject.MODEL_PUBLIC_ID ),
283                                                     new ObjectFactory().createModlets( modlets ) ) );
284 
285                 final Map<String, Schemas> schemasByModel = new HashMap<String, Schemas>( 128 );
286                 final Map<Schema, Modlet> modletBySchema = new HashMap<Schema, Modlet>( 128 );
287 
288                 for ( final Modlet modlet : modlets.getModlet() )
289                 {
290                     if ( modlet.getSchemas() != null )
291                     {
292                         Schemas modelSchemas = schemasByModel.get( modlet.getModel() );
293 
294                         if ( modelSchemas == null )
295                         {
296                             modelSchemas = new Schemas();
297                             schemasByModel.put( modlet.getModel(), modelSchemas );
298                         }
299 
300                         for ( int i = 0, s0 = modlet.getSchemas().getSchema().size(); i < s0; i++ )
301                         {
302                             final Schema schema = modlet.getSchemas().getSchema().get( i );
303                             modletBySchema.put( schema, modlet );
304 
305                             final Schema existingPublicIdSchema =
306                                 modelSchemas.getSchemaByPublicId( schema.getPublicId() );
307 
308                             final Schema existingSystemIdSchema =
309                                 modelSchemas.getSchemaBySystemId( schema.getSystemId() );
310 
311                             if ( existingPublicIdSchema != null )
312                             {
313                                 final Modlet modletOfSchema = modletBySchema.get( existingPublicIdSchema );
314                                 final ModelValidationReport.Detail detail =
315                                     new ModelValidationReport.Detail(
316                                         "MODEL_SCHEMA_PUBLIC_ID_CONSTRAINT",
317                                         Level.SEVERE,
318                                         getMessage( "modelSchemaPublicIdConstraint", modlet.getModel(),
319                                                     modlet.getName(), modletOfSchema.getName(),
320                                                     schema.getPublicId() ),
321                                         new ObjectFactory().createModlet( modlet ) );
322 
323                                 report.getDetails().add( detail );
324                             }
325 
326                             if ( existingSystemIdSchema != null )
327                             {
328                                 final Modlet modletOfSchema = modletBySchema.get( existingSystemIdSchema );
329                                 final ModelValidationReport.Detail detail =
330                                     new ModelValidationReport.Detail(
331                                         "MODEL_SCHEMA_SYSTEM_ID_CONSTRAINT",
332                                         Level.SEVERE,
333                                         getMessage( "modelSchemaSystemIdConstraint", modlet.getModel(),
334                                                     modlet.getName(), modletOfSchema.getName(),
335                                                     schema.getSystemId() ),
336                                         new ObjectFactory().createModlet( modlet ) );
337 
338                                 report.getDetails().add( detail );
339                             }
340 
341                             modelSchemas.getSchema().add( schema );
342                         }
343                     }
344                 }
345             }
346             else if ( context.isLoggable( Level.FINER ) )
347             {
348                 context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName() ), null );
349             }
350 
351             return report;
352         }
353         catch ( final IOException e )
354         {
355             throw new ModelException( getMessage( e ), e );
356         }
357         catch ( final JAXBException e )
358         {
359             String message = getMessage( e );
360 
361             if ( message == null && e.getLinkedException() != null )
362             {
363                 message = getMessage( e.getLinkedException() );
364             }
365 
366             throw new ModelException( message, e );
367         }
368         catch ( final SAXException e )
369         {
370             String message = getMessage( e );
371 
372             if ( message == null && e.getException() != null )
373             {
374                 message = getMessage( e.getException() );
375             }
376 
377             throw new ModelException( message, e );
378         }
379     }
380 
381     private static String getMessage( final String key, final Object... arguments )
382     {
383         return MessageFormat.format( ResourceBundle.getBundle(
384             DefaultModletValidator.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
385 
386     }
387 
388     private static String getMessage( final Throwable t )
389     {
390         return t != null
391                    ? t.getMessage() != null && t.getMessage().trim().length() > 0
392                          ? t.getMessage()
393                          : getMessage( t.getCause() )
394                    : null;
395 
396     }
397 
398 }