/* * ModeShape (http://www.modeshape.org) * * 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 org.modeshape.jcr.value.binary; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Callable; import java.util.concurrent.locks.Lock; import org.modeshape.jcr.value.BinaryKey; /** * A {@link InputStream} implementation around a file that creates a shared lock when reading the file, ensuring the file is not * changed by other writers in this or other JVM processes. The stream automatically closes itself and releases the lock when * {@link #close() closed explicitly} or if there are any errors or exceptions while reading. Caution: be very careful when * working with this class, as any open without close operations can produce "readLocks" which do not get released, blocking any * potential subsequent writes. */ public final class SharedLockingInputStream extends InputStream { protected final BinaryKey key; protected final File file; protected final NamedLocks lockManager; protected InputStream stream; protected Lock processLock; protected FileLocks.WrappedLock fileLock; protected boolean eofReached; /** * Create a self-closing, (shared) locking {@link InputStream} to read the content of the supplied {@link File file}. * * @param key the binary key; may not be null * @param file the file that is to be read; may not be null * @param lockManager the manager of the locks, from which a read lock is to be obtained; may be null if no read lock is * needed */ public SharedLockingInputStream( BinaryKey key, File file, NamedLocks lockManager ) { assert key != null; assert file != null; this.key = key; this.file = file; this.lockManager = lockManager; } protected void open() throws IOException { doOperation(new Callable<Object>() { @Override public Object call() throws Exception { if (SharedLockingInputStream.this.stream == null) { // At this point, we know the lock exists and we just need to wait until the write (if there is one) is done. // We do that by getting a read lock for the SHA-1 (to prevent other threads from modifying the file) ... if (lockManager != null) { processLock = lockManager.readLock(key.toString()); } // Also get a shared file lock to prevent other processes from modifying the file ... SharedLockingInputStream.this.fileLock = FileLocks.get().readLock(file); // Now create a buffered stream ... SharedLockingInputStream.this.stream = new BufferedInputStream( new FileInputStream(file), AbstractBinaryStore.bestBufferSize(file.length())); SharedLockingInputStream.this.eofReached = false; } return null; } }); } @Override public int available() throws IOException { return doOperation(new Callable<Integer>() { @Override public Integer call() throws Exception { if (eofReached) { return 0; } open(); return stream.available(); } }); } @Override public void close() throws IOException { if (this.stream != null) { try { // this will release the lock automatically ... stream.close(); } finally { stream = null; if (fileLock != null) { try { fileLock.unlock(); } finally { fileLock = null; if (processLock != null) { try { processLock.unlock(); } finally { processLock = null; } } } } } } } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof File) return file.equals(obj); if (obj instanceof BinaryKey) return key.equals(obj); return false; } @Override public int hashCode() { return key.hashCode(); } @Override public void mark( final int readlimit ) { try { doOperation(new Callable<Void>() { @Override public Void call() throws Exception { open(); if (stream.markSupported()) { stream.mark(readlimit); } return null; } }); } catch (IOException e) { throw new RuntimeException(e); } } @Override public boolean markSupported() { try { return doOperation(new Callable<Boolean>() { @Override public Boolean call() throws Exception { open(); return stream.markSupported(); } }); } catch (IOException e) { throw new RuntimeException(e); } } @Override public int read( final byte[] b, final int off, final int len ) throws IOException { return doOperation(new Callable<Integer>() { @Override public Integer call() throws Exception { if (eofReached) { return -1; } open(); int result = stream.read(b, off, len); if (result == -1) { eofReached = true; close(); } return result; } }); } @Override public int read( final byte[] b ) throws IOException { return doOperation(new Callable<Integer>() { @Override public Integer call() throws Exception { if (eofReached) { return -1; } open(); int result = stream.read(b); if (result == -1) { eofReached = true; close(); } return result; } }); } @Override public int read() throws IOException { return doOperation(new Callable<Integer>() { @Override public Integer call() throws Exception { if (eofReached) { return -1; } open(); int result = stream.read(); if (result == -1) { eofReached = true; close(); // without this, there might be locks } return result; } }); } @Override public void reset() throws IOException { doOperation(new Callable<Void>() { @Override public Void call() throws Exception { open(); if (stream.markSupported()) { stream.reset(); } return null; } }); } @Override public long skip( final long n ) throws IOException { return doOperation(new Callable<Long>() { @Override public Long call() throws Exception { open(); return stream.skip(n); } }); } @Override public String toString() { return key.toString(); } private <T> T doOperation( Callable<T> streamOperation ) throws IOException { try { return streamOperation.call(); } catch (Throwable t) { try { close(); } catch (IOException e) { // ignore } if (t instanceof IOException) { throw (IOException)t; } throw new RuntimeException(t); } } }