View Javadoc

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