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