/** * Copyright (c) 2002-2013 "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 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 General Public License for more details. * * You should have received a copy of the GNU 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.helpers.UTF8; import org.neo4j.kernel.IdGeneratorFactory; import org.neo4j.kernel.IdType; import org.neo4j.kernel.impl.core.ReadOnlyDbException; import org.neo4j.kernel.impl.util.StringLogger; /** * 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 { /** * Returns the fixed size of each record in this store. * * @return The record size */ public abstract int getRecordSize(); @Override protected long figureOutHighestIdInUse() { try { return getFileChannel().size()/getRecordSize(); } catch ( IOException e ) { throw new RuntimeException( e ); } } /** * 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>getTypeDescriptor()</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, IdGeneratorFactory idGeneratorFactory ) { // 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 = UTF8.encode( typeAndVersionDescriptor ).length; ByteBuffer buffer = ByteBuffer.allocate( endHeaderSize ); buffer.put( UTF8.encode( typeAndVersionDescriptor ) ).flip(); channel.write( buffer ); channel.force( false ); channel.close(); } catch ( IOException e ) { throw new UnderlyingStorageException( "Unable to create store " + fileName, e ); } idGeneratorFactory.create( fileName + ".id" ); } public AbstractStore( String fileName, Map<?,?> config, IdType idType ) { super( fileName, config, idType ); } protected int getEffectiveRecordSize() { return getRecordSize(); } protected void readAndVerifyBlockSize() throws IOException { // record size is fixed for non-dynamic stores, so nothing to do here } protected void verifyFileSizeAndTruncate() throws IOException { int expectedVersionLength = UTF8.encode( buildTypeDescriptorAndVersion( getTypeDescriptor() ) ).length; long fileSize = getFileChannel().size(); if ( getRecordSize() != 0 && (fileSize - expectedVersionLength) % getRecordSize() != 0 && !isReadOnly() ) { setStoreNotOk( new IllegalStateException( "Misaligned file size " + fileSize + " for " + this + ", expected version length:" + expectedVersionLength ) ); } if ( getStoreOk() && !isReadOnly() ) { getFileChannel().truncate( fileSize - expectedVersionLength ); } } /** * Sets the high id of {@link IdGenerator}. * * @param id * The high id */ public void setHighId( int id ) { super.setHighId( id ); } private long findHighIdBackwards() throws IOException { // Duplicated method FileChannel fileChannel = getFileChannel(); int recordSize = getRecordSize(); long fileSize = fileChannel.size(); long highId = fileSize / recordSize; ByteBuffer byteBuffer = ByteBuffer.allocate( getRecordSize() ); for ( long i = highId; i > 0; i-- ) { fileChannel.position( i * recordSize ); if ( fileChannel.read( byteBuffer ) > 0 ) { byteBuffer.flip(); boolean isInUse = isRecordInUse( byteBuffer ); byteBuffer.clear(); if ( isInUse ) { return i; } } } return 0; } protected boolean isRecordInUse(ByteBuffer buffer) { byte inUse = buffer.get(); return ( ( inUse & 0x1 ) == Record.IN_USE.byteValue() ); } /** * 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 */ @Override protected void rebuildIdGenerator() { if ( isReadOnly() && !isBackupSlave() ) { throw new ReadOnlyDbException(); } logger.fine( "Rebuilding id generator for[" + getStorageFileName() + "] ..." ); closeIdGenerator(); File file = new File( getStorageFileName() + ".id" ); if ( file.exists() ) { boolean success = file.delete(); assert success; } createIdGenerator( 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] ); // Duplicated code block LinkedList<Long> freeIdList = new LinkedList<Long>(); 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 & 0x1) == Record.NOT_IN_USE.byteValue() ) { freeIdList.add( 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 ); if ( getConfig() != null ) { String storeDir = (String) getConfig().get( "store_dir" ); StringLogger msgLog = StringLogger.getLogger( storeDir ); msgLog.logMessage( getStorageFileName() + " rebuild id generator, highId=" + getHighId() + " defragged count=" + defraggedCount, true ); } logger.fine( "[" + getStorageFileName() + "] high id=" + getHighId() + " (defragged=" + defraggedCount + ")" ); closeIdGenerator(); openIdGenerator(); } public abstract List<WindowPoolStats> getAllWindowPoolStats(); }