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