4278 |
20 Dec 16 |
nicklas |
1 |
package net.sf.basedb.opengrid; |
4278 |
20 Dec 16 |
nicklas |
2 |
|
4278 |
20 Dec 16 |
nicklas |
3 |
import java.io.BufferedInputStream; |
4338 |
07 Feb 17 |
nicklas |
4 |
import java.io.BufferedOutputStream; |
4278 |
20 Dec 16 |
nicklas |
5 |
import java.io.ByteArrayOutputStream; |
4278 |
20 Dec 16 |
nicklas |
6 |
import java.io.Closeable; |
4338 |
07 Feb 17 |
nicklas |
7 |
import java.io.FilterOutputStream; |
4278 |
20 Dec 16 |
nicklas |
8 |
import java.io.IOException; |
4278 |
20 Dec 16 |
nicklas |
9 |
import java.io.InputStream; |
4278 |
20 Dec 16 |
nicklas |
10 |
import java.io.OutputStream; |
6074 |
20 Nov 20 |
nicklas |
11 |
import java.nio.charset.StandardCharsets; |
4278 |
20 Dec 16 |
nicklas |
12 |
import java.util.EnumSet; |
4338 |
07 Feb 17 |
nicklas |
13 |
import java.util.Set; |
4278 |
20 Dec 16 |
nicklas |
14 |
import java.util.concurrent.TimeUnit; |
6071 |
20 Nov 20 |
nicklas |
15 |
import java.util.concurrent.TimeoutException; |
4278 |
20 Dec 16 |
nicklas |
16 |
|
4278 |
20 Dec 16 |
nicklas |
17 |
import org.slf4j.LoggerFactory; |
4278 |
20 Dec 16 |
nicklas |
18 |
|
4278 |
20 Dec 16 |
nicklas |
19 |
import net.schmizz.sshj.SSHClient; |
6071 |
20 Nov 20 |
nicklas |
20 |
import net.schmizz.sshj.connection.ConnectionException; |
4278 |
20 Dec 16 |
nicklas |
21 |
import net.schmizz.sshj.connection.channel.direct.Session; |
4278 |
20 Dec 16 |
nicklas |
22 |
import net.schmizz.sshj.connection.channel.direct.Session.Command; |
7028 |
09 Feb 23 |
nicklas |
23 |
import net.schmizz.sshj.connection.channel.direct.Signal; |
4278 |
20 Dec 16 |
nicklas |
24 |
import net.schmizz.sshj.sftp.FileAttributes; |
4278 |
20 Dec 16 |
nicklas |
25 |
import net.schmizz.sshj.sftp.OpenMode; |
4278 |
20 Dec 16 |
nicklas |
26 |
import net.schmizz.sshj.sftp.RemoteFile; |
5997 |
26 Aug 20 |
nicklas |
27 |
import net.schmizz.sshj.sftp.Response; |
4278 |
20 Dec 16 |
nicklas |
28 |
import net.schmizz.sshj.sftp.SFTPClient; |
5997 |
26 Aug 20 |
nicklas |
29 |
import net.schmizz.sshj.sftp.SFTPException; |
4278 |
20 Dec 16 |
nicklas |
30 |
import net.schmizz.sshj.xfer.LocalFileFilter; |
4278 |
20 Dec 16 |
nicklas |
31 |
import net.schmizz.sshj.xfer.LocalSourceFile; |
5984 |
10 Jul 20 |
nicklas |
32 |
import net.sf.basedb.opengrid.CmdResult.StdioResult; |
4278 |
20 Dec 16 |
nicklas |
33 |
import net.sf.basedb.opengrid.filetransfer.DownloadTarget; |
4278 |
20 Dec 16 |
nicklas |
34 |
import net.sf.basedb.opengrid.filetransfer.FileMetaData; |
4278 |
20 Dec 16 |
nicklas |
35 |
import net.sf.basedb.opengrid.filetransfer.FilePermission; |
4278 |
20 Dec 16 |
nicklas |
36 |
import net.sf.basedb.opengrid.filetransfer.UploadSource; |
7075 |
27 Mar 23 |
nicklas |
37 |
import net.sf.basedb.opengrid.service.OpenGridService; |
4278 |
20 Dec 16 |
nicklas |
38 |
import net.sf.basedb.util.FileCopyRunnable; |
4278 |
20 Dec 16 |
nicklas |
39 |
import net.sf.basedb.util.FileUtil; |
7075 |
27 Mar 23 |
nicklas |
40 |
import net.sf.basedb.util.extensions.logging.ExtensionsLog; |
7075 |
27 Mar 23 |
nicklas |
41 |
import net.sf.basedb.util.extensions.logging.ExtensionsLogger; |
4278 |
20 Dec 16 |
nicklas |
42 |
import net.sf.basedb.util.uri.CloseResourceInputStream; |
4278 |
20 Dec 16 |
nicklas |
43 |
|
4278 |
20 Dec 16 |
nicklas |
44 |
/** |
4278 |
20 Dec 16 |
nicklas |
Represents an open SSH session to a remote host. It is possible to |
4278 |
20 Dec 16 |
nicklas |
execute commands, upload and download files as long as the session |
4278 |
20 Dec 16 |
nicklas |
is open. Do not forget to {@link #close()} the session after use. |
4278 |
20 Dec 16 |
nicklas |
48 |
|
4278 |
20 Dec 16 |
nicklas |
A session is not thread-safe and need external synchronization if |
4278 |
20 Dec 16 |
nicklas |
used with multiple threads. |
4278 |
20 Dec 16 |
nicklas |
51 |
|
4278 |
20 Dec 16 |
nicklas |
@author nicklas |
4278 |
20 Dec 16 |
nicklas |
@since 1.0 |
4278 |
20 Dec 16 |
nicklas |
54 |
*/ |
4278 |
20 Dec 16 |
nicklas |
55 |
public abstract class AbstractSession<T extends AbstractHost<?>> |
4278 |
20 Dec 16 |
nicklas |
56 |
implements Closeable |
4278 |
20 Dec 16 |
nicklas |
57 |
{ |
4278 |
20 Dec 16 |
nicklas |
58 |
|
7075 |
27 Mar 23 |
nicklas |
59 |
private static final ExtensionsLogger logger = |
7075 |
27 Mar 23 |
nicklas |
60 |
ExtensionsLog.getLogger(OpenGridService.ID, true).wrap(LoggerFactory.getLogger(AbstractSession.class)); |
4278 |
20 Dec 16 |
nicklas |
61 |
|
4278 |
20 Dec 16 |
nicklas |
62 |
private final T remote; |
4278 |
20 Dec 16 |
nicklas |
63 |
private final SSHClient ssh; |
4278 |
20 Dec 16 |
nicklas |
64 |
private SFTPClient sftp; |
4278 |
20 Dec 16 |
nicklas |
65 |
|
4278 |
20 Dec 16 |
nicklas |
66 |
/** |
4278 |
20 Dec 16 |
nicklas |
Session are typically created from |
4278 |
20 Dec 16 |
nicklas |
{@link RemoteHost#connect(int)}. |
4278 |
20 Dec 16 |
nicklas |
69 |
*/ |
4278 |
20 Dec 16 |
nicklas |
70 |
protected AbstractSession(T remote, SSHClient ssh) |
4278 |
20 Dec 16 |
nicklas |
71 |
{ |
4278 |
20 Dec 16 |
nicklas |
72 |
this.remote = remote; |
4278 |
20 Dec 16 |
nicklas |
73 |
this.ssh = ssh; |
4278 |
20 Dec 16 |
nicklas |
74 |
} |
4278 |
20 Dec 16 |
nicklas |
75 |
|
4278 |
20 Dec 16 |
nicklas |
76 |
/** |
4278 |
20 Dec 16 |
nicklas |
Check if the session has been closed or not. |
4278 |
20 Dec 16 |
nicklas |
78 |
*/ |
4278 |
20 Dec 16 |
nicklas |
79 |
public boolean isClosed() |
4278 |
20 Dec 16 |
nicklas |
80 |
{ |
4278 |
20 Dec 16 |
nicklas |
81 |
return !ssh.isConnected(); |
4278 |
20 Dec 16 |
nicklas |
82 |
} |
4278 |
20 Dec 16 |
nicklas |
83 |
|
4278 |
20 Dec 16 |
nicklas |
84 |
@Override |
4278 |
20 Dec 16 |
nicklas |
85 |
public void close() |
4278 |
20 Dec 16 |
nicklas |
86 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
87 |
{ |
4278 |
20 Dec 16 |
nicklas |
88 |
OpenGrid.close(sftp); |
4278 |
20 Dec 16 |
nicklas |
89 |
if (ssh.isConnected()) ssh.close(); |
4278 |
20 Dec 16 |
nicklas |
90 |
} |
4278 |
20 Dec 16 |
nicklas |
91 |
|
4278 |
20 Dec 16 |
nicklas |
92 |
/** |
4278 |
20 Dec 16 |
nicklas |
Get the host that created this session. |
4278 |
20 Dec 16 |
nicklas |
94 |
*/ |
4278 |
20 Dec 16 |
nicklas |
95 |
public T getHost() |
4278 |
20 Dec 16 |
nicklas |
96 |
{ |
4278 |
20 Dec 16 |
nicklas |
97 |
return remote; |
4278 |
20 Dec 16 |
nicklas |
98 |
} |
4278 |
20 Dec 16 |
nicklas |
99 |
|
4278 |
20 Dec 16 |
nicklas |
100 |
/** |
4278 |
20 Dec 16 |
nicklas |
Execute a command on the remote host. This method should normally |
4278 |
20 Dec 16 |
nicklas |
not throw any exceptions. The result is returned as a {@link CmdResult} |
5984 |
10 Jul 20 |
nicklas |
object where the {@link CmdResult#getResult()} returns either the |
5984 |
10 Jul 20 |
nicklas |
stdout or stderr stream (depending on exit status). |
4278 |
20 Dec 16 |
nicklas |
105 |
|
6071 |
20 Nov 20 |
nicklas |
Note! Since 1.4 the timeout is a soft timeout. The command may take up |
6071 |
20 Nov 20 |
nicklas |
to 10 times longer as long as it is returning data back in either the |
6071 |
20 Nov 20 |
nicklas |
stdout or stderr streams. |
6071 |
20 Nov 20 |
nicklas |
109 |
|
4278 |
20 Dec 16 |
nicklas |
@param cmd The command to execute |
4278 |
20 Dec 16 |
nicklas |
@param timeout Timeout in seconds to wait for the command to finish |
4278 |
20 Dec 16 |
nicklas |
@return The result of the command |
4278 |
20 Dec 16 |
nicklas |
113 |
*/ |
4278 |
20 Dec 16 |
nicklas |
114 |
public CmdResult<String> executeCmd(String cmd, int timeout) |
4278 |
20 Dec 16 |
nicklas |
115 |
{ |
4278 |
20 Dec 16 |
nicklas |
116 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
117 |
{ |
4278 |
20 Dec 16 |
nicklas |
118 |
logger.debug("Executing command on " + remote + ": " + cmd); |
4278 |
20 Dec 16 |
nicklas |
119 |
} |
5984 |
10 Jul 20 |
nicklas |
120 |
CmdResult<String> result = execute(new StdioResult(cmd), timeout); |
4278 |
20 Dec 16 |
nicklas |
121 |
return result; |
4278 |
20 Dec 16 |
nicklas |
122 |
} |
4278 |
20 Dec 16 |
nicklas |
123 |
|
5984 |
10 Jul 20 |
nicklas |
124 |
/** |
5984 |
10 Jul 20 |
nicklas |
Execute a command on the remote host and store the results |
5984 |
10 Jul 20 |
nicklas |
in the CmdResult instance. Exit status, stdout and stderr will |
5984 |
10 Jul 20 |
nicklas |
be updated on the 'cmd' instance and then |
5984 |
10 Jul 20 |
nicklas |
{@link CmdResult#parseResult()} is called to allow implementations |
5984 |
10 Jul 20 |
nicklas |
to parse the output and update the other result information. |
5984 |
10 Jul 20 |
nicklas |
130 |
|
6071 |
20 Nov 20 |
nicklas |
Note! Since 1.4 the timeout is a soft timeout. The command may take up |
6071 |
20 Nov 20 |
nicklas |
to 10 times longer as long as it is returning data back in either the |
6071 |
20 Nov 20 |
nicklas |
stdout or stderr streams. |
6071 |
20 Nov 20 |
nicklas |
134 |
|
5984 |
10 Jul 20 |
nicklas |
@param cmd The command to execute |
5984 |
10 Jul 20 |
nicklas |
@param timeout Timeout in seconds to wait for the command to finish |
5984 |
10 Jul 20 |
nicklas |
@return The 'cmd' instance |
5984 |
10 Jul 20 |
nicklas |
@since 1.4 |
5984 |
10 Jul 20 |
nicklas |
139 |
*/ |
6071 |
20 Nov 20 |
nicklas |
140 |
public <R, C extends CmdResult<R>> C execute(C cmd, final int timeout) |
4278 |
20 Dec 16 |
nicklas |
141 |
{ |
4278 |
20 Dec 16 |
nicklas |
142 |
Session s = null; |
4278 |
20 Dec 16 |
nicklas |
143 |
try |
4278 |
20 Dec 16 |
nicklas |
144 |
{ |
4278 |
20 Dec 16 |
nicklas |
// Collect stdout and stderr output |
4278 |
20 Dec 16 |
nicklas |
146 |
ByteArrayOutputStream stdout = new ByteArrayOutputStream(); |
4278 |
20 Dec 16 |
nicklas |
147 |
ByteArrayOutputStream stderr = new ByteArrayOutputStream(); |
4278 |
20 Dec 16 |
nicklas |
148 |
s = ssh.startSession(); |
5984 |
10 Jul 20 |
nicklas |
149 |
Command c = s.exec(cmd.getCmd()); |
4278 |
20 Dec 16 |
nicklas |
150 |
|
4278 |
20 Dec 16 |
nicklas |
151 |
Thread stdoutReader = new Thread(new FileCopyRunnable(c.getInputStream(), stdout)); |
4278 |
20 Dec 16 |
nicklas |
152 |
Thread stderrReader = new Thread(new FileCopyRunnable(c.getErrorStream(), stderr)); |
4278 |
20 Dec 16 |
nicklas |
153 |
stdoutReader.start(); |
4278 |
20 Dec 16 |
nicklas |
154 |
stderrReader.start(); |
4278 |
20 Dec 16 |
nicklas |
155 |
|
4278 |
20 Dec 16 |
nicklas |
// Wait for SSH command and reader threads |
6071 |
20 Nov 20 |
nicklas |
157 |
int bytesReceivedLast = 0; |
6071 |
20 Nov 20 |
nicklas |
158 |
int numTimeouts = 0; |
6071 |
20 Nov 20 |
nicklas |
// Convert timeout to milliseconds since we may want to use a smaller timeout (20%) later |
6071 |
20 Nov 20 |
nicklas |
160 |
long timeoutM = 1000 * timeout; |
6071 |
20 Nov 20 |
nicklas |
// A hard timeout that will cause the command to fail no matter what |
6072 |
20 Nov 20 |
nicklas |
162 |
long hardTimeout = 1000 * cmd.getHardTimeout(timeout); |
6071 |
20 Nov 20 |
nicklas |
163 |
long startTime = System.currentTimeMillis(); |
6071 |
20 Nov 20 |
nicklas |
164 |
while (true) |
6071 |
20 Nov 20 |
nicklas |
165 |
{ |
6071 |
20 Nov 20 |
nicklas |
166 |
try |
6071 |
20 Nov 20 |
nicklas |
167 |
{ |
6071 |
20 Nov 20 |
nicklas |
168 |
c.join(timeoutM, TimeUnit.MILLISECONDS); |
6071 |
20 Nov 20 |
nicklas |
// The command ended successfully -- exit loop |
6071 |
20 Nov 20 |
nicklas |
170 |
break; |
6071 |
20 Nov 20 |
nicklas |
171 |
} |
6071 |
20 Nov 20 |
nicklas |
172 |
catch (ConnectionException ex) |
6071 |
20 Nov 20 |
nicklas |
173 |
{ |
6071 |
20 Nov 20 |
nicklas |
174 |
if (ex.getCause() instanceof TimeoutException) |
6071 |
20 Nov 20 |
nicklas |
175 |
{ |
6071 |
20 Nov 20 |
nicklas |
176 |
numTimeouts++; |
6071 |
20 Nov 20 |
nicklas |
177 |
int bytesReceived = stdout.size()+stderr.size(); |
6071 |
20 Nov 20 |
nicklas |
178 |
long time = System.currentTimeMillis()-startTime; |
6071 |
20 Nov 20 |
nicklas |
179 |
if (logger.isDebugEnabled()) |
6071 |
20 Nov 20 |
nicklas |
180 |
{ |
6071 |
20 Nov 20 |
nicklas |
181 |
logger.debug(numTimeouts + " timeouts and " + bytesReceived + " bytes received at time " + time); |
6071 |
20 Nov 20 |
nicklas |
182 |
} |
6071 |
20 Nov 20 |
nicklas |
183 |
if (bytesReceived > bytesReceivedLast && time < hardTimeout) |
6071 |
20 Nov 20 |
nicklas |
184 |
{ |
6071 |
20 Nov 20 |
nicklas |
185 |
bytesReceivedLast = bytesReceived; |
6071 |
20 Nov 20 |
nicklas |
186 |
if (numTimeouts == 1 && timeout > 10) |
6071 |
20 Nov 20 |
nicklas |
187 |
{ |
6071 |
20 Nov 20 |
nicklas |
// If the original timeout is >10 seconds, decrease timeout to 20% of the |
6071 |
20 Nov 20 |
nicklas |
// original (but not lower than 5 seconds) so that we can check if data is still coming in |
6071 |
20 Nov 20 |
nicklas |
190 |
timeoutM = Math.max(5000, timeoutM / 5); |
6071 |
20 Nov 20 |
nicklas |
191 |
} |
6071 |
20 Nov 20 |
nicklas |
192 |
continue; |
6071 |
20 Nov 20 |
nicklas |
193 |
} |
6071 |
20 Nov 20 |
nicklas |
194 |
if (logger.isDebugEnabled()) |
6071 |
20 Nov 20 |
nicklas |
195 |
{ |
6071 |
20 Nov 20 |
nicklas |
196 |
logger.debug("Giving up after " + numTimeouts + " timeouts and " + bytesReceived + " bytes received at time " + time); |
6071 |
20 Nov 20 |
nicklas |
197 |
} |
6071 |
20 Nov 20 |
nicklas |
198 |
} |
7028 |
09 Feb 23 |
nicklas |
// Send SIGTERM to the process otherwise the close() may have to wait for the command to finish |
7028 |
09 Feb 23 |
nicklas |
200 |
c.signal(Signal.TERM); |
7028 |
09 Feb 23 |
nicklas |
// Set a short timeout in case the command didn't respond to the TERM signal |
7028 |
09 Feb 23 |
nicklas |
202 |
ssh.getConnection().setTimeoutMs(500); |
7028 |
09 Feb 23 |
nicklas |
203 |
OpenGrid.close(c); |
6071 |
20 Nov 20 |
nicklas |
204 |
throw ex; |
6071 |
20 Nov 20 |
nicklas |
205 |
} |
6071 |
20 Nov 20 |
nicklas |
206 |
} |
6072 |
20 Nov 20 |
nicklas |
207 |
stdoutReader.join(timeoutM); |
6072 |
20 Nov 20 |
nicklas |
208 |
stderrReader.join(timeoutM); |
4278 |
20 Dec 16 |
nicklas |
209 |
OpenGrid.close(c); |
4278 |
20 Dec 16 |
nicklas |
210 |
|
5984 |
10 Jul 20 |
nicklas |
211 |
cmd.setExitStatus(c.getExitStatus()); |
6074 |
20 Nov 20 |
nicklas |
212 |
cmd.setStdout(stdout.toString(StandardCharsets.UTF_8)); |
6074 |
20 Nov 20 |
nicklas |
213 |
cmd.setStderr(stderr.toString(StandardCharsets.UTF_8)); |
5984 |
10 Jul 20 |
nicklas |
214 |
cmd.parseResult(); |
7380 |
18 Oct 23 |
nicklas |
215 |
if (logger.isTraceEnabled()) |
4278 |
20 Dec 16 |
nicklas |
216 |
{ |
7380 |
18 Oct 23 |
nicklas |
217 |
logger.trace(cmd.toString()); |
4278 |
20 Dec 16 |
nicklas |
218 |
} |
4278 |
20 Dec 16 |
nicklas |
219 |
} |
4278 |
20 Dec 16 |
nicklas |
220 |
catch (Exception ex) |
4278 |
20 Dec 16 |
nicklas |
221 |
{ |
4278 |
20 Dec 16 |
nicklas |
222 |
logger.error("Executing command on " + remote + ": " + cmd, ex); |
5984 |
10 Jul 20 |
nicklas |
223 |
cmd.setException(ex); |
4278 |
20 Dec 16 |
nicklas |
224 |
} |
4278 |
20 Dec 16 |
nicklas |
225 |
finally |
4278 |
20 Dec 16 |
nicklas |
226 |
{ |
4278 |
20 Dec 16 |
nicklas |
227 |
OpenGrid.close(s); |
4278 |
20 Dec 16 |
nicklas |
228 |
} |
5984 |
10 Jul 20 |
nicklas |
229 |
return cmd; |
4278 |
20 Dec 16 |
nicklas |
230 |
} |
4278 |
20 Dec 16 |
nicklas |
231 |
|
4278 |
20 Dec 16 |
nicklas |
232 |
|
4278 |
20 Dec 16 |
nicklas |
233 |
private SFTPClient getSFTPClient() |
4278 |
20 Dec 16 |
nicklas |
234 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
235 |
{ |
4278 |
20 Dec 16 |
nicklas |
236 |
if (sftp == null) sftp = ssh.newSFTPClient(); |
4278 |
20 Dec 16 |
nicklas |
237 |
return sftp; |
4278 |
20 Dec 16 |
nicklas |
238 |
} |
4338 |
07 Feb 17 |
nicklas |
239 |
|
4338 |
07 Feb 17 |
nicklas |
240 |
/** |
4338 |
07 Feb 17 |
nicklas |
Create directories on the remote server if they do not already |
4338 |
07 Feb 17 |
nicklas |
exists. |
4278 |
20 Dec 16 |
nicklas |
243 |
|
4338 |
07 Feb 17 |
nicklas |
@param permission The permission to give to directories that |
4338 |
07 Feb 17 |
nicklas |
are created. Null to use default permissions. Already |
4338 |
07 Feb 17 |
nicklas |
existing directories are not affected. |
4338 |
07 Feb 17 |
nicklas |
@param paths The paths to create |
4338 |
07 Feb 17 |
nicklas |
@return The result of the command (do not forget to check the exit status) |
4338 |
07 Feb 17 |
nicklas |
@since 1.1 |
4338 |
07 Feb 17 |
nicklas |
250 |
*/ |
4338 |
07 Feb 17 |
nicklas |
251 |
public CmdResult<String> mkdirs(FilePermission permission, String... paths) |
4338 |
07 Feb 17 |
nicklas |
252 |
{ |
4338 |
07 Feb 17 |
nicklas |
253 |
ScriptBuilder mkdir = new ScriptBuilder(); |
4338 |
07 Feb 17 |
nicklas |
254 |
mkdir.cmd("set -e"); |
4338 |
07 Feb 17 |
nicklas |
255 |
if (permission != null) |
4338 |
07 Feb 17 |
nicklas |
256 |
{ |
4338 |
07 Feb 17 |
nicklas |
257 |
mkdir.cmd("umask " + permission.umask()); |
4338 |
07 Feb 17 |
nicklas |
258 |
} |
4338 |
07 Feb 17 |
nicklas |
259 |
for (String path : paths) |
4338 |
07 Feb 17 |
nicklas |
260 |
{ |
4338 |
07 Feb 17 |
nicklas |
261 |
if (path != null) mkdir.cmd("mkdir -p " + path); |
4338 |
07 Feb 17 |
nicklas |
262 |
} |
4338 |
07 Feb 17 |
nicklas |
263 |
return executeCmd(mkdir.toString(), 5); |
4338 |
07 Feb 17 |
nicklas |
264 |
} |
4338 |
07 Feb 17 |
nicklas |
265 |
|
4278 |
20 Dec 16 |
nicklas |
266 |
/** |
4278 |
20 Dec 16 |
nicklas |
Upload a file with SFTP. The target directory should |
4278 |
20 Dec 16 |
nicklas |
be a full absolute path and must already exist. |
4278 |
20 Dec 16 |
nicklas |
269 |
*/ |
4278 |
20 Dec 16 |
nicklas |
270 |
public void uploadFile(UploadSource source, String toPath, FilePermission permission) |
4278 |
20 Dec 16 |
nicklas |
271 |
{ |
4278 |
20 Dec 16 |
nicklas |
272 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
273 |
{ |
4278 |
20 Dec 16 |
nicklas |
274 |
logger.debug("Uploading file '" + source.getName() + "' to " + remote + ":" + toPath); |
4278 |
20 Dec 16 |
nicklas |
275 |
} |
4278 |
20 Dec 16 |
nicklas |
276 |
try |
4278 |
20 Dec 16 |
nicklas |
277 |
{ |
4278 |
20 Dec 16 |
nicklas |
278 |
getSFTPClient(); |
4278 |
20 Dec 16 |
nicklas |
279 |
sftp.put(new UploadSourceWrapper(source, permission), toPath); |
4278 |
20 Dec 16 |
nicklas |
280 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
281 |
{ |
4278 |
20 Dec 16 |
nicklas |
282 |
logger.debug("File '" + source.getName() + "' uploaded to " + remote + ":" + toPath); |
4278 |
20 Dec 16 |
nicklas |
283 |
} |
4278 |
20 Dec 16 |
nicklas |
284 |
} |
4278 |
20 Dec 16 |
nicklas |
285 |
catch (IOException ex) |
4278 |
20 Dec 16 |
nicklas |
286 |
{ |
4338 |
07 Feb 17 |
nicklas |
287 |
logger.error("File upload failed: " + toPath, ex); |
4278 |
20 Dec 16 |
nicklas |
288 |
throw new RuntimeException(ex); |
4278 |
20 Dec 16 |
nicklas |
289 |
} |
4278 |
20 Dec 16 |
nicklas |
290 |
finally |
4278 |
20 Dec 16 |
nicklas |
291 |
{} |
4278 |
20 Dec 16 |
nicklas |
292 |
} |
4278 |
20 Dec 16 |
nicklas |
293 |
|
4278 |
20 Dec 16 |
nicklas |
294 |
/** |
4338 |
07 Feb 17 |
nicklas |
Write to a file on the remote server. If the file doesn't exists it is |
4338 |
07 Feb 17 |
nicklas |
created. The target directory should be a full absolute path and |
4338 |
07 Feb 17 |
nicklas |
must already exist. |
4338 |
07 Feb 17 |
nicklas |
298 |
|
4338 |
07 Feb 17 |
nicklas |
@param toPath Absolute path where the uploaded file should be stored |
4338 |
07 Feb 17 |
nicklas |
@param overwrite TRUE to overwrite existing files |
4338 |
07 Feb 17 |
nicklas |
@param metadata Information about the file (or null if not important) |
4338 |
07 Feb 17 |
nicklas |
@param permission Permission to set on the file |
4338 |
07 Feb 17 |
nicklas |
@return An output stream to write the file data to |
4338 |
07 Feb 17 |
nicklas |
@since 1.1 |
4338 |
07 Feb 17 |
nicklas |
305 |
*/ |
4338 |
07 Feb 17 |
nicklas |
306 |
public OutputStream writeFile(String toPath, boolean overwrite, FileMetaData metadata, FilePermission permission) |
4338 |
07 Feb 17 |
nicklas |
307 |
{ |
4338 |
07 Feb 17 |
nicklas |
308 |
if (logger.isDebugEnabled()) |
4338 |
07 Feb 17 |
nicklas |
309 |
{ |
4338 |
07 Feb 17 |
nicklas |
310 |
logger.debug("Writing file '" + toPath + "' from " + remote); |
4338 |
07 Feb 17 |
nicklas |
311 |
} |
4338 |
07 Feb 17 |
nicklas |
312 |
try |
4338 |
07 Feb 17 |
nicklas |
313 |
{ |
4338 |
07 Feb 17 |
nicklas |
314 |
return getRemoteOutputStream(toPath, overwrite, metadata, permission); |
4338 |
07 Feb 17 |
nicklas |
315 |
} |
4338 |
07 Feb 17 |
nicklas |
316 |
catch (IOException ex) |
4338 |
07 Feb 17 |
nicklas |
317 |
{ |
4338 |
07 Feb 17 |
nicklas |
318 |
logger.error("File upload failed: " + toPath, ex); |
4338 |
07 Feb 17 |
nicklas |
319 |
throw new RuntimeException(ex); |
4338 |
07 Feb 17 |
nicklas |
320 |
} |
4338 |
07 Feb 17 |
nicklas |
321 |
finally |
4338 |
07 Feb 17 |
nicklas |
322 |
{} |
4338 |
07 Feb 17 |
nicklas |
323 |
} |
4338 |
07 Feb 17 |
nicklas |
324 |
|
4338 |
07 Feb 17 |
nicklas |
325 |
|
4338 |
07 Feb 17 |
nicklas |
326 |
/** |
4278 |
20 Dec 16 |
nicklas |
Download a remote file to a local destination. |
4278 |
20 Dec 16 |
nicklas |
328 |
|
4278 |
20 Dec 16 |
nicklas |
@param fromPath The path of the remote file |
4278 |
20 Dec 16 |
nicklas |
@param target The local target the remote file is downloaded to |
4278 |
20 Dec 16 |
nicklas |
331 |
*/ |
4278 |
20 Dec 16 |
nicklas |
332 |
public void downloadFile(String fromPath, DownloadTarget target) |
4278 |
20 Dec 16 |
nicklas |
333 |
{ |
4278 |
20 Dec 16 |
nicklas |
334 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
335 |
{ |
4278 |
20 Dec 16 |
nicklas |
336 |
logger.debug("Downloading to '" + target.getName() + "' from " + remote + ": " + fromPath); |
4278 |
20 Dec 16 |
nicklas |
337 |
} |
4278 |
20 Dec 16 |
nicklas |
338 |
InputStream in = null; |
4278 |
20 Dec 16 |
nicklas |
339 |
OutputStream out = null; |
4278 |
20 Dec 16 |
nicklas |
340 |
try |
4278 |
20 Dec 16 |
nicklas |
341 |
{ |
4278 |
20 Dec 16 |
nicklas |
342 |
in = getRemoteInputStream(fromPath, target.getMetadata()); |
5997 |
26 Aug 20 |
nicklas |
343 |
if (in != null) |
4278 |
20 Dec 16 |
nicklas |
344 |
{ |
5997 |
26 Aug 20 |
nicklas |
345 |
out = target.getOutputStream(); |
5997 |
26 Aug 20 |
nicklas |
346 |
FileUtil.copy(in, out); |
5997 |
26 Aug 20 |
nicklas |
347 |
if (logger.isDebugEnabled()) |
5997 |
26 Aug 20 |
nicklas |
348 |
{ |
5997 |
26 Aug 20 |
nicklas |
349 |
logger.debug("File '" + target.getName() + "' downloaded from " + remote + ": " + fromPath); |
5997 |
26 Aug 20 |
nicklas |
350 |
} |
4278 |
20 Dec 16 |
nicklas |
351 |
} |
5997 |
26 Aug 20 |
nicklas |
352 |
else |
5997 |
26 Aug 20 |
nicklas |
353 |
{ |
5997 |
26 Aug 20 |
nicklas |
354 |
if (logger.isDebugEnabled()) |
5997 |
26 Aug 20 |
nicklas |
355 |
{ |
5997 |
26 Aug 20 |
nicklas |
356 |
logger.debug("File doesn't exists: " + fromPath); |
5997 |
26 Aug 20 |
nicklas |
357 |
} |
5997 |
26 Aug 20 |
nicklas |
358 |
} |
4278 |
20 Dec 16 |
nicklas |
359 |
} |
4278 |
20 Dec 16 |
nicklas |
360 |
catch (IOException ex) |
4278 |
20 Dec 16 |
nicklas |
361 |
{ |
4338 |
07 Feb 17 |
nicklas |
362 |
logger.error("File download failed: " + fromPath, ex); |
4278 |
20 Dec 16 |
nicklas |
363 |
throw new RuntimeException(ex); |
4278 |
20 Dec 16 |
nicklas |
364 |
} |
4278 |
20 Dec 16 |
nicklas |
365 |
finally |
4278 |
20 Dec 16 |
nicklas |
366 |
{ |
4278 |
20 Dec 16 |
nicklas |
367 |
FileUtil.close(in); |
4278 |
20 Dec 16 |
nicklas |
368 |
FileUtil.close(out); |
4278 |
20 Dec 16 |
nicklas |
369 |
} |
4278 |
20 Dec 16 |
nicklas |
370 |
} |
4278 |
20 Dec 16 |
nicklas |
371 |
|
4338 |
07 Feb 17 |
nicklas |
372 |
|
4278 |
20 Dec 16 |
nicklas |
373 |
/** |
4278 |
20 Dec 16 |
nicklas |
Read from a file on the remote server. |
4278 |
20 Dec 16 |
nicklas |
375 |
|
4278 |
20 Dec 16 |
nicklas |
@param fromPath The path of the remote file |
4278 |
20 Dec 16 |
nicklas |
@param metadata A metadata instance for collecting metadata |
4278 |
20 Dec 16 |
nicklas |
about the remote file or null if not important. The metadata |
4278 |
20 Dec 16 |
nicklas |
will be set before the file download is started |
4278 |
20 Dec 16 |
nicklas |
@return An InputStream reading from the remote file |
4278 |
20 Dec 16 |
nicklas |
381 |
*/ |
4278 |
20 Dec 16 |
nicklas |
382 |
public InputStream readFile(String fromPath, FileMetaData metadata) |
4278 |
20 Dec 16 |
nicklas |
383 |
{ |
4278 |
20 Dec 16 |
nicklas |
384 |
if (logger.isDebugEnabled()) |
4278 |
20 Dec 16 |
nicklas |
385 |
{ |
4278 |
20 Dec 16 |
nicklas |
386 |
logger.debug("Reading file '" + fromPath + "' from " + remote); |
4278 |
20 Dec 16 |
nicklas |
387 |
} |
4278 |
20 Dec 16 |
nicklas |
388 |
try |
4278 |
20 Dec 16 |
nicklas |
389 |
{ |
4278 |
20 Dec 16 |
nicklas |
390 |
return getRemoteInputStream(fromPath, metadata); |
4278 |
20 Dec 16 |
nicklas |
391 |
} |
4278 |
20 Dec 16 |
nicklas |
392 |
catch (IOException ex) |
4278 |
20 Dec 16 |
nicklas |
393 |
{ |
4338 |
07 Feb 17 |
nicklas |
394 |
logger.error("File download failed: " + fromPath, ex); |
4278 |
20 Dec 16 |
nicklas |
395 |
throw new RuntimeException(ex); |
4278 |
20 Dec 16 |
nicklas |
396 |
} |
4278 |
20 Dec 16 |
nicklas |
397 |
finally |
4278 |
20 Dec 16 |
nicklas |
398 |
{} |
4278 |
20 Dec 16 |
nicklas |
399 |
} |
4278 |
20 Dec 16 |
nicklas |
400 |
|
4278 |
20 Dec 16 |
nicklas |
401 |
private InputStream getRemoteInputStream(String fromPath, FileMetaData metadata) |
4278 |
20 Dec 16 |
nicklas |
402 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
403 |
{ |
4278 |
20 Dec 16 |
nicklas |
404 |
getSFTPClient(); |
5997 |
26 Aug 20 |
nicklas |
405 |
RemoteFile rf = null; |
5997 |
26 Aug 20 |
nicklas |
406 |
try |
5997 |
26 Aug 20 |
nicklas |
407 |
{ |
5997 |
26 Aug 20 |
nicklas |
408 |
rf = sftp.open(fromPath, EnumSet.of(OpenMode.READ)); |
5997 |
26 Aug 20 |
nicklas |
409 |
} |
5997 |
26 Aug 20 |
nicklas |
410 |
catch (SFTPException ex) |
5997 |
26 Aug 20 |
nicklas |
411 |
{ |
5997 |
26 Aug 20 |
nicklas |
412 |
if (ex.getStatusCode() == Response.StatusCode.NO_SUCH_FILE) return null; |
5997 |
26 Aug 20 |
nicklas |
413 |
throw ex; |
5997 |
26 Aug 20 |
nicklas |
414 |
} |
4278 |
20 Dec 16 |
nicklas |
415 |
|
4278 |
20 Dec 16 |
nicklas |
416 |
if (metadata != null) |
4278 |
20 Dec 16 |
nicklas |
417 |
{ |
4278 |
20 Dec 16 |
nicklas |
418 |
FileAttributes attr = rf.fetchAttributes(); |
4278 |
20 Dec 16 |
nicklas |
419 |
metadata.setLastAccessedTime(attr.getAtime() * 1000); |
4278 |
20 Dec 16 |
nicklas |
420 |
metadata.setLastModifiedTime(attr.getMtime() * 1000); |
4278 |
20 Dec 16 |
nicklas |
421 |
metadata.setSize(attr.getSize()); |
4278 |
20 Dec 16 |
nicklas |
422 |
} |
4278 |
20 Dec 16 |
nicklas |
423 |
|
4278 |
20 Dec 16 |
nicklas |
424 |
return new CloseResourceInputStream( |
4278 |
20 Dec 16 |
nicklas |
425 |
new BufferedInputStream(rf.new RemoteFileInputStream(), 1000000), |
4278 |
20 Dec 16 |
nicklas |
426 |
rf); |
4278 |
20 Dec 16 |
nicklas |
427 |
} |
4278 |
20 Dec 16 |
nicklas |
428 |
|
4338 |
07 Feb 17 |
nicklas |
429 |
private OutputStream getRemoteOutputStream(String toPath, boolean overwrite, FileMetaData metadata, FilePermission permission) |
4338 |
07 Feb 17 |
nicklas |
430 |
throws IOException |
4338 |
07 Feb 17 |
nicklas |
431 |
{ |
4338 |
07 Feb 17 |
nicklas |
432 |
getSFTPClient(); |
4338 |
07 Feb 17 |
nicklas |
433 |
FileAttributes.Builder builder = new FileAttributes.Builder(); |
4338 |
07 Feb 17 |
nicklas |
434 |
if (metadata != null) |
4338 |
07 Feb 17 |
nicklas |
435 |
{ |
4338 |
07 Feb 17 |
nicklas |
436 |
if (metadata.getLastAccessedTime() != 0 || metadata.getLastModifiedTime() != 0) |
4338 |
07 Feb 17 |
nicklas |
437 |
{ |
4338 |
07 Feb 17 |
nicklas |
438 |
builder.withAtimeMtime(metadata.getLastAccessedTime(), metadata.getLastModifiedTime()); |
4338 |
07 Feb 17 |
nicklas |
439 |
} |
4338 |
07 Feb 17 |
nicklas |
440 |
if (metadata.getSize() != 0) builder.withSize(metadata.getSize()); |
4338 |
07 Feb 17 |
nicklas |
441 |
} |
4338 |
07 Feb 17 |
nicklas |
442 |
if (permission != null) |
4338 |
07 Feb 17 |
nicklas |
443 |
{ |
4338 |
07 Feb 17 |
nicklas |
444 |
builder.withPermissions(permission.getPermissions()); |
4338 |
07 Feb 17 |
nicklas |
445 |
} |
4338 |
07 Feb 17 |
nicklas |
446 |
|
4338 |
07 Feb 17 |
nicklas |
447 |
Set<OpenMode> openMode = overwrite ? |
4338 |
07 Feb 17 |
nicklas |
448 |
EnumSet.of(OpenMode.CREAT, OpenMode.WRITE, OpenMode.TRUNC) : |
4338 |
07 Feb 17 |
nicklas |
449 |
EnumSet.of(OpenMode.CREAT, OpenMode.WRITE, OpenMode.EXCL); |
4338 |
07 Feb 17 |
nicklas |
450 |
|
4338 |
07 Feb 17 |
nicklas |
451 |
RemoteFile rf = sftp.open(toPath, openMode, builder.build()); |
4338 |
07 Feb 17 |
nicklas |
452 |
|
4338 |
07 Feb 17 |
nicklas |
// NOTE! If the BufferedOutputStream has a larget buffer the bytes |
4338 |
07 Feb 17 |
nicklas |
// are lost somewhere, but I have no idea why or where this happens |
4338 |
07 Feb 17 |
nicklas |
// 10k seems to be ok, 1M is not |
4338 |
07 Feb 17 |
nicklas |
456 |
return new CloseResourceOutputStream( |
4338 |
07 Feb 17 |
nicklas |
457 |
new BufferedOutputStream(rf.new RemoteFileOutputStream(), 10000), |
4338 |
07 Feb 17 |
nicklas |
458 |
rf); |
4338 |
07 Feb 17 |
nicklas |
459 |
} |
4338 |
07 Feb 17 |
nicklas |
460 |
|
4338 |
07 Feb 17 |
nicklas |
461 |
|
4278 |
20 Dec 16 |
nicklas |
462 |
/** |
4278 |
20 Dec 16 |
nicklas |
Wraps an UploadSource implementation with a LocalSourceFile |
4278 |
20 Dec 16 |
nicklas |
implementation so that the file data can be sent to the remote |
4278 |
20 Dec 16 |
nicklas |
server with SSHj. We do it this way so we don't have to expose |
4278 |
20 Dec 16 |
nicklas |
SSHj-specific classes to the other extensions. |
4278 |
20 Dec 16 |
nicklas |
467 |
|
4278 |
20 Dec 16 |
nicklas |
@since 1.0 |
4278 |
20 Dec 16 |
nicklas |
469 |
*/ |
4278 |
20 Dec 16 |
nicklas |
470 |
static class UploadSourceWrapper |
4278 |
20 Dec 16 |
nicklas |
471 |
implements LocalSourceFile |
4278 |
20 Dec 16 |
nicklas |
472 |
{ |
4278 |
20 Dec 16 |
nicklas |
473 |
|
4278 |
20 Dec 16 |
nicklas |
474 |
private final UploadSource source; |
4278 |
20 Dec 16 |
nicklas |
475 |
private final FileMetaData metadata; |
4278 |
20 Dec 16 |
nicklas |
476 |
private final FilePermission permission; |
4278 |
20 Dec 16 |
nicklas |
477 |
|
4278 |
20 Dec 16 |
nicklas |
478 |
UploadSourceWrapper(UploadSource source, FilePermission permission) |
4278 |
20 Dec 16 |
nicklas |
479 |
{ |
4278 |
20 Dec 16 |
nicklas |
480 |
this.source = source; |
4278 |
20 Dec 16 |
nicklas |
481 |
this.metadata = source.getMetadata(); |
4278 |
20 Dec 16 |
nicklas |
482 |
this.permission = permission; |
4278 |
20 Dec 16 |
nicklas |
483 |
} |
4278 |
20 Dec 16 |
nicklas |
484 |
|
4278 |
20 Dec 16 |
nicklas |
485 |
@Override |
4278 |
20 Dec 16 |
nicklas |
486 |
public String getName() |
4278 |
20 Dec 16 |
nicklas |
487 |
{ |
4278 |
20 Dec 16 |
nicklas |
488 |
return source.getName(); |
4278 |
20 Dec 16 |
nicklas |
489 |
} |
4278 |
20 Dec 16 |
nicklas |
490 |
|
4278 |
20 Dec 16 |
nicklas |
491 |
@Override |
4278 |
20 Dec 16 |
nicklas |
492 |
public long getLength() |
4278 |
20 Dec 16 |
nicklas |
493 |
{ |
4278 |
20 Dec 16 |
nicklas |
494 |
return metadata == null ? 0 : metadata.getSize(); |
4278 |
20 Dec 16 |
nicklas |
495 |
} |
4278 |
20 Dec 16 |
nicklas |
496 |
|
4278 |
20 Dec 16 |
nicklas |
497 |
@Override |
4278 |
20 Dec 16 |
nicklas |
498 |
public InputStream getInputStream() |
4278 |
20 Dec 16 |
nicklas |
499 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
500 |
{ |
4278 |
20 Dec 16 |
nicklas |
501 |
return source.getInputStream(); |
4278 |
20 Dec 16 |
nicklas |
502 |
} |
4278 |
20 Dec 16 |
nicklas |
503 |
|
4278 |
20 Dec 16 |
nicklas |
504 |
@Override |
4278 |
20 Dec 16 |
nicklas |
505 |
public int getPermissions() |
4278 |
20 Dec 16 |
nicklas |
506 |
{ |
4278 |
20 Dec 16 |
nicklas |
507 |
return permission.getPermissions(); |
4278 |
20 Dec 16 |
nicklas |
508 |
} |
4278 |
20 Dec 16 |
nicklas |
509 |
|
4278 |
20 Dec 16 |
nicklas |
510 |
/** |
4278 |
20 Dec 16 |
nicklas |
We only support files. |
4278 |
20 Dec 16 |
nicklas |
512 |
*/ |
4278 |
20 Dec 16 |
nicklas |
513 |
@Override |
4278 |
20 Dec 16 |
nicklas |
514 |
public boolean isFile() |
4278 |
20 Dec 16 |
nicklas |
515 |
{ |
4278 |
20 Dec 16 |
nicklas |
516 |
return true; |
4278 |
20 Dec 16 |
nicklas |
517 |
} |
4278 |
20 Dec 16 |
nicklas |
518 |
@Override |
4278 |
20 Dec 16 |
nicklas |
519 |
public boolean isDirectory() |
4278 |
20 Dec 16 |
nicklas |
520 |
{ |
4278 |
20 Dec 16 |
nicklas |
521 |
return false; |
4278 |
20 Dec 16 |
nicklas |
522 |
} |
4278 |
20 Dec 16 |
nicklas |
523 |
|
4278 |
20 Dec 16 |
nicklas |
524 |
/** |
4278 |
20 Dec 16 |
nicklas |
No children. |
4278 |
20 Dec 16 |
nicklas |
526 |
*/ |
4278 |
20 Dec 16 |
nicklas |
527 |
@Override |
4278 |
20 Dec 16 |
nicklas |
528 |
public Iterable<? extends LocalSourceFile> getChildren(LocalFileFilter filter) |
4278 |
20 Dec 16 |
nicklas |
529 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
530 |
{ |
4278 |
20 Dec 16 |
nicklas |
531 |
return null; |
4278 |
20 Dec 16 |
nicklas |
532 |
} |
4278 |
20 Dec 16 |
nicklas |
533 |
|
4278 |
20 Dec 16 |
nicklas |
534 |
@Override |
4278 |
20 Dec 16 |
nicklas |
535 |
public boolean providesAtimeMtime() |
4278 |
20 Dec 16 |
nicklas |
536 |
{ |
4278 |
20 Dec 16 |
nicklas |
537 |
return metadata != null && metadata.getLastAccessedTime() > 0 && metadata.getLastModifiedTime() > 0; |
4278 |
20 Dec 16 |
nicklas |
538 |
} |
4278 |
20 Dec 16 |
nicklas |
539 |
|
4278 |
20 Dec 16 |
nicklas |
540 |
@Override |
4278 |
20 Dec 16 |
nicklas |
541 |
public long getLastAccessTime() |
4278 |
20 Dec 16 |
nicklas |
542 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
543 |
{ |
4278 |
20 Dec 16 |
nicklas |
544 |
return metadata == null ? 0 : metadata.getLastAccessedTime() / 1000; |
4278 |
20 Dec 16 |
nicklas |
545 |
} |
4278 |
20 Dec 16 |
nicklas |
546 |
|
4278 |
20 Dec 16 |
nicklas |
547 |
@Override |
4278 |
20 Dec 16 |
nicklas |
548 |
public long getLastModifiedTime() |
4278 |
20 Dec 16 |
nicklas |
549 |
throws IOException |
4278 |
20 Dec 16 |
nicklas |
550 |
{ |
4278 |
20 Dec 16 |
nicklas |
551 |
return metadata == null ? 0 : metadata.getLastModifiedTime() / 1000; |
4278 |
20 Dec 16 |
nicklas |
552 |
} |
4278 |
20 Dec 16 |
nicklas |
553 |
} |
4338 |
07 Feb 17 |
nicklas |
554 |
|
4338 |
07 Feb 17 |
nicklas |
555 |
/** |
4338 |
07 Feb 17 |
nicklas |
Wrapper around the outputstream so that we can |
4338 |
07 Feb 17 |
nicklas |
close the related resource when the stream is closed. |
4338 |
07 Feb 17 |
nicklas |
@since 1.1 |
4338 |
07 Feb 17 |
nicklas |
559 |
*/ |
4338 |
07 Feb 17 |
nicklas |
560 |
static class CloseResourceOutputStream |
4338 |
07 Feb 17 |
nicklas |
561 |
extends FilterOutputStream |
4338 |
07 Feb 17 |
nicklas |
562 |
{ |
4338 |
07 Feb 17 |
nicklas |
563 |
private final Closeable resource; |
4338 |
07 Feb 17 |
nicklas |
564 |
|
4338 |
07 Feb 17 |
nicklas |
565 |
CloseResourceOutputStream(OutputStream parent, Closeable resource) |
4338 |
07 Feb 17 |
nicklas |
566 |
{ |
4338 |
07 Feb 17 |
nicklas |
567 |
super(parent); |
4338 |
07 Feb 17 |
nicklas |
568 |
this.resource = resource; |
4338 |
07 Feb 17 |
nicklas |
569 |
} |
4278 |
20 Dec 16 |
nicklas |
570 |
|
4338 |
07 Feb 17 |
nicklas |
571 |
@Override |
4338 |
07 Feb 17 |
nicklas |
572 |
public void close() |
4338 |
07 Feb 17 |
nicklas |
573 |
throws IOException |
4338 |
07 Feb 17 |
nicklas |
574 |
{ |
4338 |
07 Feb 17 |
nicklas |
575 |
try |
4338 |
07 Feb 17 |
nicklas |
576 |
{ |
4338 |
07 Feb 17 |
nicklas |
577 |
super.close(); |
4338 |
07 Feb 17 |
nicklas |
578 |
} |
4338 |
07 Feb 17 |
nicklas |
579 |
finally |
4338 |
07 Feb 17 |
nicklas |
580 |
{ |
4338 |
07 Feb 17 |
nicklas |
581 |
resource.close(); |
4338 |
07 Feb 17 |
nicklas |
582 |
} |
4338 |
07 Feb 17 |
nicklas |
583 |
} |
4338 |
07 Feb 17 |
nicklas |
584 |
|
4338 |
07 Feb 17 |
nicklas |
585 |
} |
4278 |
20 Dec 16 |
nicklas |
586 |
} |