Return-Path: X-Original-To: apmail-geode-commits-archive@minotaur.apache.org Delivered-To: apmail-geode-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 92CA110B03 for ; Thu, 20 Aug 2015 21:47:30 +0000 (UTC) Received: (qmail 53760 invoked by uid 500); 20 Aug 2015 21:47:30 -0000 Delivered-To: apmail-geode-commits-archive@geode.apache.org Received: (qmail 53722 invoked by uid 500); 20 Aug 2015 21:47:30 -0000 Mailing-List: contact commits-help@geode.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@geode.incubator.apache.org Delivered-To: mailing list commits@geode.incubator.apache.org Received: (qmail 53713 invoked by uid 99); 20 Aug 2015 21:47:30 -0000 Received: from Unknown (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 20 Aug 2015 21:47:30 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id F31691C66E8 for ; Thu, 20 Aug 2015 21:47:29 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.794 X-Spam-Level: * X-Spam-Status: No, score=1.794 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RP_MATCHES_RCVD=-0.006] autolearn=disabled Received: from mx1-eu-west.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id 9gjxHfjLQd6P for ; Thu, 20 Aug 2015 21:47:14 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-eu-west.apache.org (ASF Mail Server at mx1-eu-west.apache.org) with SMTP id B6FA121381 for ; Thu, 20 Aug 2015 21:47:12 +0000 (UTC) Received: (qmail 52871 invoked by uid 99); 20 Aug 2015 21:47:11 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 20 Aug 2015 21:47:11 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id D50ABE7DEE; Thu, 20 Aug 2015 21:47:11 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: upthewaterspout@apache.org To: commits@geode.incubator.apache.org Message-Id: <4aa3aa6626eb42118e2359746b7d587b@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: incubator-geode git commit: Adding a couple of simple junit tests for FileSystem Date: Thu, 20 Aug 2015 21:47:11 +0000 (UTC) Repository: incubator-geode Updated Branches: refs/heads/feature/GEODE-11 a376120dd -> ebb357ec1 Adding a couple of simple junit tests for FileSystem Testing some basic file write and read operations, as well as file renames and deletes. I also added some more javadocs to the filesystem classes and moved them into a separate package. Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/ebb357ec Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/ebb357ec Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/ebb357ec Branch: refs/heads/feature/GEODE-11 Commit: ebb357ec159a930608cef63be79d4e56c2e9e0f7 Parents: a376120 Author: Dan Smith Authored: Thu Aug 20 12:56:02 2015 -0700 Committer: Dan Smith Committed: Thu Aug 20 14:46:42 2015 -0700 ---------------------------------------------------------------------- gemfire-lucene/build.gradle | 2 +- .../gemfire/cache/lucene/internal/ChunkKey.java | 66 ------- .../gemfire/cache/lucene/internal/File.java | 74 -------- .../cache/lucene/internal/FileInputStream.java | 97 ---------- .../cache/lucene/internal/FileOutputStream.java | 70 ------- .../cache/lucene/internal/FileSystem.java | 111 ----------- .../cache/lucene/internal/RegionDirectory.java | 3 + .../lucene/internal/filesystem/ChunkKey.java | 69 +++++++ .../cache/lucene/internal/filesystem/File.java | 86 +++++++++ .../internal/filesystem/FileInputStream.java | 103 +++++++++++ .../internal/filesystem/FileOutputStream.java | 74 ++++++++ .../lucene/internal/filesystem/FileSystem.java | 130 +++++++++++++ .../filesystem/FileSystemJUnitTest.java | 182 +++++++++++++++++++ 13 files changed, 648 insertions(+), 419 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/build.gradle ---------------------------------------------------------------------- diff --git a/gemfire-lucene/build.gradle b/gemfire-lucene/build.gradle index b360c59..fdc1e4e 100644 --- a/gemfire-lucene/build.gradle +++ b/gemfire-lucene/build.gradle @@ -5,5 +5,5 @@ dependencies { compile 'org.apache.lucene:lucene-queries:5.0.0' compile 'org.apache.lucene:lucene-queryparser:5.0.0' - testCompile project(path: ':gemfire-junit', configuration: 'testOutput') + provided project(path: ':gemfire-junit', configuration: 'testOutput') } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/ChunkKey.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/ChunkKey.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/ChunkKey.java deleted file mode 100644 index 86d9bcb..0000000 --- a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/ChunkKey.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.gemstone.gemfire.cache.lucene.internal; - -import java.io.Serializable; - -public class ChunkKey implements Serializable { - - private static final long serialVersionUID = 1L; - - String fileName; - int chunkId; - - ChunkKey(String fileName, int chunkId) { - this.fileName = fileName; - this.chunkId = chunkId; - } - - /** - * @return the fileName - */ - public String getFileName() { - return fileName; - } - - /** - * @return the chunkId - */ - public int getChunkId() { - return chunkId; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + fileName.hashCode(); - result = prime * result + chunkId; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof ChunkKey)) { - return false; - } - ChunkKey other = (ChunkKey) obj; - if (chunkId != other.chunkId) { - return false; - } - if (fileName == null) { - if (other.fileName != null) { - return false; - } - } else if (!fileName.equals(other.fileName)) { - return false; - } - return true; - } - - -} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/File.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/File.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/File.java deleted file mode 100644 index 9df6641..0000000 --- a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/File.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.gemstone.gemfire.cache.lucene.internal; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; - -final public class File implements Serializable { - private static final long serialVersionUID = 1L; - - private transient FileSystem fileSystem; - private transient int chunkSize; - - String name; - long length = 0; - int chunks = 0; - long created = System.currentTimeMillis(); - long modified = created; - - File(final FileSystem fileSystem, final String name) { - setFileSystem(fileSystem); - - this.name = name; - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @return the length - */ - public long getLength() { - return length; - } - - /** - * @return the created - */ - public long getCreated() { - return created; - } - - /** - * @return the modified - */ - public long getModified() { - return modified; - } - - public InputStream getInputStream() { - // TODO get read lock? - return new FileInputStream(this); - } - - public OutputStream getOutputStream() { - return new FileOutputStream(this); - } - - void setFileSystem(final FileSystem fileSystem) { - this.fileSystem = fileSystem; - this.chunkSize = fileSystem.chunkSize; - } - - int getChunkSize() { - return chunkSize; - } - - public FileSystem getFileSystem() { - return fileSystem; - } -} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileInputStream.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileInputStream.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileInputStream.java deleted file mode 100644 index 0565974..0000000 --- a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileInputStream.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.gemstone.gemfire.cache.lucene.internal; - -import java.io.IOException; -import java.io.InputStream; - -final class FileInputStream extends InputStream { - - private final File file; - private byte[] chunk = null; - private int chunkPosition = 0; - private int chunkId = 0; - private boolean open = true; - - public FileInputStream(File file) { - this.file = file; - nextChunk(); - } - - @Override - public int read() throws IOException { - assertOpen(); - - checkAndFetchNextChunk(); - - if (null == chunk) { - return -1; - } - - return chunk[chunkPosition++] & 0xff; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - assertOpen(); - - checkAndFetchNextChunk(); - - if (null == chunk) { - return -1; - } - - int read = 0; - while (len > 0) { - final int min = Math.min(remaining(), len); - System.arraycopy(chunk, chunkPosition, b, off, min); - off += min; - len -= min; - chunkPosition += min; - read += min; - - if (len > 0) { - // we read to the end of the chunk, fetch another. - nextChunk(); - if (null == chunk) { - break; - } - } - } - - return read; - } - - @Override - public int available() throws IOException { - assertOpen(); - - return remaining(); - } - - @Override - public void close() throws IOException { - if (open) { - open = false; - } - } - - private int remaining() { - return chunk.length - chunkPosition; - } - - private void checkAndFetchNextChunk() { - if (null != chunk && remaining() <= 0) { - nextChunk(); - } - } - - private void nextChunk() { - chunk = file.getFileSystem().getChunk(this.file, chunkId++); - chunkPosition = 0; - } - - private void assertOpen() throws IOException { - if (!open) { - throw new IOException("Closed"); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileOutputStream.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileOutputStream.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileOutputStream.java deleted file mode 100644 index 58d9572..0000000 --- a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileOutputStream.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.gemstone.gemfire.cache.lucene.internal; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -final class FileOutputStream extends OutputStream { - - private final File file; - private ByteBuffer buffer; - private boolean open = true; - - public FileOutputStream(final File file) { - this.file = file; - buffer = ByteBuffer.allocate(file.getChunkSize()); - } - - @Override - public void write(final int b) throws IOException { - assertOpen(); - - if (buffer.remaining() == 0) { - flushBuffer(); - } - - buffer.put((byte) b); - file.length++; - } - - @Override - public void write(final byte[] b, int off, int len) throws IOException { - assertOpen(); - - while (len > 0) { - if (buffer.remaining() == 0) { - flushBuffer(); - } - - final int min = Math.min(buffer.remaining(), len); - buffer.put(b, off, min); - off += min; - len -= min; - file.length += min; - } - } - - @Override - public void close() throws IOException { - if (open) { - flushBuffer(); - file.modified = System.currentTimeMillis(); - file.getFileSystem().updateFile(file); - open = false; - buffer = null; - } - } - - private void flushBuffer() { - byte[] chunk = Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.position()); - file.getFileSystem().putChunk(file, file.chunks++, chunk); - buffer.rewind(); - } - - private void assertOpen() throws IOException { - if (!open) { - throw new IOException("Closed"); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileSystem.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileSystem.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileSystem.java deleted file mode 100644 index 8bba0df..0000000 --- a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/FileSystem.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.gemstone.gemfire.cache.lucene.internal; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Collection; - - -import com.gemstone.gemfire.cache.Region; - -public class FileSystem { - // private final Cache cache; - private final Region fileRegion; - private final Region chunkRegion; - - final int chunkSize = 1_000_000; - - public FileSystem(Region fileRegion, Region chunkRegion) { - super(); - this.fileRegion = fileRegion; - this.chunkRegion = chunkRegion; - } - - public Collection listFileNames() { - return fileRegion.keySet(); - } - - public File createFile(final String name) throws IOException { - // TODO lock region ? - final File file = new File(this, name); - if (null != fileRegion.putIfAbsent(name, file)) { - throw new IOException("File exists."); - } - // TODO unlock region ? - return file; - } - - public File getFile(final String name) throws FileNotFoundException { - final File file = fileRegion.get(name); - - if (null == file) { - throw new FileNotFoundException(name); - } - - file.setFileSystem(this); - return file; - } - - public void deleteFile(final String name) { - // TODO locks? - - // TODO consider removeAll with all ChunkKeys listed. - final ChunkKey key = new ChunkKey(name, 0); - while (true) { - // TODO consider mutable ChunkKey - if (null == chunkRegion.remove(key)) { - // no more chunks - break; - } - key.chunkId++; - } - - fileRegion.remove(name); - } - - public void renameFile(String source, String dest) throws IOException { - final File destFile = createFile(dest); - - final File sourceFile = fileRegion.remove(source); - if (null == sourceFile) { - throw new FileNotFoundException(source); - } - - destFile.chunks = sourceFile.chunks; - destFile.created = sourceFile.created; - destFile.length = sourceFile.length; - destFile.modified = sourceFile.modified; - - // TODO copy on write? - final ChunkKey sourceKey = new ChunkKey(source, 0); - while (true) { - byte[] chunk = chunkRegion.remove(sourceKey); - if (null == chunk) { - // no more chunks - break; - } - putChunk(destFile, sourceKey.chunkId, chunk); - sourceKey.chunkId++; - } - - updateFile(destFile); - } - - byte[] getChunk(final File file, final int id) { - final ChunkKey key = new ChunkKey(file.getName(), id); - final byte[] chunk = chunkRegion.get(key); - return chunk; - } - - public void putChunk(final File file, final int id, final byte[] chunk) { - final ChunkKey key = new ChunkKey(file.getName(), id); - chunkRegion.put(key, chunk); - } - - void updateFile(File file) { - fileRegion.put(file.getName(), file); - } - - - - -} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/RegionDirectory.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/RegionDirectory.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/RegionDirectory.java index 13700a7..ade257a 100644 --- a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/RegionDirectory.java +++ b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/RegionDirectory.java @@ -22,6 +22,9 @@ import com.gemstone.gemfire.cache.PartitionAttributesFactory; import com.gemstone.gemfire.cache.Region; import com.gemstone.gemfire.cache.RegionAttributes; import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.lucene.internal.filesystem.ChunkKey; +import com.gemstone.gemfire.cache.lucene.internal.filesystem.File; +import com.gemstone.gemfire.cache.lucene.internal.filesystem.FileSystem; import com.gemstone.gemfire.internal.i18n.LocalizedStrings; import com.gemstone.gemfire.internal.logging.LogService; http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/ChunkKey.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/ChunkKey.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/ChunkKey.java new file mode 100644 index 0000000..5564c02 --- /dev/null +++ b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/ChunkKey.java @@ -0,0 +1,69 @@ +package com.gemstone.gemfire.cache.lucene.internal.filesystem; + +import java.io.Serializable; + +/** + * The key for a single chunk on a file stored within a region. + */ +public class ChunkKey implements Serializable { + + private static final long serialVersionUID = 1L; + + String fileName; + int chunkId; + + ChunkKey(String fileName, int chunkId) { + this.fileName = fileName; + this.chunkId = chunkId; + } + + /** + * @return the fileName + */ + public String getFileName() { + return fileName; + } + + /** + * @return the chunkId + */ + public int getChunkId() { + return chunkId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + fileName.hashCode(); + result = prime * result + chunkId; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ChunkKey)) { + return false; + } + ChunkKey other = (ChunkKey) obj; + if (chunkId != other.chunkId) { + return false; + } + if (fileName == null) { + if (other.fileName != null) { + return false; + } + } else if (!fileName.equals(other.fileName)) { + return false; + } + return true; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/File.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/File.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/File.java new file mode 100644 index 0000000..1f5edb7 --- /dev/null +++ b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/File.java @@ -0,0 +1,86 @@ +package com.gemstone.gemfire.cache.lucene.internal.filesystem; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; + +/** + * A file that is stored in a gemfire region. + */ +public class File implements Serializable { + private static final long serialVersionUID = 1L; + + private transient FileSystem fileSystem; + private transient int chunkSize; + + private String name; + long length = 0; + int chunks = 0; + long created = System.currentTimeMillis(); + long modified = created; + + File(final FileSystem fileSystem, final String name) { + setFileSystem(fileSystem); + + this.name = name; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the length + */ + public long getLength() { + return length; + } + + /** + * @return the created + */ + public long getCreated() { + return created; + } + + /** + * @return the modified + */ + public long getModified() { + return modified; + } + + /** + * Get an input stream that reads from the beginning the file + * + * The input stream is not threadsafe + */ + public InputStream getInputStream() { + // TODO get read lock? + return new FileInputStream(this); + } + + /** + * Get an output stream that appends to the end + * of the file. + */ + public OutputStream getOutputStream() { + return new FileOutputStream(this); + } + + void setFileSystem(final FileSystem fileSystem) { + this.fileSystem = fileSystem; + this.chunkSize = fileSystem.chunkSize; + } + + int getChunkSize() { + return chunkSize; + } + + public FileSystem getFileSystem() { + return fileSystem; + } +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileInputStream.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileInputStream.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileInputStream.java new file mode 100644 index 0000000..5304a55 --- /dev/null +++ b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileInputStream.java @@ -0,0 +1,103 @@ +package com.gemstone.gemfire.cache.lucene.internal.filesystem; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An input stream that reads chunks from + * a File saved in the region. This input stream + * will keep going back to the region to look for + * chunks until nothing is found. + */ +final class FileInputStream extends InputStream { + + private final File file; + private byte[] chunk = null; + private int chunkPosition = 0; + private int chunkId = 0; + private boolean open = true; + + public FileInputStream(File file) { + this.file = file; + nextChunk(); + } + + @Override + public int read() throws IOException { + assertOpen(); + + checkAndFetchNextChunk(); + + if (null == chunk) { + return -1; + } + + return chunk[chunkPosition++] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + assertOpen(); + + checkAndFetchNextChunk(); + + if (null == chunk) { + return -1; + } + + int read = 0; + while (len > 0) { + final int min = Math.min(remaining(), len); + System.arraycopy(chunk, chunkPosition, b, off, min); + off += min; + len -= min; + chunkPosition += min; + read += min; + + if (len > 0) { + // we read to the end of the chunk, fetch another. + nextChunk(); + if (null == chunk) { + break; + } + } + } + + return read; + } + + @Override + public int available() throws IOException { + assertOpen(); + + return remaining(); + } + + @Override + public void close() throws IOException { + if (open) { + open = false; + } + } + + private int remaining() { + return chunk.length - chunkPosition; + } + + private void checkAndFetchNextChunk() { + if (null != chunk && remaining() <= 0) { + nextChunk(); + } + } + + private void nextChunk() { + chunk = file.getFileSystem().getChunk(this.file, chunkId++); + chunkPosition = 0; + } + + private void assertOpen() throws IOException { + if (!open) { + throw new IOException("Closed"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileOutputStream.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileOutputStream.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileOutputStream.java new file mode 100644 index 0000000..4006be2 --- /dev/null +++ b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileOutputStream.java @@ -0,0 +1,74 @@ +package com.gemstone.gemfire.cache.lucene.internal.filesystem; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; + +final class FileOutputStream extends OutputStream { + + private final File file; + private ByteBuffer buffer; + private boolean open = true; + + public FileOutputStream(final File file) { + this.file = file; + buffer = ByteBuffer.allocate(file.getChunkSize()); + } + + @Override + public void write(final int b) throws IOException { + assertOpen(); + + if (buffer.remaining() == 0) { + flushBuffer(); + } + + buffer.put((byte) b); + file.length++; + } + + @Override + public void write(final byte[] b, int off, int len) throws IOException { + assertOpen(); + + // TODO - What is the state of the system if + // things crash without close? + // Seems like a file metadata will be out of sync + + while (len > 0) { + if (buffer.remaining() == 0) { + flushBuffer(); + } + + final int min = Math.min(buffer.remaining(), len); + buffer.put(b, off, min); + off += min; + len -= min; + file.length += min; + } + } + + @Override + public void close() throws IOException { + if (open) { + flushBuffer(); + file.modified = System.currentTimeMillis(); + file.getFileSystem().updateFile(file); + open = false; + buffer = null; + } + } + + private void flushBuffer() { + byte[] chunk = Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.position()); + file.getFileSystem().putChunk(file, file.chunks++, chunk); + buffer.rewind(); + } + + private void assertOpen() throws IOException { + if (!open) { + throw new IOException("Closed"); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystem.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystem.java b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystem.java new file mode 100644 index 0000000..62b3700 --- /dev/null +++ b/gemfire-lucene/src/main/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystem.java @@ -0,0 +1,130 @@ +package com.gemstone.gemfire.cache.lucene.internal.filesystem; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Collection; +import java.util.concurrent.ConcurrentMap; + +/** + * A Filesystem like interface that stores file data in gemfire regions. + * + * This filesystem is safe for use with multiple threads if the threads are not + * modifying the same files. A single file is not safe to modify by multiple + * threads, even between different members of the distributed system. + * + * Changes to a file may not be visible to other members of the system until the + * FileOutputStream is closed. + */ +public class FileSystem { + // private final Cache cache; + private final ConcurrentMap fileRegion; + private final ConcurrentMap chunkRegion; + + final int chunkSize = 1_000_000; + + public FileSystem(ConcurrentMap fileRegion, ConcurrentMap chunkRegion) { + super(); + this.fileRegion = fileRegion; + this.chunkRegion = chunkRegion; + } + + public Collection listFileNames() { + return fileRegion.keySet(); + } + + public File createFile(final String name) throws IOException { + // TODO lock region ? + final File file = new File(this, name); + if (null != fileRegion.putIfAbsent(name, file)) { + throw new IOException("File exists."); + } + // TODO unlock region ? + return file; + } + + public File getFile(final String name) throws FileNotFoundException { + final File file = fileRegion.get(name); + + if (null == file) { + throw new FileNotFoundException(name); + } + + file.setFileSystem(this); + return file; + } + + public void deleteFile(final String name) { + // TODO locks? + + // TODO - What is the state of the system if + // things crash in the middle of removing this file? + // Seems like a file will be left with some + // dangling chunks at the end of the file + + // TODO consider removeAll with all ChunkKeys listed. + final ChunkKey key = new ChunkKey(name, 0); + while (true) { + // TODO consider mutable ChunkKey + if (null == chunkRegion.remove(key)) { + // no more chunks + break; + } + key.chunkId++; + } + + fileRegion.remove(name); + } + + public void renameFile(String source, String dest) throws IOException { + final File destFile = createFile(dest); + + // TODO - What is the state of the system if + // things crash in the middle of moving this file? + // Seems like a file will be left with some + // dangling chunks at the end of the file + + final File sourceFile = fileRegion.get(source); + if (null == sourceFile) { + throw new FileNotFoundException(source); + } + + destFile.chunks = sourceFile.chunks; + destFile.created = sourceFile.created; + destFile.length = sourceFile.length; + destFile.modified = sourceFile.modified; + + // TODO copy on write? + final ChunkKey sourceKey = new ChunkKey(source, 0); + while (true) { + byte[] chunk = chunkRegion.remove(sourceKey); + if (null == chunk) { + // no more chunks + break; + } + putChunk(destFile, sourceKey.chunkId, chunk); + sourceKey.chunkId++; + } + + updateFile(destFile); + fileRegion.remove(source); + } + + byte[] getChunk(final File file, final int id) { + final ChunkKey key = new ChunkKey(file.getName(), id); + final byte[] chunk = chunkRegion.get(key); + return chunk; + } + + public void putChunk(final File file, final int id, final byte[] chunk) { + final ChunkKey key = new ChunkKey(file.getName(), id); + chunkRegion.put(key, chunk); + } + + void updateFile(File file) { + fileRegion.put(file.getName(), file); + } + + + + +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/ebb357ec/gemfire-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystemJUnitTest.java ---------------------------------------------------------------------- diff --git a/gemfire-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystemJUnitTest.java b/gemfire-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystemJUnitTest.java new file mode 100644 index 0000000..b79bd1e --- /dev/null +++ b/gemfire-lucene/src/test/java/com/gemstone/gemfire/cache/lucene/internal/filesystem/FileSystemJUnitTest.java @@ -0,0 +1,182 @@ +package com.gemstone.gemfire.cache.lucene.internal.filesystem; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.gemstone.gemfire.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +public class FileSystemJUnitTest { + + private static final int SMALL_CHUNK = 523; + private static final int LARGE_CHUNK = 1024 * 1024 * 5; + private FileSystem system; + private Random rand = new Random(); + + @Before + public void setUp() { + ConcurrentHashMap fileRegion = new ConcurrentHashMap(); + ConcurrentHashMap chunkRegion = new ConcurrentHashMap(); + system = new FileSystem(fileRegion, chunkRegion); + } + + @Test + public void testReadWriteBytes() throws IOException { + long start = System.currentTimeMillis(); + + File file1= system.createFile("testFile1"); + + assertEquals(0, file1.getLength()); + + OutputStream outputStream1 = file1.getOutputStream(); + + outputStream1.write(2); + byte[] data = new byte[LARGE_CHUNK]; + rand.nextBytes(data); + outputStream1.write(data); + outputStream1.write(44); + outputStream1.close(); + + assertEquals(2 + LARGE_CHUNK, file1.getLength()); + assertTrue(file1.getModified() >= start); + + OutputStream outputStream2 = file1.getOutputStream(); + + outputStream2.write(123); + byte[] data2 = new byte[SMALL_CHUNK]; + rand.nextBytes(data2); + outputStream2.write(data2); + outputStream2.close(); + + assertEquals(3 + LARGE_CHUNK + SMALL_CHUNK, file1.getLength()); + + InputStream is = file1.getInputStream(); + + assertEquals(2, is.read()); + byte[] resultData = new byte[LARGE_CHUNK]; + assertEquals(LARGE_CHUNK, is.read(resultData)); + assertArrayEquals(data, resultData); + assertEquals(44, is.read()); + assertEquals(123, is.read()); + + + //Test read to an offset + Arrays.fill(resultData, (byte) 0); + assertEquals(SMALL_CHUNK, is.read(resultData, 50, SMALL_CHUNK)); + + //Make sure the data read matches + byte[] expectedData = new byte[LARGE_CHUNK]; + Arrays.fill(expectedData, (byte) 0); + System.arraycopy(data2, 0, expectedData, 50, data2.length); + assertArrayEquals(expectedData, resultData); + + assertEquals(-1, is.read()); + assertEquals(-1, is.read(data)); + is.close(); + + //Test the skip interface + is = file1.getInputStream(); + is.skip(LARGE_CHUNK + 3); + + + Arrays.fill(resultData, (byte) 0); + assertEquals(SMALL_CHUNK, is.read(resultData)); + + Arrays.fill(expectedData, (byte) 0); + System.arraycopy(data2, 0, expectedData, 0, data2.length); + assertArrayEquals(expectedData, resultData); + + assertEquals(-1, is.read()); + } + + @Test + public void testFileOperations() throws IOException { + String name1 = "testFile1"; + File file1= system.createFile(name1); + byte[] file1Data = writeRandomBytes(file1); + + String name2 = "testFile2"; + File file2= system.createFile(name2); + byte[] file2Data = writeRandomBytes(file2); + + file1 = system.getFile(name1); + file2 = system.getFile(name2); + + assertEquals(new HashSet(Arrays.asList(name1, name2)), system.listFileNames()); + assertContents(file1Data, file1); + assertContents(file2Data, file2); + + + try { + system.renameFile(name1, name2); + fail("Should have received an exception"); + } catch(IOException expected) { + + } + assertEquals(new HashSet(Arrays.asList(name1, name2)), system.listFileNames()); + assertContents(file1Data, file1); + assertContents(file2Data, file2); + + String name3 = "testFile3"; + + system.renameFile(name1, name3); + + File file3 = system.getFile(name3); + + assertEquals(new HashSet(Arrays.asList(name3, name2)), system.listFileNames()); + assertContents(file1Data, file3); + assertContents(file2Data, file2); + + system.deleteFile(name2); + + assertEquals(new HashSet(Arrays.asList(name3)), system.listFileNames()); + + system.renameFile(name3, name2); + + assertEquals(new HashSet(Arrays.asList(name2)), system.listFileNames()); + + file2 = system.getFile(name2); + assertContents(file1Data, file2); + } + + private void assertContents(byte[] data, File file) throws IOException { + assertEquals(data.length, file.getLength()); + InputStream is = file.getInputStream(); + byte[] results = new byte[data.length]; + assertEquals(file.getLength(), is.read(results)); + assertEquals(-1, is.read()); + is.close(); + + assertArrayEquals(data, results); + } + + private byte[] writeRandomBytes(File file) throws IOException { + byte[] file1Data = getRandomBytes(); + OutputStream outputStream = file.getOutputStream(); + outputStream.write(file1Data); + outputStream.close(); + return file1Data; + } + + public byte[] getRandomBytes() { + byte[] data = new byte[rand.nextInt(LARGE_CHUNK) + SMALL_CHUNK]; + rand.nextBytes(data); + + return data; + } + +}