View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs.provider.sftp;
18  
19  import com.jcraft.jsch.ChannelSftp;
20  import com.jcraft.jsch.ChannelSftp.LsEntry;
21  import com.jcraft.jsch.SftpATTRS;
22  import com.jcraft.jsch.SftpException;
23  import org.apache.commons.vfs.FileName;
24  import org.apache.commons.vfs.FileObject;
25  import org.apache.commons.vfs.FileSystemException;
26  import org.apache.commons.vfs.FileType;
27  import org.apache.commons.vfs.NameScope;
28  import org.apache.commons.vfs.RandomAccessContent;
29  import org.apache.commons.vfs.VFS;
30  import org.apache.commons.vfs.provider.AbstractFileObject;
31  import org.apache.commons.vfs.provider.UriParser;
32  import org.apache.commons.vfs.util.MonitorOutputStream;
33  import org.apache.commons.vfs.util.RandomAccessMode;
34  import org.apache.commons.vfs.util.FileObjectUtils;
35  
36  import java.io.ByteArrayInputStream;
37  import java.io.ByteArrayOutputStream;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.io.OutputStream;
41  import java.util.ArrayList;
42  import java.util.Iterator;
43  import java.util.Vector;
44  
45  /***
46   * An SFTP file.
47   *
48   * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
49   * @version $Revision: 480428 $ $Date: 2005-10-14 19:59:47 +0200 (Fr, 14 Okt
50   *          2005) $
51   */
52  public class SftpFileObject extends AbstractFileObject implements FileObject
53  {
54  	private final SftpFileSystem fileSystem;
55  	private SftpATTRS attrs;
56  	private final String relPath;
57  
58  	protected SftpFileObject(final FileName name,
59  			final SftpFileSystem fileSystem) throws FileSystemException
60  	{
61  		super(name, fileSystem);
62  		this.fileSystem = fileSystem;
63  		relPath = UriParser.decode(fileSystem.getRootName().getRelativeName(
64  				name));
65  	}
66  
67  	/***
68  	 * Determines the type of this file, returns null if the file does not
69  	 * exist.
70  	 */
71  	protected FileType doGetType() throws Exception
72  	{
73  		if (attrs == null)
74  		{
75  			statSelf();
76  		}
77  
78  		if (attrs == null)
79  		{
80  			return FileType.IMAGINARY;
81  		}
82  
83  		if ((attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS) == 0)
84  		{
85  			throw new FileSystemException(
86  					"vfs.provider.sftp/unknown-permissions.error");
87  		}
88  		if (attrs.isDir())
89  		{
90  			return FileType.FOLDER;
91  		}
92  		else
93  		{
94  			return FileType.FILE;
95  		}
96  	}
97  
98  	/***
99  	 * Called when the type or content of this file changes.
100 	 */
101 	protected void onChange() throws Exception
102 	{
103 		statSelf();
104 	}
105 
106 	/***
107 	 * Fetches file attrs from server.
108 	 */
109 	private void statSelf() throws Exception
110 	{
111 		ChannelSftp channel = fileSystem.getChannel();
112 		try
113 		{
114 			setStat(channel.stat(relPath));
115 		}
116 		catch (final SftpException e)
117 		{
118 			try
119 			{
120 				// maybe the channel has some problems, so recreate the channel and retry
121 				if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE)
122 				{
123 					channel.disconnect();
124 					channel = fileSystem.getChannel();
125 					setStat(channel.stat(relPath));
126 				}
127 				else
128 				{
129 					// Really does not exist
130 					attrs = null;
131 				}
132 			}
133 			catch (final SftpException e2)
134 			{
135 				// TODO - not strictly true, but jsch 0.1.2 does not give us
136 				// enough info in the exception. Should be using:
137 				// if ( e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE )
138 				// However, sometimes the exception has the correct id, and
139 				// sometimes
140 				// it does not. Need to look into why.
141 	
142 				// Does not exist
143 				attrs = null;
144 			}
145 		}
146 		finally
147 		{
148 			fileSystem.putChannel(channel);
149 		}
150 	}
151 
152 	/***
153 	 * Set attrs from listChildrenResolved
154 	 */
155 	private void setStat(SftpATTRS attrs)
156 	{
157 		this.attrs = attrs;
158 	}
159 
160 	/***
161 	 * Creates this file as a folder.
162 	 */
163 	protected void doCreateFolder() throws Exception
164 	{
165 		final ChannelSftp channel = fileSystem.getChannel();
166 		try
167 		{
168 			channel.mkdir(relPath);
169 		}
170 		finally
171 		{
172 			fileSystem.putChannel(channel);
173 		}
174 	}
175 
176 	protected long doGetLastModifiedTime() throws Exception
177 	{
178 		if (attrs == null
179 				|| (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_ACMODTIME) == 0)
180 		{
181 			throw new FileSystemException(
182 					"vfs.provider.sftp/unknown-modtime.error");
183 		}
184 		return attrs.getMTime() * 1000L;
185 	}
186 
187 	/***
188 	 * Sets the last modified time of this file. Is only called if
189 	 * {@link #doGetType} does not return {@link FileType#IMAGINARY}. <p/>
190 	 *
191 	 * @param modtime
192 	 *            is modification time in milliseconds. SFTP protocol can send
193 	 *            times with nanosecond precision but at the moment jsch send
194 	 *            them with second precision.
195 	 */
196 	protected void doSetLastModifiedTime(final long modtime) throws Exception
197 	{
198 		final ChannelSftp channel = fileSystem.getChannel();
199 		try
200 		{
201 			int newMTime = (int) (modtime / 1000L);
202 
203 			attrs.setACMODTIME(attrs.getATime(), newMTime);
204 			channel.setStat(relPath, attrs);
205 		}
206 		finally
207 		{
208 			fileSystem.putChannel(channel);
209 		}
210 	}
211 
212 	/***
213 	 * Deletes the file.
214 	 */
215 	protected void doDelete() throws Exception
216 	{
217 		final ChannelSftp channel = fileSystem.getChannel();
218 		try
219 		{
220 			if (getType() == FileType.FILE)
221 			{
222 				channel.rm(relPath);
223 			}
224 			else
225 			{
226 				channel.rmdir(relPath);
227 			}
228 		}
229 		finally
230 		{
231 			fileSystem.putChannel(channel);
232 		}
233 	}
234 
235 	/***
236 	 * Rename the file.
237 	 */
238 	protected void doRename(FileObject newfile) throws Exception
239 	{
240 		final ChannelSftp channel = fileSystem.getChannel();
241 		try
242 		{
243 			channel.rename(relPath, ((SftpFileObject) newfile).relPath);
244 		}
245 		finally
246 		{
247 			fileSystem.putChannel(channel);
248 		}
249 	}
250 
251 	/***
252 	 * Lists the children of this file.
253 	 */
254 	protected FileObject[] doListChildrenResolved() throws Exception
255 	{
256 		// List the contents of the folder
257 		final Vector vector;
258 		final ChannelSftp channel = fileSystem.getChannel();
259 		try
260 		{
261 			vector = channel.ls(relPath);
262 		}
263 		finally
264 		{
265 			fileSystem.putChannel(channel);
266 		}
267 		if (vector == null)
268 		{
269 			throw new FileSystemException(
270 					"vfs.provider.sftp/list-children.error");
271 		}
272 
273 		// Extract the child names
274 		final ArrayList children = new ArrayList();
275 		for (Iterator iterator = vector.iterator(); iterator.hasNext();)
276 		{
277 			final LsEntry stat = (LsEntry) iterator.next();
278 
279 			String name = stat.getFilename();
280 			if (VFS.isUriStyle())
281 			{
282 				if (stat.getAttrs().isDir()
283 						&& name.charAt(name.length() - 1) != '/')
284 				{
285 					name = name + "/";
286 				}
287 			}
288 
289 			if (name.equals(".") || name.equals("..") || name.equals("./")
290 					|| name.equals("../"))
291 			{
292 				continue;
293 			}
294 
295 			FileObject fo =
296 				getFileSystem()
297 					.resolveFile(
298 							getFileSystem().getFileSystemManager().resolveName(
299 									getName(), UriParser.encode(name),
300 									NameScope.CHILD));
301 
302 			((SftpFileObject) FileObjectUtils.getAbstractFileObject(fo)).setStat(stat.getAttrs());
303 
304 			children.add(fo);
305 		}
306 
307 		return (FileObject[]) children.toArray(new FileObject[children
308 				.size()]);
309 	}
310 
311 	/***
312 	 * Lists the children of this file.
313 	 */
314 	protected String[] doListChildren() throws Exception
315 	{
316 		// use doListChildrenResolved for performance
317 		return null;
318 	}
319 
320 	/***
321 	 * Returns the size of the file content (in bytes).
322 	 */
323 	protected long doGetContentSize() throws Exception
324 	{
325 		if (attrs == null
326 				|| (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_SIZE) == 0)
327 		{
328 			throw new FileSystemException(
329 					"vfs.provider.sftp/unknown-size.error");
330 		}
331 		return attrs.getSize();
332 	}
333 
334 	protected RandomAccessContent doGetRandomAccessContent(
335 			final RandomAccessMode mode) throws Exception
336 	{
337 		return new SftpRandomAccessContent(this, mode);
338 	}
339 
340 	/***
341 	 * Creates an input stream to read the file content from.
342 	 */
343 	InputStream getInputStream(long filePointer) throws IOException
344 	{
345 		final ChannelSftp channel = fileSystem.getChannel();
346 		try
347 		{
348 			// hmmm - using the in memory method is soooo much faster ...
349 			// TODO - Don't read the entire file into memory. Use the
350 			// stream-based methods on ChannelSftp once they work properly final
351 			ByteArrayOutputStream outstr = new ByteArrayOutputStream();
352 			try
353 			{
354 				channel.get(getName().getPathDecoded(), outstr, null,
355 						ChannelSftp.RESUME, filePointer);
356 			}
357 			catch (SftpException e)
358 			{
359 				throw new FileSystemException(e);
360 			}
361 			outstr.close();
362 			return new ByteArrayInputStream(outstr.toByteArray());
363 		}
364 		finally
365 		{
366 			fileSystem.putChannel(channel);
367 		}
368 	}
369 
370 	/***
371 	 * Creates an input stream to read the file content from.
372 	 */
373 	protected InputStream doGetInputStream() throws Exception
374 	{
375 		final ChannelSftp channel = fileSystem.getChannel();
376 		try
377 		{
378 			// return channel.get(getName().getPath());
379 			// hmmm - using the in memory method is soooo much faster ...
380 
381 			// TODO - Don't read the entire file into memory. Use the
382 			// stream-based methods on ChannelSftp once they work properly
383 			final ByteArrayOutputStream outstr = new ByteArrayOutputStream();
384 			channel.get(relPath, outstr);
385 			outstr.close();
386 			return new ByteArrayInputStream(outstr.toByteArray());
387 
388 		}
389 		finally
390 		{
391 			fileSystem.putChannel(channel);
392 		}
393 	}
394 
395 	/***
396 	 * Creates an output stream to write the file content to.
397 	 */
398 	protected OutputStream doGetOutputStream(boolean bAppend) throws Exception
399 	{
400 		// TODO - Don't write the entire file into memory. Use the stream-based
401 		// methods on ChannelSftp once the work properly
402 		final ChannelSftp channel = fileSystem.getChannel();
403 		return new SftpOutputStream(channel);
404 	}
405 
406 	/***
407 	 * An OutputStream that wraps an sftp OutputStream, and closes the channel
408 	 * when the stream is closed.
409 	 */
410 	private class SftpOutputStream extends MonitorOutputStream
411 	{
412 		private final ChannelSftp channel;
413 
414 		public SftpOutputStream(final ChannelSftp channel)
415 		{
416 			super(new ByteArrayOutputStream());
417 			this.channel = channel;
418 		}
419 
420 		/***
421 		 * Called after this stream is closed.
422 		 */
423 		protected void onClose() throws IOException
424 		{
425 			try
426 			{
427 				final ByteArrayOutputStream outstr = (ByteArrayOutputStream) out;
428 				channel.put(new ByteArrayInputStream(outstr.toByteArray()),
429 						relPath);
430 			}
431 			catch (final SftpException e)
432 			{
433 				throw new FileSystemException(e);
434 			}
435 			finally
436 			{
437 				fileSystem.putChannel(channel);
438 			}
439 		}
440 	}
441 }