/* * Copyright (c) 2002-2009 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.nioneo.store; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.neo4j.kernel.impl.core.ReadOnlyDbException; /** * An abstract representation of a store. A store is a file that contains * records. Each record has a fixed size (<CODE>getRecordSize()</CODE>) so * the position for a record can be calculated by * <CODE>id * getRecordSize()</CODE>. * <p> * A store has an {@link IdGenerator} managing the records that are free or in * use. */ public abstract class AbstractStore extends CommonAbstractStore { /** * Returnes the fixed size of each record in this store. * * @return The record size */ public abstract int getRecordSize(); /** * Creates a new empty store. The factory method returning an implementation * of some store type should make use of this method to initialize an empty * store. * <p> * This method will create a empty store containing the descriptor returned * by the <CODE>getTypeAndVersionDescriptor()</CODE>. The id generator * used by this store will also be created * * @param fileName * The file name of the store that will be created * @param typeAndVersionDescriptor * The type and version descriptor that identifies this store * @throws IOException * If fileName is null or if file exists */ protected static void createEmptyStore( String fileName, String typeAndVersionDescriptor ) { // sanity checks if ( fileName == null ) { throw new IllegalArgumentException( "Null filename" ); } File file = new File( fileName ); if ( file.exists() ) { throw new IllegalStateException( "Can't create store[" + fileName + "], file already exists" ); } // write the header try { FileChannel channel = new FileOutputStream( fileName ).getChannel(); int endHeaderSize = typeAndVersionDescriptor.getBytes().length; ByteBuffer buffer = ByteBuffer.allocate( endHeaderSize ); buffer.put( typeAndVersionDescriptor.getBytes() ).flip(); channel.write( buffer ); channel.force( false ); channel.close(); } catch ( IOException e ) { throw new UnderlyingStorageException( "Unable to create store " + fileName, e ); } IdGeneratorImpl.createGenerator( fileName + ".id" ); } public AbstractStore( String fileName, Map<?,?> config ) { super( fileName, config ); } public AbstractStore( String fileName ) { super( fileName ); } protected void loadStorage() { try { long fileSize = getFileChannel().size(); String expectedVersion = getTypeAndVersionDescriptor(); byte version[] = new byte[expectedVersion.getBytes().length]; ByteBuffer buffer = ByteBuffer.wrap( version ); if ( fileSize >= version.length ) { getFileChannel().position( fileSize - version.length ); } else if ( !isReadOnly() ) { setStoreNotOk(); } getFileChannel().read( buffer ); if ( !expectedVersion.equals( new String( version ) ) && !isReadOnly() ) { if ( !versionFound( new String( version ) ) ) { setStoreNotOk(); } } if ( getRecordSize() != 0 && (fileSize - version.length) % getRecordSize() != 0 && !isReadOnly() ) { setStoreNotOk(); } if ( getStoreOk() && !isReadOnly() ) { getFileChannel().truncate( fileSize - version.length ); } } catch ( IOException e ) { throw new UnderlyingStorageException( "Unable to load store " + getStorageFileName(), e ); } try { if ( !isReadOnly() || isBackupSlave() ) { openIdGenerator(); } else { openReadOnlyIdGenerator( getRecordSize() ); } } catch ( InvalidIdGeneratorException e ) { setStoreNotOk(); } setWindowPool( new PersistenceWindowPool( getStorageFileName(), getRecordSize(), getFileChannel(), getMappedMem(), getIfMemoryMapped(), isReadOnly() && !isBackupSlave() ) ); } /** * Returns the highest id in use by this store. * * @return The highest id in use */ public long getHighId() { return super.getHighId(); } /** * Sets the high id of {@link IdGenerator}. * * @param id * The high id */ public void setHighId( int id ) { super.setHighId( id ); } protected void updateHighId() { try { long highId = getFileChannel().size() / getRecordSize(); setHighId( highId ); } catch ( IOException e ) { throw new UnderlyingStorageException( e ); } } private int findHighIdBackwards() throws IOException { FileChannel fileChannel = getFileChannel(); int recordSize = getRecordSize(); long fileSize = fileChannel.size(); long highId = fileSize / recordSize; ByteBuffer byteBuffer = ByteBuffer.allocate( 1 ); for ( long i = highId; i > 0; i-- ) { fileChannel.position( i * recordSize ); if ( fileChannel.read( byteBuffer ) > 0 ) { byteBuffer.flip(); byte inUse = byteBuffer.get(); byteBuffer.clear(); if ( inUse != 0 ) { return (int) i; } } } return 0; } /** * Rebuilds the {@link IdGenerator} by looping through all records and * checking if record in use or not. * * @throws IOException * if unable to rebuild the id generator */ protected void rebuildIdGenerator() { if ( isReadOnly() && !isBackupSlave() ) { throw new ReadOnlyDbException(); } // TODO: fix this hardcoding final byte RECORD_NOT_IN_USE = 0; logger.fine( "Rebuilding id generator for[" + getStorageFileName() + "] ..." ); closeIdGenerator(); File file = new File( getStorageFileName() + ".id" ); if ( file.exists() ) { boolean success = file.delete(); assert success; } IdGeneratorImpl.createGenerator( getStorageFileName() + ".id" ); openIdGenerator(); FileChannel fileChannel = getFileChannel(); long highId = 1; long defraggedCount = 0; try { long fileSize = fileChannel.size(); int recordSize = getRecordSize(); boolean fullRebuild = true; if ( getConfig() != null ) { String mode = (String) getConfig().get( "rebuild_idgenerators_fast" ); if ( mode != null && mode.toLowerCase().equals( "true" ) ) { fullRebuild = false; highId = findHighIdBackwards(); } } ByteBuffer byteBuffer = ByteBuffer.wrap( new byte[1] ); LinkedList<Integer> freeIdList = new LinkedList<Integer>(); if ( fullRebuild ) { for ( long i = 0; i * recordSize < fileSize && recordSize > 0; i++ ) { fileChannel.position( i * recordSize ); fileChannel.read( byteBuffer ); byteBuffer.flip(); byte inUse = byteBuffer.get(); byteBuffer.flip(); nextId(); if ( inUse == RECORD_NOT_IN_USE ) { freeIdList.add( (int) i ); } else { highId = i; while ( !freeIdList.isEmpty() ) { freeId( freeIdList.removeFirst() ); defraggedCount++; } } } } } catch ( IOException e ) { throw new UnderlyingStorageException( "Unable to rebuild id generator " + getStorageFileName(), e ); } setHighId( highId + 1 ); logger.fine( "[" + getStorageFileName() + "] high id=" + getHighId() + " (defragged=" + defraggedCount + ")" ); closeIdGenerator(); openIdGenerator(); } public abstract List<WindowPoolStats> getAllWindowPoolStats(); }