View Javadoc

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