Coverage Report - org.mule.routing.inbound.AbstractEventAggregator
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractEventAggregator
82%
33/40
64%
9/14
2
 
 1  
 /*
 2  
  * $Id: AbstractEventAggregator.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.inbound;
 12  
 
 13  
 import org.mule.impl.MuleEvent;
 14  
 import org.mule.impl.endpoint.MuleEndpoint;
 15  
 import org.mule.routing.AggregationException;
 16  
 import org.mule.umo.MessagingException;
 17  
 import org.mule.umo.UMOEvent;
 18  
 import org.mule.umo.UMOMessage;
 19  
 import org.mule.umo.endpoint.UMOEndpoint;
 20  
 
 21  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
 22  
 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
 23  
 
 24  
 /**
 25  
  * <code>AbstractEventAggregator</code> will aggregate a set of messages into a
 26  
  * single message.
 27  
  */
 28  
 
 29  2
 public abstract class AbstractEventAggregator extends SelectiveConsumer
 30  
 {
 31  
     public static final String NO_CORRELATION_ID = "no-id";
 32  
 
 33  2
     private final ConcurrentMap eventGroups = new ConcurrentHashMap();
 34  
 
 35  
     // @Override
 36  
     public UMOEvent[] process(UMOEvent event) throws MessagingException
 37  
     {
 38  6
         UMOEvent[] result = null;
 39  
 
 40  6
         if (this.isMatch(event))
 41  
         {
 42  
             // indicates interleaved EventGroup removal (very rare)
 43  6
             boolean miss = false;
 44  
 
 45  
             // match event to its group
 46  6
             final Object groupId = this.getEventGroupIdForEvent(event);
 47  
 
 48  
             // spinloop for the EventGroup lookup
 49  
             while (true)
 50  
             {
 51  6
                 if (miss)
 52  
                 {
 53  
                     try
 54  
                     {
 55  
                         // recommended over Thread.yield()
 56  0
                         Thread.sleep(1);
 57  
                     }
 58  0
                     catch (InterruptedException interrupted)
 59  
                     {
 60  0
                         Thread.currentThread().interrupt();
 61  0
                     }
 62  
                 }
 63  
 
 64  
                 // check for an existing group first
 65  6
                 EventGroup group = this.getEventGroup(groupId);
 66  
 
 67  
                 // does the group exist?
 68  6
                 if (group == null)
 69  
                 {
 70  
                     // ..apparently not, so create a new one & add it
 71  2
                     group = this.addEventGroup(this.createEventGroup(event, groupId));
 72  
                 }
 73  
 
 74  
                 // ensure that only one thread at a time evaluates this EventGroup
 75  6
                 synchronized (group)
 76  
                 {
 77  
                     // make sure no other thread removed the group in the meantime
 78  6
                     if (group != this.getEventGroup(groupId))
 79  
                     {
 80  
                         // if that is the (rare) case, spin
 81  0
                         miss = true;
 82  0
                         continue;
 83  
                     }
 84  
 
 85  
                     // add the incoming event to the group
 86  6
                     group.addEvent(event);
 87  
 
 88  6
                     if (this.shouldAggregateEvents(group))
 89  
                     {
 90  2
                         UMOMessage returnMessage = this.aggregateEvents(group);
 91  2
                         UMOEndpoint endpoint = new MuleEndpoint(event.getEndpoint());
 92  2
                         endpoint.setTransformer(null);
 93  2
                         endpoint.setName(this.getClass().getName());
 94  2
                         UMOEvent returnEvent = new MuleEvent(returnMessage, endpoint, event.getComponent(),
 95  
                             event);
 96  2
                         result = new UMOEvent[]{returnEvent};
 97  2
                         this.removeEventGroup(group);
 98  
                     }
 99  
 
 100  
                     // result or not: exit spinloop
 101  6
                     break;
 102  0
                 }
 103  
             }
 104  
         }
 105  
 
 106  6
         return result;
 107  
     }
 108  
 
 109  
     /**
 110  
      * Create a new EventGroup with the specified groupId.
 111  
      * 
 112  
      * @param event the event that caused creation of this group; can be used for
 113  
      *            additional information
 114  
      * @param groupId the id to use for the new EventGroup
 115  
      * @return a new EventGroup
 116  
      */
 117  
     protected EventGroup createEventGroup(UMOEvent event, Object groupId)
 118  
     {
 119  2
         return new EventGroup(groupId);
 120  
     }
 121  
 
 122  
     /**
 123  
      * Returns the identifier by which events will be correlated. By default this is
 124  
      * the value as returned by {@link org.mule.umo.provider.UMOMessageAdapter#getCorrelationId()}.
 125  
      * 
 126  
      * @param event the event use for determining the correlation group id
 127  
      * @return the id used to correlate related events
 128  
      */
 129  
     protected Object getEventGroupIdForEvent(UMOEvent event)
 130  
     {
 131  6
         String groupId = event.getMessage().getCorrelationId();
 132  
 
 133  6
         if (groupId == null)
 134  
         {
 135  6
             groupId = NO_CORRELATION_ID;
 136  
         }
 137  
 
 138  6
         return groupId;
 139  
     }
 140  
 
 141  
     /**
 142  
      * Look up the existing EventGroup with the given id.
 143  
      * 
 144  
      * @param groupId
 145  
      * @return the EventGroup with the given id or <code>null</code> if the group
 146  
      *         does not exist.
 147  
      */
 148  
     protected EventGroup getEventGroup(Object groupId)
 149  
     {
 150  12
         return (EventGroup) eventGroups.get(groupId);
 151  
     }
 152  
 
 153  
     /**
 154  
      * Add the given EventGroup to this aggregator's "group store". Currently this is
 155  
      * only a ConcurrentHashMap, and the group's id as returned by
 156  
      * {@link EventGroup#getGroupId()} is used to match the group. Since group
 157  
      * creation/lookup/storage can happen fully concurrent, we return the stored
 158  
      * group. Callers are required to switch their method-local references when a
 159  
      * different group is returned.
 160  
      * 
 161  
      * @param group the EventGroup to "store"
 162  
      * @return the stored EventGroup (may be different from the one passed as
 163  
      *         argument)
 164  
      */
 165  
     protected EventGroup addEventGroup(EventGroup group)
 166  
     {
 167  
         // a parallel thread might have removed the EventGroup already,
 168  
         // therefore we need to validate our current reference
 169  2
         EventGroup previous = (EventGroup) eventGroups.putIfAbsent(group.getGroupId(), group);
 170  2
         return (previous != null ? previous : group);
 171  
     }
 172  
 
 173  
     /**
 174  
      * Remove the group from this aggregator's "store". The group's id as returned by
 175  
      * {@link EventGroup#getGroupId()} is used to match the group.
 176  
      * 
 177  
      * @param group the EventGroup to remove
 178  
      */
 179  
     protected void removeEventGroup(EventGroup group)
 180  
     {
 181  2
         eventGroups.remove(group.getGroupId());
 182  2
     }
 183  
 
 184  
     /**
 185  
      * Determines if the event group is ready to be aggregated. if the group is ready
 186  
      * to be aggregated (this is entirely up to the application. it could be
 187  
      * determined by volume, last modified time or some oher criteria based on the
 188  
      * last event received).
 189  
      * 
 190  
      * @param events
 191  
      * @return true if the group is ready for aggregation
 192  
      */
 193  
     protected abstract boolean shouldAggregateEvents(EventGroup events);
 194  
 
 195  
     /**
 196  
      * This method is invoked if the shouldAggregate method is called and returns
 197  
      * true. Once this method returns an aggregated message, the event group is
 198  
      * removed from the router.
 199  
      * 
 200  
      * @param events the event group for this request
 201  
      * @return an aggregated message
 202  
      * @throws AggregationException if the aggregation fails. in this scenario the
 203  
      *             whole event group is removed and passed to the exception handler
 204  
      *             for this componenet
 205  
      */
 206  
     protected abstract UMOMessage aggregateEvents(EventGroup events) throws AggregationException;
 207  
 
 208  
 }