View Javadoc

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