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}