/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jan 31, 2007 */ package com.bigdata.rawstore; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.UUID; import com.bigdata.counters.CounterSet; import com.bigdata.journal.TemporaryRawStore; import com.bigdata.mdi.IResourceMetadata; /** * A simple persistent unbuffered implementation backed by a file. * * @see {@link TemporaryRawStore}, which provides a solution for temporary data * that begins with the benefits of a memory-resident buffer and then * converts to a disk-based store on overflow. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class SimpleFileRawStore extends AbstractRawWormStore { private boolean open = true; private final UUID uuid = UUID.randomUUID(); public final File file; protected final RandomAccessFile raf; // /** // * This provides a purely transient means to identify deleted records. This // * data does NOT survive restart of the store. // */ // private final Set<Long> deleted = new HashSet<Long>(); /** * Open a store. The file will be created if it does not exist and it is * opened for writing. If the file is opened for writing, then an exception * will be thrown unless an exclusive lock can be obtained on the file. * * @param file * The name of the file to use as the backing store. * @param mode * The file open mode for * {@link RandomAccessFile#RandomAccessFile(File, String)()}. */ public SimpleFileRawStore(final File file, final String mode) throws IOException { super(WormAddressManager.SCALE_UP_OFFSET_BITS); if (file == null) throw new IllegalArgumentException("file is null"); this.file = file; raf = new RandomAccessFile(file,mode); if( mode.indexOf("w") != -1 ) { if (raf.getChannel().tryLock() == null) { throw new IOException("Could not lock file: " + file.getAbsoluteFile()); } } } public boolean isOpen() { return open; } public boolean isReadOnly() { if (!open) throw new IllegalArgumentException(); return false; } public boolean isStable() { return true; } public boolean isFullyBuffered() { return false; } public File getFile() { return file; } public UUID getUUID() { return uuid; } public IResourceMetadata getResourceMetadata() { return new ResourceMetadata(uuid, file); } /** * Static class since must be {@link Serializable}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ private static final class ResourceMetadata implements IResourceMetadata { /** * */ private static final long serialVersionUID = -419665851049132640L; private final UUID uuid; private final String fileStr; // private final long length; public ResourceMetadata(final UUID uuid, final File file) { this.uuid = uuid; this.fileStr = file.toString(); // this.length = file.length(); } public boolean equals(IResourceMetadata o) { return this == o; } public long getCreateTime() { // does not support commit return 0L; } public long getCommitTime() { // does not support commit return 0L; } public String getFile() { return fileStr; } public UUID getUUID() { return uuid; } public boolean isIndexSegment() { // not index segment. return false; } public boolean isJournal() { // not journal. return false; } // public long size() { // // return length; // // } } /** * This also releases the lock if any obtained by the constructor. */ public void close() { if (!open) throw new IllegalStateException(); open = false; try { raf.close(); } catch(IOException ex) { throw new RuntimeException(ex); } } @Override public void deleteResources() { if (open) throw new IllegalStateException(); // @see BLZG-1501 (remove LRUNexus) // if (LRUNexus.INSTANCE != null) { // // LRUNexus.INSTANCE.deleteCache(getUUID()); // // } if(!file.delete()) { throw new RuntimeException("Could not delete: " + file.getAbsolutePath()); } } public void destroy() { if (open) close(); deleteResources(); } public ByteBuffer read(long addr) { if (addr == 0L) throw new IllegalArgumentException("Address is 0L"); final long offset = getOffset(addr); final int nbytes = getByteCount(addr); if (nbytes == 0) { throw new IllegalArgumentException( "Address encodes record length of zero"); } // if (deleted.contains(addr)) { // // throw new IllegalArgumentException( // "Address was deleted in this session"); // // } try { if (offset + nbytes > raf.length()) { throw new IllegalArgumentException("Address never written."); } // allocate a new buffer of the exact capacity. ByteBuffer dst = ByteBuffer.allocate(nbytes); // copy the data into the buffer. raf.getChannel().read(dst, offset); // flip for reading. dst.flip(); // return the buffer. return dst; } catch (IOException ex) { throw new RuntimeException(ex); } } public long write(ByteBuffer data) { if (data == null) throw new IllegalArgumentException("Buffer is null"); // #of bytes to store. final int nbytes = data.remaining(); if (nbytes == 0) throw new IllegalArgumentException("No bytes remaining in buffer"); try { // the new record will be appended to the end of the file. long pos = raf.length(); if (pos + nbytes > Integer.MAX_VALUE) { throw new IOException("Would exceed int32 bytes in file."); } // the offset into the file at which the record will be written. final long offset = pos; // // extend the file to have sufficient space for this record. // raf.setLength(pos + nbytes); // write the data onto the end of the file. raf.getChannel().write(data, pos); // formulate the address that can be used to recover that record. return toAddr(nbytes, offset); } catch (IOException ex) { throw new RuntimeException(ex); } } // /** // * Note: the delete implementation checks its arguments and makes a // * <em>transient</em> note that the record has been deleted but that // * information does NOT survive restart of the store. // */ // public void delete(long addr) { // // if(addr==0L) throw new IllegalArgumentException("Address is 0L"); // // final int offset = Addr.getOffset(addr); // // final int nbytes = Addr.getByteCount(addr); // // if(nbytes==0) { // // throw new IllegalArgumentException( // "Address encodes record length of zero"); // // } // // try { // // if (offset + nbytes > raf.length()) { // // throw new IllegalArgumentException("Address never written."); // // } // // } catch (IOException ex) { // // throw new RuntimeException(ex); // // } // // Long l = Long.valueOf(addr); // // if(deleted.contains(l)) { // // throw new IllegalArgumentException("Address was deleted in this session"); // // } // // deleted.add(l); // // } public void force(boolean metadata) { try { raf.getChannel().force(metadata); } catch( IOException ex) { throw new RuntimeException(ex); } } public long size() { try { return raf.length(); } catch(IOException ex) { throw new RuntimeException(ex); } } synchronized public CounterSet getCounters() { if(root==null) { root = new CounterSet(); } return root; } private CounterSet root; }