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.tck.functional;
8   
9   import org.mule.RequestContext;
10  import org.mule.api.MuleContext;
11  import org.mule.api.MuleEventContext;
12  import org.mule.api.MuleException;
13  import org.mule.api.MuleMessage;
14  import org.mule.api.context.MuleContextAware;
15  import org.mule.api.lifecycle.Callable;
16  import org.mule.api.lifecycle.Disposable;
17  import org.mule.api.lifecycle.Initialisable;
18  import org.mule.api.lifecycle.Startable;
19  import org.mule.api.lifecycle.Stoppable;
20  import org.mule.tck.exceptions.FunctionalTestException;
21  import org.mule.util.NumberUtils;
22  import org.mule.util.StringMessageUtils;
23  import org.mule.util.SystemUtils;
24  
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  
33  /**
34   * <code>FunctionalTestComponent</code> is a service that can be used by
35   * functional tests. This service accepts an EventCallback that can be used to
36   * assert the state of the current event.
37   * <p/>
38   * Also, this service fires {@link org.mule.tck.functional.FunctionalTestNotification} via Mule for every message received.
39   * Tests can register with Mule to receive these events by implementing
40   * {@link org.mule.tck.functional.FunctionalTestNotificationListener}.
41   *
42   * @see EventCallback
43   * @see FunctionalTestNotification
44   * @see FunctionalTestNotificationListener
45   */
46  // TODO This should really extend StaticComponent from mule-core as it is quite similar.
47  public class FunctionalTestComponent implements Callable, Initialisable, Disposable, MuleContextAware, Receiveable, Startable, Stoppable
48  {
49      protected transient Log logger = LogFactory.getLog(getClass());
50  
51      public static final int STREAM_SAMPLE_SIZE = 4;
52      public static final int STREAM_BUFFER_SIZE = 4096;
53      private EventCallback eventCallback;
54      private Object returnData = null;
55      private boolean throwException = false;
56      private boolean enableMessageHistory = true;
57      private boolean enableNotifications = true;
58      private boolean doInboundTransform = true;
59      private String appendString;
60      private Class<? extends Throwable> exceptionToThrow;
61      private long waitTime = 0;
62      private boolean logMessageDetails = false;
63      private String id = "<none>";
64      private MuleContext muleContext;
65      private static List<LifecycleCallback> lifecycleCallbacks = new ArrayList<LifecycleCallback>();
66  
67      
68      /**
69       * Keeps a list of any messages received on this service. Note that only references
70       * to the messages (objects) are stored, so any subsequent changes to the objects
71       * will change the history.
72       */
73      private List<Object> messageHistory;
74  
75      @SuppressWarnings("unchecked")
76      public void initialise()
77      {
78          if (enableMessageHistory)
79          {
80              messageHistory = new CopyOnWriteArrayList();
81          }
82          for (LifecycleCallback callback : lifecycleCallbacks)
83          {
84              callback.onTransition(id, Initialisable.PHASE_NAME);
85          }
86      }
87  
88      public void start() throws MuleException
89      {
90          for (LifecycleCallback callback : lifecycleCallbacks)
91          {
92              callback.onTransition(id, Startable.PHASE_NAME);
93          }
94      }
95  
96      public void setMuleContext(MuleContext context)
97      {
98          this.muleContext = context;
99      }
100 
101     public void stop() throws MuleException
102     {
103         for (LifecycleCallback callback : lifecycleCallbacks)
104         {
105             callback.onTransition(id, Stoppable.PHASE_NAME);
106         }
107     }
108 
109     public void dispose()
110     {
111         for (LifecycleCallback callback : lifecycleCallbacks)
112         {
113             callback.onTransition(id, Disposable.PHASE_NAME);
114         }
115     }
116 
117     /**
118      * {@inheritDoc}
119      */
120     public Object onCall(MuleEventContext context) throws Exception
121     {
122         if (isThrowException())
123         {
124             throwException();
125         }
126         return process(getMessageFromContext(context), context);
127     }
128 
129     private Object getMessageFromContext(MuleEventContext context) throws MuleException
130     {
131         if (isDoInboundTransform())
132         {
133             Object o = context.getMessage().getPayload();
134             if (getAppendString() != null && !(o instanceof String))
135             {
136                 o = context.transformMessageToString();
137             }
138             return o;
139         }
140         else if (getAppendString()!=null)
141         {
142             return context.getMessageAsString();
143         }
144         else
145         {
146             return context.getMessage().getPayload();
147         }
148     }
149 
150     /**
151      * This method is used by some WebServices tests where you don' want to be introducing the {@link org.mule.api.MuleEventContext} as
152      * a complex type.
153      *
154      * @param data the event data received
155      * @return the processed message
156      * @throws Exception
157      */
158     public Object onReceive(Object data) throws Exception
159     {
160         MuleEventContext context = RequestContext.getEventContext();
161 
162         if (isThrowException())
163         {
164             throwException();
165         }
166         return process(data, context);
167     }
168 
169 
170     /**
171      * Always throws a {@link org.mule.tck.exceptions.FunctionalTestException}.  This methodis only called if
172      * {@link #isThrowException()} is true.
173      *
174      * @throws FunctionalTestException or the exception specified in 'exceptionType
175      */
176     protected void throwException() throws Exception
177     {
178         if (getExceptionToThrow() != null)
179         {
180             throw (Exception)getExceptionToThrow().newInstance();
181         }
182         else
183         {
184             throw new FunctionalTestException();
185         }
186     }
187 
188     /**
189      * Will append the value of {@link #getAppendString()} to the contents of the message. This has a side affect
190      * that the inbound message will be converted to a string and the return payload will be a string.
191      * Note that the value of {@link #getAppendString()} can contain expressions.
192      *
193      * @param contents the string vlaue of the current message payload
194      * @param message  the current message
195      * @return a concatenated string of the current payload and the appendString
196      */
197     protected String append(String contents, MuleMessage message)
198     {
199         return contents + muleContext.getExpressionManager().parse(appendString, message);
200     }
201 
202     /**
203      * The service method that implements the test component logic.  This method can be called publically through
204      * either {@link #onCall(org.mule.api.MuleEventContext)} or {@link #onReceive(Object)}
205      *
206      * @param data    The message payload
207      * @param context the current {@link org.mule.api.MuleEventContext}
208      * @return a new message payload according to the configuration of the component
209      * @throws Exception if there is a general failure or if {@link #isThrowException()} is true.
210      */
211     protected Object process(Object data, MuleEventContext context) throws Exception
212     {
213         // System.out.println(data + " at " + new java.util.Date());
214         if (enableMessageHistory)
215         {
216             messageHistory.add(data);
217         }
218 
219         if (logger.isInfoEnabled())
220         {
221             String msg = StringMessageUtils.getBoilerPlate("Message Received in service: "
222                     + context.getFlowConstruct().getName() + ". Content is: "
223                     + StringMessageUtils.truncate(data.toString(), 100, true), '*', 80);
224 
225             logger.info(msg);
226         }
227 
228         final MuleMessage message = context.getMessage();
229         if (isLogMessageDetails() && logger.isInfoEnabled())
230         {
231             StringBuilder sb = new StringBuilder();
232 
233             sb.append("Full Message payload: ").append(SystemUtils.LINE_SEPARATOR);
234             sb.append(message.getPayload()).append(SystemUtils.LINE_SEPARATOR);
235             sb.append(StringMessageUtils.headersToString(message));
236             logger.info(sb.toString());
237         }
238 
239         if (eventCallback != null)
240         {
241             eventCallback.eventReceived(context, this);
242         }
243 
244         Object replyMessage;
245         if (returnData != null)
246         {
247             if (returnData instanceof String && muleContext.getExpressionManager().isExpression(returnData.toString()))
248             {
249                 replyMessage = muleContext.getExpressionManager().parse(returnData.toString(), message);
250             }
251             else
252             {
253                 replyMessage = returnData;
254             }
255         }
256         else
257         {
258             if (appendString != null)
259             {
260                 replyMessage = append(data.toString(), message);
261             }
262             else
263             {
264                 replyMessage = data;
265             }
266         }
267 
268         if (isEnableNotifications())
269         {
270             muleContext.fireNotification(
271                     new FunctionalTestNotification(context, replyMessage, FunctionalTestNotification.EVENT_RECEIVED));
272         }
273 
274         //Time to wait before returning
275         if (waitTime > 0)
276         {
277             try
278             {
279                 Thread.sleep(waitTime);
280             }
281             catch (InterruptedException e)
282             {
283                 logger.info("FunctionalTestComponent waitTime was interrupted");
284             }
285         }
286         return replyMessage;
287     }
288 
289     /**
290      * An event callback is called when a message is received by the service.
291      * An MuleEvent callback isn't strictly required but it is usfal for performing assertions
292      * on the current message being received.
293      * Note that the FunctionalTestComponent should be made a singleton
294      * when using MuleEvent callbacks
295      * <p/>
296      * Another option is to register a {@link org.mule.tck.functional.FunctionalTestNotificationListener} with Mule and this
297      * will deleiver a {@link org.mule.tck.functional.FunctionalTestNotification} for every message received by this service
298      *
299      * @return the callback to call when a message is received
300      * @see FunctionalTestNotification
301      * @see FunctionalTestNotificationListener
302      */
303     public EventCallback getEventCallback()
304     {
305         return eventCallback;
306     }
307 
308     /**
309      * An event callback is called when a message is received by the service.
310      * An MuleEvent callback isn't strictly required but it is usfal for performing assertions
311      * on the current message being received.
312      * Note that the FunctionalTestComponent should be made a singleton
313      * when using MuleEvent callbacks
314      * <p/>
315      * Another option is to register a {@link org.mule.tck.functional.FunctionalTestNotificationListener} with Mule and this
316      * will deleiver a {@link org.mule.tck.functional.FunctionalTestNotification} for every message received by this service
317      *
318      * @param eventCallback the callback to call when a message is received
319      * @see FunctionalTestNotification
320      * @see FunctionalTestNotificationListener
321      */
322     public void setEventCallback(EventCallback eventCallback)
323     {
324         this.eventCallback = eventCallback;
325     }
326 
327     /**
328      * Often you will may want to return a fixed message payload to simulate and external system call.
329      * This can be done using the 'returnData' property. Note that you can return complex objects by
330      * using the <container-property> element in the Xml configuration.
331      *
332      * @return the message payload to always return from this service instance
333      */
334     public Object getReturnData()
335     {
336         return returnData;
337     }
338 
339     /**
340      * Often you will may want to return a fixed message payload to simulate and external system call.
341      * This can be done using the 'returnData' property. Note that you can return complex objects by
342      * using the <container-property> element in the Xml configuration.
343      *
344      * @param returnData the message payload to always return from this service instance
345      */
346     public void setReturnData(Object returnData)
347     {
348         this.returnData = returnData;
349     }
350 
351     /**
352      * Sometimes you will want the service to always throw an exception, if this is the case you can
353      * set the 'throwException' property to true.
354      *
355      * @return throwException true if an exception should always be thrown from this instance.
356      *         If the {@link #returnData} property is set and is of type
357      *         java.lang.Exception, that exception will be thrown.
358      */
359     public boolean isThrowException()
360     {
361         return throwException;
362     }
363 
364     /**
365      * Sometimes you will want the service to always throw an exception, if this is the case you can
366      * set the 'throwException' property to true.
367      *
368      * @param throwException true if an exception should always be thrown from this instance.
369      *                       If the {@link #returnData} property is set and is of type
370      *                       java.lang.Exception, that exception will be thrown.
371      */
372     public void setThrowException(boolean throwException)
373     {
374         this.throwException = throwException;
375     }
376 
377     public boolean isEnableMessageHistory()
378     {
379         return enableMessageHistory;
380     }
381 
382     public void setEnableMessageHistory(boolean enableMessageHistory)
383     {
384         this.enableMessageHistory = enableMessageHistory;
385     }
386 
387     /**
388      * If enableMessageHistory = true, returns the number of messages received by this service.
389      * @return -1 if no message history, otherwise the history size
390      */
391     public int getReceivedMessagesCount()
392     {
393         if (messageHistory != null)
394         {
395             return messageHistory.size();
396         }
397         else
398         {
399             return NumberUtils.INTEGER_MINUS_ONE.intValue();
400         }
401     }
402 
403     /**
404      * If enableMessageHistory = true, returns a message received by the service in chronological order.
405      * For example, getReceivedMessage(1) returns the first message received by the service,
406      * getReceivedMessage(2) returns the second message received by the service, etc.
407      */
408     public Object getReceivedMessage(int number)
409     {
410         Object message = null;
411         if (messageHistory != null)
412         {
413             if (number <= messageHistory.size())
414             {
415                 message = messageHistory.get(number - 1);
416             }
417         }
418         return message;
419     }
420 
421     /**
422      * If enableMessageHistory = true, returns the last message received by the service in chronological order.
423      */
424     public Object getLastReceivedMessage()
425     {
426         if (messageHistory != null)
427         {
428             return messageHistory.get(messageHistory.size() - 1);
429         }
430         else
431         {
432             return null;
433         }
434     }
435 
436     public String getAppendString()
437     {
438         return appendString;
439     }
440 
441     public void setAppendString(String appendString)
442     {
443         this.appendString = appendString;
444     }
445 
446     public boolean isEnableNotifications()
447     {
448         return enableNotifications;
449     }
450 
451     public void setEnableNotifications(boolean enableNotifications)
452     {
453         this.enableNotifications = enableNotifications;
454     }
455 
456     public Class<? extends Throwable> getExceptionToThrow()
457     {
458         return exceptionToThrow;
459     }
460 
461     public void setExceptionToThrow(Class<? extends Throwable> exceptionToThrow)
462     {
463         this.exceptionToThrow = exceptionToThrow;
464     }
465 
466     public long getWaitTime()
467     {
468         return waitTime;
469     }
470 
471     public void setWaitTime(long waitTime)
472     {
473         this.waitTime = waitTime;
474     }
475 
476     public boolean isDoInboundTransform()
477     {
478         return doInboundTransform;
479     }
480 
481     public void setDoInboundTransform(boolean doInboundTransform)
482     {
483         this.doInboundTransform = doInboundTransform;
484     }
485 
486     public boolean isLogMessageDetails()
487     {
488         return logMessageDetails;
489     }
490 
491     public void setLogMessageDetails(boolean logMessageDetails)
492     {
493         this.logMessageDetails = logMessageDetails;
494     }
495 
496     public void setId(String id)
497     {
498         this.id = id;
499     }
500 
501     public static void addLifecycleCallback(LifecycleCallback callback)
502     {
503         lifecycleCallbacks.add(callback);
504     }
505 
506     public static void removeLifecycleCallback(LifecycleCallback callback)
507     {
508         lifecycleCallbacks.remove(callback);
509     }
510 
511     public interface LifecycleCallback
512     {
513         void onTransition(String name, String newPhase);
514     }
515 }