View Javadoc
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.context.notification;
8   
9   import org.mule.api.context.notification.ServerNotification;
10  import org.mule.api.context.notification.BlockingServerEvent;
11  
12  import java.util.Set;
13  import java.util.HashSet;
14  import java.util.LinkedList;
15  import java.util.Iterator;
16  
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  
20  /**
21   * We test notifications by defining a "tree" of expected responses (this is needed because the system is
22   * multithreaded and only some ordering is guaranteed serial; other actions happen in parallel)
23   * Each node can test for a notification and then has a set of parallel nodes, which describe which notifications
24   * happen next in any order.
25   * Finally, after all parallel nodes are matched, a node has a set of serial nodes, which are matched in order.
26   *
27   * <p>Note that nested nodes may themselves have structure and that empty nodes are available, which can
28   * help group dependencies.
29   *
30   * <p>More exactly, we specify a tree and a traversal - the traversal is hardcoded below, and implicit in
31   * the instructions above.
32   */
33  class Node implements RestrictedNode
34  {
35  
36      // enumeration describing result of checking at this node
37      public static final int SUCCESS = 0;
38      public static final int FAILURE = 1;
39      public static final int EMPTY = 2;
40  
41      // the data for this node
42      private Class clazz = null;
43      private int action;
44      private String id;
45      private boolean isIdDefined = false; // allow null IDs to be specified
46      private boolean nodeOk = false;
47  
48      protected final transient Log logger = LogFactory.getLog(this.getClass());
49  
50      // any of these can run after this
51      private Set parallel = new HashSet();
52      // only once the parallel are done, this runs
53      private LinkedList serial = new LinkedList();
54  
55      public Node(Class clazz, int action, String id)
56      {
57          this(clazz, action);
58          this.id = id;
59          isIdDefined = true;
60      }
61  
62      public Node(Class clazz, int action)
63      {
64          this.clazz = clazz;
65          this.action = action;
66      }
67  
68      public Node()
69      {
70          nodeOk = true;
71      }
72  
73      public Node parallel(RestrictedNode node)
74      {
75          if (null != node.getNotificationClass() &&
76              BlockingServerEvent.class.isAssignableFrom(node.getNotificationClass()))
77          {
78              logger.warn("Registered blocking event as parallel: " + node);
79          }
80          parallel.add(node);
81          return this;
82      }
83  
84      /**
85       * Avoid warnings when we need to add a synch event as parallel for other reasons
86       * (typically because there's more than one model generating some event) 
87       */
88      public Node parallelSynch(RestrictedNode node)
89      {
90          if (null != node.getNotificationClass() &&
91              !BlockingServerEvent.class.isAssignableFrom(node.getNotificationClass()))
92          {
93              throw new IllegalStateException("Node " + node + " is not a synch event");
94          }
95          parallel.add(node);
96          return this;
97      }
98  
99      public RestrictedNode serial(RestrictedNode node)
100     {
101         if (null != node.getNotificationClass() &&
102                 !BlockingServerEvent.class.isAssignableFrom(node.getNotificationClass()))
103         {
104             logger.warn("Registered non-blocking event as serial: " + node);
105         }
106         serial.addLast(node);
107         return this;
108     }
109 
110     /**
111      * @param notification
112      * @return whether the notification was matched or not (for this node or any child)
113      */
114     public int match(ServerNotification notification)
115     {
116         // if we need to check ourselves, just do that
117         if (!nodeOk)
118         {
119             if (testLocal(notification))
120             {
121                 nodeOk = true;
122                 return SUCCESS;
123             }
124             else
125             {
126                 return FAILURE;
127             }
128         }
129 
130         // otherwise, if we have parallel children, try them
131         if (parallel.size() > 0)
132         {
133             for (Iterator children = parallel.iterator(); children.hasNext();)
134             {
135                 Node child = (Node) children.next();
136                 switch (child.match(notification))
137                 {
138                 case SUCCESS:
139                     return SUCCESS;
140                 case EMPTY: // the node was empty, clean out
141                     children.remove();
142                     break;
143                 case FAILURE:
144                     break;
145                 default:
146                     throw new IllegalStateException("Bad return from child");
147                 }
148             }
149         }
150 
151         // if we've still got parallel children, we failed
152         if (parallel.size() > 0)
153         {
154             return FAILURE;
155         }
156 
157         // otherwise, serial children
158         if (serial.size() > 0)
159         {
160             for (Iterator children = serial.iterator(); children.hasNext();)
161             {
162                 Node child = (Node) children.next();
163                 switch (child.match(notification))
164                 {
165                 case SUCCESS:
166                     return SUCCESS;
167                 case EMPTY: // the node was empty, clean out
168                     children.remove();
169                     break;
170                 case FAILURE:
171                     return FAILURE; // note this is different to parallel case
172                 default:
173                     throw new IllegalStateException("Bad return from child");
174                 }
175             }
176 
177         }
178 
179         if (serial.size() > 0)
180         {
181             return FAILURE;
182         }
183         else
184         {
185             return EMPTY;
186         }
187     }
188 
189     private boolean testLocal(ServerNotification notification)
190     {
191         return clazz.equals(notification.getClass())
192                 && action == notification.getAction()
193                 && (!isIdDefined ||
194                 (null == id && null == notification.getResourceIdentifier()) ||
195                 (null != id && id.equals(notification.getResourceIdentifier())));
196     }
197 
198     public boolean contains(Class clazz, int action)
199     {
200         if (null != this.clazz && this.clazz.equals(clazz) && this.action == action)
201         {
202             return true;
203         }
204         for (Iterator children = parallel.iterator(); children.hasNext();)
205         {
206             if (((RestrictedNode) children.next()).contains(clazz, action))
207             {
208                 return true;
209             }
210         }
211         for (Iterator children = serial.iterator(); children.hasNext();)
212         {
213             if (((RestrictedNode) children.next()).contains(clazz, action))
214             {
215                 return true;
216             }
217         }
218         return false;
219     }
220 
221     public RestrictedNode getAnyRemaining()
222     {
223         if (! nodeOk)
224         {
225             return this;
226         }
227         for (Iterator children = parallel.iterator(); children.hasNext();)
228         {
229             RestrictedNode any = ((RestrictedNode) children.next()).getAnyRemaining();
230             if (null != any)
231             {
232                 return any;
233             }
234         }
235         for (Iterator children = serial.iterator(); children.hasNext();)
236         {
237             RestrictedNode any = ((RestrictedNode) children.next()).getAnyRemaining();
238             if (null != any)
239             {
240                 return any;
241             }
242         }
243         return null;
244     }
245 
246     public boolean isExhausted()
247     {
248         return null == getAnyRemaining();
249     }
250 
251     public Class getNotificationClass()
252     {
253         return clazz;
254     }
255 
256     public String toString()
257     {
258         return clazz + ": " + action + (isIdDefined ? ": " + id : "");
259     }
260 
261 }