From e4c2cde9943e2bbd17076b78c27d84c7afaffad3 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Thu, 17 Nov 2016 02:32:32 +0000 Subject: [PATCH] better fix for switching read-only file formats to read-only mode, wrap with read-only FileChannel. fixes feature #34 git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@1057 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/impl/DatabaseImpl.java | 19 +-- .../jackcess/impl/UsageMap.java | 6 +- .../jackcess/util/ReadOnlyFileChannel.java | 144 ++++++++++++++++++ 3 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java index ab01d0b..ac253fb 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/DatabaseImpl.java @@ -69,6 +69,7 @@ import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher; import com.healthmarketscience.jackcess.util.ColumnValidatorFactory; import com.healthmarketscience.jackcess.util.ErrorHandler; import com.healthmarketscience.jackcess.util.LinkResolver; +import com.healthmarketscience.jackcess.util.ReadOnlyFileChannel; import com.healthmarketscience.jackcess.util.SimpleColumnValidatorFactory; import com.healthmarketscience.jackcess.util.TableIterableBuilder; import org.apache.commons.lang.builder.ToStringBuilder; @@ -389,22 +390,14 @@ public class DatabaseImpl implements Database JetFormat jetFormat = JetFormat.getFormat(channel); if(jetFormat.READ_ONLY) { - - if(closeChannel) { - // we own the channel, close and re-open read only - ByteUtil.closeQuietly(channel); - channel = null; - readOnly = true; - channel = openChannel(mdbFile, readOnly); - } else { - throw new IOException("file format " + - jetFormat.getPossibleFileFormats().values() + - " does not support writing for " + mdbFile); - } + // wrap the channel with a read-only version to enforce + // non-writability + channel = new ReadOnlyFileChannel(channel); + readOnly = true; } } - DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, + DatabaseImpl db = new DatabaseImpl(mdbFile, channel, closeChannel, autoSync, null, charset, timeZone, provider); success = true; return db; diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java b/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java index 4a9eab0..c67bcba 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/UsageMap.java @@ -477,8 +477,7 @@ public class UsageMap { private final int _maxInlinePages; - protected InlineHandler() - throws IOException + protected InlineHandler() throws IOException { _maxInlinePages = (getInlineDataEnd() - getInlineDataStart()) * 8; int startPage = getTableBuffer().getInt(getRowStart() + 1); @@ -714,8 +713,7 @@ public class UsageMap TempPageHolder.newHolder(TempBufferHolder.Type.SOFT); private final int _maxPagesPerUsageMapPage; - private ReferenceHandler() - throws IOException + private ReferenceHandler() throws IOException { _maxPagesPerUsageMapPage = ((getFormat().PAGE_SIZE - getFormat().OFFSET_USAGE_MAP_PAGE_DATA) * 8); diff --git a/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java b/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java new file mode 100644 index 0000000..85620e8 --- /dev/null +++ b/src/main/java/com/healthmarketscience/jackcess/util/ReadOnlyFileChannel.java @@ -0,0 +1,144 @@ +/* +Copyright (c) 2016 James Ahlborn + +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 + + http://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 com.healthmarketscience.jackcess.util; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import com.healthmarketscience.jackcess.Database; + +/** + * Wrapper for existing FileChannel which is read-only. + *

+ * Implementation note: this class is optimized for use with {@link Database}. + * Therefore not all methods may be implemented. + * + * @author James Ahlborn + * @usage _advanced_class_ + */ +public class ReadOnlyFileChannel extends FileChannel +{ + private final FileChannel _delegate; + + public ReadOnlyFileChannel(FileChannel delegate) { + _delegate = delegate; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return _delegate.read(dst); + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + return _delegate.read(dsts, offset, length); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + return _delegate.read(dst, position); + } + + @Override + public long position() throws IOException { + return _delegate.position(); + } + + @Override + public FileChannel position(long newPosition) throws IOException { + _delegate.position(newPosition); + return this; + } + + @Override + public long size() throws IOException { + return _delegate.size(); + } + + @Override + public FileChannel truncate(long size) throws IOException { + throw new NonWritableChannelException(); + } + + @Override + public void force(boolean metaData) throws IOException { + // do nothing + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) + throws IOException + { + return _delegate.transferTo(position, count, target); + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) + throws IOException + { + throw new NonWritableChannelException(); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + throw new NonWritableChannelException(); + } + + + @Override + public int write(ByteBuffer src) throws IOException { + throw new NonWritableChannelException(); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + throw new NonWritableChannelException(); + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public FileLock lock(long position, long size, boolean shared) + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + protected void implCloseChannel() throws IOException { + _delegate.close(); + } +} -- 2.39.5