Coverage Report - org.mule.routing.response.AbstractResponseAggregator
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractResponseAggregator
0%
0/86
0%
0/50
4.75
 
 1  
 /*
 2  
  * $Id: AbstractResponseAggregator.java 7963 2007-08-21 08:53:15Z dirk.olmes $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 
 11  
 package org.mule.routing.response;
 12  
 
 13  
 import org.mule.config.i18n.CoreMessages;
 14  
 import org.mule.routing.inbound.AbstractEventAggregator;
 15  
 import org.mule.routing.inbound.EventGroup;
 16  
 import org.mule.umo.UMOEvent;
 17  
 import org.mule.umo.UMOMessage;
 18  
 import org.mule.umo.routing.ResponseTimeoutException;
 19  
 import org.mule.umo.routing.RoutingException;
 20  
 import org.mule.util.MapUtils;
 21  
 import org.mule.util.concurrent.Latch;
 22  
 
 23  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
 24  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
 25  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 26  
 
 27  
 /**
 28  
  * <code>AbstractResponseAggregator</code> provides a base class for implementing
 29  
  * response aggregator routers. This provides a thread-safe implemenetation and
 30  
  * allows developers to customise how and when events are grouped and collated.
 31  
  * Response Agrregators are used to collect responses that are usually sent to
 32  
  * replyTo endpoints set on outbound routers. When an event is sent out via an
 33  
  * outbound router, the response router will block the response flow on an
 34  
  * UMOComponent until the Response Router resolves a reply or times out.
 35  
  */
 36  0
 public abstract class AbstractResponseAggregator extends AbstractResponseRouter
 37  
 {
 38  
     /**
 39  
      * A map of EventGroup objects. These represent one or more messages to be
 40  
      * agregated, keyed by message id. There will be one response message for every
 41  
      * EventGroup.
 42  
      */
 43  0
     protected final ConcurrentMap eventGroups = new ConcurrentHashMap();
 44  
 
 45  
     /**
 46  
      * A map of locks used to wait for response messages for a given message id
 47  
      */
 48  0
     protected final ConcurrentMap locks = new ConcurrentHashMap();
 49  
 
 50  
     /**
 51  
      * The collection of messages that are ready to be returned to the callee. Keyed
 52  
      * by Message ID
 53  
      */
 54  0
     protected final ConcurrentMap responseMessages = new ConcurrentHashMap();
 55  
 
 56  
     public void process(UMOEvent event) throws RoutingException
 57  
     {
 58  
         // the correlationId of the event's message
 59  0
         final Object groupId = this.getReplyAggregateIdentifier(event.getMessage());
 60  0
         if (groupId == null || groupId.equals("-1"))
 61  
         {
 62  0
             throw new RoutingException(CoreMessages.noCorrelationId(), event.getMessage(), event
 63  
                 .getEndpoint());
 64  
         }
 65  
 
 66  
         // indicates interleaved EventGroup removal (very rare)
 67  0
         boolean lookupMiss = false;
 68  
 
 69  
         // spinloop for the EventGroup lookup
 70  
         while (true)
 71  
         {
 72  0
             if (lookupMiss)
 73  
             {
 74  
                 try
 75  
                 {
 76  
                     // recommended over Thread.yield()
 77  0
                     Thread.sleep(1);
 78  
                 }
 79  0
                 catch (InterruptedException interrupted)
 80  
                 {
 81  0
                     Thread.currentThread().interrupt();
 82  0
                 }
 83  
             }
 84  
 
 85  
             // check for an existing group first
 86  0
             EventGroup group = this.getEventGroup(groupId);
 87  
 
 88  
             // does the group exist?
 89  0
             if (group == null)
 90  
             {
 91  
                 // ..apparently not, so create a new one & add it
 92  0
                 group = this.addEventGroup(this.createEventGroup(event, groupId));
 93  
             }
 94  
 
 95  
             // ensure that only one thread at a time evaluates this EventGroup
 96  0
             synchronized (group)
 97  
             {
 98  
                 // make sure no other thread removed the group in the meantime
 99  0
                 if (group != this.getEventGroup(groupId))
 100  
                 {
 101  
                     // if that is the (rare) case, spin
 102  0
                     lookupMiss = true;
 103  0
                     continue;
 104  
                 }
 105  
 
 106  0
                 if (logger.isDebugEnabled())
 107  
                 {
 108  0
                     logger.debug("Adding event to response aggregator group: " + groupId);
 109  
                 }
 110  
 
 111  
                 // add the incoming event to the group
 112  0
                 group.addEvent(event);
 113  
 
 114  
                 // check to see if the event group is ready to be aggregated
 115  0
                 if (this.shouldAggregateEvents(group))
 116  
                 {
 117  
                     // create the response message
 118  0
                     UMOMessage returnMessage = this.aggregateEvents(group);
 119  
 
 120  
                     // remove the eventGroup as no further message will be received
 121  
                     // for this group once we aggregate
 122  0
                     this.removeEventGroup(group);
 123  
 
 124  
                     // add the new response message so that it can be collected by
 125  
                     // the response Thread
 126  0
                     UMOMessage previousResult = (UMOMessage) responseMessages.putIfAbsent(groupId,
 127  
                         returnMessage);
 128  0
                     if (previousResult != null)
 129  
                     {
 130  
                         // this would indicate that we need a better way to prevent
 131  
                         // continued aggregation for a group that is currently being
 132  
                         // processed. Can this actually happen?
 133  0
                         throw new IllegalStateException(
 134  
                             "Detected duplicate aggregation result message with id: " + groupId);
 135  
                     }
 136  
 
 137  
                     // will get/create a latch for the response Message ID and
 138  
                     // release it, notifying other threads that the response message
 139  
                     // is available
 140  0
                     Latch l = (Latch) locks.get(groupId);
 141  0
                     if (l == null)
 142  
                     {
 143  0
                         if (logger.isDebugEnabled())
 144  
                         {
 145  0
                             logger.debug("Creating latch for " + groupId + " in " + this);
 146  
                         }
 147  
 
 148  0
                         l = new Latch();
 149  0
                         Latch previous = (Latch) locks.putIfAbsent(groupId, l);
 150  0
                         if (previous != null)
 151  
                         {
 152  0
                             l = previous;
 153  
                         }
 154  
                     }
 155  
 
 156  0
                     l.countDown();
 157  
                 }
 158  
 
 159  
                 // result or not: exit spinloop
 160  0
                 break;
 161  0
             }
 162  
         }
 163  0
     }
 164  
 
 165  
     /**
 166  
      * @see AbstractEventAggregator#createEventGroup(UMOEvent, Object)
 167  
      */
 168  
     protected EventGroup createEventGroup(UMOEvent event, Object groupId)
 169  
     {
 170  0
         if (logger.isDebugEnabled())
 171  
         {
 172  0
             logger.debug("Creating new event group: " + groupId + " in " + this);
 173  
         }
 174  
 
 175  0
         return new EventGroup(groupId);
 176  
     }
 177  
 
 178  
     /**
 179  
      * @see AbstractEventAggregator#getEventGroup(Object)
 180  
      */
 181  
     protected EventGroup getEventGroup(Object groupId)
 182  
     {
 183  0
         return (EventGroup) eventGroups.get(groupId);
 184  
     }
 185  
 
 186  
     /**
 187  
      * @see AbstractEventAggregator#addEventGroup(EventGroup)
 188  
      */
 189  
     protected EventGroup addEventGroup(EventGroup group)
 190  
     {
 191  0
         EventGroup previous = (EventGroup) eventGroups.putIfAbsent(group.getGroupId(), group);
 192  
         // a parallel thread might have removed the EventGroup already,
 193  
         // therefore we need to validate our current reference
 194  0
         return (previous != null ? previous : group);
 195  
     }
 196  
 
 197  
     /**
 198  
      * @see AbstractEventAggregator#removeEventGroup(EventGroup)
 199  
      */
 200  
     protected void removeEventGroup(EventGroup group)
 201  
     {
 202  0
         eventGroups.remove(group.getGroupId());
 203  0
     }
 204  
 
 205  
     /**
 206  
      * This method is called by the responding callee thread and should return the
 207  
      * aggregated response message
 208  
      * 
 209  
      * @param message
 210  
      * @return
 211  
      * @throws RoutingException
 212  
      */
 213  
     public UMOMessage getResponse(UMOMessage message) throws RoutingException
 214  
     {
 215  0
         Object responseId = this.getCallResponseAggregateIdentifier(message);
 216  
 
 217  0
         if (logger.isDebugEnabled())
 218  
         {
 219  0
             logger.debug("Waiting for response for message id: " + responseId + " in " + this);
 220  
         }
 221  
 
 222  0
         Latch l = (Latch) locks.get(responseId);
 223  0
         if (l == null)
 224  
         {
 225  0
             if (logger.isDebugEnabled())
 226  
             {
 227  0
                 logger.debug("Got response but no one is waiting for it yet. Creating latch for "
 228  
                                 + responseId + " in " + this);
 229  
             }
 230  
 
 231  0
             l = new Latch();
 232  0
             Latch previous = (Latch) locks.putIfAbsent(responseId, l);
 233  0
             if (previous != null)
 234  
             {
 235  0
                 l = previous;
 236  
             }
 237  
         }
 238  
 
 239  0
         if (logger.isDebugEnabled())
 240  
         {
 241  0
             logger.debug("Got latch for message: " + responseId);
 242  
         }
 243  
 
 244  
         // the final result message
 245  
         UMOMessage result;
 246  
 
 247  
         // indicates whether the result message could be obtained in the required
 248  
         // timeout interval
 249  0
         boolean resultAvailable = false;
 250  
 
 251  
         // flag for catching the interrupted status of the Thread waiting for a
 252  
         // result
 253  0
         boolean interruptedWhileWaiting = false;
 254  
 
 255  
         try
 256  
         {
 257  0
             if (logger.isDebugEnabled())
 258  
             {
 259  0
                 logger.debug("Waiting for response to message: " + responseId);
 260  
             }
 261  
 
 262  
             // how long should we wait for the lock?
 263  0
             if (this.getTimeout() <= 0)
 264  
             {
 265  0
                 l.await();
 266  0
                 resultAvailable = true;
 267  
             }
 268  
             else
 269  
             {
 270  0
                 resultAvailable = l.await(this.getTimeout(), TimeUnit.MILLISECONDS);
 271  
             }
 272  
         }
 273  0
         catch (InterruptedException e)
 274  
         {
 275  0
             interruptedWhileWaiting = true;
 276  
         }
 277  
         finally
 278  
         {
 279  0
             locks.remove(responseId);
 280  0
             result = (UMOMessage) responseMessages.remove(responseId);
 281  
 
 282  0
             if (interruptedWhileWaiting)
 283  
             {
 284  0
                 Thread.currentThread().interrupt();
 285  
             }
 286  
         }
 287  
 
 288  0
         if (!resultAvailable)
 289  
         {
 290  0
             if (logger.isTraceEnabled())
 291  
             {
 292  0
                 logger.trace("Current responses are: \n" + MapUtils.toString(responseMessages, true));
 293  
             }
 294  
 
 295  0
             throw new ResponseTimeoutException(
 296  
                 CoreMessages.responseTimedOutWaitingForId(
 297  
                     this.getTimeout(), responseId), message, null);
 298  
         }
 299  
 
 300  0
         if (result == null)
 301  
         {
 302  
             // this should never happen, just using it as a safe guard for now
 303  0
             throw new IllegalStateException("Response Message is null");
 304  
         }
 305  
 
 306  0
         if (logger.isDebugEnabled())
 307  
         {
 308  0
             logger.debug("remaining locks  : " + locks.keySet());
 309  0
             logger.debug("remaining results: " + responseMessages.keySet());
 310  
         }
 311  
 
 312  0
         return result;
 313  
     }
 314  
 
 315  
     /**
 316  
      * @see AbstractEventAggregator#shouldAggregateEvents(EventGroup)
 317  
      */
 318  
     protected abstract boolean shouldAggregateEvents(EventGroup events);
 319  
 
 320  
     /**
 321  
      * @see AbstractEventAggregator#aggregateEvents(EventGroup)
 322  
      */
 323  
     protected abstract UMOMessage aggregateEvents(EventGroup events) throws RoutingException;
 324  
 
 325  
 }