Coverage Report - org.mule.routing.requestreply.AbstractAsyncRequestReplyRequester
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractAsyncRequestReplyRequester
0%
0/79
0%
0/28
0
AbstractAsyncRequestReplyRequester$InternalAsyncReplyMessageProcessor
0%
0/16
0%
0/8
0
 
 1  
 /*
 2  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 3  
  * The software in this package is published under the terms of the CPAL v1.0
 4  
  * license, a copy of which has been included with this distribution in the
 5  
  * LICENSE.txt file.
 6  
  */
 7  
 package org.mule.routing.requestreply;
 8  
 
 9  
 import org.mule.DefaultMuleEvent;
 10  
 import org.mule.RequestContext;
 11  
 import org.mule.api.MuleEvent;
 12  
 import org.mule.api.MuleException;
 13  
 import org.mule.api.MuleMessageCollection;
 14  
 import org.mule.api.config.MuleProperties;
 15  
 import org.mule.api.construct.FlowConstruct;
 16  
 import org.mule.api.construct.FlowConstructAware;
 17  
 import org.mule.api.processor.MessageProcessor;
 18  
 import org.mule.api.processor.RequestReplyRequesterMessageProcessor;
 19  
 import org.mule.api.routing.ResponseTimeoutException;
 20  
 import org.mule.api.source.MessageSource;
 21  
 import org.mule.config.i18n.CoreMessages;
 22  
 import org.mule.context.notification.RoutingNotification;
 23  
 import org.mule.processor.AbstractInterceptingMessageProcessor;
 24  
 import org.mule.util.ObjectUtils;
 25  
 import org.mule.util.concurrent.Latch;
 26  
 
 27  
 import java.util.Map;
 28  
 
 29  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
 30  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
 31  
 import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
 32  
 
 33  
 import org.apache.commons.collections.buffer.BoundedFifoBuffer;
 34  
 
 35  0
 public abstract class AbstractAsyncRequestReplyRequester extends AbstractInterceptingMessageProcessor
 36  
     implements RequestReplyRequesterMessageProcessor, FlowConstructAware
 37  
 {
 38  
     public static final int MAX_PROCESSED_GROUPS = 50000;
 39  
 
 40  0
     protected volatile long timeout = -1;
 41  0
     protected volatile boolean failOnTimeout = true;
 42  
     protected MessageSource replyMessageSource;
 43  
     protected FlowConstruct flowConstruct;
 44  0
     private final MessageProcessor internalAsyncReplyMessageProcessor = new InternalAsyncReplyMessageProcessor();
 45  
 
 46  0
     @SuppressWarnings("unchecked")
 47  
     protected final Map<String, Latch> locks = new ConcurrentHashMap();
 48  
     
 49  0
     protected final ConcurrentMap responseEvents = new ConcurrentHashMap();
 50  0
     protected final Object processedLock = new Object();
 51  
     // @GuardedBy processedLock
 52  0
     protected final BoundedFifoBuffer processed = new BoundedFifoBuffer(MAX_PROCESSED_GROUPS);
 53  
 
 54  
     public MuleEvent process(MuleEvent event) throws MuleException
 55  
     {
 56  0
         if (replyMessageSource == null)
 57  
         {
 58  0
             return processNext(event);
 59  
         }
 60  
         else
 61  
         {
 62  0
             locks.put(getAsyncReplyCorrelationId(event), createEventLock());
 63  
 
 64  0
             sendAsyncRequest(event);
 65  
 
 66  0
             MuleEvent resultEvent = receiveAsyncReply(event);
 67  0
                            if (resultEvent != null)
 68  
                            {
 69  0
                                resultEvent = RequestContext.setEvent(new DefaultMuleEvent(resultEvent.getMessage(), event));
 70  
             }
 71  0
                  return resultEvent;
 72  
         }
 73  
     }
 74  
 
 75  
     /**
 76  
      * Creates the lock used to synchronize a given event
 77  
      * @return a new Latch instance
 78  
      */
 79  
     protected Latch createEventLock()
 80  
     {
 81  0
         return new Latch();
 82  
     }
 83  
 
 84  
     public void setTimeout(long timeout)
 85  
     {
 86  0
         this.timeout = timeout;
 87  0
     }
 88  
 
 89  
     public void setFailOnTimeout(boolean failOnTimeout)
 90  
     {
 91  0
         this.failOnTimeout = failOnTimeout;
 92  0
     }
 93  
 
 94  
     public void setReplySource(MessageSource messageSource)
 95  
     {
 96  0
         verifyReplyMessageSource(messageSource);
 97  0
         replyMessageSource = messageSource;
 98  0
         messageSource.setListener(internalAsyncReplyMessageProcessor);
 99  0
     }
 100  
 
 101  
     protected void verifyReplyMessageSource(MessageSource messageSource)
 102  
     {
 103  
         // template method
 104  0
     }
 105  
 
 106  
     protected String getAsyncReplyCorrelationId(MuleEvent event)
 107  
     {
 108  0
         if (event.getMessage() instanceof MuleMessageCollection)
 109  
         {
 110  0
             return event.getMessage().getCorrelationId();
 111  
         }
 112  
         else
 113  
         {
 114  0
             return event.getFlowConstruct().getMessageInfoMapping().getCorrelationId(event.getMessage());
 115  
         }
 116  
     }
 117  
 
 118  
     protected void sendAsyncRequest(MuleEvent event) throws MuleException
 119  
     {
 120  0
         processNext(event);
 121  0
     }
 122  
     
 123  
     protected MuleEvent receiveAsyncReply(MuleEvent event) throws ResponseTimeoutException
 124  
     {
 125  0
         String asyncReplyCorrelationId = getAsyncReplyCorrelationId(event);
 126  0
         Latch asyncReplyLatch = locks.get(asyncReplyCorrelationId);
 127  
         // flag for catching the interrupted status of the Thread waiting for a
 128  
         // result
 129  0
         boolean interruptedWhileWaiting = false;
 130  0
         boolean resultAvailable = false;
 131  0
         MuleEvent result = null;
 132  
 
 133  
         try
 134  
         {
 135  0
             if (logger.isDebugEnabled())
 136  
             {
 137  0
                 logger.debug("Waiting for async reply message with id: " + asyncReplyCorrelationId);
 138  
             }
 139  
             // how long should we wait for the lock?
 140  0
             if (timeout <= 0)
 141  
             {
 142  0
                 asyncReplyLatch.await();
 143  0
                 resultAvailable = true;
 144  
             }
 145  
             else
 146  
             {
 147  0
                 resultAvailable = asyncReplyLatch.await(timeout, TimeUnit.MILLISECONDS);
 148  
             }
 149  0
             if (!resultAvailable)
 150  
             {
 151  0
                 postLatchAwait(asyncReplyCorrelationId);
 152  0
                 resultAvailable = asyncReplyLatch.getCount() == 0;
 153  
             }
 154  
         }
 155  0
         catch (InterruptedException e)
 156  
         {
 157  0
             interruptedWhileWaiting = true;
 158  
         }
 159  
         finally
 160  
         {
 161  0
             locks.remove(asyncReplyCorrelationId);
 162  0
             result = (MuleEvent) responseEvents.remove(asyncReplyCorrelationId);
 163  0
             if (interruptedWhileWaiting)
 164  
             {
 165  0
                 Thread.currentThread().interrupt();
 166  0
                 return null;
 167  
             }
 168  
         }
 169  
 
 170  0
         if (resultAvailable)
 171  
         {
 172  0
             if (result == null)
 173  
             {
 174  
                 // this should never happen, just using it as a safe guard for now
 175  0
                 throw new IllegalStateException("Response MuleEvent is null");
 176  
             }
 177  
 
 178  
             // If result has MULE_SESSION property then merge session properties returned with existing
 179  
             // session properties.  See MULE-5852
 180  0
             if (result.getMessage().getInboundProperty(MuleProperties.MULE_SESSION_PROPERTY) != null)
 181  
             {
 182  0
                 event.getSession().merge(result.getSession());
 183  
             }
 184  
             
 185  
             // Copy event because the async-reply message was received by a different
 186  
             // receiver thread (or the senders dispatcher thread in case of vm
 187  
             // with queueEvents="false") and the current thread may need to mutate
 188  
             // the even. See MULE-4370
 189  0
             if (result != null)
 190  
             {
 191  0
                 result = RequestContext.setEvent(new DefaultMuleEvent(result.getMessage(), event));
 192  
             }
 193  0
             return result;
 194  
         }
 195  
         else
 196  
         {
 197  0
             addProcessed(asyncReplyCorrelationId);
 198  
 
 199  0
             if (failOnTimeout)
 200  
             {
 201  0
                 event.getMuleContext()
 202  
                     .fireNotification(
 203  
                         new RoutingNotification(event.getMessage(), null,
 204  
                             RoutingNotification.ASYNC_REPLY_TIMEOUT));
 205  
 
 206  0
                 throw new ResponseTimeoutException(CoreMessages.responseTimedOutWaitingForId((int) timeout,
 207  
                     asyncReplyCorrelationId), event, null);
 208  
             }
 209  
             else
 210  
             {
 211  0
                 return null;
 212  
             }
 213  
         }
 214  
     }
 215  
 
 216  
     protected void postLatchAwait(String asyncReplyCorrelationId)
 217  
     {
 218  
         // Template method
 219  0
     }
 220  
 
 221  
     protected void addProcessed(Object id)
 222  
     {
 223  0
         synchronized (processedLock)
 224  
         {
 225  0
             if (processed.isFull())
 226  
             {
 227  0
                 processed.remove();
 228  
             }
 229  0
             processed.add(id);
 230  0
         }
 231  0
     }
 232  
 
 233  
     protected boolean isAlreadyProcessed(Object id)
 234  
     {
 235  0
         synchronized (processedLock)
 236  
         {
 237  0
             return processed.contains(id);
 238  0
         }
 239  
     }
 240  
 
 241  0
     class InternalAsyncReplyMessageProcessor implements MessageProcessor
 242  
     {
 243  
         public MuleEvent process(MuleEvent event) throws MuleException
 244  
         {
 245  0
             String messageId = getAsyncReplyCorrelationId(event);
 246  
 
 247  0
             if (isAlreadyProcessed(messageId))
 248  
             {
 249  0
                 if (logger.isDebugEnabled())
 250  
                 {
 251  0
                     logger.debug("An event was received for an event group that has already been processed, "
 252  
                                  + "this is probably because the async-reply timed out. Correlation Id is: "
 253  
                                  + messageId + ". Dropping event");
 254  
                 }
 255  
                 // Fire a notification to say we received this message
 256  0
                 event.getMuleContext().fireNotification(
 257  
                     new RoutingNotification(event.getMessage(), event.getEndpoint()
 258  
                         .getEndpointURI()
 259  
                         .toString(), RoutingNotification.MISSED_ASYNC_REPLY));
 260  0
                 return null;
 261  
             }
 262  
 
 263  0
             addProcessed(messageId);
 264  0
             MuleEvent previousResult = (MuleEvent) responseEvents.putIfAbsent(messageId, event);
 265  0
             if (previousResult != null)
 266  
             {
 267  
                 // this would indicate that we need a better way to prevent
 268  
                 // continued aggregation for a group that is currently being
 269  
                 // processed. Can this actually happen?
 270  0
                 throw new IllegalStateException("Detected duplicate result message with id: " + messageId);
 271  
             }
 272  0
             Latch l = locks.get(messageId);
 273  0
             if (l != null)
 274  
             {
 275  0
                 l.countDown();
 276  
             }
 277  
             else
 278  
             {
 279  0
                 logger.warn("Unexpected  message with id " + messageId
 280  
                             + " received.   This message will be discarded.");
 281  
             }
 282  0
             return null;
 283  
         }
 284  
     }
 285  
 
 286  
     @Override
 287  
     public String toString()
 288  
     {
 289  0
         return ObjectUtils.toString(this);
 290  
     }
 291  
     
 292  
     public void setFlowConstruct(FlowConstruct flowConstruct)
 293  
     {
 294  0
         this.flowConstruct = flowConstruct;
 295  0
     }
 296  
 }