From 4723fe8c0578a9ace9a11e2abc0e3e0737156430 Mon Sep 17 00:00:00 2001 From: Adama Sorho Date: Fri, 1 Sep 2023 23:56:31 -0500 Subject: [PATCH] GH-8691: Support age FTP, SMB over time filters --- .../LastModifiedFTPFileListFilter.java | 139 ++++++++++++++++++ .../LastModifiedFTPFileListFilterTests.java | 78 ++++++++++ .../LastModifiedSmbFileListFilter.java | 139 ++++++++++++++++++ .../LastModifiedSmbFileListFilterTests.java | 76 ++++++++++ 4 files changed, 432 insertions(+) create mode 100644 spring-integration-ftp/src/main/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilter.java create mode 100644 spring-integration-ftp/src/test/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilterTests.java create mode 100644 spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilter.java create mode 100644 spring-integration-smb/src/test/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilterTests.java diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilter.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilter.java new file mode 100644 index 00000000000..c843384dc72 --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilter.java @@ -0,0 +1,139 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.ftp.filters; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.apache.commons.net.ftp.FTPFile; + +import org.springframework.integration.file.filters.DiscardAwareFileListFilter; +import org.springframework.lang.Nullable; + +/** + * The {@link org.springframework.integration.file.filters.FileListFilter} implementation to filter those files which + * {@link FTPFile#getTimestamp()} is less than the {@link #age} in comparison + * with the current time. + *

+ * The resolution is done in seconds. + *

+ * When {@link #discardCallback} is provided, it called for all the rejected files. + * + * @author Adama Sorho + * + * @since 6.2 + */ +public class LastModifiedFTPFileListFilter implements DiscardAwareFileListFilter { + + private static final long ONE_SECOND = 1000; + private static final long DEFAULT_AGE = 60; + private volatile long age = DEFAULT_AGE; + + @Nullable + private Consumer discardCallback; + + public LastModifiedFTPFileListFilter() { + } + + /** + * Construct a {@link LastModifiedFTPFileListFilter} instance with provided {@link #age}. + * Defaults to 60 seconds. + * @param age the age in seconds. + */ + public LastModifiedFTPFileListFilter(long age) { + this.age = age; + } + + /** + * Set the age that the files have to be before being passed by this filter. + * If {@link FTPFile#getTimestamp()} plus age is greater than the current time, the file + * is filtered. The resolution is seconds. + * Defaults to 60 seconds. + * @param age the age in seconds. + * @param unit the timeUnit. + */ + public void setAge(long age, TimeUnit unit) { + this.age = unit.toSeconds(age); + } + + /** + * Set the age that files have to be before being passed by this filter. + * If {@link FTPFile#getTimestamp()} plus age is greater than the current time, the file + * is filtered. The resolution is seconds. + * Defaults to 60 seconds. + * @param age the age in seconds. + */ + public void setAge(Duration age) { + setAge(age.getSeconds()); + } + + /** + * Set the age that files have to be before being passed by this filter. + * If {@link FTPFile#getTimestamp()} plus age is greater than the current time, the file + * is filtered. The resolution is seconds. + * Defaults to 60 seconds. + * @param age the age in seconds. + */ + public void setAge(long age) { + setAge(age, TimeUnit.SECONDS); + } + + @Override + public void addDiscardCallback(@Nullable Consumer discardCallback) { + this.discardCallback = discardCallback; + } + + @Override + public List filterFiles(FTPFile[] files) { + List list = new ArrayList<>(); + long now = System.currentTimeMillis() / ONE_SECOND; + for (FTPFile file: files) { + if (fileIsAged(file, now)) { + list.add(file); + } + else if (this.discardCallback != null) { + this.discardCallback.accept(file); + } + } + + return list; + } + + @Override + public boolean accept(FTPFile file) { + if (fileIsAged(file, System.currentTimeMillis() / ONE_SECOND)) { + return true; + } + else if (this.discardCallback != null) { + this.discardCallback.accept(file); + } + + return false; + } + + private boolean fileIsAged(FTPFile file, long now) { + return file.getTimestamp().getTimeInMillis() / ONE_SECOND + this.age <= now; + } + + @Override + public boolean supportsSingleFileFiltering() { + return true; + } +} diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilterTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilterTests.java new file mode 100644 index 00000000000..bf8c877f055 --- /dev/null +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/filters/LastModifiedFTPFileListFilterTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.ftp.filters; + +import java.util.Calendar; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.net.ftp.FTPFile; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.ftp.FtpTestSupport; +import org.springframework.integration.ftp.session.FtpRemoteFileTemplate; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Adama Sorho + * @since 6.2 + */ +@SpringJUnitConfig +@DirtiesContext +public class LastModifiedFTPFileListFilterTests extends FtpTestSupport { + + @Autowired + private FtpRemoteFileTemplate ftpRemoteFileTemplate; + + @Test + public void testAge() { + LastModifiedFTPFileListFilter filter = new LastModifiedFTPFileListFilter(); + filter.setAge(60, TimeUnit.SECONDS); + FTPFile[] files = ftpRemoteFileTemplate.list("ftpSource"); + assertThat(files.length).isGreaterThan(0); + assertThat(filter.filterFiles(files)).hasSize(0); + FTPFile ftpFile = files[1]; + assertThat(filter.accept(ftpFile)).isFalse(); + + // Make a file as of yesterday's + final Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, -1); + ftpFile.setTimestamp(calendar); + assertThat(filter.filterFiles(files)).hasSize(1); + assertThat(filter.accept(ftpFile)).isTrue(); + } + + @Configuration + public static class Config { + + @Bean + public SessionFactory ftpFileSessionFactory() { + return LastModifiedFTPFileListFilterTests.sessionFactory(); + } + + @Bean + public FtpRemoteFileTemplate ftpRemoteFileTemplate() { + return new FtpRemoteFileTemplate(ftpFileSessionFactory()); + } + } +} diff --git a/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilter.java b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilter.java new file mode 100644 index 00000000000..b32a4f6ce81 --- /dev/null +++ b/spring-integration-smb/src/main/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilter.java @@ -0,0 +1,139 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.filters; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import jcifs.smb.SmbFile; + +import org.springframework.integration.file.filters.DiscardAwareFileListFilter; +import org.springframework.lang.Nullable; + +/** + * The {@link org.springframework.integration.file.filters.FileListFilter} implementation to filter those files which + * {@link SmbFile#getLastModified()} is less than the {@link #age} in comparison + * with the current time. + *

+ * The resolution is done in seconds. + *

+ * When {@link #discardCallback} is provided, it called for all the rejected files. + * + * @author Adama Sorho + * + * @since 6.2 + */ +public class LastModifiedSmbFileListFilter implements DiscardAwareFileListFilter { + + private static final long ONE_SECOND = 1000; + private static final long DEFAULT_AGE = 60; + private volatile long age = DEFAULT_AGE; + + @Nullable + private Consumer discardCallback; + + public LastModifiedSmbFileListFilter() { + } + + /** + * Construct a {@link LastModifiedSmbFileListFilter} instance with provided {@link #age}. + * Defaults to 60 seconds. + * @param age the age in seconds. + */ + public LastModifiedSmbFileListFilter(long age) { + this.age = age; + } + + /** + * Set the age that the files have to be before being passed by this filter. + * If {@link SmbFile#getLastModified()} plus age is greater than the current time, the file + * is filtered. The resolution is seconds. + * Defaults to 60 seconds. + * @param age the age in seconds. + * @param unit the timeUnit. + */ + public void setAge(long age, TimeUnit unit) { + this.age = unit.toSeconds(age); + } + + /** + * Set the age that the files have to be before being passed by this filter. + * If {@link SmbFile#getLastModified()} plus age is greater than the current time, the file + * is filtered. The resolution is seconds. + * Defaults to 60 seconds. + * @param age the age in seconds. + */ + public void setAge(Duration age) { + setAge(age.getSeconds()); + } + + /** + * Set the age that the files have to be before being passed by this filter. + * If {@link SmbFile#getLastModified()} plus age is greater than the current time, the file + * is filtered. The resolution is seconds. + * Defaults to 60 seconds. + * @param age the age in seconds. + */ + public void setAge(long age) { + setAge(age, TimeUnit.SECONDS); + } + + @Override + public void addDiscardCallback(@Nullable Consumer discardCallback) { + this.discardCallback = discardCallback; + } + + @Override + public List filterFiles(SmbFile[] files) { + List list = new ArrayList<>(); + long now = System.currentTimeMillis() / ONE_SECOND; + for (SmbFile file: files) { + if (fileIsAged(file, now)) { + list.add(file); + } + else if (this.discardCallback != null) { + this.discardCallback.accept(file); + } + } + + return list; + } + + @Override + public boolean accept(SmbFile file) { + if (fileIsAged(file, System.currentTimeMillis() / ONE_SECOND)) { + return true; + } + else if (this.discardCallback != null) { + this.discardCallback.accept(file); + } + + return false; + } + + private boolean fileIsAged(SmbFile file, long now) { + return file.getLastModified() / ONE_SECOND + this.age <= now; + } + + @Override + public boolean supportsSingleFileFiltering() { + return true; + } +} diff --git a/spring-integration-smb/src/test/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilterTests.java b/spring-integration-smb/src/test/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilterTests.java new file mode 100644 index 00000000000..5f8c400b2f3 --- /dev/null +++ b/spring-integration-smb/src/test/java/org/springframework/integration/smb/filters/LastModifiedSmbFileListFilterTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.smb.filters; + +import java.util.concurrent.TimeUnit; + +import jcifs.smb.SmbException; +import jcifs.smb.SmbFile; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.smb.SmbTestSupport; +import org.springframework.integration.smb.session.SmbRemoteFileTemplate; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Adama Sorho + * @since 6.2 + */ +@SpringJUnitConfig +@DirtiesContext +public class LastModifiedSmbFileListFilterTests extends SmbTestSupport { + + @Autowired + private SmbRemoteFileTemplate smbRemoteFileTemplate; + + @Test + public void testAge() throws SmbException { + LastModifiedSmbFileListFilter filter = new LastModifiedSmbFileListFilter(); + filter.setAge(60, TimeUnit.SECONDS); + SmbFile[] files = smbRemoteFileTemplate.list("smbSource"); + assertThat(files.length).isGreaterThan(0); + assertThat(filter.filterFiles(files)).hasSize(0); + SmbFile smbFile = files[1]; + assertThat(filter.accept(smbFile)).isFalse(); + + // Make a file as of yesterday's + smbFile.setLastModified(System.currentTimeMillis() - 1000 * 60 * 60 * 24); + assertThat(filter.filterFiles(files)).hasSize(1); + assertThat(filter.accept(smbFile)).isTrue(); + } + + @Configuration + public static class Config { + + @Bean + public SessionFactory smbFileSessionFactory() { + return LastModifiedSmbFileListFilterTests.sessionFactory(); + } + + @Bean + public SmbRemoteFileTemplate smbRemoteFileTemplate() { + return new SmbRemoteFileTemplate(smbFileSessionFactory()); + } + } +}