1
2
3
4
5
6
7
8
9
10
11 package org.mule.transport.sftp;
12
13 import org.mule.api.endpoint.ImmutableEndpoint;
14 import org.mule.transport.sftp.notification.SftpNotifier;
15
16 import com.jcraft.jsch.Channel;
17 import com.jcraft.jsch.ChannelSftp;
18 import com.jcraft.jsch.ChannelSftp.LsEntry;
19 import com.jcraft.jsch.JSch;
20 import com.jcraft.jsch.JSchException;
21 import com.jcraft.jsch.Session;
22 import com.jcraft.jsch.SftpATTRS;
23 import com.jcraft.jsch.SftpException;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Properties;
31 import java.util.Vector;
32
33 import org.apache.commons.lang.NotImplementedException;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_DELETE_ACTION;
38 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_GET_ACTION;
39 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_PUT_ACTION;
40 import static org.mule.transport.sftp.notification.SftpTransportNotification.SFTP_RENAME_ACTION;
41
42
43
44
45
46
47 public class SftpClient
48 {
49 private Log logger = LogFactory.getLog(getClass());
50
51 public static final String CHANNEL_SFTP = "sftp";
52
53 public static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
54
55 private ChannelSftp channelSftp;
56
57 private JSch jsch;
58 private SftpNotifier notifier;
59
60 private Session session;
61
62 private final String host;
63
64 private int port = 22;
65
66 private String home;
67
68
69 private String currentDirectory = "";
70
71 private static final Object lock = new Object();
72
73 public SftpClient(String host)
74 {
75 this(host, null);
76 }
77
78 public SftpClient(String host, SftpNotifier notifier)
79 {
80 this.host = host;
81 this.notifier = notifier;
82
83 jsch = new JSch();
84 }
85
86 public void changeWorkingDirectory(String wd) throws IOException
87 {
88 currentDirectory = wd;
89
90 try
91 {
92 wd = getAbsolutePath(wd);
93 if (logger.isDebugEnabled())
94 {
95 logger.debug("Attempting to cwd to: " + wd);
96 }
97 channelSftp.cd(wd);
98 }
99 catch (SftpException e)
100 {
101 String message = "Error '" + e.getMessage() + "' occurred when trying to CDW to '" + wd + "'.";
102 logger.error(message);
103 throw new IOException(message);
104 }
105 }
106
107
108
109
110
111
112
113
114 public String getAbsolutePath(String path)
115 {
116 if (path.startsWith("/~"))
117 {
118 return home + path.substring(2, path.length());
119 }
120
121
122 return path;
123 }
124
125 public void login(String user, String password) throws IOException
126 {
127 try
128 {
129 Properties hash = new Properties();
130 hash.put(STRICT_HOST_KEY_CHECKING, "no");
131
132 session = jsch.getSession(user, host);
133 session.setConfig(hash);
134 session.setPort(port);
135 session.setPassword(password);
136 session.connect();
137
138 Channel channel = session.openChannel(CHANNEL_SFTP);
139 channel.connect();
140
141 channelSftp = (ChannelSftp) channel;
142 setHome(channelSftp.pwd());
143 }
144 catch (JSchException e)
145 {
146 logAndThrowLoginError(user, e);
147 }
148 catch (SftpException e)
149 {
150 logAndThrowLoginError(user, e);
151 }
152 }
153
154 public void login(String user, String identityFile, String passphrase) throws IOException
155 {
156
157 if (!new File(identityFile).exists())
158 {
159 throw new IOException("IdentityFile '" + identityFile + "' not found");
160 }
161
162 try
163 {
164 if (passphrase == null || "".equals(passphrase))
165 {
166 jsch.addIdentity(new File(identityFile).getAbsolutePath());
167 }
168 else
169 {
170 jsch.addIdentity(new File(identityFile).getAbsolutePath(), passphrase);
171 }
172
173 Properties hash = new Properties();
174 hash.put(STRICT_HOST_KEY_CHECKING, "no");
175
176 session = jsch.getSession(user, host);
177 session.setConfig(hash);
178 session.setPort(port);
179 session.connect();
180
181 Channel channel = session.openChannel(CHANNEL_SFTP);
182 channel.connect();
183
184 channelSftp = (ChannelSftp) channel;
185 setHome(channelSftp.pwd());
186 }
187 catch (JSchException e)
188 {
189 logAndThrowLoginError(user, e);
190 }
191 catch (SftpException e)
192 {
193 logAndThrowLoginError(user, e);
194 }
195 }
196
197 private void logAndThrowLoginError(String user, Exception e) throws IOException
198 {
199 logger.error("Error during login to " + user + "@" + host, e);
200 throw new IOException("Error during login to " + user + "@" + host + ": " + e.getMessage());
201 }
202
203 public void setPort(int port)
204 {
205 this.port = port;
206 }
207
208 public void rename(String filename, String dest) throws IOException
209 {
210
211 if (notifier != null)
212 {
213 notifier.notify(SFTP_RENAME_ACTION, "from: " + currentDirectory + "/" + filename + " - to: "
214 + dest);
215 }
216
217 String absolutePath = getAbsolutePath(dest);
218 try
219 {
220 if (logger.isDebugEnabled())
221 {
222 logger.debug("Will try to rename " + currentDirectory + "/" + filename + " to "
223 + absolutePath);
224 }
225 channelSftp.rename(filename, absolutePath);
226 }
227 catch (SftpException e)
228 {
229 throw new IOException(e.getMessage());
230
231
232
233 }
234 }
235
236 public void deleteFile(String fileName) throws IOException
237 {
238
239 if (notifier != null)
240 {
241 notifier.notify(SFTP_DELETE_ACTION, currentDirectory + "/" + fileName);
242 }
243
244 try
245 {
246 if (logger.isDebugEnabled())
247 {
248 logger.debug("Will try to delete " + fileName);
249 }
250 channelSftp.rm(fileName);
251 }
252 catch (SftpException e)
253 {
254 throw new IOException(e.getMessage());
255 }
256 }
257
258 public void disconnect()
259 {
260 if (channelSftp != null)
261 {
262 channelSftp.disconnect();
263 }
264 if ((session != null) && session.isConnected())
265 {
266 session.disconnect();
267 }
268 }
269
270 public boolean isConnected()
271 {
272 return (channelSftp != null) && channelSftp.isConnected() && !channelSftp.isClosed()
273 && (session != null) && session.isConnected();
274 }
275
276 public String[] listFiles() throws IOException
277 {
278 return listFiles(".");
279 }
280
281 public String[] listFiles(String path) throws IOException
282 {
283 return listDirectory(path, true, false);
284 }
285
286 public String[] listDirectories() throws IOException
287 {
288 return listDirectory(".", false, true);
289 }
290
291 public String[] listDirectories(String path) throws IOException
292 {
293 return listDirectory(path, false, true);
294 }
295
296 private String[] listDirectory(String path, boolean includeFiles, boolean includeDirectories)
297 throws IOException
298 {
299 try
300 {
301 Vector vv = channelSftp.ls(path);
302 if (vv != null)
303 {
304 List<String> ret = new ArrayList<String>();
305 for (int i = 0; i < vv.size(); i++)
306 {
307 Object obj = vv.elementAt(i);
308 if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry)
309 {
310 LsEntry entry = (LsEntry) obj;
311 if (includeFiles && !entry.getAttrs().isDir())
312 {
313 ret.add(entry.getFilename());
314 }
315 if (includeDirectories && entry.getAttrs().isDir())
316 {
317 if (!entry.getFilename().equals(".") && !entry.getFilename().equals(".."))
318 {
319 ret.add(entry.getFilename());
320 }
321 }
322 }
323 }
324 return ret.toArray(new String[ret.size()]);
325 }
326 }
327 catch (SftpException e)
328 {
329 throw new IOException(e.getMessage());
330 }
331 return null;
332 }
333
334
335
336
337
338
339 public InputStream retrieveFile(String fileName) throws IOException
340 {
341
342 long size = getSize(fileName);
343 if (notifier != null)
344 {
345 notifier.notify(SFTP_GET_ACTION, currentDirectory + "/" + fileName, size);
346 }
347
348 try
349 {
350 return channelSftp.get(fileName);
351 }
352 catch (SftpException e)
353 {
354 throw new IOException(e.getMessage() + ". Filename is " + fileName);
355 }
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369 public void storeFile(String fileName, InputStream stream) throws IOException
370 {
371 try
372 {
373
374
375 if (notifier != null)
376 {
377 notifier.notify(SFTP_PUT_ACTION, currentDirectory + "/" + fileName);
378 }
379
380 if (logger.isDebugEnabled())
381 {
382 logger.debug("Sending to SFTP service: Stream = " + stream + " , filename = " + fileName);
383 }
384
385 channelSftp.put(stream, fileName);
386 }
387 catch (SftpException e)
388 {
389 logger.error("Error writing data over SFTP service, error was: " + e.getMessage(), e);
390 throw new IOException(e.getMessage());
391 }
392 }
393
394 public void storeFile(String fileNameLocal, String fileNameRemote) throws IOException
395 {
396 try
397 {
398 channelSftp.put(fileNameLocal, fileNameRemote);
399 }
400 catch (SftpException e)
401 {
402 throw new IOException(e.getMessage());
403 }
404 }
405
406 public long getSize(String filename) throws IOException
407 {
408 try
409 {
410 return channelSftp.stat(filename).getSize();
411 }
412 catch (SftpException e)
413 {
414 throw new IOException(e.getMessage() + " (" + currentDirectory + "/" + filename + ")");
415 }
416 }
417
418
419
420
421
422
423 public long getLastModifiedTime(String filename) throws IOException
424 {
425 try
426 {
427 SftpATTRS attrs = channelSftp.stat("./" + filename);
428 return attrs.getMTime() * 1000L;
429 }
430 catch (SftpException e)
431 {
432 throw new IOException(e.getMessage());
433 }
434 }
435
436
437
438
439
440
441
442 public void mkdir(String directoryName) throws IOException
443 {
444 try
445 {
446 if (logger.isDebugEnabled())
447 {
448 logger.debug("Will try to create directory " + directoryName);
449 }
450 channelSftp.mkdir(directoryName);
451 }
452 catch (SftpException e)
453 {
454
455 throw new IOException("Could not create the directory '" + directoryName + "', caused by: "
456 + e.getMessage());
457
458
459
460 }
461 }
462
463 public void deleteDirectory(String path) throws IOException
464 {
465 path = getAbsolutePath(path);
466 try
467 {
468 if (logger.isDebugEnabled())
469 {
470 logger.debug("Will try to delete directory " + path);
471 }
472 channelSftp.rmdir(path);
473 }
474 catch (SftpException e)
475 {
476 throw new IOException(e.getMessage());
477 }
478 }
479
480
481
482
483
484
485 void setHome(String home)
486 {
487 this.home = home;
488 }
489
490
491
492
493 public ChannelSftp getChannelSftp()
494 {
495 return channelSftp;
496 }
497
498
499
500
501
502
503
504
505
506
507
508 public void createSftpDirIfNotExists(ImmutableEndpoint endpoint, String newDir) throws IOException
509 {
510 String newDirAbs = endpoint.getEndpointURI().getPath() + "/" + newDir;
511
512 String currDir = currentDirectory;
513
514 if (logger.isDebugEnabled())
515 {
516 logger.debug("CHANGE DIR FROM " + currentDirectory + " TO " + newDirAbs);
517 }
518
519
520
521 synchronized (lock)
522 {
523
524 try
525 {
526
527
528 changeWorkingDirectory(newDirAbs);
529 }
530 catch (IOException e)
531 {
532 logger.info("Got an exception when trying to change the working directory to the new dir. "
533 + "Will try to create the directory " + newDirAbs);
534 changeWorkingDirectory(endpoint.getEndpointURI().getPath());
535 mkdir(newDir);
536
537
538 changeWorkingDirectory(newDirAbs);
539 }
540 finally
541 {
542 changeWorkingDirectory(currDir);
543 if (logger.isDebugEnabled())
544 {
545 logger.debug("DIR IS NOW BACK TO " + currentDirectory);
546 }
547 }
548 }
549 }
550
551 public String duplicateHandling(String destDir, String filename, String duplicateHandling)
552 throws IOException
553 {
554 if (duplicateHandling.equals(SftpConnector.PROPERTY_DUPLICATE_HANDLING_ASS_SEQ_NO))
555 {
556 filename = createUniqueName(destDir, filename);
557
558 }
559 else if (duplicateHandling.equals(SftpConnector.PROPERTY_DUPLICATE_HANDLING_OVERWRITE))
560 {
561
562 throw new NotImplementedException("Strategy "
563 + SftpConnector.PROPERTY_DUPLICATE_HANDLING_OVERWRITE
564 + " is not yet implemented");
565
566 }
567 else
568 {
569
570
571
572 }
573
574 return filename;
575 }
576
577 private String createUniqueName(String dir, String path) throws IOException
578 {
579 int fileIdx = 1;
580
581 String filename;
582 String fileType;
583 int fileTypeIdx = path.lastIndexOf('.');
584 if (fileTypeIdx == -1)
585 {
586
587 filename = path;
588 fileType = "";
589 }
590 else
591 {
592 fileType = path.substring(fileTypeIdx);
593
594 filename = path.substring(0, fileTypeIdx);
595 }
596
597 if (logger.isDebugEnabled())
598 {
599 logger.debug("Create a unique name for: " + path + " (" + dir + " - " + filename + " - "
600 + fileType + ")");
601 }
602
603 String uniqueFilename = filename;
604 String[] existingFiles = listFiles(getAbsolutePath(dir));
605
606 while (existsFile(existingFiles, uniqueFilename, fileType))
607 {
608 uniqueFilename = filename + '_' + fileIdx++;
609 }
610
611 uniqueFilename = uniqueFilename + fileType;
612 if (!path.equals(uniqueFilename) && logger.isInfoEnabled())
613 {
614 logger.info("A file with the original filename (" + dir + "/" + path
615 + ") already exists, new name: " + uniqueFilename);
616 }
617 if (logger.isDebugEnabled())
618 {
619 logger.debug("Unique name returned: " + uniqueFilename);
620 }
621 return uniqueFilename;
622 }
623
624 private boolean existsFile(String[] files, String filename, String fileType)
625 {
626 boolean existsFile = false;
627 filename += fileType;
628 for (String file : files)
629 {
630 if (file.equals(filename))
631 {
632 if (logger.isDebugEnabled())
633 {
634 logger.debug("Found existing file: " + file);
635 }
636 existsFile = true;
637 }
638 }
639 return existsFile;
640 }
641
642 public void chmod(String path, int permissions) throws SftpException
643 {
644 path = getAbsolutePath(path);
645 if (logger.isDebugEnabled())
646 {
647 logger.debug("Will try to chmod directory '" + path + "' to permission " + permissions);
648 }
649 channelSftp.chmod(permissions, path);
650 }
651
652 public void setNotifier(SftpNotifier notifier)
653 {
654 this.notifier = notifier;
655 }
656
657 public String getHost()
658 {
659 return host;
660 }
661 }