Skip to content

Commit 03cee94

Browse files
committed
GH-643: provide interfaces for caching file attributes on paths
Remote file systems may have a need to cache file attributes on Path instances. A typical use case is iterating over all files in a directory: the directory listing returns paths, but the underlying remote listing operation may also return file attributes for each entry. This is the case in Sftp, but may also occur for other file systems, for instance a file system wrapping Amazon S3. It would be unfortunate and inefficient if iterating through the paths returned and doing something with the attributes would have to re-fetch the attributes again if they were already available. By implementing WithFileAttributes of its Paths, a file system can associate file attributes with a path instance, and client code can access them. If a file system also makes its paths implement the second interface WithFileAttributeCache, then the SftpSubsystem uses it internally to avoid making repeated remote calls to get file attributes.
1 parent 7cc9c49 commit 03cee94

8 files changed

Lines changed: 127 additions & 71 deletions

File tree

sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpFileSystemProvider.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@
100100
import org.apache.sshd.sftp.client.SftpErrorDataHandler;
101101
import org.apache.sshd.sftp.client.SftpVersionSelector;
102102
import org.apache.sshd.sftp.client.extensions.CopyFileExtension;
103-
import org.apache.sshd.sftp.client.impl.SftpPathImpl;
104103
import org.apache.sshd.sftp.client.impl.SftpRemotePathChannel;
105104
import org.apache.sshd.sftp.common.SftpConstants;
106105
import org.apache.sshd.sftp.common.SftpException;
@@ -1117,7 +1116,7 @@ public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... o
11171116
// SftpPathImpl.withAttributeCache() invocation. So we ensure here that if we are already within a caching
11181117
// scope, we do use the cached attributes, but if we are not, we clear any possibly cached attributes and
11191118
// do actually read them from the remote.
1120-
return SftpPathImpl.withAttributeCache(path, p -> resolveRemoteFileAttributes(path, options));
1119+
return WithFileAttributeCache.withAttributeCache(path, p -> resolveRemoteFileAttributes(path, options));
11211120
}
11221121

11231122
protected SftpClient.Attributes resolveRemoteFileAttributes(SftpPath path, LinkOption... options) throws IOException {
@@ -1136,8 +1135,8 @@ protected SftpClient.Attributes resolveRemoteFileAttributes(SftpPath path, LinkO
11361135
if (log.isTraceEnabled()) {
11371136
log.trace("resolveRemoteFileAttributes({})[{}]: {}", fs, path, attrs);
11381137
}
1139-
if (path instanceof SftpPathImpl) {
1140-
((SftpPathImpl) path).cacheAttributes(attrs);
1138+
if (path instanceof WithFileAttributeCache) {
1139+
((WithFileAttributeCache) path).setAttributes(attrs);
11411140
}
11421141
return attrs;
11431142
} catch (SftpException e) {

sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPath.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,13 @@
3030
/**
3131
* A {@link java.nio.file.Path} on an {@link SftpFileSystem}.
3232
*/
33-
public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
33+
public class SftpPath extends BasePath<SftpPath, SftpFileSystem> implements WithFileAttributes {
3434

3535
public SftpPath(SftpFileSystem fileSystem, String root, List<String> names) {
3636
super(fileSystem, root, names);
3737
}
3838

39-
/**
40-
* Retrieves the cached {@link SftpClient.Attributes} of this {@link SftpPath}, if it has any.
41-
*
42-
* @return the cached {@link SftpClient.Attributes} or {@code null} if there are none cached
43-
*/
44-
@SuppressWarnings("javadoc")
39+
@Override
4540
public SftpClient.Attributes getAttributes() {
4641
// Subclasses may override
4742
return null;

sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.util.Objects;
3131

3232
import org.apache.sshd.sftp.client.SftpClient;
33-
import org.apache.sshd.sftp.client.impl.SftpPathImpl;
3433

3534
/**
3635
* Implements and {@link Iterator} of {@link SftpPath}-s returned by a {@link DirectoryStream#iterator()} method.
@@ -116,8 +115,8 @@ protected SftpPath nextEntry(SftpPath root, DirectoryStream.Filter<? super Path>
116115
dotdotIgnored = true;
117116
} else {
118117
SftpPath candidate = root.resolve(entry.getFilename());
119-
if (candidate instanceof SftpPathImpl) {
120-
((SftpPathImpl) candidate).setAttributes(entry.getAttributes());
118+
if (candidate instanceof WithFileAttributeCache) {
119+
((WithFileAttributeCache) candidate).setAttributes(entry.getAttributes());
121120
}
122121
try {
123122
if ((selector == null) || selector.accept(candidate)) {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sshd.sftp.client.fs;
20+
21+
import java.io.IOException;
22+
import java.nio.file.Path;
23+
24+
import org.apache.sshd.common.util.io.functors.IOFunction;
25+
import org.apache.sshd.sftp.client.SftpClient;
26+
27+
/**
28+
* A mix-in interface for paths that can carry and cache file attributes of the referenced file.
29+
*/
30+
public interface WithFileAttributeCache extends WithFileAttributes {
31+
32+
/**
33+
* Sets the attributes.
34+
*
35+
* @param attributes {@link SftpClient.Attributes} to set
36+
*/
37+
void setAttributes(SftpClient.Attributes attributes);
38+
39+
/**
40+
* Performs the given operation with attribute caching. If {@code SftpClient.Attributes} are fetched by the
41+
* operation, they will be cached and subsequently these cached attributes will be re-used for this {@link SftpPath}
42+
* instance throughout the operation. Calls to {@link #withAttributeCache(IOFunction)} may be nested. The cache is
43+
* cleared at the start and at the end of the outermost invocation.
44+
*
45+
* @param <T> result type of the {@code operation}
46+
* @param operation to perform; may return {@code null} if it has no result
47+
* @return the result of the {@code operation}
48+
* @throws IOException if thrown by the {@code operation}
49+
*/
50+
<T> T withAttributeCache(IOFunction<Path, T> operation) throws IOException;
51+
52+
/**
53+
* Performs the given operation with attribute caching, if the given {@link Path} implements the
54+
* {@link WithFileAttributeCache} interface, otherwise simply executes the operation.
55+
*
56+
* @param <T> result type of the {@code operation}
57+
* @param path {@link Path} to operate on
58+
* @param operation to perform; may return {@code null} if it has no result
59+
* @return the result of the {@code operation}
60+
* @throws IOException if thrown by the {@code operation}
61+
*
62+
* @see #withAttributeCache(IOFunction)
63+
*/
64+
static <T> T withAttributeCache(Path path, IOFunction<Path, T> operation) throws IOException {
65+
if (path instanceof WithFileAttributeCache) {
66+
return ((WithFileAttributeCache) path).withAttributeCache(operation);
67+
}
68+
return operation.apply(path);
69+
}
70+
71+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sshd.sftp.client.fs;
20+
21+
import org.apache.sshd.sftp.client.SftpClient.Attributes;
22+
23+
/**
24+
* A mix-in interface for paths that may have file attributes of the file referenced by the path.
25+
*/
26+
public interface WithFileAttributes {
27+
28+
/**
29+
* Retrieves the {@link Attributes} of this object, if it has any.
30+
*
31+
* @return the {@link Attributes} or {@code null} if there are none
32+
*/
33+
Attributes getAttributes();
34+
35+
}

sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SftpPathImpl.java

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
import org.apache.sshd.sftp.client.SftpClient;
2727
import org.apache.sshd.sftp.client.fs.SftpFileSystem;
2828
import org.apache.sshd.sftp.client.fs.SftpPath;
29+
import org.apache.sshd.sftp.client.fs.WithFileAttributeCache;
2930

3031
/**
3132
* An {@link SftpPath} that can cache {@code SftpClient.Attributes}.
3233
*/
33-
public class SftpPathImpl extends SftpPath {
34+
public class SftpPathImpl extends SftpPath implements WithFileAttributeCache {
3435

3536
private SftpClient.Attributes attributes;
3637

@@ -77,23 +78,7 @@ protected void cacheAttributes(boolean doCache) {
7778
}
7879
}
7980

80-
/**
81-
* Sets the cached attributes to the argument if this {@link SftpPath} instance is currently caching attributes.
82-
* Otherwise a no-op.
83-
*
84-
* @param attributes the {@code SftpClient.Attributes} to cache
85-
*/
86-
public void cacheAttributes(SftpClient.Attributes attributes) {
87-
if (cachingLevel > 0) {
88-
setAttributes(attributes);
89-
}
90-
}
91-
92-
/**
93-
* Unconditionally set the cached attributes, whether or not this instance's attribute cache is enabled.
94-
*
95-
* @param attributes the {@code SftpClient.Attributes} to cache
96-
*/
81+
@Override
9782
public void setAttributes(SftpClient.Attributes attributes) {
9883
this.attributes = attributes;
9984
}
@@ -103,17 +88,7 @@ public SftpClient.Attributes getAttributes() {
10388
return attributes;
10489
}
10590

106-
/**
107-
* Performs the given operation with attribute caching. If {@code SftpClient.Attributes} are fetched by the
108-
* operation, they will be cached and subsequently these cached attributes will be re-used for this {@link SftpPath}
109-
* instance throughout the operation. Calls to {@link #withAttributeCache(IOFunction)} may be nested. The cache is
110-
* cleared at the start and at the end of the outermost invocation.
111-
*
112-
* @param <T> result type of the {@code operation}
113-
* @param operation to perform; may return {@code null} if it has no result
114-
* @return the result of the {@code operation}
115-
* @throws IOException if thrown by the {@code operation}
116-
*/
91+
@Override
11792
public <T> T withAttributeCache(IOFunction<Path, T> operation) throws IOException {
11893
cacheAttributes(true);
11994
try {
@@ -123,22 +98,4 @@ public <T> T withAttributeCache(IOFunction<Path, T> operation) throws IOExceptio
12398
}
12499
}
125100

126-
/**
127-
* Performs the given operation with attribute caching, if the given {@link Path} can cache attributes, otherwise
128-
* simply executes the operation.
129-
*
130-
* @param <T> result type of the {@code operation}
131-
* @param path {@link Path} to operate on
132-
* @param operation to perform; may return {@code null} if it has no result
133-
* @return the result of the {@code operation}
134-
* @throws IOException if thrown by the {@code operation}
135-
*
136-
* @see #withAttributeCache(IOFunction)
137-
*/
138-
public static <T> T withAttributeCache(Path path, IOFunction<Path, T> operation) throws IOException {
139-
if (path instanceof SftpPathImpl) {
140-
return ((SftpPathImpl) path).withAttributeCache(operation);
141-
}
142-
return operation.apply(path);
143-
}
144101
}

sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@
9494
import org.apache.sshd.sftp.SftpModuleProperties;
9595
import org.apache.sshd.sftp.client.SftpClient;
9696
import org.apache.sshd.sftp.client.extensions.openssh.OpenSSHLimitsExtensionInfo;
97-
import org.apache.sshd.sftp.client.fs.SftpPath;
98-
import org.apache.sshd.sftp.client.impl.SftpPathImpl;
97+
import org.apache.sshd.sftp.client.fs.WithFileAttributeCache;
98+
import org.apache.sshd.sftp.client.fs.WithFileAttributes;
9999
import org.apache.sshd.sftp.common.SftpConstants;
100100
import org.apache.sshd.sftp.common.SftpException;
101101
import org.apache.sshd.sftp.common.SftpHelper;
@@ -1654,7 +1654,7 @@ protected void doMakeDirectory(
16541654
LinkOption[] options = accessor.resolveFileAccessLinkOptions(
16551655
this, resolvedPath, SftpConstants.SSH_FXP_MKDIR, "", false);
16561656
final boolean followLinks = resolvePathResolutionFollowLinks(SftpConstants.SSH_FXP_MKDIR, "", resolvedPath);
1657-
SftpPathImpl.withAttributeCache(resolvedPath, p -> {
1657+
WithFileAttributeCache.withAttributeCache(resolvedPath, p -> {
16581658
Boolean symlinkCheck = validateParentExistWithNoSymlinksIfNeverFollowSymlinks(p, !followLinks);
16591659
if (!Boolean.TRUE.equals(symlinkCheck)) {
16601660
throw new AccessDeniedException(p.toString(), p.toString(),
@@ -1715,7 +1715,7 @@ protected void doRemoveFile(int id, String path) throws IOException {
17151715
// never resolve links in the final path to remove as we want to remove the symlink, not the target
17161716
LinkOption[] options = accessor.resolveFileAccessLinkOptions(
17171717
this, resolvedPath, SftpConstants.SSH_FXP_REMOVE, "", false);
1718-
SftpPathImpl.withAttributeCache(resolvedPath, p -> {
1718+
WithFileAttributeCache.withAttributeCache(resolvedPath, p -> {
17191719
Boolean status = checkSymlinkState(p, followLinks, options);
17201720
if (status == null) {
17211721
throw signalRemovalPreConditionFailure(id, path, p,
@@ -2253,8 +2253,8 @@ protected int doReadDir(
22532253
} else {
22542254
Path f = dir.next();
22552255
String shortName = getShortName(f);
2256-
if (f instanceof SftpPath) {
2257-
SftpClient.Attributes attributes = ((SftpPath) f).getAttributes();
2256+
if (f instanceof WithFileAttributes) {
2257+
SftpClient.Attributes attributes = ((WithFileAttributes) f).getAttributes();
22582258
if (attributes != null) {
22592259
entries.put(shortName, f);
22602260
writeDirEntry(session, id, buffer, nb, f, shortName, attributes);
@@ -2416,7 +2416,7 @@ protected String getShortName(Path f) throws IOException {
24162416
protected NavigableMap<String, Object> resolveFileAttributes(
24172417
Path path, int flags, boolean neverFollowSymLinks, LinkOption... options)
24182418
throws IOException {
2419-
return SftpPathImpl.withAttributeCache(path, file -> {
2419+
return WithFileAttributeCache.withAttributeCache(path, file -> {
24202420
Boolean status = checkSymlinkState(file, neverFollowSymLinks, options);
24212421
if (status == null) {
24222422
return handleUnknownStatusFileAttributes(file, flags, options);
@@ -2492,7 +2492,7 @@ protected NavigableMap<String, Object> handleUnknownStatusFileAttributes(
24922492
protected NavigableMap<String, Object> getAttributes(Path path, int flags, LinkOption... options)
24932493
throws IOException {
24942494
NavigableMap<String, Object> attrs
2495-
= SftpPathImpl.withAttributeCache(path, file -> resolveReportedFileAttributes(file, flags, options));
2495+
= WithFileAttributeCache.withAttributeCache(path, file -> resolveReportedFileAttributes(file, flags, options));
24962496
SftpFileSystemAccessor accessor = getFileSystemAccessor();
24972497
return accessor.resolveReportedFileAttributes(this, path, flags, attrs, options);
24982498
}

sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
import org.apache.sshd.server.session.ServerSession;
8383
import org.apache.sshd.sftp.SftpModuleProperties;
8484
import org.apache.sshd.sftp.client.fs.SftpPath;
85-
import org.apache.sshd.sftp.client.impl.SftpPathImpl;
85+
import org.apache.sshd.sftp.client.fs.WithFileAttributeCache;
8686
import org.apache.sshd.sftp.common.SftpConstants;
8787
import org.apache.sshd.sftp.common.SftpException;
8888
import org.apache.sshd.sftp.common.SftpHelper;
@@ -782,7 +782,7 @@ protected void doReadDir(Buffer buffer, int id) throws IOException {
782782

783783
@Override
784784
protected String doOpenDir(int id, String path, Path dir, LinkOption... options) throws IOException {
785-
SftpPathImpl.withAttributeCache(dir, p -> {
785+
WithFileAttributeCache.withAttributeCache(dir, p -> {
786786
Boolean status = IoUtils.checkFileExistsAnySymlinks(p, !IoUtils.followLinks(options));
787787
if (status == null) {
788788
throw signalOpenFailure(id, path, p, true,

0 commit comments

Comments
 (0)