Skip to content

Commit 3d72c9a

Browse files
committed
Fix refresh() not clearing cached state when file was never
attached #758 - Use longer lines - Sort members - Reduce vertical whitespace
1 parent f6d991c commit 3d72c9a

3 files changed

Lines changed: 47 additions & 72 deletions

File tree

commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/sftp/SftpFileObject.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,6 @@ protected synchronized void doDetach() throws Exception {
147147
attrs = null;
148148
}
149149

150-
/**
151-
* {@inheritDoc}
152-
* <p>
153-
* Clears the cached {@code attrs} regardless of the {@code attached} state.
154-
* The {@code attrs} field can be populated without going through {@code attach()}
155-
* (e.g. via {@code setStat()} in {@code doListChildrenResolved()}), so
156-
* {@code doDetach()} alone may not clear it if the file was never attached.
157-
* </p>
158-
*/
159-
@Override
160-
public synchronized void refresh() throws FileSystemException {
161-
super.refresh();
162-
attrs = null;
163-
}
164-
165150
/**
166151
* Returns the size of the file content (in bytes).
167152
*/
@@ -511,6 +496,19 @@ private void putChannel(final ChannelSftp channel) {
511496
getAbstractFileSystem().putChannel(channel);
512497
}
513498

499+
/**
500+
* {@inheritDoc}
501+
* <p>
502+
* Clears the cached {@code attrs} regardless of the {@code attached} state. The {@code attrs} field can be populated without going through {@code attach()}
503+
* (e.g. via {@code setStat()} in {@code doListChildrenResolved()}), so {@code doDetach()} alone may not clear it if the file was never attached.
504+
* </p>
505+
*/
506+
@Override
507+
public synchronized void refresh() throws FileSystemException {
508+
super.refresh();
509+
attrs = null;
510+
}
511+
514512
/**
515513
* Sets attrs from listChildrenResolved
516514
*/

commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/ftp/FtpRefreshCachedStateTest.java

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
1718
package org.apache.commons.vfs2.provider.ftp;
1819

1920
import java.io.File;
@@ -29,18 +30,25 @@
2930
import org.junit.jupiter.api.Test;
3031

3132
/**
32-
* Tests that {@link org.apache.commons.vfs2.provider.AbstractFileObject#refresh()} correctly clears cached state
33-
* even when the file was never attached.
33+
* Tests that {@link org.apache.commons.vfs2.provider.AbstractFileObject#refresh()} correctly clears cached state even when the file was never attached.
3434
* <p>
35-
* Regression test for the bug in {@code AbstractFileObject.detach()} where the {@code if (attached)} guard
36-
* prevented clearing cached fields ({@code type}, {@code parent}, {@code children}) on objects that were never
37-
* attached. Provider-specific cached fields like {@code FtpFileObject.childMap} can be populated without going
38-
* through {@code attach()} (e.g. via {@code getChildFile()} → {@code doGetChildren()}), so {@code refresh()}
39-
* must clear cached state regardless of the {@code attached} flag.
35+
* Regression test for the bug in {@code AbstractFileObject.detach()} where the {@code if (attached)} guard prevented clearing cached fields ({@code type},
36+
* {@code parent}, {@code children}) on objects that were never attached. Provider-specific cached fields like {@code FtpFileObject.childMap} can be populated
37+
* without going through {@code attach()} (e.g. via {@code getChildFile()} → {@code doGetChildren()}), so {@code refresh()} must clear cached state regardless
38+
* of the {@code attached} flag.
4039
* </p>
4140
*/
4241
public class FtpRefreshCachedStateTest {
4342

43+
private static FileSystemOptions createOptions() {
44+
final FileSystemOptions options = new FileSystemOptions();
45+
final FtpFileSystemConfigBuilder builder = FtpFileSystemConfigBuilder.getInstance();
46+
builder.setUserDirIsRoot(options, true);
47+
builder.setPassiveMode(options, true);
48+
builder.setConnectTimeout(options, Duration.ofSeconds(10));
49+
return options;
50+
}
51+
4452
@BeforeEach
4553
public void setUp() throws Exception {
4654
FtpProviderTest.setUpClass(VfsTestUtils.getTestDirectory(), null, null);
@@ -51,27 +59,16 @@ public void tearDown() {
5159
FtpProviderTest.tearDownClass();
5260
}
5361

54-
private static FileSystemOptions createOptions() {
55-
final FileSystemOptions options = new FileSystemOptions();
56-
final FtpFileSystemConfigBuilder builder = FtpFileSystemConfigBuilder.getInstance();
57-
builder.setUserDirIsRoot(options, true);
58-
builder.setPassiveMode(options, true);
59-
builder.setConnectTimeout(options, Duration.ofSeconds(10));
60-
return options;
61-
}
62-
6362
/**
64-
* Tests that {@code exists()} returns {@code false} for a file that was deleted from the FTP server,
65-
* even when the parent's cached {@code childMap} was populated without {@code attach()}.
63+
* Tests that {@code exists()} returns {@code false} for a file that was deleted from the FTP server, even when the parent's cached {@code childMap} was
64+
* populated without {@code attach()}.
6665
* <p>
67-
* The scenario: a file exists, its parent's {@code childMap} is populated (via {@code getChildFile()}
68-
* during the first {@code exists()} call), the file is then deleted on the server, and the parent's
69-
* {@code refresh()} must clear the stale {@code childMap} so the next {@code exists()} returns
70-
* {@code false}.
66+
* The scenario: a file exists, its parent's {@code childMap} is populated (via {@code getChildFile()} during the first {@code exists()} call), the file is
67+
* then deleted on the server, and the parent's {@code refresh()} must clear the stale {@code childMap} so the next {@code exists()} returns {@code false}.
7168
* </p>
7269
* <p>
73-
* Before the fix, {@code refresh()} → {@code detach()} skipped cache clearing because the parent was
74-
* never explicitly attached ({@code attached == false}), leaving stale data in {@code childMap}.
70+
* Before the fix, {@code refresh()} → {@code detach()} skipped cache clearing because the parent was never explicitly attached ({@code attached == false}),
71+
* leaving stale data in {@code childMap}.
7572
* </p>
7673
*/
7774
@Test
@@ -80,32 +77,24 @@ public void testExistsReturnsFalseAfterFileDeletedAndParentRefreshed() throws Ex
8077
final File ftpHome = new File(VfsTestUtils.getTestDirectory());
8178
final File tempFile = new File(ftpHome, "refresh-test-file.txt");
8279
tempFile.createNewFile();
83-
8480
try (final DefaultFileSystemManager manager = new DefaultFileSystemManager()) {
8581
manager.addProvider("ftp", new FtpFileProvider());
8682
manager.init();
87-
8883
final FileSystemOptions options = createOptions();
89-
final FileObject file = manager.resolveFile(
90-
FtpProviderTest.getConnectionUri() + "/refresh-test-file.txt", options);
91-
84+
final FileObject file = manager.resolveFile(FtpProviderTest.getConnectionUri() + "/refresh-test-file.txt", options);
9285
// Verify the file exists. This populates the parent's childMap
9386
// via setFTPFile() → getParent().getChildFile() → doGetChildren().
9487
Assertions.assertTrue(file.exists(), "File should exist on the FTP server");
95-
9688
// Delete the file directly on the filesystem.
9789
Assertions.assertTrue(tempFile.delete(), "Temp file should be deleted");
98-
9990
// Refresh the parent to clear its cached childMap, then refresh the file.
10091
// Before the fix, the parent's refresh() skipped clearing childMap because
10192
// the parent was never attached (attached == false).
10293
final FileObject parent = file.getParent();
10394
parent.refresh();
10495
file.refresh();
105-
10696
// exists() must return false now that the file is deleted.
107-
Assertions.assertFalse(file.exists(),
108-
"exists() must return false after file is deleted and parent is refreshed");
97+
Assertions.assertFalse(file.exists(), "exists() must return false after file is deleted and parent is refreshed");
10998
} finally {
11099
tempFile.delete();
111100
}

commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpRefreshCachedStateTest.java

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
1718
package org.apache.commons.vfs2.provider.sftp;
1819

1920
import java.io.File;
@@ -28,13 +29,11 @@
2829
import org.junit.jupiter.api.Test;
2930

3031
/**
31-
* Tests that {@link SftpFileObject#refresh()} correctly clears cached {@code attrs}
32-
* even when the file was never attached.
32+
* Tests that {@link SftpFileObject#refresh()} correctly clears cached {@code attrs} even when the file was never attached.
3333
* <p>
34-
* Regression test for the bug where {@code attrs} can be populated without going through
35-
* {@code attach()} (via {@code setStat()} in {@code doListChildrenResolved()}). Without
36-
* the fix, {@code refresh()} skips clearing {@code attrs} because {@code attached == false},
37-
* causing {@code exists()} to return stale results.
34+
* Regression test for the bug where {@code attrs} can be populated without going through {@code attach()} (via {@code setStat()} in
35+
* {@code doListChildrenResolved()}). Without the fix, {@code refresh()} skips clearing {@code attrs} because {@code attached == false}, causing
36+
* {@code exists()} to return stale results.
3837
* </p>
3938
*/
4039
public class SftpRefreshCachedStateTest {
@@ -50,37 +49,30 @@ public void tearDown() throws InterruptedException {
5049
}
5150

5251
/**
53-
* Tests that {@code exists()} returns {@code false} for a file that was deleted,
54-
* when the child's {@code attrs} was populated via the parent's listing without
55-
* {@code attach()}.
52+
* Tests that {@code exists()} returns {@code false} for a file that was deleted, when the child's {@code attrs} was populated via the parent's listing
53+
* without {@code attach()}.
5654
* <p>
57-
* The key is that we do NOT call {@code exists()} or {@code getType()} on the child
58-
* before deleting — only the parent's {@code getChildren()} populates the child's
59-
* {@code attrs} via {@code setStat()} (without triggering {@code attach()}).
60-
* Then after deleting the file, {@code refresh()} must clear the stale {@code attrs}.
55+
* The key is that we do NOT call {@code exists()} or {@code getType()} on the child before deleting — only the parent's {@code getChildren()} populates the
56+
* child's {@code attrs} via {@code setStat()} (without triggering {@code attach()}). Then after deleting the file, {@code refresh()} must clear the stale
57+
* {@code attrs}.
6158
* </p>
6259
*/
6360
@Test
6461
public void testExistsReturnsFalseAfterFileDeletedWithStaleAttrs() throws Exception {
6562
final File testDir = VfsTestUtils.getTestDirectoryFile();
6663
final File tempFile = new File(testDir, "sftp-refresh-test-file.txt");
6764
tempFile.createNewFile();
68-
6965
try (final DefaultFileSystemManager manager = new DefaultFileSystemManager()) {
7066
manager.addProvider("sftp", new SftpFileProvider());
7167
manager.init();
72-
7368
final FileSystemOptions options = new FileSystemOptions();
7469
final SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();
7570
builder.setStrictHostKeyChecking(options, "no");
76-
7771
final String uri = SftpTestServerHelper.getConnectionUri();
78-
7972
// List the parent's children. doListChildrenResolved() calls setStat()
8073
// on each child, populating attrs WITHOUT calling attach().
8174
final FileObject parent = manager.resolveFile(uri, options);
8275
final FileObject[] children = parent.getChildren();
83-
8476
// Find our file among the children — its attrs is set but attached=false.
8577
FileObject file = null;
8678
for (final FileObject child : children) {
@@ -90,18 +82,14 @@ public void testExistsReturnsFalseAfterFileDeletedWithStaleAttrs() throws Except
9082
}
9183
}
9284
Assertions.assertNotNull(file, "File should be found in parent listing");
93-
9485
// Delete the file directly on the filesystem.
9586
Assertions.assertTrue(tempFile.delete(), "Temp file should be deleted");
96-
9787
// Refresh the child. Before the fix, refresh() skipped clearing attrs
9888
// because attached == false (attrs was set via setStat, not attach).
9989
file.refresh();
100-
10190
// exists() must return false. Before the fix, doGetType() found the stale
10291
// attrs (non-null), skipped statSelf(), and returned FILE instead of IMAGINARY.
103-
Assertions.assertFalse(file.exists(),
104-
"exists() must return false after file is deleted and refreshed");
92+
Assertions.assertFalse(file.exists(), "exists() must return false after file is deleted and refreshed");
10593
} finally {
10694
tempFile.delete();
10795
}

0 commit comments

Comments
 (0)