From d01f2abe458f49b0b1015c0eb1aa56701a0e1e45 Mon Sep 17 00:00:00 2001 From: Mike Nakhimovich Date: Wed, 8 Feb 2017 15:46:35 -0500 Subject: [PATCH] Feature/filepersister (#115) * generic file persister with path resolver --- .../external/fs/BarCodePathResolver.java | 10 +++ .../nytimes/android/external/fs/FSReader.java | 41 ++++++++++ .../nytimes/android/external/fs/FSWriter.java | 40 ++++++++++ .../external/fs/FileSystemPersister.java | 48 +++++++++++ .../android/external/fs/PathResolver.java | 15 ++++ .../android/external/fs/SourceFileReader.java | 26 +----- .../android/external/fs/SourceFileWriter.java | 30 +------ .../android/external/fs/SourcePersister.java | 2 +- .../external/fs/SourcePersisterFactory.java | 3 +- .../external/fs/FilePersisterTest.java | 79 +++++++++++++++++++ .../external/fs/RecordPersisterTest.java | 4 +- .../external/fs/SourcePersisterTest.java | 4 +- .../external/store/base/Persister.java | 1 + 13 files changed, 245 insertions(+), 58 deletions(-) create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs/BarCodePathResolver.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs/FSReader.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs/FSWriter.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs/FileSystemPersister.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs/PathResolver.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs/FilePersisterTest.java diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/BarCodePathResolver.java b/filesystem/src/main/java/com/nytimes/android/external/fs/BarCodePathResolver.java new file mode 100644 index 0000000..cd04d37 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/BarCodePathResolver.java @@ -0,0 +1,10 @@ +package com.nytimes.android.external.fs; + +import com.nytimes.android.external.store.base.impl.BarCode; + +class BarCodePathResolver implements PathResolver { + @Override + public String resolve(BarCode key) { + return key.toString(); + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/FSReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs/FSReader.java new file mode 100644 index 0000000..43c6461 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/FSReader.java @@ -0,0 +1,41 @@ +package com.nytimes.android.external.fs; + +import com.nytimes.android.external.fs.filesystem.FileSystem; +import com.nytimes.android.external.store.base.DiskRead; + +import java.io.FileNotFoundException; +import java.util.concurrent.Callable; + +import javax.annotation.Nonnull; + +import okio.BufferedSource; +import rx.Observable; + +/** + * FSReader is used when persisting from file system + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * @param key type + */ +public class FSReader implements DiskRead { + final FileSystem fileSystem; + final PathResolver pathResolver; + + public FSReader(FileSystem fileSystem, PathResolver pathResolver) { + this.fileSystem = fileSystem; + this.pathResolver = pathResolver; + } + + @Nonnull + @Override + public Observable read(final T id) { + return fileSystem.exists(pathResolver.resolve(id)) ? + Observable.fromCallable(new Callable() { + @Override + public BufferedSource call() throws FileNotFoundException { + return fileSystem.read(pathResolver.resolve(id)); + } + }) : + Observable.empty(); + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/FSWriter.java b/filesystem/src/main/java/com/nytimes/android/external/fs/FSWriter.java new file mode 100644 index 0000000..c6026f7 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/FSWriter.java @@ -0,0 +1,40 @@ +package com.nytimes.android.external.fs; + +import com.nytimes.android.external.fs.filesystem.FileSystem; +import com.nytimes.android.external.store.base.DiskWrite; + +import java.util.concurrent.Callable; + +import javax.annotation.Nonnull; + +import okio.BufferedSource; +import rx.Observable; +/** + * FSReader is used when persisting to file system + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * @param key type + */ +public class FSWriter implements DiskWrite { + final FileSystem fileSystem; + final PathResolver pathResolver; + + public FSWriter(FileSystem fileSystem, PathResolver pathResolver) { + this.fileSystem = fileSystem; + this.pathResolver = pathResolver; + } + + @Nonnull + @Override + public Observable write(final T barCode, final BufferedSource data) { + return Observable.fromCallable(new Callable() { + @Nonnull + @Override + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + public Boolean call() throws Exception { + fileSystem.write(pathResolver.resolve(barCode), data); + return true; + } + }); + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/FileSystemPersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs/FileSystemPersister.java new file mode 100644 index 0000000..44f0bda --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/FileSystemPersister.java @@ -0,0 +1,48 @@ +package com.nytimes.android.external.fs; + +import com.nytimes.android.external.fs.filesystem.FileSystem; +import com.nytimes.android.external.store.base.Persister; + +import javax.annotation.Nonnull; + +import okio.BufferedSource; +import rx.Observable; +/** + * FileSystemPersister is used when persisting to/from file system + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * @param key type + */ +public final class FileSystemPersister implements Persister { + final FileSystem fileSystem; + private final FSReader fileReader; + private final FSWriter fileWriter; + + private FileSystemPersister(FileSystem fileSystem, PathResolver pathResolver) { + + fileReader = new FSReader<>(fileSystem, pathResolver); + fileWriter = new FSWriter<>(fileSystem, pathResolver); + this.fileSystem = fileSystem; + } + + @Nonnull + public static Persister create(FileSystem fileSystem, + PathResolver pathResolver) { + if (fileSystem == null) { + throw new IllegalArgumentException("root file cannot be null."); + } + return new FileSystemPersister<>(fileSystem, pathResolver); + } + + @Nonnull + @Override + public Observable read(final T id) { + return fileReader.read(id); + } + + @Nonnull + @Override + public Observable write(final T barCode, final BufferedSource data) { + return fileWriter.write(barCode, data); + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/PathResolver.java b/filesystem/src/main/java/com/nytimes/android/external/fs/PathResolver.java new file mode 100644 index 0000000..894165f --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/PathResolver.java @@ -0,0 +1,15 @@ +package com.nytimes.android.external.fs; + +/** + * Created by 206847 on 2/8/17. + */ + +/** + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * @param Store key/request param type + */ +public interface PathResolver { + + String resolve(T key); +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileReader.java index 6cf1873..61a872e 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileReader.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileReader.java @@ -5,42 +5,20 @@ import com.nytimes.android.external.store.base.DiskRead; import com.nytimes.android.external.store.base.RecordState; import com.nytimes.android.external.store.base.impl.BarCode; -import java.io.FileNotFoundException; -import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; -import javax.inject.Inject; import okio.BufferedSource; -import rx.Observable; import static com.nytimes.android.external.fs.SourcePersister.pathForBarcode; -public class SourceFileReader implements DiskRead { +public class SourceFileReader extends FSReader implements DiskRead { - final FileSystem fileSystem; - - @Inject public SourceFileReader(FileSystem fileSystem) { - this.fileSystem = fileSystem; + super(fileSystem, new BarCodePathResolver()); } - @Nonnull - @Override - public Observable read(@Nonnull final BarCode barCode) { - return Observable.fromCallable(new Callable() { - @Nonnull - @Override - public BufferedSource call() throws FileNotFoundException { - return fileSystem.read(pathForBarcode(barCode)); - } - }); - } - - public boolean exists(@Nonnull BarCode barCode) { - return fileSystem.exists(pathForBarcode(barCode)); - } @Nonnull public RecordState getRecordState(@Nonnull BarCode barCode, diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileWriter.java b/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileWriter.java index d4c659f..08580c7 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileWriter.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/SourceFileWriter.java @@ -4,38 +4,12 @@ import com.nytimes.android.external.fs.filesystem.FileSystem; import com.nytimes.android.external.store.base.DiskWrite; import com.nytimes.android.external.store.base.impl.BarCode; -import java.util.concurrent.Callable; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - import okio.BufferedSource; -import rx.Observable; -import static com.nytimes.android.external.fs.SourcePersister.pathForBarcode; -import static okio.Okio.buffer; +public class SourceFileWriter extends FSWriter implements DiskWrite { -public class SourceFileWriter implements DiskWrite { - - final FileSystem fileSystem; - - @Inject public SourceFileWriter(FileSystem fileSystem) { - this.fileSystem = fileSystem; + super(fileSystem, new BarCodePathResolver()); } - - @Nonnull - @Override - public Observable write(@Nonnull final BarCode barCode, @Nonnull final BufferedSource data) { - return Observable.fromCallable(new Callable() { - @Nonnull - @Override - @SuppressWarnings("PMD.SignatureDeclareThrowsException") - public Boolean call() throws Exception { - fileSystem.write(pathForBarcode(barCode), buffer(data)); - return true; - } - }); - } } diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersister.java index efa6d28..0428b96 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersister.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersister.java @@ -41,7 +41,7 @@ public class SourcePersister implements Persister{ @Nonnull @Override public Observable read(@Nonnull final BarCode barCode) { - return sourceFileReader.exists(barCode) ? sourceFileReader.read(barCode) : Observable.empty(); + return sourceFileReader.read(barCode); } @Nonnull diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersisterFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersisterFactory.java index c1d402f..d721585 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersisterFactory.java +++ b/filesystem/src/main/java/com/nytimes/android/external/fs/SourcePersisterFactory.java @@ -39,10 +39,11 @@ public final class SourcePersisterFactory { * persistence {@link com.nytimes.android.external.fs.filesystem.FileSystem}. **/ @Nonnull - public static Persister create(@Nonnull FileSystem fileSystem) { + public static Persister create(@Nonnull FileSystem fileSystem) { if (fileSystem == null) { throw new IllegalArgumentException("fileSystem cannot be null."); } return new SourcePersister(fileSystem); } + } diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs/FilePersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs/FilePersisterTest.java new file mode 100644 index 0000000..8c275f1 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs/FilePersisterTest.java @@ -0,0 +1,79 @@ +package com.nytimes.android.external.fs; + +import com.nytimes.android.external.fs.filesystem.FileSystem; +import com.nytimes.android.external.store.base.Persister; +import com.nytimes.android.external.store.base.impl.BarCode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.NoSuchElementException; + +import okio.BufferedSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.when; + +public class FilePersisterTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Mock + FileSystem fileSystem; + @Mock + BufferedSource bufferedSource; + + private final BarCode simple = new BarCode("type", "key"); + private final String resolvedPath = new BarCodePathResolver().resolve(simple); + private Persister fileSystemPersister; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + fileSystemPersister = FileSystemPersister.create(fileSystem, new BarCodePathResolver()); + } + + @Test + public void readExists() throws FileNotFoundException { + when(fileSystem.exists(resolvedPath)) + .thenReturn(true); + when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource); + + BufferedSource returnedValue = fileSystemPersister.read(simple).toBlocking().single(); + assertThat(returnedValue).isEqualTo(bufferedSource); + } + + @Test + public void readDoesNotExist() throws FileNotFoundException { + expectedException.expect(NoSuchElementException.class); + when(fileSystem.exists(resolvedPath)) + .thenReturn(false); + + fileSystemPersister.read(simple).toBlocking().single(); + } + + @Test + public void writeThenRead() throws IOException { + when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource); + when(fileSystem.exists(resolvedPath)).thenReturn(true); + fileSystemPersister.write(simple, bufferedSource).toBlocking().single(); + BufferedSource source = fileSystemPersister.read(simple).toBlocking().first(); + InOrder inOrder = inOrder(fileSystem); + inOrder.verify(fileSystem).write(resolvedPath, bufferedSource); + inOrder.verify(fileSystem).exists(resolvedPath); + inOrder.verify(fileSystem).read(resolvedPath); + + assertThat(source).isEqualTo(bufferedSource); + + + } + +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs/RecordPersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs/RecordPersisterTest.java index 27b7fc6..d2a0a36 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs/RecordPersisterTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs/RecordPersisterTest.java @@ -41,9 +41,9 @@ public class RecordPersisterTest { @Test public void readExists() throws FileNotFoundException { - when(fileSystem.exists(SourcePersister.pathForBarcode(simple))) + when(fileSystem.exists(simple.toString())) .thenReturn(true); - when(fileSystem.read(SourcePersister.pathForBarcode(simple))).thenReturn(bufferedSource); + when(fileSystem.read(simple.toString())).thenReturn(bufferedSource); BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single(); assertThat(returnedValue).isEqualTo(bufferedSource); diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs/SourcePersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs/SourcePersisterTest.java index 291b449..befc8b2 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs/SourcePersisterTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs/SourcePersisterTest.java @@ -40,9 +40,9 @@ public class SourcePersisterTest { @Test public void readExists() throws FileNotFoundException { - when(fileSystem.exists(SourcePersister.pathForBarcode(simple))) + when(fileSystem.exists(simple.toString())) .thenReturn(true); - when(fileSystem.read(SourcePersister.pathForBarcode(simple))).thenReturn(bufferedSource); + when(fileSystem.read(simple.toString())).thenReturn(bufferedSource); BufferedSource returnedValue = sourcePersister.read(simple).toBlocking().single(); assertThat(returnedValue).isEqualTo(bufferedSource); diff --git a/store/src/main/java/com/nytimes/android/external/store/base/Persister.java b/store/src/main/java/com/nytimes/android/external/store/base/Persister.java index 0a34096..e5b5953 100644 --- a/store/src/main/java/com/nytimes/android/external/store/base/Persister.java +++ b/store/src/main/java/com/nytimes/android/external/store/base/Persister.java @@ -6,6 +6,7 @@ import rx.Observable; /** * Interface for fetching data from persister + * when implementing also think about implementing PathResolver to ease in creating primary keys * * @param data type before parsing */