View Javadoc

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