View Javadoc

1   /* 
2    * Copyright (c) 2007, Fraunhofer-Gesellschaft
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 are
7    * met:
8    * 
9    * (1) Redistributions of source code must retain the above copyright
10   *     notice, this list of conditions and the disclaimer at the end.
11   *     Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in
13   *     the documentation and/or other materials provided with the
14   *     distribution.
15   * 
16   * (2) Neither the name of Fraunhofer nor the names of its
17   *     contributors may be used to endorse or promote products derived
18   *     from this software without specific prior written permission.
19   * 
20   * DISCLAIMER
21   * 
22   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33   *  
34   */
35  package org.ogf.graap.wsag.server.engine;
36  
37  import java.io.File;
38  import java.io.InputStream;
39  import java.net.URL;
40  import java.text.MessageFormat;
41  import java.util.ArrayList;
42  import java.util.HashMap;
43  import java.util.Vector;
44  
45  import javax.xml.namespace.QName;
46  
47  import org.apache.log4j.Logger;
48  import org.apache.xml.resolver.tools.CatalogResolver;
49  import org.apache.xmlbeans.SchemaType;
50  import org.apache.xmlbeans.SchemaTypeLoader;
51  import org.apache.xmlbeans.SchemaTypeSystem;
52  import org.apache.xmlbeans.XmlBeans;
53  import org.apache.xmlbeans.XmlError;
54  import org.apache.xmlbeans.XmlObject;
55  import org.apache.xmlbeans.XmlOptions;
56  import org.apache.xmlbeans.impl.common.ResolverUtil;
57  import org.apache.xmlbeans.impl.xb.xsdschema.ComplexRestrictionType;
58  import org.apache.xmlbeans.impl.xb.xsdschema.ComplexType;
59  import org.apache.xmlbeans.impl.xb.xsdschema.FormChoice;
60  import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument;
61  import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument.Schema;
62  import org.apache.xmlbeans.impl.xb.xsdschema.SimpleType;
63  import org.ogf.graap.wsag.api.AgreementOffer;
64  import org.ogf.graap.wsag.api.logging.LogMessage;
65  import org.ogf.graap.wsag.api.types.AgreementOfferType;
66  import org.ogf.graap.wsag4j.types.configuration.SchemaImportType;
67  import org.ogf.graap.wsag4j.types.configuration.ValidatorConfigurationDocument;
68  import org.ogf.graap.wsag4j.types.configuration.ValidatorType;
69  import org.ogf.graap.wsag4j.types.engine.ConstraintAnnotationDocument;
70  import org.ogf.graap.wsag4j.types.engine.ConstraintAnnotationType;
71  import org.ogf.graap.wsag4j.types.engine.ItemCardinalityType;
72  import org.ogf.schemas.graap.wsAgreement.AgreementOfferDocument;
73  import org.ogf.schemas.graap.wsAgreement.AgreementTemplateType;
74  import org.ogf.schemas.graap.wsAgreement.AgreementType;
75  import org.ogf.schemas.graap.wsAgreement.OfferItemType;
76  import org.ogf.schemas.graap.wsAgreement.OfferItemType.ItemConstraint;
77  import org.ogf.schemas.graap.wsAgreement.TemplateDocument;
78  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationConstraintType;
79  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferDocument;
80  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferItemType;
81  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferType;
82  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationRoleType.Enum;
83  
84  /**
85   * The {@link TemplateValidator} implements the required methods to validate the compliance of an agreement
86   * offer with respect to the creation constraints that are defined in the template that was used to create the
87   * offer.
88   * 
89   * @author Oliver Waeldrich
90   * 
91   */
92  public class TemplateValidator
93  {
94  
95      private static final String GENERATED_TYPE_NAME = "GeneratedConstraintValidationType";
96  
97      private static final String XML_SCHEMA_FILENAME = "/validator/XMLSchema.xml";
98  
99      private static final String WSAG_SCHEMA_FILENAME = "/validator/ws-agreement-xsd-types.xsd";
100 
101     private static final Logger LOG = Logger.getLogger( TemplateValidator.class );
102 
103     private final HashMap<String, Boolean[]> knownSchemaFormChoice = new HashMap<String, Boolean[]>();
104 
105     private XmlOptions options = null;
106 
107     private ValidatorType configuration;
108 
109     //
110     // type system loader
111     //
112     private SchemaTypeLoader loader;
113 
114     /**
115      * Command line invocation of the validator. For program parameters see info message.
116      * 
117      * @param args
118      *            program arguments, see info
119      */
120     public static void main( String[] args )
121     {
122         if ( args.length != 3 )
123         {
124             LOG.info( "Usage: TemplateValidator agreement_template.xml agreement_offer.xml validator_config.xml" );
125             return;
126         }
127 
128         URL templateURL = System.class.getResource( args[0] );
129         URL offerURL = System.class.getResource( args[1] );
130         URL configURL = System.class.getResource( args[2] );
131 
132         if ( templateURL == null )
133         {
134             LOG.error( "Template does not exist..." );
135             return;
136         }
137         if ( offerURL == null )
138         {
139             LOG.error( "Offer does not exist..." );
140             return;
141         }
142         if ( configURL == null )
143         {
144             LOG.error( "Config file does not exist..." );
145             return;
146         }
147 
148         File templateFile;
149         File offerFile;
150         File configFile;
151 
152         try
153         {
154             templateFile = new File( templateURL.toURI() );
155             offerFile = new File( offerURL.toURI() );
156             configFile = new File( configURL.toURI() );
157         }
158         catch ( Exception ex )
159         {
160             LOG.error( "Error opening file. Reason: " + ex.getMessage() );
161             return;
162         }
163 
164         if ( !( templateFile.exists() && templateFile.isFile() ) )
165         {
166             LOG.error( LogMessage.getMessage( "Template file <{0}> does not exist or is a directory...",
167                 templateURL.toExternalForm() ) );
168             return;
169         }
170 
171         if ( !( offerFile.exists() && offerFile.isFile() ) )
172         {
173             LOG.error( LogMessage.getMessage( "Offer file <{0}> does not exist or is a directory...",
174                 offerURL.toExternalForm() ) );
175             return;
176         }
177 
178         if ( !( configFile.exists() && configFile.isFile() ) )
179         {
180             LOG.error( LogMessage.getMessage( "Config file <{0}> does not exist or is a directory...",
181                 offerURL.toExternalForm() ) );
182             return;
183         }
184 
185         AgreementTemplateType template;
186         try
187         {
188             template = parseTemplate( templateFile );
189         }
190         catch ( Exception e )
191         {
192             LOG.error( "Could not load template file. Reason: " + e.getMessage() );
193             return;
194         }
195 
196         AgreementType offer;
197         try
198         {
199             XmlObject parsedResult = XmlObject.Factory.parse( offerFile );
200 
201             if ( parsedResult instanceof AgreementOfferDocument )
202             {
203                 offer = ( (AgreementOfferDocument) parsedResult ).getAgreementOffer();
204             }
205             else
206             {
207                 Object[] filler =
208                     new Object[] { ( parsedResult.schemaType().getName() != null ) ? parsedResult.schemaType()
209                                                                                                  .getName()
210                                     : parsedResult.schemaType().getDocumentElementName() };
211                 String message =
212                     MessageFormat.format( "Offer file of type {0} is not a valid Agreement Offer Document. ",
213                         filler );
214                 LOG.error( message );
215                 return;
216             }
217         }
218         catch ( Exception e )
219         {
220             LOG.error( "Could not load template file. Reason: " + e.getMessage() );
221             return;
222         }
223 
224         ValidatorType validatorConfig;
225         try
226         {
227             XmlObject parsedResult = XmlObject.Factory.parse( configFile );
228 
229             if ( parsedResult instanceof ValidatorConfigurationDocument )
230             {
231                 validatorConfig =
232                     ( (ValidatorConfigurationDocument) parsedResult ).getValidatorConfiguration();
233             }
234             else
235             {
236                 Object[] filler =
237                     new Object[] { ( parsedResult.schemaType().getName() != null ) ? parsedResult.schemaType()
238                                                                                                  .getName()
239                                     : parsedResult.schemaType().getDocumentElementName() };
240 
241                 String msgNoValidConfig =
242                     "Config file of type {0} is not a valid Validator Config Document. ";
243                 String message = MessageFormat.format( msgNoValidConfig, filler );
244                 LOG.error( message );
245                 return;
246             }
247         }
248         catch ( Exception e )
249         {
250             LOG.error( "Could not load config file. Reason: " + e.getMessage() );
251             return;
252         }
253 
254         TemplateValidator validator = new TemplateValidator();
255         validator.setConfiguration( validatorConfig );
256 
257         AgreementTemplateType offerTemplate = AgreementTemplateType.Factory.newInstance();
258         offerTemplate.setAgreementId( offer.getAgreementId() );
259         offerTemplate.setTemplateId( "" );
260         offerTemplate.setName( offer.getName() );
261         offerTemplate.setAgreementId( offer.getAgreementId() );
262         offerTemplate.addNewContext().set( offer.getContext() );
263         offerTemplate.addNewTerms().set( offer.getTerms() );
264         offerTemplate.addNewCreationConstraints();
265 
266         AgreementOffer offerInstance = new AgreementOfferType( offerTemplate );
267 
268         boolean result = validator.validate( offerInstance, template );
269         LOG.info( "Validation result: " + result );
270     }
271 
272     /**
273      * @param templateFile
274      * @param template
275      * @return
276      * @throws Exception
277      */
278     private static AgreementTemplateType parseTemplate( File templateFile ) throws Exception
279     {
280         XmlObject parsedResult = XmlObject.Factory.parse( templateFile );
281 
282         if ( parsedResult instanceof TemplateDocument )
283         {
284             return ( (TemplateDocument) parsedResult ).getTemplate();
285         }
286         else
287         {
288             Object[] filler =
289                 new Object[] { ( parsedResult.schemaType().getName() != null ) ? parsedResult.schemaType()
290                                                                                              .getName()
291                                 : parsedResult.schemaType().getDocumentElementName() };
292             String message =
293                 MessageFormat.format(
294                     "Template file of type {0} is not a valid Agreement Template Document. ", filler );
295             throw new Exception( message );
296         }
297     }
298 
299     /**
300      * 
301      */
302     public TemplateValidator()
303     {
304 
305         //
306         // configure global XmlBeans entity resolver and make sure that
307         // the resolver is initialized correctly.
308         //
309         // TODO: if we use i.e. Xerces as parser for schema parsing,
310         // we could omit the GlobalEntityResolver configuration
311         //
312         System.setProperty( "xmlbean.entityResolver", CatalogResolver.class.getName() );
313         if ( ResolverUtil.getGlobalEntityResolver() == null )
314         {
315             String warn0 =
316                 "The XmlBeans global entity resolver is not set. "
317                     + "This might cause problems in the WSAG4J offer validation process.";
318 
319             String warn1 =
320                 "Make sure that the 'xmlbean.entityResolver' system property is set to {0} "
321                     + "before executing the first XmlObject.FACTORY.parse() operation.";
322 
323             LOG.warn( warn0 );
324             LOG.warn( MessageFormat.format( warn1, new Object[] { CatalogResolver.class.getName() } ) );
325         }
326 
327         //
328         // initialize the XMLOptions
329         //
330         options = new XmlOptions();
331         options.setLoadLineNumbers();
332         options.setLoadLineNumbers( XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT );
333         options.setLoadMessageDigest();
334         options.setSavePrettyPrint();
335         options.setSaveOuter();
336 
337         //
338         // initialize the XML Resolver, essentially we must make sure
339         // that the catalog resolver is set
340         //
341         options.setEntityResolver( new CatalogResolver() );
342 
343         //
344         // initialize the validator configuration
345         //
346         configuration = ValidatorType.Factory.newInstance();
347         configuration.addNewSchemaImports();
348         configuration.getSchemaImports().addNewSchemaFilename().setStringValue( XML_SCHEMA_FILENAME );
349         configuration.getSchemaImports().addNewSchemaFilename().setStringValue( WSAG_SCHEMA_FILENAME );
350     }
351 
352     /**
353      * Validates an agreement offer document against a template document.
354      * 
355      * @param offer
356      *            the offer to validate
357      * 
358      * @param template
359      *            the template containing the creation constraints
360      * 
361      * @return <code>true</code> if the offer is valid, otherwise <code>false</code>
362      */
363     public boolean validate( AgreementOfferDocument offer, TemplateDocument template )
364     {
365         return validate( offer, template, new StringBuffer() );
366     }
367 
368     private boolean validate( AgreementOfferDocument offer, TemplateDocument template, StringBuffer error )
369     {
370 
371         LOG.debug( "start agreement offer validation process" );
372         LOG.debug( LogMessage.getMessage( "offer name: {0}", offer.getAgreementOffer().getName() ) );
373         LOG.debug( LogMessage.getMessage( "template name: {0}, id: {1}", template.getTemplate().getName(),
374             template.getTemplate().getTemplateId() ) );
375 
376         try
377         {
378             //
379             // Parse the template and offer documents in order to make sure XMLBeans uses the
380             // WSAG4J type systems during parsing. We need to make sure that our custom types
381             // defined in the WSAG4J type system are used when selecting entities via XPath.
382             //
383 
384             //
385             // TODO: For better performance, parsing templates without line numbers
386             // could be done using the getDom() method.
387             //
388             // template = (TemplateDocument)getWSAGLoader().parse(template.getDomNode(),
389             // TemplateDocument.type, new
390             // XmlOptions().setLoadLineNumbers());
391             // offer = (AgreementOfferDocument) getWSAGLoader().parse(offer.getDomNode(),
392             // AgreementOfferDocument.type,
393             // new XmlOptions().setLoadLineNumbers());
394             //
395             try
396             {
397                 XmlObject parsedTemplate =
398                     getWSAGCompiledTypeLoader().parse( template.xmlText( options ), TemplateDocument.type,
399                         new XmlOptions().setLoadLineNumbers() );
400 
401                 if ( LOG.isTraceEnabled() )
402                 {
403                     LOG.trace( "Successfully parsed agreement template with WSAJ4J type system" );
404                 }
405 
406                 XmlObject parsedOffer =
407                     getWSAGCompiledTypeLoader().parse( offer.xmlText( options ), AgreementOfferDocument.type,
408                         new XmlOptions().setLoadLineNumbers() );
409 
410                 if ( LOG.isTraceEnabled() )
411                 {
412                     LOG.trace( "Successfully parsed agreement offer with WSAJ4J type system" );
413                 }
414 
415                 template = (TemplateDocument) parsedTemplate;
416                 offer = (AgreementOfferDocument) parsedOffer;
417 
418                 if ( LOG.isTraceEnabled() )
419                 {
420                     LOG.trace( "Successfully converted template/offer using WSAJ4J build in type system" );
421                 }
422             }
423             catch ( Exception e )
424             {
425                 LOG.error( "Failed to parse the AgreementTemplateDocument or AgreementOfferDocument.", e );
426                 LOG.error( "Agreement offer validation failed." );
427                 return false;
428             }
429 
430             //
431             // trace the agreement template
432             //
433             if ( LOG.isTraceEnabled() )
434             {
435                 LOG.trace( "Agreement template:\n" + template.xmlText( options ) );
436             }
437 
438             //
439             // validate the template, log errors (if any), and log validation result
440             // if the template is not valid, return
441             //
442             boolean validTemplate = validate( template, error );
443 
444             LOG.debug( LogMessage.getMessage( "Template validation result: {0}", validTemplate ) );
445 
446             if ( !validTemplate )
447             {
448                 LOG.error( "Agreement offer validation failed. The agreement template document is not valid." );
449                 return false;
450             }
451 
452             //
453             // trace the agreement offer
454             //
455             if ( LOG.isTraceEnabled() )
456             {
457                 LOG.trace( "Agreement offer:\n" + offer.xmlText( options ) );
458             }
459 
460             //
461             // validate the offer, log errors (if any), and log validation result
462             // if the offer is not valid, return
463             //
464             boolean validOffer = validate( offer, error );
465 
466             LOG.debug( LogMessage.getMessage( "Offer validation result: {0}", validOffer ) );
467 
468             if ( !validOffer )
469             {
470                 LOG.error( "Agreement offer validation failed. The agreement offer document is not valid." );
471                 return false;
472             }
473 
474             //
475             // if agreement template and offer are valid, go on with the validation process
476             //
477             OfferItemType[] items = template.getTemplate().getCreationConstraints().getItemArray();
478             for ( int i = 0; i < items.length; i++ )
479             {
480                 if ( !validateConstraint( offer, items[i], error ) )
481                 {
482 
483                     return false;
484                 }
485             }
486 
487             return true;
488 
489         }
490         finally
491         {
492             LOG.debug( "Finished agreement offer validation process." );
493         }
494     }
495 
496     private boolean validate( NegotiationOfferDocument counterOfferDoc,
497                               NegotiationOfferDocument parentOfferDoc, StringBuffer error )
498     {
499         NegotiationOfferType counterOffer = counterOfferDoc.getNegotiationOffer();
500         NegotiationOfferType parentOffer = parentOfferDoc.getNegotiationOffer();
501 
502         boolean validationResult = true;
503 
504         //
505         // check, if this is the correct (counter) offer combination
506         //
507         String offerId = parentOffer.getOfferId();
508         String counterOfferId = counterOffer.getNegotiationOfferContext().getCounterOfferTo();
509 
510         if ( !offerId.equals( counterOfferId ) )
511         {
512             String msgText = "(Counter) Offer combination is not valid [''{0}'' <-> ''{1}''].";
513             LOG.debug( LogMessage.getMessage( msgText, offerId, counterOfferId ) );
514 
515             validationResult = false;
516         }
517 
518         LOG.debug( "start agreement offer validation process" );
519         Enum parentCreator = parentOffer.getNegotiationOfferContext().getCreator();
520         LOG.debug( LogMessage.getMessage( "offer creator: {0}", parentCreator ) );
521         LOG.debug( LogMessage.getMessage( "counter offer name: {0}, id: {1}",
522             counterOffer.getNegotiationOfferContext().getCounterOfferTo(), counterOffer.getOfferId() ) );
523 
524         try
525         {
526             try
527             {
528 
529                 XmlObject parsedCounterOffer = parseOffer( counterOfferDoc );
530                 LOG.trace( "Successfully parsed agreement template with WSAG4J type system." );
531 
532                 XmlObject parsedOffer = parseOffer( parentOfferDoc );
533                 LOG.trace( "Successfully parsed agreement offer with WSAG4J type system" );
534 
535                 counterOffer = ( (NegotiationOfferDocument) parsedCounterOffer ).getNegotiationOffer();
536                 parentOffer = ( (NegotiationOfferDocument) parsedOffer ).getNegotiationOffer();
537                 LOG.trace( "Successfully converted counter offer/offer using WSAJ4J build in type system" );
538             }
539             catch ( Exception e )
540             {
541                 LOG.error( "Failed to parse the NegotiationOfferType or AgreementOfferDocument.", e );
542                 LOG.error( "Agreement counter offer validation failed." );
543 
544                 validationResult = false;
545             }
546 
547             //
548             // trace the agreement template
549             //
550             if ( LOG.isTraceEnabled() )
551             {
552                 LOG.trace( "Agreement counter offer:\n" + counterOffer.xmlText( options ) );
553             }
554 
555             //
556             // validate the template, log errors (if any), and log validation result
557             // if the template is not valid, return
558             //
559             boolean validCounterOffer = validate( counterOffer, error );
560 
561             LOG.debug( LogMessage.getMessage( "Counter offer validation result: {0}", validCounterOffer ) );
562 
563             if ( !validCounterOffer )
564             {
565                 LOG.error( "Counter offer validation failed. The counter offer document is not valid." );
566                 validationResult = false;
567             }
568 
569             //
570             // trace the agreement offer
571             //
572             LOG.trace( LogMessage.getMessage( "Agreement offer:\n{0}", parentOffer.xmlText( options ) ) );
573 
574             //
575             // validate the offer, log errors (if any), and log validation result
576             // if the offer is not valid, return
577             //
578             boolean validOffer = validate( parentOffer, error );
579 
580             LOG.debug( LogMessage.getMessage( "Offer validation result: {0}", validOffer ) );
581 
582             if ( !validOffer )
583             {
584                 validationResult = false;
585                 LOG.error( "Agreement offer validation failed. The agreement offer document is not valid." );
586             }
587 
588             //
589             // if agreement template and offer are valid, go on with the validation process
590             //
591 
592             NegotiationOfferItemType[] items = parentOffer.getNegotiationConstraints().getItemArray();
593             for ( int i = 0; i < items.length; i++ )
594             {
595                 if ( !validateConstraint( counterOfferDoc, items[i], error ) )
596                 {
597                     if ( items[i].getType() == NegotiationConstraintType.OPTIONAL )
598                     {
599                         if ( LOG.isInfoEnabled() )
600                         {
601                             LOG.info( "Validation of an optional term failed. Continue with the term validation." );
602                         }
603                         continue;
604                     }
605 
606                     validationResult = false;
607                     break;
608                 }
609             }
610         }
611         finally
612         {
613             final String message = "Finished agreement offer validation process. Result is: {0}";
614             LOG.debug( LogMessage.getMessage( message, validationResult ) );
615         }
616 
617         return validationResult;
618     }
619 
620     /**
621      * @param offerDoc
622      * @return
623      * @throws Exception
624      */
625     private NegotiationOfferDocument parseOffer( NegotiationOfferDocument offerDoc ) throws Exception
626     {
627         //
628         // Parse the template and offer documents in order to make sure XMLBeans uses the
629         // WSAG4J type systems during parsing. We need to make sure that our custom types
630         // defined in the WSAG4J type system are used when selecting entities via XPath.
631         //
632 
633         //
634         // TODO: For better performance, parsing templates without line numbers
635         // could be done using the getDom() method.
636         //
637         // template = (TemplateDocument)getWSAGLoader().parse(template.getDomNode(), TemplateDocument.type,
638         // new
639         // XmlOptions().setLoadLineNumbers());
640         // offer = (AgreementOfferDocument) getWSAGLoader().parse(offer.getDomNode(),
641         // AgreementOfferDocument.type,
642         // new XmlOptions().setLoadLineNumbers());
643         //
644 
645         return (NegotiationOfferDocument) getWSAGCompiledTypeLoader().parse( offerDoc.xmlText( options ),
646             NegotiationOfferDocument.type, new XmlOptions().setLoadLineNumbers() );
647     }
648 
649     /**
650      * Validates an agreement offer against a template.
651      * 
652      * @param offer
653      *            the offer to validate
654      * 
655      * @param template
656      *            the template containing the creation constraints
657      * 
658      * @param error
659      *            contains the error message if the validation process failed
660      * 
661      * @return <code>true</code> if the offer is valid, otherwise <code>false</code>
662      */
663     public boolean validate( AgreementOffer offer, AgreementTemplateType template, StringBuffer error )
664     {
665         AgreementOfferDocument offerDoc = AgreementOfferDocument.Factory.newInstance( options );
666 
667         offerDoc.addNewAgreementOffer();
668         offerDoc.getAgreementOffer().setName( offer.getName() );
669         offerDoc.getAgreementOffer().setContext( offer.getContext() );
670         offerDoc.getAgreementOffer().setTerms( offer.getTerms() );
671 
672         TemplateDocument templateDoc = TemplateDocument.Factory.newInstance();
673         templateDoc.addNewTemplate().set( template );
674 
675         return validate( offerDoc, templateDoc, error );
676     }
677 
678     /**
679      * Validates an agreement offer against a template.
680      * 
681      * @param offer
682      *            the offer to validate
683      * 
684      * @param template
685      *            the template containing the creation constraints
686      * 
687      * @return <code>true</code> if the offer is valid, otherwise <code>false</code>
688      */
689     public boolean validate( AgreementOffer offer, AgreementTemplateType template )
690     {
691         return validate( offer, template, new StringBuffer() );
692     }
693 
694     /**
695      * Validates a negotiation offer against a template.
696      * 
697      * @param offer
698      *            the offer to validate
699      * 
700      * @param template
701      *            the template containing the creation constraints
702      * 
703      * @return <code>true</code> if the offer is valid, otherwise <code>false</code>
704      */
705     public boolean validate( NegotiationOfferType offer, AgreementTemplateType template )
706     {
707         AgreementOfferDocument offerDoc = AgreementOfferDocument.Factory.newInstance( options );
708 
709         offerDoc.addNewAgreementOffer();
710         offerDoc.getAgreementOffer().setName( offer.getName() );
711         offerDoc.getAgreementOffer().setContext( offer.getContext() );
712         offerDoc.getAgreementOffer().setTerms( offer.getTerms() );
713 
714         TemplateDocument templateDoc = TemplateDocument.Factory.newInstance();
715         templateDoc.addNewTemplate().set( template );
716 
717         return validate( offerDoc, templateDoc );
718     }
719 
720     /**
721      * Validates a negotiation counter offer against a negotiation offer.
722      * 
723      * @param offer
724      *            the offer containing the negotiation constraints
725      * 
726      * @param counterOffer
727      *            the counter offer to validate
728      * 
729      * @return <code>true</code> if the offer is valid, otherwise <code>false</code>
730      */
731     public boolean validate( NegotiationOfferType offer, NegotiationOfferType counterOffer )
732     {
733         NegotiationOfferDocument offerDoc = NegotiationOfferDocument.Factory.newInstance( options );
734         offerDoc.addNewNegotiationOffer();
735         offerDoc.getNegotiationOffer().setOfferId( offer.getOfferId() );
736         offerDoc.getNegotiationOffer().setName( offer.getName() );
737         offerDoc.getNegotiationOffer().setContext( offer.getContext() );
738         offerDoc.getNegotiationOffer().setTerms( offer.getTerms() );
739         offerDoc.getNegotiationOffer().setNegotiationConstraints( offer.getNegotiationConstraints() );
740         offerDoc.getNegotiationOffer().setNegotiationOfferContext( offer.getNegotiationOfferContext() );
741 
742         NegotiationOfferDocument counterOfferDoc = NegotiationOfferDocument.Factory.newInstance( options );
743         counterOfferDoc.addNewNegotiationOffer();
744         counterOfferDoc.getNegotiationOffer().setOfferId( counterOffer.getOfferId() );
745         counterOfferDoc.getNegotiationOffer().setName( counterOffer.getName() );
746         counterOfferDoc.getNegotiationOffer().setContext( counterOffer.getContext() );
747         counterOfferDoc.getNegotiationOffer().setTerms( counterOffer.getTerms() );
748         counterOfferDoc.getNegotiationOffer().setNegotiationConstraints(
749             counterOffer.getNegotiationConstraints() );
750         counterOfferDoc.getNegotiationOffer().setNegotiationOfferContext(
751             counterOffer.getNegotiationOfferContext() );
752 
753         return validate( offerDoc, counterOfferDoc, new StringBuffer() );
754     }
755 
756     /**
757      * Returns the validator configuration.
758      * 
759      * @return the validator configuration
760      */
761     public ValidatorType getConfiguration()
762     {
763         return configuration;
764     }
765 
766     /**
767      * Sets the validator configuration. The configuration contains a set of XML schemas that are used to
768      * validate the XML documents (template, offer, couonter offer).
769      * 
770      * @param configuration
771      *            the validator configuration
772      */
773     public void setConfiguration( ValidatorType configuration )
774     {
775         this.configuration = configuration;
776     }
777 
778     private boolean validateConstraint( XmlObject target, OfferItemType item, StringBuffer error )
779     {
780 
781         try
782         {
783             boolean result = true;
784 
785             if ( LOG.isTraceEnabled() )
786             {
787                 LOG.trace( "**************************************************************"
788                     + "********************************************************************************" );
789                 LOG.trace( "validation of item constraint:\n" + item.xmlText( options ) );
790                 LOG.trace( "--------------------------------------------------------------"
791                     + "--------------------------------------------------------------------------------" );
792             }
793 
794             XmlObject[] items = target.selectPath( item.getLocation() );
795 
796             //
797             // Check whether a cardinality of the expected selection result is specified
798             // an whether the result fits to the specified selection constraints
799             //
800             result = checkItemCardinality( item, items );
801             if ( !result )
802             {
803                 return result;
804             }
805 
806             //
807             // create schema for type validation
808             //
809             HashMap<SchemaType, SchemaTypeLoader> schemaLoaderMap =
810                 new HashMap<SchemaType, SchemaTypeLoader>();
811             HashMap<SchemaType, SchemaType> schemaTypeMap = new HashMap<SchemaType, SchemaType>();
812 
813             for ( int i = 0; i < items.length; i++ )
814             {
815 
816                 SchemaType sourcetype = getSourceType( items[i] );
817 
818                 if ( !schemaTypeMap.containsKey( sourcetype ) )
819                 {
820 
821                     SchemaDocument schema = initializeSchema( sourcetype );
822 
823                     Schema generatedSchema =
824                         createSchemaType( schema.getSchema(), sourcetype, item.getItemConstraint() );
825 
826                     if ( LOG.isTraceEnabled() )
827                     {
828                         LOG.trace( "generated schema for type [" + sourcetype.getName() + "]" );
829                         LOG.trace( "--------------------------------------------------------------------" );
830                         LOG.trace( "generated xml schema:\n" + generatedSchema.xmlText( options ) );
831                     }
832 
833                     try
834                     {
835                         QName generatedTypeQName =
836                             new QName( sourcetype.getName().getNamespaceURI(), GENERATED_TYPE_NAME, "wsag4j" );
837                         SchemaTypeLoader schemaLoader = getLoader( generatedSchema );
838                         SchemaType schemaType = schemaLoader.findType( generatedTypeQName );
839                         schemaTypeMap.put( sourcetype, schemaType );
840                         schemaLoaderMap.put( sourcetype, schemaLoader );
841                     }
842                     catch ( Exception e )
843                     {
844                         LOG.debug( LogMessage.getMessage(
845                             "Failed to create schema for item constraint. Error: {0}", e.getMessage() ) );
846 
847                         LOG.debug( LogMessage.getMessage( "{0}", item.xmlText( options ) ) );
848 
849                         String message =
850                             LogMessage.format(
851                                 "Failed to create schema for agreement offer validation. Error: {0}",
852                                 e.getMessage() );
853 
854                         LOG.error( message );
855                         error.append( message );
856 
857                         return false;
858                     }
859                 }
860             }
861 
862             for ( int i = 0; i < items.length; i++ )
863             {
864 
865                 XmlOptions serializeOptions = new XmlOptions( options );
866                 serializeOptions.setSaveOuter();
867 
868                 String serializedItem = items[i].xmlText( serializeOptions );
869 
870                 if ( LOG.isTraceEnabled() )
871                 {
872                     LOG.trace( "restricted item:\n" + serializedItem );
873                     LOG.trace( "document validation result: " + items[i].validate() );
874                 }
875 
876                 try
877                 {
878                     SchemaType sourceType = getSourceType( items[i] );
879 
880                     SchemaType restrictionType = schemaTypeMap.get( sourceType );
881 
882                     XmlOptions schemaOptions = new XmlOptions( options );
883                     schemaOptions.setLoadReplaceDocumentElement( null );
884                     schemaOptions.setDocumentType( restrictionType );
885 
886                     //
887                     // TODO: For better performance, parsing templates without line numbers
888                     // could be done using the getDom() method.
889                     //
890                     SchemaTypeLoader schemaLoader = schemaLoaderMap.get( sourceType );
891 
892                     XmlObject check = schemaLoader.parse( serializedItem, restrictionType, schemaOptions );
893                     result = result && validate( check, error );
894 
895                 }
896                 catch ( Exception e )
897                 {
898                     LOG.error( "Could not parse target element: " + e.getMessage() );
899 
900                     result = false;
901                     break;
902                 }
903             }
904 
905             if ( LOG.isTraceEnabled() )
906             {
907                 LOG.trace( MessageFormat.format( "Item constraint validation result: {0}",
908                     new Object[] { ( result ) ? "successful" : "failed" } ) );
909             }
910 
911             return result;
912         }
913         catch ( Exception ex )
914         {
915             LOG.error( "Failed to validate creation constraint: " + ex.getMessage() );
916             return false;
917         }
918     }
919 
920     /**
921      * @param sourcetype
922      * @return
923      */
924     private SchemaDocument initializeSchema( SchemaType sourcetype )
925     {
926         SchemaDocument schema = SchemaDocument.Factory.newInstance();
927         schema.addNewSchema();
928 
929         //
930         // FIXME: add support for anonymous type validation
931         //
932         // TODO: sourcetype.getName().getNamespaceURI() does not work for anonymous
933         // type declarations, such as:
934         //
935         // <element>
936         // <complexType>
937         // </complexType>
938         // </element>
939         //
940         schema.getSchema().setTargetNamespace( sourcetype.getName().getNamespaceURI() );
941         schema.getSchema().setVersion( "1.0" );
942 
943         schema.getSchema().setElementFormDefault( FormChoice.UNQUALIFIED );
944         schema.getSchema().setAttributeFormDefault( FormChoice.UNQUALIFIED );
945 
946         String sourceNamespace = sourcetype.getName().getNamespaceURI();
947         if ( knownSchemaFormChoice.containsKey( sourceNamespace ) )
948         {
949             Boolean[] efq = knownSchemaFormChoice.get( sourceNamespace );
950             boolean isElementQualified = efq[0].booleanValue();
951             boolean isAttributeQualified = efq[1].booleanValue();
952 
953             if ( isElementQualified )
954             {
955                 schema.getSchema().setElementFormDefault( FormChoice.QUALIFIED );
956             }
957             if ( isAttributeQualified )
958             {
959                 schema.getSchema().setAttributeFormDefault( FormChoice.QUALIFIED );
960             }
961         }
962         return schema;
963     }
964 
965     /**
966      * @param item
967      * @param items
968      * @param annotation
969      */
970     private boolean checkItemCardinality( OfferItemType item, XmlObject[] items )
971     {
972 
973         ConstraintAnnotationType annotation = ConstraintAnnotationType.Factory.newInstance();
974         annotation.setMultiplicity( ItemCardinalityType.X_0_TO_N );
975         XmlObject[] cadinalityDoc =
976             item.selectChildren( ConstraintAnnotationDocument.type.getDocumentElementName() );
977         if ( cadinalityDoc.length > 0 )
978         {
979             annotation = (ConstraintAnnotationType) cadinalityDoc[0];
980         }
981 
982         switch ( annotation.getMultiplicity().intValue() )
983         {
984             case ItemCardinalityType.INT_X_0_TO_1:
985 
986                 if ( items.length > 1 )
987                 {
988                     LogMessage message =
989                         LogMessage.getMessage( "Selected {0} elements for item constraint {1}, "
990                             + "but constraint annotation specified multiplicity of 0..1", items.length,
991                             item.getName() );
992                     LOG.error( message );
993 
994                     return false;
995                 }
996                 return true;
997 
998             case ItemCardinalityType.INT_X_1:
999 
1000                 if ( items.length != 1 )
1001                 {
1002                     LogMessage message =
1003                         LogMessage.getMessage( "Selected {0} elements for item constraint {1}, "
1004                             + "but constraint annotation specified multiplicity of 1", items.length,
1005                             item.getName() );
1006                     LOG.error( message );
1007 
1008                     return false;
1009                 }
1010                 return true;
1011 
1012             case ItemCardinalityType.INT_X_1_TO_N:
1013 
1014                 if ( items.length == 0 )
1015                 {
1016                     LogMessage message =
1017                         LogMessage.getMessage( "Selected 0 elements for item constraint {0}, "
1018                             + "but constraint annotation specified multiplicity of 1..N", item.getName() );
1019                     LOG.error( message );
1020 
1021                     return false;
1022                 }
1023                 return true;
1024 
1025             default:
1026                 //
1027                 // is satisfied anyway
1028                 //
1029                 return true;
1030         }
1031     }
1032 
1033     private boolean validate( XmlObject object, StringBuffer error )
1034     {
1035         ArrayList<XmlError> list = new ArrayList<XmlError>();
1036 
1037         XmlOptions voptions = new XmlOptions( options );
1038         voptions.setErrorListener( list );
1039 
1040         if ( !object.validate( voptions ) )
1041         {
1042             for ( int i = 0; i < list.size(); i++ )
1043             {
1044                 if ( LOG.isDebugEnabled() )
1045                 {
1046                     XmlError e = list.get( i );
1047                     String message =
1048                         MessageFormat.format( "Type validation error [line {0}]: {1}. Code: {2}",
1049                             e.getLine(), e.getMessage(), e.getErrorCode() );
1050                     error.append( message + "\n" );
1051                     LOG.debug( message );
1052                 }
1053             }
1054 
1055             return false;
1056         }
1057 
1058         return true;
1059     }
1060 
1061     private SchemaType getSourceType( XmlObject item ) throws Exception
1062     {
1063         SchemaType sourcetype = item.schemaType().getPrimitiveType();
1064 
1065         if ( sourcetype == null )
1066         {
1067             sourcetype = item.schemaType();
1068 
1069             if ( sourcetype.isNoType() )
1070             {
1071                 LOG.error( "No type information found for restricted item:" );
1072                 LOG.error( item.xmlText( options ) );
1073 
1074                 throw new Exception( "No type information found for item: " + item.xmlText() );
1075             }
1076         }
1077 
1078         return sourcetype;
1079     }
1080 
1081     private Schema createSchemaType( Schema schema, SchemaType type, ItemConstraint constraint )
1082     {
1083 
1084         Schema result = null;
1085 
1086         // We first check the type of the constraint. We can have a
1087         // typeDefParticle or a simpleRestrictionModel constraint
1088         if ( constraint.isSetAll() || constraint.isSetChoice() || constraint.isSetGroup()
1089             || constraint.isSetSequence() )
1090         {
1091 
1092             result = createTypeDefParticleSchema( schema, type, constraint );
1093         }
1094         else
1095         {
1096             result = createSimpleRestrictionModelSchema( schema, type, constraint );
1097         }
1098 
1099         return result;
1100     }
1101 
1102     private Schema createSimpleRestrictionModelSchema( Schema schema, SchemaType type,
1103                                                        ItemConstraint constraint )
1104     {
1105         QName typeName =
1106             new QName( "http://wsag4j.scai.fraunhofer.de/generated", "GeneratedConstraintValidationType",
1107                 "wsag4j" );
1108 
1109         SimpleType simple = schema.addNewSimpleType();
1110         simple.setName( typeName.getLocalPart() );
1111         simple.addNewRestriction();
1112 
1113         if ( constraint.isSetSimpleType() )
1114         {
1115             simple.getRestriction().setSimpleType( constraint.getSimpleType() );
1116         }
1117         else
1118         {
1119             simple.getRestriction().setBase( type.getPrimitiveType().getName() );
1120         }
1121 
1122         simple.getRestriction().setLengthArray( constraint.getLengthArray() );
1123 
1124         simple.getRestriction().setMinInclusiveArray( constraint.getMinInclusiveArray() );
1125         simple.getRestriction().setMaxInclusiveArray( constraint.getMaxInclusiveArray() );
1126 
1127         simple.getRestriction().setMinExclusiveArray( constraint.getMinExclusiveArray() );
1128         simple.getRestriction().setMaxExclusiveArray( constraint.getMaxExclusiveArray() );
1129 
1130         simple.getRestriction().setEnumerationArray( constraint.getEnumerationArray() );
1131 
1132         simple.getRestriction().setLengthArray( constraint.getLengthArray() );
1133         simple.getRestriction().setMaxLengthArray( constraint.getMaxLengthArray() );
1134         simple.getRestriction().setMinLengthArray( constraint.getMinLengthArray() );
1135 
1136         simple.getRestriction().setFractionDigitsArray( constraint.getFractionDigitsArray() );
1137 
1138         simple.getRestriction().setPatternArray( constraint.getPatternArray() );
1139         simple.getRestriction().setTotalDigitsArray( constraint.getTotalDigitsArray() );
1140         simple.getRestriction().setWhiteSpaceArray( constraint.getWhiteSpaceArray() );
1141 
1142         return schema;
1143     }
1144 
1145     private Schema createTypeDefParticleSchema( Schema schema, SchemaType type, ItemConstraint constraint )
1146     {
1147         ComplexType complex = schema.addNewComplexType();
1148         complex.setName( GENERATED_TYPE_NAME );
1149 
1150         ComplexRestrictionType restriction = complex.addNewComplexContent().addNewRestriction();
1151         restriction.setBase( type.getName() );
1152 
1153         if ( constraint.isSetAll() )
1154         {
1155             restriction.setAll( constraint.getAll() );
1156         }
1157         if ( constraint.isSetChoice() )
1158         {
1159             restriction.setChoice( constraint.getChoice() );
1160         }
1161         if ( constraint.isSetSequence() )
1162         {
1163             restriction.setSequence( constraint.getSequence() );
1164         }
1165         if ( constraint.isSetGroup() )
1166         {
1167             restriction.setGroup( constraint.getGroup() );
1168         }
1169 
1170         return schema;
1171     }
1172 
1173     /**
1174      * This method mixes dynamic loaded type systems (defined by the validator configuration) with the
1175      * compiled WSAG4J-types type system. This means we can use it in order to validate agreement template and
1176      * offer documents, but we must not use it for constraint validation. (mixed type systems will produce
1177      * errors due to a XMLBeans bug)
1178      * 
1179      * @return mixed WSAG4J type system loader
1180      * @throws Exception
1181      */
1182     private SchemaTypeLoader getWSAGCompiledTypeLoader() throws Exception
1183     {
1184         SchemaTypeLoader dynamicLoader = getLoader();
1185         SchemaTypeLoader compiledLoader = AgreementTemplateType.type.getTypeSystem();
1186         SchemaTypeLoader compiledNegotiationLoader = NegotiationOfferDocument.type.getTypeSystem();
1187         SchemaTypeLoader compiledLoaderEngine = ConstraintAnnotationType.type.getTypeSystem();
1188 
1189         return XmlBeans.typeLoaderUnion( new SchemaTypeLoader[] { compiledLoader, compiledNegotiationLoader,
1190             compiledLoaderEngine, dynamicLoader } );
1191     }
1192 
1193     /**
1194      * Creates a type system loader based on the validator configuration. This loader is completely compiled
1195      * from XML schema files specified in the validator configuration.
1196      * 
1197      * @param schema
1198      *            an additional schema file to include in the type system loader
1199      * @return union of the validator type system loader and the loader of the provided schema file
1200      * @throws Exception
1201      */
1202     private synchronized SchemaTypeLoader getLoader( Schema schema ) throws Exception
1203     {
1204 
1205         //
1206         // First, we get the WSAG loader. This loader contains all global type systems
1207         // for the WSAG4J engine.
1208         //
1209         SchemaTypeLoader wsagLoader = getLoader();
1210 
1211         //
1212         // now we parse and compile the schema while using the global type system
1213         //
1214         SchemaDocument importSchema = SchemaDocument.Factory.parse( schema.getDomNode() );
1215         SchemaTypeSystem schemats =
1216             XmlBeans.compileXsd( new XmlObject[] { importSchema }, wsagLoader, options );
1217 
1218         //
1219         // the last step is to do a type loader union of our local schema and our global type systems
1220         //
1221         return XmlBeans.typeLoaderUnion( new SchemaTypeLoader[] { schemats, wsagLoader } );
1222     }
1223 
1224     /**
1225      * Creates a type system loader based on the validator configuration. This loader is completely compiled
1226      * from XML schema files specified in the validator configuration.
1227      * 
1228      * @return the validator type system loader
1229      * @throws Exception
1230      */
1231     private synchronized SchemaTypeLoader getLoader() throws Exception
1232     {
1233         //
1234         // If the WSAG4J Loader is not initialized, do the initialization
1235         // based on the validator configuration.
1236         //
1237         if ( loader == null )
1238         {
1239 
1240             // Remove for now, this would introduce system specific dependencies
1241             // XmlOptions parserOptions = new XmlOptions();
1242             // parserOptions.setLoadUseXMLReader( SAXParserFactory.newInstance().newSAXParser().getXMLReader()
1243             // );
1244 
1245             Vector<SchemaTypeSystem> wsag4jTypeSystems = new Vector<SchemaTypeSystem>();
1246 
1247             //
1248             // add the build in type system as initial type system
1249             //
1250             wsag4jTypeSystems.add( XmlBeans.getBuiltinTypeSystem() );
1251 
1252             //
1253             // for each explicitly referenced schema, create a new type system
1254             // and add the type system as a wsag4j type system
1255             //
1256             SchemaImportType imports = getConfiguration().getSchemaImports();
1257             if ( imports != null )
1258             {
1259                 String[] schemaFilenames = imports.getSchemaFilenameArray();
1260                 for ( int i = 0; i < schemaFilenames.length; i++ )
1261                 {
1262                     try
1263                     {
1264                         InputStream resource =
1265                             TemplateValidator.class.getResourceAsStream( schemaFilenames[i] );
1266                         SchemaDocument importSchema = SchemaDocument.Factory.parse( resource );
1267 
1268                         if ( !knownSchemaFormChoice.containsKey( importSchema.getSchema()
1269                                                                              .getTargetNamespace() ) )
1270                         {
1271                             boolean isAttributeQualified = false;
1272                             boolean isElementQualified = false;
1273 
1274                             if ( importSchema.getSchema().isSetAttributeFormDefault() )
1275                             {
1276                                 isAttributeQualified =
1277                                     importSchema.getSchema().getAttributeFormDefault() == FormChoice.QUALIFIED;
1278                             }
1279 
1280                             if ( importSchema.getSchema().isSetElementFormDefault() )
1281                             {
1282                                 isElementQualified =
1283                                     importSchema.getSchema().getElementFormDefault() == FormChoice.QUALIFIED;
1284                             }
1285 
1286                             knownSchemaFormChoice.put(
1287                                 importSchema.getSchema().getTargetNamespace(),
1288                                 new Boolean[] { Boolean.valueOf( isElementQualified ),
1289                                     Boolean.valueOf( isAttributeQualified ) } );
1290                         }
1291 
1292                         SchemaTypeSystem schemats =
1293                             XmlBeans.compileXsd( new XmlObject[] { importSchema }, loader, options );
1294                         wsag4jTypeSystems.add( schemats );
1295 
1296                         LOG.debug( LogMessage.getMessage( "Loaded schema file {0}", schemaFilenames[i] ) );
1297 
1298                     }
1299                     catch ( Exception e )
1300                     {
1301                         LOG.error( LogMessage.getMessage( "Could not load imported schema {0}. Error: {1}",
1302                             schemaFilenames[i], e.getMessage() ) );
1303                         LOG.debug( e );
1304                         LOG.error( "Hint: check the order of the import schema entries in the wsag4j config file." );
1305                     }
1306                 }
1307             }
1308 
1309             SchemaTypeLoader[] typeSystem =
1310                 wsag4jTypeSystems.toArray( new SchemaTypeLoader[wsag4jTypeSystems.size()] );
1311 
1312             loader = XmlBeans.typeLoaderUnion( typeSystem );
1313         }
1314 
1315         return loader;
1316     }
1317 }