1 /*
2 * Copyright (C) 2015 Christian Schulte <cs@schulte.it>
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 5269 2016-08-11 23:38:09Z 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 5269 2016-08-11 23:38:09Z 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 volatile 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 volatile 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 }