View Javadoc

1   /*
2    * $Id: IdempotentMessageFilter.java 22690 2011-08-17 06:43:14Z mike.schilling $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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;
12  
13  import org.mule.api.MessagingException;
14  import org.mule.api.MuleEvent;
15  import org.mule.api.MuleException;
16  import org.mule.api.config.MuleProperties;
17  import org.mule.api.construct.FlowConstruct;
18  import org.mule.api.construct.FlowConstructAware;
19  import org.mule.api.expression.ExpressionManager;
20  import org.mule.api.lifecycle.Initialisable;
21  import org.mule.api.lifecycle.InitialisationException;
22  import org.mule.api.routing.RoutingException;
23  import org.mule.api.store.ObjectAlreadyExistsException;
24  import org.mule.api.store.ObjectStore;
25  import org.mule.api.store.ObjectStoreManager;
26  import org.mule.config.i18n.CoreMessages;
27  import org.mule.processor.AbstractFilteringMessageProcessor;
28  import org.mule.util.concurrent.ThreadNameHelper;
29  import org.mule.util.store.InMemoryObjectStore;
30  
31  import java.text.MessageFormat;
32  
33  /**
34   * <code>IdempotentMessageFilter</code> ensures that only unique messages are passed
35   * on. It does this by checking the unique ID of the incoming message. Note that the
36   * underlying endpoint must support unique message IDs for this to work, otherwise a
37   * <code>UniqueIdNotSupportedException</code> is thrown.<br>
38   * <p>
39   * <b>EIP Reference:</b> <a
40   * href="http://www.eaipatterns.com/IdempotentReceiver.html">
41   * http://www.eaipatterns.com/IdempotentReceiver.html</a>
42   */
43  public class IdempotentMessageFilter extends AbstractFilteringMessageProcessor implements FlowConstructAware, Initialisable
44  {
45      protected volatile ObjectStore<String> store;
46      protected FlowConstruct flowConstruct;
47      protected String storePrefix;
48  
49      protected String idExpression = MessageFormat.format("{0}message:id{1}",
50          ExpressionManager.DEFAULT_EXPRESSION_PREFIX, ExpressionManager.DEFAULT_EXPRESSION_POSTFIX);
51  
52      protected String valueExpression = MessageFormat.format("{0}message:id{1}",
53          ExpressionManager.DEFAULT_EXPRESSION_PREFIX, ExpressionManager.DEFAULT_EXPRESSION_POSTFIX);
54  
55      public IdempotentMessageFilter()
56      {
57          super();
58      }
59  
60      @Override
61      public void initialise() throws InitialisationException
62      {
63          if (storePrefix == null)
64          {
65              storePrefix = String.format("%s.%s.%s", ThreadNameHelper.getPrefix(muleContext),
66                  (flowConstruct == null ? "" : flowConstruct.getName()), this.getClass().getName());
67          }
68          if (store == null)
69          {
70              this.store = createMessageIdStore();
71          }
72      }
73  
74      protected ObjectStore<String> createMessageIdStore() throws InitialisationException
75      {
76          ObjectStoreManager objectStoreManager = (ObjectStoreManager) muleContext.getRegistry().get(
77                  MuleProperties.OBJECT_STORE_MANAGER);
78          return objectStoreManager.getObjectStore(storePrefix, false, -1,  60 * 5 * 1000, 6000 );
79      }
80  
81      @Override
82      protected MuleEvent processNext(MuleEvent event) throws MuleException
83      {
84          String id = getIdForEvent(event);
85          String value = getValueForEvent(event);
86          try
87          {
88              try
89              {
90                  store.store(id, value);
91              }
92              catch (ObjectAlreadyExistsException ex)
93              {
94                  // another thread got in ahead of us
95                  logger.debug("Possible duplicate message due to race condition: " + id);
96              }
97              return super.processNext(event);
98          }
99          catch (Exception e)
100         {
101             throw new RoutingException(CoreMessages.failedToWriteMessageToStore(id, storePrefix),
102                 event, this, e);
103         }
104     }
105 
106     protected String getValueForEvent(MuleEvent event) throws MessagingException
107     {
108         return event.getMuleContext().getExpressionManager().parse(valueExpression, event.getMessage(), true);
109     }
110 
111     protected String getIdForEvent(MuleEvent event) throws MessagingException
112     {
113         return event.getMuleContext().getExpressionManager().parse(idExpression, event.getMessage(), true);
114     }
115 
116     public String getIdExpression()
117     {
118         return idExpression;
119     }
120 
121     public void setIdExpression(String idExpression)
122     {
123         this.idExpression = idExpression;
124     }
125 
126     public ObjectStore<String> getStore()
127     {
128         return store;
129     }
130 
131     public void setStore(ObjectStore<String> store)
132     {
133         this.store = store;
134     }
135 
136     @Override
137     protected boolean accept(MuleEvent event)
138     {
139         return event != null && acceptMessageForFlowConstruct(event) && isNewMessage(event);
140     }
141 
142     protected boolean acceptMessageForFlowConstruct(MuleEvent event)
143     {
144         if (flowConstruct.getName().equals(event.getFlowConstruct().getName()))
145         {
146             return true;
147         }
148         else
149         {
150             logger.error("This IdempotentMessageFilter was configured on the service: "
151                          + storePrefix + " but has received an event for service: "
152                          + flowConstruct.getName() + ". Please check your config to make sure each service"
153                          + "has its own instance of IdempotentMessageFilter.");
154             return false;
155         }
156     }
157 
158     protected boolean isNewMessage(MuleEvent event)
159     {
160         try
161         {
162             String id = this.getIdForEvent(event);
163             if (store == null)
164             {
165                 synchronized (this)
166                 {
167                     initialise();
168                 }
169             }
170             return !store.contains(id);
171         }
172         catch (MuleException e)
173         {
174             logger.error("Exception attempting to determine idempotency of incoming message for "
175                          + event.getFlowConstruct().getName() + " from the endpoint "
176                          + event.getMessageSourceURI(), e);
177             return false;
178         }
179     }
180 
181     @Override
182     public void setFlowConstruct(FlowConstruct flowConstruct)
183     {
184         this.flowConstruct = flowConstruct;
185     }
186 
187     public String getValueExpression()
188     {
189         return valueExpression;
190     }
191 
192     public void setValueExpression(String valueExpression)
193     {
194         this.valueExpression = valueExpression;
195     }
196 
197     public void setStorePrefix(String storePrefix)
198     {
199         this.storePrefix = storePrefix;
200     }
201 }