View Javadoc

1   /* 
2    * Copyright (c) 2005-2011, 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.text.MessageFormat;
38  import java.util.Collection;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.UUID;
43  import java.util.Vector;
44  
45  import org.apache.log4j.Logger;
46  import org.apache.xmlbeans.XmlObject;
47  import org.ogf.graap.wsag.api.Negotiation;
48  import org.ogf.graap.wsag.api.exceptions.NegotiationException;
49  import org.ogf.graap.wsag.api.logging.LogMessage;
50  import org.ogf.graap.wsag.server.actions.impl.AgreementFactoryAction;
51  import org.ogf.graap.wsag.server.api.IAgreementFactory;
52  import org.ogf.schemas.graap.wsAgreement.AgreementTemplateType;
53  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationContextType;
54  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferContextType;
55  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferDocument;
56  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferStateType;
57  import org.ogf.schemas.graap.wsAgreement.negotiation.NegotiationOfferType;
58  import org.w3c.dom.Node;
59  
60  /**
61   * GenericNegotiation implements the default negotiation behavior in the WSAG4J framework. It validates
62   * incoming offers with respect to adherence to creation constraints defined in the originating templates,
63   * with respect to negotiation constraints defined in the parent offers, and invokes the appropriate
64   * negotiation strategies for incoming offers.
65   * 
66   * @author owaeld
67   */
68  public class GenericNegotiation
69      implements Negotiation
70  {
71  
72      /**
73       * Default logger.
74       */
75      private static final Logger LOG = Logger.getLogger( GenericNegotiation.class );
76  
77      /**
78       * Critical extensions key
79       */
80      public static final String CRITICAL_EXTENSIONS = "org.wsag4j.negotiation.properties.critical";
81  
82      /**
83       * Non-critical extensions key
84       */
85      public static final String NON_CRITICAL_EXTENSIONS = "org.wsag4j.negotiation.properties.noncritical";
86  
87      /**
88       * Negotiation context key
89       */
90      public static final String NEGOTIATION_CONTEXT = "org.wsag4j.negotiation.context";
91  
92      /**
93       * The agreement factory that is used to create the negotiated agreements.
94       */
95      @SuppressWarnings( "unused" )
96      private final IAgreementFactory factory;
97  
98      /**
99       * The template registry where the supported negotiation actions are registered.
100      */
101     private final TemplateRegistry registry;
102 
103     /**
104      * The context used to create the negotiation instance.
105      */
106     private final NegotiationContextType context;
107 
108     /**
109      * The negotiation properties contain a set of parameters passed from the negotiation factory.
110      */
111     private final Map<String, Object> negotiationProperties;
112 
113     private final Map<String, NegotiationOfferType> offerHistory =
114         new HashMap<String, NegotiationOfferType>();
115 
116     private final TemplateValidator templateValidator;
117 
118     /**
119      * @param factory
120      *            The factory that agreement offers are negotiated for.
121      * 
122      * @param registry
123      *            The registry to lookup the factory actions.
124      * 
125      * @param context
126      *            The context in which the negotiation is created in.
127      */
128     public GenericNegotiation( IAgreementFactory factory, TemplateRegistry registry,
129                                NegotiationContextType context )
130     {
131 
132         this( factory, registry, context, new HashMap<String, Object>() );
133     }
134 
135     private void checkOfferIntegrity( NegotiationOfferType offer ) throws NegotiationException
136     {
137         NegotiationOfferContextType negotiationOfferContext = offer.getNegotiationOfferContext();
138         if ( negotiationOfferContext == null )
139         {
140             String message = "missing negotiation offer context";
141             throw new NegotiationException( message );
142         }
143 
144         String parentOfferId = negotiationOfferContext.getCounterOfferTo();
145         if ( parentOfferId == null )
146         {
147             String message = "missing counter-offer-to-id in negotiation offer context";
148             throw new NegotiationException( message );
149         }
150 
151         String offerTemplateId = offer.getContext().getTemplateId();
152         if ( offerTemplateId == null )
153         {
154             String message = "missing template-id in context";
155             throw new NegotiationException( message );
156         }
157 
158         String offerTemplateName = offer.getContext().getTemplateName();
159         if ( offerTemplateName == null )
160         {
161             String message = "missing template name in context";
162             throw new NegotiationException( message );
163         }
164     }
165 
166     /**
167      * 
168      * @param factory
169      *            The factory that agreement offers are negotiated for.
170      * 
171      * @param registry
172      *            The registry to lookup the factory actions.
173      * 
174      * @param context
175      *            The context in which the negotiation is created in.
176      * 
177      * @param negotiationProperties
178      *            Additional parameters that were passed from the negotiation factory.
179      */
180     public GenericNegotiation( IAgreementFactory factory, TemplateRegistry registry,
181                                NegotiationContextType context, Map<String, Object> negotiationProperties )
182     {
183 
184         this.factory = factory;
185         this.registry = registry;
186         this.context = context;
187         this.negotiationProperties = negotiationProperties;
188 
189         this.templateValidator = new TemplateValidator();
190         this.templateValidator.setConfiguration( factory.getEngine().getConfiguration().getValidator() );
191     }
192 
193     /**
194      * {@inheritDoc}
195      * 
196      * @see Negotiation#advertise(NegotiationOfferType[], XmlObject[])
197      */
198     public void advertise( NegotiationOfferType[] quotes, XmlObject[] noncriticalExtensions )
199         throws NegotiationException
200     {
201         //
202         // TODO: implement advertise method using callback handlers
203         //
204         throw new UnsupportedOperationException( "not implemented" );
205     }
206 
207     /**
208      * {@inheritDoc}
209      * 
210      * @see Negotiation#negotiate(NegotiationOfferType[], XmlObject[])
211      */
212     public NegotiationOfferType[] negotiate( NegotiationOfferType[] counterOffers,
213                                              XmlObject[] noncriticalExtensions ) throws NegotiationException
214     {
215         // list of all negotiated (counter) offers
216         List<NegotiationOfferType> result = new Vector<NegotiationOfferType>();
217 
218         // build global invocation context
219         Map<String, Object> invocationContext = new HashMap<String, Object>();
220         invocationContext.put( NON_CRITICAL_EXTENSIONS, noncriticalExtensions );
221         invocationContext.put( NEGOTIATION_CONTEXT, negotiationProperties );
222 
223         // start negotiation process for each counter offer
224         for ( int i = 0; i < counterOffers.length; i++ )
225         {
226             NegotiationOfferType counterOffer = counterOffers[i];
227 
228             try
229             {
230                 //
231                 // load the original (parent) offers' id
232                 //
233                 checkOfferIntegrity( counterOffer );
234                 NegotiationOfferContextType negotiationOfferContext =
235                     counterOffer.getNegotiationOfferContext();
236                 String parentOfferId = negotiationOfferContext.getCounterOfferTo();
237                 String offerTemplateId = counterOffer.getContext().getTemplateId();
238                 String offerTemplateName = counterOffer.getContext().getTemplateName();
239 
240                 //
241                 // check integrity of the referenced parent, throw an exception in case of error
242                 //
243                 checkParentOffer( counterOffer, parentOfferId );
244 
245                 //
246                 // validate counter offer against the template
247                 //
248                 AgreementTemplateType templateType =
249                     registry.findTemplate( offerTemplateName, offerTemplateId );
250 
251                 if ( templateType == null )
252                 {
253                     throw new NegotiationException( "Could not load any template for passed offer." );
254                 }
255 
256                 boolean isValid = templateValidator.validate( counterOffer, templateType );
257                 if ( isValid )
258                 {
259                     LOG.debug( "Counter offer validated against template." );
260 
261                     //
262                     // check if we start at the root node or some leaf
263                     //
264                     String templateId = MessageFormat.format( "{0}-{1}", offerTemplateId, offerTemplateName );
265                     if ( offerHistory.containsKey( parentOfferId ) )
266                     {
267                         String message = "Found parent offer with ID ''{0}''.";
268                         LOG.debug( LogMessage.getMessage( message, parentOfferId ) );
269 
270                         NegotiationOfferType parentOffer = offerHistory.get( parentOfferId );
271 
272                         //
273                         // validate counter offer against the parent offer
274                         //
275                         isValid = templateValidator.validate( counterOffer, parentOffer );
276                         if ( isValid )
277                         {
278                             LOG.debug( "Counter offer validated against parent offer." );
279 
280                             Vector<NegotiationOfferType> results =
281                                 performNegotiation( counterOffer, invocationContext, offerTemplateId,
282                                     offerTemplateName );
283 
284                             //
285                             // store negotiated offer in offer history
286                             //
287                             for ( NegotiationOfferType negotiatedOffer : results )
288                             {
289                                 offerHistory.put( negotiatedOffer.getOfferId(),
290                                     (NegotiationOfferType) negotiatedOffer.copy() );
291                                 result.add( negotiatedOffer );
292                             }
293                         }
294                         else
295                         {
296                             String msgValidationFailed = "offer / counter offer validation failed";
297                             throw new NegotiationException( msgValidationFailed );
298                         }
299                     }
300                     else if ( parentOfferId.equals( templateId ) )
301                     {
302                         final String message =
303                             "Found root node. Start negotiation process for template ''{0}''.";
304                         LOG.debug( LogMessage.getMessage( message, templateId ) );
305 
306                         Vector<NegotiationOfferType> results =
307                             performNegotiation( counterOffer, invocationContext, offerTemplateId,
308                                 offerTemplateName );
309 
310                         //
311                         // change counterOfferTo to the template ID
312                         // store negotiated offer in offer history
313                         //
314                         for ( NegotiationOfferType negotiatedOffer : results )
315                         {
316                             negotiatedOffer.getNegotiationOfferContext().setCounterOfferTo( templateId );
317 
318                             offerHistory.put( negotiatedOffer.getOfferId(),
319                                 (NegotiationOfferType) negotiatedOffer.copy() );
320                             result.add( negotiatedOffer );
321                         }
322                     }
323                     else
324                     {
325                         String msgUnknownOffer =
326                             "Negotiation process did not start from root or any other node.";
327                         throw new NegotiationException( msgUnknownOffer );
328                     }
329                 }
330                 else
331                 {
332                     //
333                     // add rejected offer to result and continue
334                     //
335 
336                     throw new NegotiationException(
337                         "Validation of the negotiation offer against template failed." );
338                 }
339             }
340             catch ( NegotiationException ex )
341             {
342                 String errorMessage = "Negotiation process aborted. Building REJECT-counter-offer.";
343                 LOG.error( errorMessage );
344 
345                 NegotiationOfferType rejected = buildRejectOffer( counterOffer, ex );
346                 result.add( rejected );
347             }
348         }
349 
350         NegotiationOfferType[] offerArray = result.toArray( new NegotiationOfferType[result.size()] );
351 
352         return offerArray;
353     }
354 
355     /**
356      * @param i
357      * @param counterOffer
358      * @param parentOfferId
359      * @throws NegotiationException
360      */
361     private void checkParentOffer( NegotiationOfferType counterOffer, String parentOfferId )
362         throws NegotiationException
363     {
364         LOG.debug( LogMessage.getMessage( "Processing offer ''{0}''.", counterOffer.getOfferId() ) );
365 
366         //
367         // check, if there is a parent counter offer and if the status of this offer is != rejected
368         //
369 
370         if ( offerHistory.containsKey( parentOfferId ) )
371         {
372             if ( offerHistory.get( parentOfferId ).getNegotiationOfferContext().getState().isSetRejected() )
373             {
374                 String errorMessage = "Negotiation based on a rejected counter offer is not possible.";
375                 LOG.error( errorMessage );
376 
377                 throw new NegotiationException( errorMessage );
378             }
379         }
380     }
381 
382     private Vector<NegotiationOfferType>
383         performNegotiation( NegotiationOfferType counterOffer, Map<String, Object> invocationContext,
384                             String offerTemplateId, String offerTemplateName ) throws NegotiationException
385     {
386         Vector<NegotiationOfferType> results = new Vector<NegotiationOfferType>();
387 
388         //
389         // Lookup negotiation action for quote. In case of an error,
390         // the negotiation offer is rejected and an appropriate error
391         // reason is added.
392         //
393 
394         AgreementFactoryAction action = loadAction( counterOffer );
395 
396         if ( action == null )
397         {
398             String msgText = "No action for template id ''{0}'' and template name ''{1}'' found.";
399             String message = MessageFormat.format( msgText, offerTemplateId, offerTemplateName );
400             throw new NegotiationException( message );
401         }
402         else
403         {
404             //
405             // Invoke the proper negotiation logic for the quote. In case of an
406             // error, the negotiation offer is rejected and an appropriate error
407             // reason is added.
408             //
409 
410             try
411             {
412                 final String message =
413                     "Negotiate (counter) offer [offer id=''{0}'', counterOfferTo=''{1}''].";
414                 LOG.debug( LogMessage.getMessage( message, counterOffer.getOfferId(),
415                     counterOffer.getNegotiationOfferContext().getCounterOfferTo() ) );
416 
417                 NegotiationOfferType[] negotiatedOffers = action.negotiate( counterOffer, invocationContext );
418 
419                 for ( NegotiationOfferType negotiatedOffer : negotiatedOffers )
420                 {
421                     //
422                     // check if the used template for the offer and the new counter offer equals
423                     //
424 
425                     String negotiatedOfferTemplateId = negotiatedOffer.getContext().getTemplateId();
426                     String negotiatedOfferTemplateName = negotiatedOffer.getContext().getTemplateName();
427 
428                     if ( !offerTemplateId.equals( negotiatedOfferTemplateId ) )
429                     {
430                         String exMessageText =
431                             "Template IDs of offer ''{0}'' and counter offer ''{1}'' not equal.";
432                         String exMessage =
433                             MessageFormat.format( exMessageText, offerTemplateId, negotiatedOfferTemplateId );
434 
435                         throw new NegotiationException( exMessage );
436                     }
437                     else if ( !offerTemplateName.equals( negotiatedOfferTemplateName ) )
438                     {
439                         String exMessageText =
440                             "Template names of offer ''{0}'' and counter offer ''{1}'' not equal.";
441                         String exMessage =
442                             MessageFormat.format( exMessageText, offerTemplateName,
443                                 negotiatedOfferTemplateName );
444 
445                         throw new NegotiationException( exMessage );
446                     }
447 
448                     //
449                     // generate a unique id for each counter offer
450                     //
451 
452                     String offerUuid = UUID.randomUUID().toString();
453 
454                     while ( offerHistory.containsKey( offerUuid ) )
455                     {
456                         offerUuid = UUID.randomUUID().toString();
457                     }
458 
459                     negotiatedOffer.setOfferId( offerUuid );
460                     negotiatedOffer.getNegotiationOfferContext()
461                                    .setCounterOfferTo( counterOffer.getOfferId() );
462                     results.add( negotiatedOffer );
463                 }
464             }
465             catch ( NegotiationException ex )
466             {
467                 String errorMessage = "Negotiation of offer failed.";
468                 LOG.error( errorMessage );
469 
470                 NegotiationOfferType rejected = buildRejectOffer( counterOffer, ex );
471                 results.add( rejected );
472             }
473         }
474 
475         return results;
476     }
477 
478     private NegotiationOfferType
479         buildRejectOffer( NegotiationOfferType counterOffer, NegotiationException ex )
480     {
481         //
482         // TODO catch the proper exception and add the rejection reason.
483         //
484         NegotiationOfferDocument negotiationOfferDocument = NegotiationOfferDocument.Factory.newInstance();
485         negotiationOfferDocument.addNewNegotiationOffer().set( counterOffer.copy() );
486 
487         NegotiationOfferType rejected = negotiationOfferDocument.getNegotiationOffer();
488         rejected.getNegotiationOfferContext().setState( NegotiationOfferStateType.Factory.newInstance() );
489         rejected.getNegotiationOfferContext().getState().addNewRejected();
490 
491         String offerUuid = UUID.randomUUID().toString();
492         while ( offerHistory.containsKey( offerUuid ) )
493         {
494             offerUuid = UUID.randomUUID().toString();
495         }
496         rejected.setOfferId( offerUuid );
497         offerHistory.put( offerUuid, (NegotiationOfferType) rejected.copy() );
498 
499         Node imported =
500             rejected.getDomNode().getOwnerDocument().importNode( ex.getBaseFault().getDomNode(), true );
501         rejected.getDomNode().appendChild( imported );
502 
503         return rejected;
504     }
505 
506     /**
507      * {@inheritDoc}
508      * 
509      * @see Negotiation#getNegotiationContext()
510      */
511     public NegotiationContextType getNegotiationContext()
512     {
513         return context;
514     }
515 
516     /**
517      * {@inheritDoc}
518      * 
519      * @see Negotiation#getNegotiationOffers()
520      */
521     public NegotiationOfferType[] getNegotiationOffers()
522     {
523         Collection<NegotiationOfferType> offers = offerHistory.values();
524         NegotiationOfferType[] offerArray = offers.toArray( new NegotiationOfferType[offers.size()] );
525 
526         return offerArray;
527     }
528 
529     /**
530      * @see Negotiation#terminate()
531      */
532     public void terminate()
533     {
534         //
535         // TODO: add handler with negotiation specific termination strategy
536         //
537     }
538 
539     /**
540      * Loads the appropriate negotiation strategy for an incoming quote. The negotiation strategy is
541      * identified by the template the incoming offer is based on.
542      * 
543      * @param quote
544      *            negotiation offer
545      * @return the {@link AgreementFactoryAction} for this quote
546      */
547     public AgreementFactoryAction loadAction( NegotiationOfferType quote )
548     {
549         String templateName = quote.getContext().getTemplateName();
550         String templateId = quote.getContext().getTemplateId();
551         AgreementFactoryAction action = registry.findAction( templateName, templateId );
552 
553         return action;
554     }
555 
556     /**
557      * Default implementation for retrieving negotiable templates from the associated agreement factory.
558      * 
559      * {@inheritDoc}
560      * 
561      * @see org.ogf.graap.wsag.api.Negotiation#getNegotiableTemplates()
562      * @see TemplateRegistry#getNegotiableTemplates()
563      */
564     public AgreementTemplateType[] getNegotiableTemplates()
565     {
566         return registry.getNegotiableTemplates();
567     }
568 }