All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.value.binary.SharedLockingInputStream Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * 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() {
            @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() {
            @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() {
                @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() {
                @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() {
            @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() {
            @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() {
            @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() {
            @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() {
            @Override
            public Long call() throws Exception {
                open();
                return stream.skip(n);
            }
        });
    }

    @Override
    public String toString() {
        return key.toString();
    }

    private  T doOperation( Callable 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);
        }
    }

}