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 }