001/* 002 * Copyright (C) 2015 Christian Schulte <cs@schulte.it> 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without 006 * modification, are permitted provided that the following conditions 007 * are met: 008 * 009 * o Redistributions of source code must retain the above copyright 010 * notice, this list of conditions and the following disclaimer. 011 * 012 * o Redistributions in binary form must reproduce the above copyright 013 * notice, this list of conditions and the following disclaimer in 014 * the documentation and/or other materials provided with the 015 * distribution. 016 * 017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, 021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 027 * 028 * $JOMC: DefaultModletValidator.java 5269 2016-08-11 23:38:09Z schulte $ 029 * 030 */ 031package org.jomc.modlet; 032 033import java.io.IOException; 034import java.text.MessageFormat; 035import java.util.HashMap; 036import java.util.Map; 037import java.util.ResourceBundle; 038import java.util.logging.Level; 039import javax.xml.bind.JAXBException; 040import javax.xml.bind.util.JAXBSource; 041import javax.xml.validation.Validator; 042import org.xml.sax.SAXException; 043 044/** 045 * Default {@code ModletValidator} implementation. 046 * 047 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 048 * @version $JOMC: DefaultModletValidator.java 5269 2016-08-11 23:38:09Z schulte $ 049 * @see ModelContext#validateModlets(org.jomc.modlet.Modlets) 050 * @since 1.9 051 */ 052public class DefaultModletValidator implements ModletValidator 053{ 054 055 /** 056 * Constant for the name of the model context attribute backing property {@code enabled}. 057 * 058 * @see #validateModlets(org.jomc.modlet.ModelContext, org.jomc.modlet.Modlets) 059 * @see ModelContext#getAttribute(java.lang.String) 060 */ 061 public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.modlet.DefaultModletValidator.enabledAttribute"; 062 063 /** 064 * Constant for the name of the system property controlling property {@code defaultEnabled}. 065 * 066 * @see #isDefaultEnabled() 067 */ 068 private static final String DEFAULT_ENABLED_PROPERTY_NAME = 069 "org.jomc.modlet.DefaultModletValidator.defaultEnabled"; 070 071 /** 072 * Default value of the flag indicating the validator is enabled by default. 073 * 074 * @see #isDefaultEnabled() 075 */ 076 private static final Boolean DEFAULT_ENABLED = Boolean.TRUE; 077 078 /** 079 * Flag indicating the validator is enabled by default. 080 */ 081 private static volatile Boolean defaultEnabled; 082 083 /** 084 * Flag indicating the validator is enabled. 085 */ 086 private volatile Boolean enabled; 087 088 /** 089 * Constant for the name of the system property controlling property {@code defaultOrdinal}. 090 * 091 * @see #getDefaultOrdinal() 092 */ 093 private static final String DEFAULT_ORDINAL_PROPERTY_NAME = 094 "org.jomc.modlet.DefaultModletValidator.defaultOrdinal"; 095 096 /** 097 * Default value of the ordinal number of the validator. 098 * 099 * @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}