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.transport.sftp;
8   
9   import org.mule.api.MuleEvent;
10  import org.mule.api.MuleEventContext;
11  import org.mule.api.MuleException;
12  import org.mule.api.component.Component;
13  import org.mule.api.context.notification.EndpointMessageNotificationListener;
14  import org.mule.api.context.notification.ServerNotification;
15  import org.mule.api.endpoint.EndpointBuilder;
16  import org.mule.api.endpoint.EndpointURI;
17  import org.mule.api.endpoint.ImmutableEndpoint;
18  import org.mule.api.endpoint.InboundEndpoint;
19  import org.mule.api.exception.MessagingExceptionHandler;
20  import org.mule.api.exception.SystemExceptionHandler;
21  import org.mule.api.model.Model;
22  import org.mule.api.service.Service;
23  import org.mule.api.transport.Connector;
24  import org.mule.context.notification.EndpointMessageNotification;
25  import org.mule.module.client.MuleClient;
26  import org.mule.routing.filters.WildcardFilter;
27  import org.mule.tck.functional.EventCallback;
28  import org.mule.tck.junit4.FunctionalTestCase;
29  import org.mule.transport.sftp.util.ValueHolder;
30  import org.mule.util.IOUtils;
31  import org.mule.util.StringMessageUtils;
32  
33  import com.jcraft.jsch.ChannelSftp;
34  import com.jcraft.jsch.SftpException;
35  
36  import java.io.BufferedInputStream;
37  import java.io.File;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.util.ArrayList;
41  import java.util.Arrays;
42  import java.util.HashMap;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  
47  import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
48  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
49  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;
50  import org.apache.commons.lang.SystemUtils;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  import static org.junit.Assert.assertEquals;
55  import static org.junit.Assert.assertTrue;
56  import static org.junit.Assert.fail;
57  import static org.mule.context.notification.EndpointMessageNotification.MESSAGE_DISPATCHED;
58  import static org.mule.context.notification.EndpointMessageNotification.MESSAGE_SENT;
59  
60  /**
61   * @author Lennart H��ggkvist, Magnus Larsson Date: Jun 8, 2009
62   */
63  public abstract class AbstractSftpTestCase extends FunctionalTestCase
64  {
65  
66      protected static final String FILE_NAME = "file.txt";
67  
68      protected final Logger logger = LoggerFactory.getLogger(AbstractSftpTestCase.class);
69  
70      /**
71       * Deletes all files in the directory, useful when testing to ensure that no
72       * files are in the way...
73       */
74      // protected void cleanupRemoteFtpDirectory(MuleClient muleClient, String
75      // endpointName) throws IOException
76      // {
77      // SftpClient sftpClient = getSftpClient(muleClient, endpointName);
78      //
79      // EndpointURI endpointURI = getUriByEndpointName(muleClient, endpointName);
80      // sftpClient.changeWorkingDirectory(sftpClient.getAbsolutePath(endpointURI.getPath()));
81      //
82      // String[] files = sftpClient.listFiles();
83      // for (String file : files)
84      // {
85      // sftpClient.deleteFile(file);
86      // }
87      // }
88  
89      /**
90       * Deletes a directory with all its files and sub-directories. The reason it do a
91       * "chmod 700" before the delete is that some tests changes the permission, and
92       * thus we have to restore the right to delete it...
93       * 
94       * @param muleClient
95       * @param endpointName
96       * @param relativePath
97       * @throws IOException
98       */
99      protected void recursiveDelete(MuleClient muleClient,
100                                    SftpClient sftpClient,
101                                    String endpointName,
102                                    String relativePath) throws IOException
103     {
104         EndpointURI endpointURI = getUriByEndpointName(muleClient, endpointName);
105         String path = endpointURI.getPath() + relativePath;
106 
107         try
108         {
109             // Ensure that we can delete the current directory and the below
110             // directories (if write is not permitted then delete is either)
111             sftpClient.chmod(path, 00700);
112 
113             sftpClient.changeWorkingDirectory(sftpClient.getAbsolutePath(path));
114 
115             // Delete all sub-directories
116             String[] directories = sftpClient.listDirectories();
117             for (String directory : directories)
118             {
119                 recursiveDelete(muleClient, sftpClient, endpointName, relativePath + "/" + directory);
120             }
121 
122             // Needs to change the directory back after the recursiveDelete
123             sftpClient.changeWorkingDirectory(sftpClient.getAbsolutePath(path));
124 
125             // Delete all files
126             String[] files = sftpClient.listFiles();
127             for (String file : files)
128             {
129                 sftpClient.deleteFile(file);
130             }
131 
132             // Delete the directory
133             try
134             {
135                 sftpClient.deleteDirectory(path);
136             }
137             catch (Exception e)
138             {
139                 if (logger.isDebugEnabled()) logger.debug("Failed delete directory " + path, e);
140             }
141 
142         }
143         catch (Exception e)
144         {
145             if (logger.isDebugEnabled()) logger.debug("Failed to recursivly delete directory " + path, e);
146         }
147     }
148 
149     /** Creates the <i>directoryName</i> under the endpoint path */
150     protected void createRemoteDirectory(MuleClient muleClient, String endpointName, String directoryName)
151         throws IOException
152     {
153         SftpClient sftpClient = getSftpClient(muleClient, endpointName);
154 
155         try
156         {
157             EndpointURI endpointURI = getUriByEndpointName(muleClient, endpointName);
158             sftpClient.changeWorkingDirectory(sftpClient.getAbsolutePath(endpointURI.getPath()));
159 
160             try
161             {
162                 sftpClient.mkdir(directoryName);
163             }
164             catch (IOException e)
165             {
166                 e.printStackTrace();
167                 // Expected if the directory didnt exist
168             }
169 
170             try
171             {
172                 sftpClient.changeWorkingDirectory(endpointURI.getPath() + "/" + directoryName);
173             }
174             catch (IOException e)
175             {
176                 fail("The directory should have been created");
177             }
178         }
179         finally
180         {
181             sftpClient.disconnect();
182         }
183     }
184 
185     protected EndpointURI getUriByEndpointName(MuleClient muleClient, String endpointName) throws IOException
186     {
187         ImmutableEndpoint endpoint = getImmutableEndpoint(muleClient, endpointName);
188         return endpoint.getEndpointURI();
189     }
190 
191     /**
192      * @param muleClient
193      * @param endpointName
194      * @return the endpoint address in the form 'sftp://user:password@host/path'
195      */
196     protected String getAddressByEndpoint(MuleClient muleClient, String endpointName)
197     {
198         ImmutableEndpoint endpoint = (ImmutableEndpoint) muleClient.getProperty(endpointName);
199         EndpointURI endpointURI = endpoint.getEndpointURI();
200 
201         return "sftp://" + endpointURI.getUser() + ":" + endpointURI.getPassword() + "@"
202                + endpointURI.getHost() + endpointURI.getPath();
203     }
204 
205     protected String getPathByEndpoint(MuleClient muleClient, SftpClient sftpClient, String endpointName)
206     {
207         ImmutableEndpoint endpoint = (ImmutableEndpoint) muleClient.getProperty(endpointName);
208         EndpointURI endpointURI = endpoint.getEndpointURI();
209 
210         return sftpClient.getAbsolutePath(endpointURI.getPath());
211     }
212 
213     /**
214      * Returns a SftpClient that is logged in to the sftp server that the endpoint is
215      * configured against.
216      * 
217      * @param muleClient
218      * @param endpointName
219      * @return
220      * @throws IOException
221      */
222     protected SftpClient getSftpClient(MuleClient muleClient, String endpointName) throws IOException
223     {
224         ImmutableEndpoint endpoint = getImmutableEndpoint(muleClient, endpointName);
225         EndpointURI endpointURI = endpoint.getEndpointURI();
226         SftpClient sftpClient = new SftpClient(endpointURI.getHost());
227 
228         SftpConnector sftpConnector = (SftpConnector) endpoint.getConnector();
229 
230         if (sftpConnector.getIdentityFile() != null)
231         {
232             try
233             {
234                 sftpClient.login(endpointURI.getUser(), sftpConnector.getIdentityFile(),
235                     sftpConnector.getPassphrase());
236             }
237             catch (Exception e)
238             {
239                 fail("Login failed: " + e);
240             }
241         }
242         else
243         {
244             try
245             {
246                 sftpClient.login(endpointURI.getUser(), endpointURI.getPassword());
247             }
248             catch (Exception e)
249             {
250                 fail("Login failed: " + e);
251             }
252         }
253         return sftpClient;
254     }
255 
256     /** Checks if the file exists on the server */
257     protected boolean verifyFileExists(SftpClient sftpClient, EndpointURI endpointURI, String file)
258         throws IOException
259     {
260         return verifyFileExists(sftpClient, endpointURI.getPath(), file);
261     }
262 
263     protected boolean verifyFileExists(SftpClient sftpClient, String path, String file) throws IOException
264     {
265         sftpClient.changeWorkingDirectory(sftpClient.getAbsolutePath(path));
266         String[] files = sftpClient.listFiles();
267 
268         for (String remoteFile : files)
269         {
270             if (remoteFile.equals(file))
271             {
272                 return true;
273             }
274         }
275         return false;
276     }
277 
278     /** Base method for executing tests... */
279     protected void executeBaseTest(String inputEndpointName,
280                                    String sendUrl,
281                                    String filename,
282                                    final int size,
283                                    String receivingTestComponentName,
284                                    long timeout) throws Exception
285     {
286         executeBaseTest(inputEndpointName, sendUrl, filename, size, receivingTestComponentName, timeout, null);
287     }
288 
289     protected void executeBaseTest(String inputEndpointName,
290                                    String sendUrl,
291                                    String filename,
292                                    final int size,
293                                    String receivingTestComponentName,
294                                    long timeout,
295                                    String expectedFailingConnector) throws Exception
296     {
297         executeBaseTest(inputEndpointName, sendUrl, filename, size, receivingTestComponentName, timeout,
298             expectedFailingConnector, null);
299     }
300 
301     /** Base method for executing tests... */
302     protected void executeBaseTest(String inputEndpointName,
303                                    String sendUrl,
304                                    String filename,
305                                    final int size,
306                                    String receivingTestComponentName,
307                                    long timeout,
308                                    String expectedFailingConnector,
309                                    String serviceName) throws Exception
310     {
311         MuleClient client = new MuleClient(muleContext);
312 
313         // Do some cleaning so that the endpoint doesn't have any other files
314         // We don't need to do this anymore since we are deleting and then creating
315         // the directory for each test
316         // cleanupRemoteFtpDirectory(client, inputEndpointName);
317 
318         final CountDownLatch latch = new CountDownLatch(1);
319         final AtomicInteger loopCount = new AtomicInteger(0);
320         final AtomicInteger totalReceivedSize = new AtomicInteger(0);
321 
322         // Random byte that we want to send a lot of
323         final int testByte = 42;
324 
325         EventCallback callback = new EventCallback()
326         {
327             public void eventReceived(MuleEventContext context, Object component) throws Exception
328             {
329 
330                 if (logger.isInfoEnabled()) logger.info("called " + loopCount.incrementAndGet() + " times");
331 
332                 InputStream sftpInputStream = (InputStream) context.getMessage().getPayload();
333                 BufferedInputStream bif = new BufferedInputStream(sftpInputStream);
334                 byte[] buffer = new byte[1024 * 4];
335 
336                 try
337                 {
338                     int n;
339                     while (-1 != (n = bif.read(buffer)))
340                     {
341                         totalReceivedSize.addAndGet(n);
342 
343                         // Simple check to verify the data...
344                         for (byte b : buffer)
345                         {
346                             if (b != testByte)
347                             {
348                                 fail("Incorrect received byte (was '" + b + "', expected '" + testByte + "'");
349                             }
350                         }
351                     }
352                 }
353                 finally
354                 {
355                     bif.close();
356                 }
357                 latch.countDown();
358             }
359         };
360         getFunctionalTestComponent(receivingTestComponentName).setEventCallback(callback);
361 
362         final ValueHolder<Exception> exceptionHolder = new ValueHolder<Exception>();
363         if (expectedFailingConnector != null)
364         {
365             // Register an exception-listener on the connector that expects to fail
366             // and count down the latch after saving the thrown exception
367             muleContext.setExceptionListener(new SystemExceptionHandler()
368             {
369                 public void handleException(Exception e)
370                 {
371                     if (logger.isInfoEnabled()) logger.info("expected exception occurred: " + e, e);
372                     exceptionHolder.value = e;
373                     latch.countDown();
374                 }
375 
376                 public WildcardFilter getCommitTxFilter()
377                 {
378                     return null;
379                 }
380 
381                 public WildcardFilter getRollbackTxFilter()
382                 {
383                     return null;
384                 }
385             });
386 
387             if (serviceName != null && !(serviceName.length() == 0))
388             {
389                 muleContext.getRegistry().lookupService(serviceName).setExceptionListener(
390                     new MessagingExceptionHandler()
391                     {
392                         public MuleEvent handleException(Exception e, MuleEvent event)
393                         {
394                             if (logger.isInfoEnabled()) logger.info("expected exception occurred: " + e, e);
395                             exceptionHolder.value = e;
396                             latch.countDown();
397                             return event;
398                         }
399 
400                         public WildcardFilter getCommitTxFilter()
401                         {
402                             return null;
403                         }
404 
405                         public WildcardFilter getRollbackTxFilter()
406                         {
407                             return null;
408                         }
409                     });
410             }
411         }
412 
413         // InputStream that generates the data without using a file
414         InputStream os = new InputStream()
415         {
416             int totSize = 0;
417 
418             public int read() throws IOException
419             {
420                 totSize++;
421                 if (totSize <= size)
422                 {
423                     return testByte;
424                 }
425                 else
426                 {
427                     return -1;
428                 }
429             }
430         };
431 
432         HashMap<String, String> props = new HashMap<String, String>(1);
433         props.put(SftpConnector.PROPERTY_FILENAME, filename);
434         props.put(SftpConnector.PROPERTY_ORIGINAL_FILENAME, filename);
435 
436         if (logger.isInfoEnabled())
437             logger.info(StringMessageUtils.getBoilerPlate("Note! If this test fails due to timeout please add '-Dmule.test.timeoutSecs=XX' to the mvn command!"));
438 
439         executeBaseAssertionsBeforeCall();
440 
441         // Send the content using stream
442         client.dispatch(sendUrl, os, props);
443 
444         boolean workDone = latch.await(timeout, TimeUnit.MILLISECONDS);
445 
446         assertTrue(
447             "Test timed out. It took more than "
448                             + timeout
449                             + " milliseconds. If this error occurs the test probably needs a longer time out (on your computer/network)",
450             workDone);
451 
452         // Rethrow any exception that we have caught in an exception-listener
453         if (exceptionHolder.value != null)
454         {
455             throw exceptionHolder.value;
456         }
457         executeBaseAssertionsAfterCall(size, totalReceivedSize.intValue());
458     }
459 
460     /**
461      * To be overridden by the test-classes if required
462      */
463     protected void executeBaseAssertionsBeforeCall()
464     {
465     }
466 
467     /**
468      * To be overridden by the test-classes if required
469      */
470     protected void executeBaseAssertionsAfterCall(int sendSize, int receivedSize)
471     {
472 
473         // Make sure that the file we received had the same size as the one we sent
474         if (logger.isInfoEnabled())
475         {
476             logger.info("Sent size: " + sendSize);
477             logger.info("Received size: " + receivedSize);
478         }
479 
480         assertEquals("The received file should have the same size as the sent file", sendSize, receivedSize);
481     }
482 
483     protected ImmutableEndpoint getImmutableEndpoint(MuleClient muleClient, String endpointName)
484         throws IOException
485     {
486         ImmutableEndpoint endpoint = null;
487 
488         Object o = muleClient.getProperty(endpointName);
489         if (o instanceof ImmutableEndpoint)
490         {
491             // For Inbound and Outbound Endpoints
492             endpoint = (ImmutableEndpoint) o;
493 
494         }
495         else if (o instanceof EndpointBuilder)
496         {
497             // For Endpoint-references
498             EndpointBuilder eb = (EndpointBuilder) o;
499             try
500             {
501                 endpoint = eb.buildInboundEndpoint();
502             }
503             catch (Exception e)
504             {
505                 throw new IOException(e.getMessage());
506             }
507         }
508         return endpoint;
509     }
510 
511     protected void remoteChmod(MuleClient muleClient,
512                                SftpClient sftpClient,
513                                String endpointName,
514                                int permissions) throws SftpException
515     {
516         ChannelSftp channelSftp = sftpClient.getChannelSftp();
517 
518         ImmutableEndpoint endpoint = (ImmutableEndpoint) muleClient.getProperty(endpointName);
519         EndpointURI endpointURI = endpoint.getEndpointURI();
520 
521         // RW - so that we can do initial cleanup
522         channelSftp.chmod(permissions, sftpClient.getAbsolutePath(endpointURI.getPath()));
523     }
524 
525     /**
526      * Initiates a list of sftp-endpoint-directories. Ensures that affected services
527      * are stopped during the initiation.
528      * 
529      * @param serviceNames
530      * @param endpointNames
531      * @throws Exception
532      */
533     protected void initEndpointDirectories(String[] serviceNames, String[] endpointNames) throws Exception
534     {
535 
536         // Stop all named services
537         List<Service> services = new ArrayList<Service>();
538         for (String serviceName : serviceNames)
539         {
540             try
541             {
542                 Service service = muleContext.getRegistry().lookupService(serviceName);
543                 service.stop();
544                 services.add(service);
545             }
546             catch (Exception e)
547             {
548                 logger.error("Error '" + e.getMessage() + "' occured while stopping the service "
549                              + serviceName + ". Perhaps the service did not exist in the config?");
550                 throw e;
551             }
552         }
553 
554         // Now init the directory for each named endpoint, one by one
555         for (String endpointName : endpointNames)
556         {
557             initEndpointDirectory(endpointName);
558         }
559 
560         // We are done, startup the services again so that the test can begin...
561         for (Service service : services)
562         {
563             service.start();
564         }
565     }
566 
567     /**
568      * Ensures that the directory exists and is writable by deleting the directory
569      * and then recreate it.
570      * 
571      * @param endpointName
572      * @throws org.mule.api.MuleException
573      * @throws java.io.IOException
574      * @throws com.jcraft.jsch.SftpException
575      */
576     protected void initEndpointDirectory(String endpointName)
577         throws MuleException, IOException, SftpException
578     {
579         MuleClient muleClient = new MuleClient(muleContext);
580         SftpClient sftpClient = getSftpClient(muleClient, endpointName);
581         try
582         {
583             ChannelSftp channelSftp = sftpClient.getChannelSftp();
584             try
585             {
586                 recursiveDelete(muleClient, sftpClient, endpointName, "");
587             }
588             catch (IOException e)
589             {
590                 if (logger.isErrorEnabled())
591                     logger.error("Failed to recursivly delete endpoint " + endpointName, e);
592             }
593 
594             String path = getPathByEndpoint(muleClient, sftpClient, endpointName);
595             channelSftp.mkdir(path);
596         }
597         finally
598         {
599             sftpClient.disconnect();
600             if (logger.isDebugEnabled()) logger.debug("Done init endpoint directory: " + endpointName);
601         }
602     }
603 
604     /**
605      * Helper method for initiating a test and wait for the test to complete. The
606      * method sends a file to an inbound endpoint and waits for a dispatch event on a
607      * outbound endpoint, i.e. that the file has been consumed by the inbound
608      * endpoint and that the content of the file has been sent to the outgoing
609      * endpoint.
610      * 
611      * @param p where inboundEndpoint and outboundEndpoint are mandatory, @see
612      *            DispatchParameters for details.
613      */
614     protected void dispatchAndWaitForDelivery(final DispatchParameters p)
615     {
616         // Declare countdown latch and listener
617         final CountDownLatch latch = new CountDownLatch(1);
618         EndpointMessageNotificationListener listener = null;
619         MuleClient muleClient = p.getMuleClient();
620         boolean localMuleClient = muleClient == null;
621 
622         try
623         {
624             // First create a local muleClient instance if not supplied
625             if (localMuleClient) muleClient = new MuleClient(muleContext);
626 
627             // Next create a listener that listens for dispatch events on the
628             // outbound endpoint
629             listener = new EndpointMessageNotificationListener()
630             {
631                 public void onNotification(ServerNotification notification)
632                 {
633 
634                     // Only care about EndpointMessageNotification
635                     if (notification instanceof EndpointMessageNotification)
636                     {
637                         EndpointMessageNotification endpointNotification = (EndpointMessageNotification) notification;
638 
639                         // Extract action and name of the endpoint
640                         int action = endpointNotification.getAction();
641                         String endpoint = endpointNotification.getEndpoint();
642 
643                         // If it is a dispatch event on our outbound endpoint then
644                         // countdown the latch.
645                         if ((action == MESSAGE_DISPATCHED || action == MESSAGE_SENT)
646                             && endpoint.equals(p.getOutboundEndpoint()))
647                         {
648                             if (logger.isDebugEnabled())
649                                 logger.debug("Expected notification received on " + p.getOutboundEndpoint()
650                                              + " (action: " + action + "), time to countdown the latch");
651                             latch.countDown();
652                         }
653                     }
654                 }
655             };
656 
657             // Now register the listener
658             muleContext.getNotificationManager().addListener(listener);
659 
660             // Initiate the test by sending a file to the SFTP server, which the
661             // inbound-endpoint then can pick up
662 
663             // Prepare message headers, set filename-header and if supplied any
664             // headers supplied in the call.
665             Map<String, String> headers = new HashMap<String, String>();
666             headers.put("filename", p.getFilename());
667 
668             if (p.getHeaders() != null)
669             {
670                 headers.putAll(p.getHeaders());
671             }
672 
673             // Setup connect string and perform the actual dispatch
674             String connectString = (p.getSftpConnector() == null) ? "" : "?connector=" + p.getSftpConnector();
675             muleClient.dispatch(getAddressByEndpoint(muleClient, p.getInboundEndpoint()) + connectString,
676                 TEST_MESSAGE, headers);
677 
678             // Wait for the delivery to occur...
679             if (logger.isDebugEnabled()) logger.debug("Waiting for file to be delivered to the endpoint...");
680             boolean workDone = latch.await(p.getTimeout(), TimeUnit.MILLISECONDS);
681             if (logger.isDebugEnabled())
682                 logger.debug((workDone)
683                                        ? "File delivered, continue..."
684                                        : "No file delivered, timeout occurred!");
685 
686             // Raise a fault if the test timed out
687             // FIXME DZ: i dont this is necessary since we have an overall test
688             // timeout
689             // assertTrue("Test timed out. It took more than " + p.getTimeout() +
690             // " milliseconds. If this error occurs the test probably needs a longer time out (on your computer/network)",
691             // workDone);
692 
693         }
694         catch (Exception e)
695         {
696             e.printStackTrace();
697             fail("An unexpected error occurred: " + e.getMessage());
698 
699         }
700         finally
701         {
702             // Dispose muleClient if created locally
703             if (localMuleClient) muleClient.dispose();
704 
705             // Always remove the listener if created
706             if (listener != null) muleContext.getNotificationManager().removeListener(listener);
707         }
708     }
709 
710     protected Exception dispatchAndWaitForException(final DispatchParameters p,
711                                                     String expectedFailingConnector)
712     {
713         return dispatchAndWaitForException(p, expectedFailingConnector, null);
714     }
715 
716     /**
717      * Helper method for initiating a test and wait for an exception to be caught by
718      * the sftp-connector.
719      * 
720      * @param p where sftpConnector and inboundEndpoint are mandatory, @see
721      *            DispatchParameters for details.
722      * @return
723      */
724     protected Exception dispatchAndWaitForException(final DispatchParameters p,
725                                                     String expectedFailingConnector,
726                                                     String serviceName)
727     {
728         // Declare countdown latch and listener
729         final CountDownLatch latch = new CountDownLatch(1);
730         SystemExceptionHandler listener = null;
731         MessagingExceptionHandler messagingListener = null;
732         MuleClient muleClient = p.getMuleClient();
733         boolean localMuleClient = muleClient == null;
734         SystemExceptionHandler currentExceptionListener = null;
735         MessagingExceptionHandler currentMessagingListener = null;
736         final ValueHolder<Exception> exceptionHolder = new ValueHolder<Exception>();
737 
738         try
739         {
740             // First create a local muleClient instance if not supplied
741             if (localMuleClient) muleClient = new MuleClient(muleContext);
742 
743             // Next create a listener that listens for exception on the
744             // sftp-connector
745             listener = new SystemExceptionHandler()
746             {
747 
748                 public void handleException(Exception e)
749                 {
750                     exceptionHolder.value = e;
751                     if (logger.isDebugEnabled())
752                         logger.debug("Expected exception occurred: " + e.getMessage()
753                                      + ", time to countdown the latch");
754                     latch.countDown();
755                 }
756 
757                 public WildcardFilter getCommitTxFilter()
758                 {
759                     return null;
760                 }
761 
762                 public WildcardFilter getRollbackTxFilter()
763                 {
764                     return null;
765                 }
766             };
767 
768             messagingListener = new MessagingExceptionHandler()
769             {
770                 public MuleEvent handleException(Exception e, MuleEvent event)
771                 {
772                     exceptionHolder.value = e;
773                     if (logger.isDebugEnabled())
774                         logger.debug("Expected exception occurred: " + e.getMessage()
775                                      + ", time to countdown the latch");
776                     latch.countDown();
777                     return event;
778                 }
779 
780                 public WildcardFilter getCommitTxFilter()
781                 {
782                     return null;
783                 }
784 
785                 public WildcardFilter getRollbackTxFilter()
786                 {
787                     return null;
788                 }
789             };
790 
791             currentMessagingListener = muleContext.getRegistry()
792                 .lookupService(serviceName)
793                 .getExceptionListener();
794             muleContext.getRegistry().lookupService(serviceName).setExceptionListener(messagingListener);
795 
796             // Now register an exception-listener on the connector that expects to
797             // fail
798             currentExceptionListener = muleContext.getExceptionListener();
799             muleContext.setExceptionListener(listener);
800 
801             // Initiate the test by sending a file to the SFTP server, which the
802             // inbound-endpoint then can pick up
803 
804             // Prepare message headers, set filename-header and if supplied any
805             // headers supplied in the call.
806             Map<String, String> headers = new HashMap<String, String>();
807             headers.put("filename", p.getFilename());
808 
809             if (p.getHeaders() != null)
810             {
811                 headers.putAll(p.getHeaders());
812             }
813 
814             // Setup connect string and perform the actual dispatch
815             String connectString = (p.getSftpConnector() == null) ? "" : "?connector=" + p.getSftpConnector();
816             muleClient.dispatch(getAddressByEndpoint(muleClient, p.getInboundEndpoint()) + connectString,
817                 TEST_MESSAGE, headers);
818 
819             // Wait for the exception to occur...
820             if (logger.isDebugEnabled()) logger.debug("Waiting for an exception to occur...");
821             boolean workDone = latch.await(p.getTimeout(), TimeUnit.MILLISECONDS);
822             if (logger.isDebugEnabled())
823                 logger.debug((workDone)
824                                        ? "Exception occurred, continue..."
825                                        : "No exception, instead a timeout occurred!");
826 
827             // Raise a fault if the test timed out
828             assertTrue(
829                 "Test timed out. It took more than "
830                                 + p.getTimeout()
831                                 + " milliseconds. If this error occurs the test probably needs a longer time out (on your computer/network)",
832                 workDone);
833 
834         }
835         catch (Exception e)
836         {
837             e.printStackTrace();
838             fail("An unexpected error occurred: " + e.getMessage());
839 
840         }
841         finally
842         {
843             // Dispose muleClient if created locally
844             if (localMuleClient) muleClient.dispose();
845 
846             // Always reset the current listener
847             muleContext.setExceptionListener(currentExceptionListener);
848             muleContext.getRegistry().lookupService(serviceName).setExceptionListener(
849                 currentMessagingListener);
850         }
851 
852         return (Exception) exceptionHolder.value;
853     }
854 
855     protected void recursiveDeleteInLocalFilesystem(File parent) throws IOException
856     {
857         // If this file is a directory then first delete all its children
858         if (parent.isDirectory())
859         {
860             for (File child : parent.listFiles())
861             {
862                 recursiveDeleteInLocalFilesystem(child);
863             }
864         }
865 
866         // Now delete this file, but first check write permissions on its parent...
867         File parentParent = parent.getParentFile();
868 
869         if (!parentParent.canWrite())
870         {
871             // setWritable is only available on JDK6 and beyond
872             //if (!parentParent.setWritable(true))
873                 //throw new IOException("Failed to set readonly-folder: " + parentParent + " to writeable");
874             // FIXME DZ: since setWritable doesnt exist on jdk5, need to detect os to make dir writable
875             if(SystemUtils.IS_OS_WINDOWS)
876             {
877                 Runtime.getRuntime().exec("attrib -r /D" + parentParent.getAbsolutePath());
878             }
879             else if(SystemUtils.IS_OS_UNIX || SystemUtils.IS_OS_LINUX)
880             {
881                 Runtime.getRuntime().exec("chmod +w " + parentParent.getAbsolutePath());
882             }
883             else
884             {
885                 throw new IOException("This test is not supported on your detected platform : " + SystemUtils.OS_NAME);
886             }
887         }
888 
889         if (parent.exists())
890         {
891             // FIXME DZ: since setWritable doesnt exist on jdk5, need to detect os to make dir writable
892             String os = System.getProperty("os.name").toLowerCase();
893             if(SystemUtils.IS_OS_WINDOWS)
894             {
895                 Runtime.getRuntime().exec("attrib -r /D" + parent.getAbsolutePath());
896             }
897             else if(SystemUtils.IS_OS_UNIX || SystemUtils.IS_OS_LINUX)
898             {
899                 Runtime.getRuntime().exec("chmod +w " + parent.getAbsolutePath());
900             }
901             else
902             {
903                 throw new IOException("This test is not supported on your detected platform : " + SystemUtils.OS_NAME);
904             }
905             if (!parent.delete()) throw new IOException("Failed to delete folder: " + parent);
906         }
907     }
908 
909     /**
910      * Asserts that there are no files found on the path <i>path</i>.
911      * 
912      * @param path The path in the local filesystem to check
913      * @throws IOException Exception
914      */
915     protected void assertNoFilesInLocalFilesystem(String path) throws IOException
916     {
917         assertFilesInLocalFilesystem(path, new String[]{});
918     }
919 
920     /**
921      * Asserts that no files are found on the path that the <i>endpointName</i> use.
922      * 
923      * @param muleClient MuleClient
924      * @param endpointName The endpoint name
925      * @throws IOException Exception
926      */
927     protected void assertNoFilesInEndpoint(MuleClient muleClient, String endpointName) throws IOException
928     {
929         assertFilesInEndpoint(muleClient, endpointName, new String[]{});
930     }
931 
932     /**
933      * Asserts that no files are found on the sub directory <i>subDirectory</i> under
934      * the path that <i>endpointName</i> use.
935      * 
936      * @param muleClient MuleClient
937      * @param endpointName The endpoint name
938      * @param subDirectory The sub directory
939      * @throws IOException Exception
940      */
941     protected void assertNoFilesInEndpoint(MuleClient muleClient, String endpointName, String subDirectory)
942         throws IOException
943     {
944         assertFilesInEndpoint(muleClient, endpointName, subDirectory, new String[]{});
945     }
946 
947     /**
948      * Asserts that only the <i>expectedFile</i> is found on the path <i>path</i>,
949      * where filenames can be expressed as a regular expression.
950      * 
951      * @param path The path in the local filesystem to check
952      * @param expectedFile Expected file
953      * @throws IOException Exception
954      */
955     protected void assertFilesInLocalFilesystem(String path, String expectedFile) throws IOException
956     {
957         assertFilesInLocalFilesystem(path, new String[]{expectedFile});
958     }
959 
960     /**
961      * Asserts that only the <i>expectedFiles</i> are found on the path <i>path</i>,
962      * where filenames can be expressed as a regular expression.
963      * 
964      * @param path The path in the local filesystem to check
965      * @param expectedFiles Expected files
966      * @throws IOException Exception
967      */
968     protected void assertFilesInLocalFilesystem(String path, String[] expectedFiles) throws IOException
969     {
970 
971         File parent = new File(path);
972         String[] files = parent.list();
973 
974         assertFilesInFileArray(path, expectedFiles, files);
975     }
976 
977     /**
978      * Asserts that only the <i>expectedFile</i> is found on the path that the
979      * <i>endpointName</i> use, where filenames can be expressed as a regular
980      * expression.
981      * 
982      * @param muleClient MuleClient
983      * @param endpointName The endpoint name
984      * @param expectedFile Expected file
985      * @throws IOException Exception
986      */
987     protected void assertFilesInEndpoint(MuleClient muleClient, String endpointName, String expectedFile)
988         throws IOException
989     {
990         assertFilesInEndpoint(muleClient, endpointName, null, new String[]{expectedFile});
991     }
992 
993     /**
994      * Asserts that only the <i>expectedFiles</i> are found on the path that the
995      * <i>endpointName</i> use, where filenames can be expressed as a regular
996      * expression.
997      * 
998      * @param muleClient MuleClient
999      * @param endpointName The endpoint name
1000      * @param expectedFiles Expected files
1001      * @throws IOException Exception
1002      */
1003     protected void assertFilesInEndpoint(MuleClient muleClient, String endpointName, String[] expectedFiles)
1004         throws IOException
1005     {
1006         assertFilesInEndpoint(muleClient, endpointName, null, expectedFiles);
1007     }
1008 
1009     /**
1010      * Asserts that only the <i>expectedFile</i> is found on the sub directory
1011      * <i>subDirectory</i> under the path that <i>endpointName</i> use, where
1012      * filenames can be expressed as a regular expression.
1013      * 
1014      * @param muleClient MuleClient
1015      * @param endpointName The endpoint name
1016      * @param subDirectory The sub directory
1017      * @param expectedFile Expected files
1018      * @throws IOException Exception
1019      */
1020     protected void assertFilesInEndpoint(MuleClient muleClient,
1021                                          String endpointName,
1022                                          String subDirectory,
1023                                          String expectedFile) throws IOException
1024     {
1025         assertFilesInEndpoint(muleClient, endpointName, subDirectory, new String[]{expectedFile});
1026     }
1027 
1028     /**
1029      * Asserts that only the <i>expectedFiles</i> are found on the sub directory
1030      * <i>subDirectory</i> under the path that <i>endpointName</i> use, where
1031      * filenames can be expressed as a regular expression.
1032      * 
1033      * @param muleClient MuleClient
1034      * @param endpointName The endpoint name
1035      * @param subDirectory The sub directory
1036      * @param expectedFiles Expected files
1037      * @throws IOException Exception
1038      */
1039     protected void assertFilesInEndpoint(MuleClient muleClient,
1040                                          String endpointName,
1041                                          String subDirectory,
1042                                          String[] expectedFiles) throws IOException
1043     {
1044         SftpClient sftpClient = getSftpClient(muleClient, endpointName);
1045         ImmutableEndpoint tEndpoint = (ImmutableEndpoint) muleClient.getProperty(endpointName);
1046         try
1047         {
1048             String path = tEndpoint.getEndpointURI().getPath();
1049             if (subDirectory != null)
1050             {
1051                 path += '/' + subDirectory;
1052             }
1053             assertFilesInPath(sftpClient, path, expectedFiles);
1054         }
1055         finally
1056         {
1057             sftpClient.disconnect();
1058         }
1059     }
1060 
1061     /**
1062      * Asserts that only the <i>expectedFiles</i> are found on the path <i>path</i>,
1063      * where filenames can be expressed as a regular expression.
1064      * 
1065      * @param sftpClient SftpClient
1066      * @param path The path to check
1067      * @param expectedFiles Expected files
1068      * @throws IOException Exception
1069      */
1070     private void assertFilesInPath(SftpClient sftpClient, String path, String[] expectedFiles)
1071         throws IOException
1072     {
1073 
1074         sftpClient.changeWorkingDirectory(sftpClient.getAbsolutePath(path));
1075         String[] files = sftpClient.listFiles();
1076 
1077         assertFilesInFileArray(path, expectedFiles, files);
1078     }
1079 
1080     /**
1081      * Asserts that only the <i>expectedFiles</i> are found in the file-array
1082      * <i>path</i>, where filenames can be expressed as a regular expression.
1083      * 
1084      * @param path
1085      * @param expectedFiles
1086      * @param foundFiles
1087      */
1088     private void assertFilesInFileArray(String path, String[] expectedFiles, String[] foundFiles)
1089     {
1090 
1091         // First, make a list of the array of found files
1092         List<String> foundFileList = new ArrayList<String>(foundFiles.length);
1093         foundFileList.addAll(Arrays.asList(foundFiles));
1094         List<String> missingExpectedFiles = new ArrayList<String>();
1095 
1096         // lookup each expected file in the list of found files and remove each found
1097         // file that match the expected file
1098         // Note that the expected file can contain a regexp
1099         for (String expectedFile : expectedFiles)
1100         {
1101             String foundFile = lookupListByRegexp(foundFileList, expectedFile);
1102             if (foundFile != null)
1103             {
1104                 foundFileList.remove(foundFile);
1105             }
1106             else
1107             {
1108                 missingExpectedFiles.add(expectedFile);
1109             }
1110         }
1111         // Check if that no remaining files are left in the list of found files, i.e.
1112         // unwanted found files
1113         assertTrue("Expected files not found on path " + path + ". File(s):" + missingExpectedFiles,
1114             missingExpectedFiles.size() == 0);
1115         assertTrue("The following file(s) was found but not expected: " + foundFileList + " on path " + path,
1116             foundFileList.size() == 0);
1117     }
1118 
1119     /**
1120      * Return the first string in a string-list that matches the regexp
1121      * 
1122      * @param list
1123      * @param regexp
1124      * @return the first string that match the regexp or null if no match
1125      */
1126     private String lookupListByRegexp(List<String> list, String regexp)
1127     {
1128 
1129         // Look for matches of the regexp in the list
1130         for (String value : list)
1131         {
1132             if (value.matches(regexp))
1133             {
1134                 // Found it, return the full string that matched
1135                 return value;
1136             }
1137         }
1138 
1139         // Noop, nothing found, return null
1140         return null;
1141     }
1142 
1143     /**
1144      * Helper class for dynamic assignment of parameters to the method
1145      * dispatchAndWaitForDelivery() Only inboundEndpoint and outboundEndpoint are
1146      * mandatory, the rest of the parameters are optional.
1147      * 
1148      * @author Magnus Larsson
1149      */
1150     public class DispatchParameters
1151     {
1152 
1153         /**
1154          * Optional MuleClient, the method will create an own if not supplied.
1155          */
1156         private MuleClient muleClient = null;
1157 
1158         /**
1159          * Optional name of sftp-connector, if not supplied it is assumed that only
1160          * one sftp-conector is speficied in the mule configuration. If more than one
1161          * sftp-connector is specified this paramter has to be specified to point out
1162          * what connector to use for the dispatch.
1163          */
1164         private String sftpConnector = null;
1165 
1166         /**
1167          * Mandatory name of the inbound endpoint, i.e. where to dispatch the file
1168          */
1169         private String inboundEndpoint = null;
1170 
1171         /**
1172          * Optional message headers
1173          */
1174         private Map<String, String> headers = null;
1175 
1176         /**
1177          * Optional content of the file, if not specified then it defaults to
1178          * AbstractMuleTestCase.TEST_MESSAGE.
1179          */
1180         private String message = TEST_MESSAGE;
1181 
1182         /**
1183          * Optional name of the file, defaults to FILE_NAME
1184          */
1185         private String filename = FILE_NAME;
1186 
1187         /**
1188          * Mandatory name of the outbound endpoint, i.e. where we will wait for a
1189          * message to be delivered to in the end
1190          */
1191         private String outboundEndpoint = null;
1192 
1193         /**
1194          * Optional timeout for how long we will wait for a message to be delivered
1195          * to the outbound endpoint
1196          */
1197         private long timeout = 10000;
1198 
1199         public DispatchParameters(String inboundEndpoint, String outboundEndpoint)
1200         {
1201             this.inboundEndpoint = inboundEndpoint;
1202             this.outboundEndpoint = outboundEndpoint;
1203         }
1204 
1205         public MuleClient getMuleClient()
1206         {
1207             return muleClient;
1208         }
1209 
1210         public void setMuleClient(MuleClient muleClient)
1211         {
1212             this.muleClient = muleClient;
1213         }
1214 
1215         public String getSftpConnector()
1216         {
1217             return sftpConnector;
1218         }
1219 
1220         public void setSftpConnector(String sftpConnector)
1221         {
1222             this.sftpConnector = sftpConnector;
1223         }
1224 
1225         public String getInboundEndpoint()
1226         {
1227             return inboundEndpoint;
1228         }
1229 
1230         public void setInboundEndpoint(String inboundEndpoint)
1231         {
1232             this.inboundEndpoint = inboundEndpoint;
1233         }
1234 
1235         public Map<String, String> getHeaders()
1236         {
1237             return headers;
1238         }
1239 
1240         public void setHeaders(Map<String, String> headers)
1241         {
1242             this.headers = headers;
1243         }
1244 
1245         public String getMessage()
1246         {
1247             return message;
1248         }
1249 
1250         public void setMessage(String message)
1251         {
1252             this.message = message;
1253         }
1254 
1255         public String getFilename()
1256         {
1257             return filename;
1258         }
1259 
1260         public void setFilename(String filename)
1261         {
1262             this.filename = filename;
1263         }
1264 
1265         public String getOutboundEndpoint()
1266         {
1267             return outboundEndpoint;
1268         }
1269 
1270         public void setOutboundEndpoint(String outboundEndpoint)
1271         {
1272             this.outboundEndpoint = outboundEndpoint;
1273         }
1274 
1275         public long getTimeout()
1276         {
1277             return timeout;
1278         }
1279 
1280         public void setTimeout(long timeout)
1281         {
1282             this.timeout = timeout;
1283         }
1284     }
1285 
1286     /**
1287      * Check that all of the connectors are running. I don't know if this makes a
1288      * difference in test reliability per se; it may just delay the start of the test
1289      * long enough for the MuleContext to be usable.
1290      */
1291     public void checkConnectors()
1292     {
1293         assertTrue("context is not started", muleContext.getLifecycleManager().getState().isStarted());
1294         Map<String, Connector> connectorMap = muleContext.getRegistry().lookupByType(Connector.class);
1295         Map<String, InboundEndpoint> inboundMap = muleContext.getRegistry().lookupByType(
1296             InboundEndpoint.class);
1297         Map<String, Service> serviceMap = muleContext.getRegistry().lookupByType(Service.class);
1298         Map<String, Model> modelMap = muleContext.getRegistry().lookupByType(Model.class);
1299         Map<String, Component> componentMap = muleContext.getRegistry().lookupByType(Component.class);
1300         // Map<String, OutboundEndpoint> outboundMap =
1301         // muleContext.getRegistry().lookupByType(OutboundEndpoint.class);
1302 
1303         Iterator it = connectorMap.entrySet().iterator();
1304         while (it.hasNext())
1305         {
1306             Map.Entry<String, Connector> pairs = (Map.Entry<String, Connector>) it.next();
1307             logger.debug("checking connector : " + pairs.getKey());
1308             assertTrue(pairs.getKey() + " is not started", pairs.getValue().isStarted());
1309         }
1310 
1311         it = serviceMap.entrySet().iterator();
1312         while (it.hasNext())
1313         {
1314             Map.Entry<String, Service> pairs = (Map.Entry<String, Service>) it.next();
1315             assertTrue(pairs.getKey() + " is not started", pairs.getValue().isStarted());
1316         }
1317 
1318         it = modelMap.entrySet().iterator();
1319         while (it.hasNext())
1320         {
1321             Map.Entry<String, Model> pairs = (Map.Entry<String, Model>) it.next();
1322             assertTrue(pairs.getKey() + " is not started", pairs.getValue().getLifecycleState().isStarted());
1323         }
1324     }
1325     
1326     /**
1327      * Look for the sftp test properties file in our environment.
1328      * If it's not found, don't run these tests 
1329      */
1330     @Override
1331     protected boolean isDisabledInThisEnvironment()
1332     {
1333 
1334         try
1335         {
1336             IOUtils.getResourceAsString("sftp-settings.properties", this.getClass());
1337         }
1338         catch (IOException e)
1339         {
1340             return true;
1341         }
1342         
1343         return false;
1344     }
1345 }