/* * 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.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Implementation of the relationship type store. Uses a dynamic store to store * relationship type names. */ public class RelationshipTypeStore extends AbstractStore implements Store { // store version, each store ends with this string (byte encoded) private static final String VERSION = "RelationshipTypeStore v0.9.5"; // record header size // in_use(byte)+type_blockId(int) private static final int RECORD_SIZE = 5; private static final int TYPE_STORE_BLOCK_SIZE = 30; private DynamicStringStore typeNameStore; /** * See {@link AbstractStore#AbstractStore(String, Map)} */ public RelationshipTypeStore( String fileName, Map<?,?> config ) { super( fileName, config ); } /** * See {@link AbstractStore#AbstractStore(String)} */ public RelationshipTypeStore( String fileName ) { super( fileName ); } @Override protected void setRecovered() { super.setRecovered(); typeNameStore.setRecovered(); } @Override protected void unsetRecovered() { super.unsetRecovered(); typeNameStore.unsetRecovered(); } @Override protected void initStorage() { typeNameStore = new DynamicStringStore( getStorageFileName() + ".names", getConfig() ); } @Override protected void closeStorage() { typeNameStore.close(); typeNameStore = null; } public void flushAll() { typeNameStore.flushAll(); super.flushAll(); } public String getTypeAndVersionDescriptor() { return VERSION; } public int getRecordSize() { return RECORD_SIZE; } /** * Creates a new relationship type store contained in <CODE>fileName</CODE> * If filename is <CODE>null</CODE> or the file already exists an * <CODE>IOException</CODE> is thrown. * * @param fileName * File name of the new relationship type store * @throws IOException * If unable to create store or name null */ public static void createStore( String fileName ) { createEmptyStore( fileName, VERSION ); DynamicStringStore.createStore( fileName + ".names", TYPE_STORE_BLOCK_SIZE ); RelationshipTypeStore store = new RelationshipTypeStore( fileName ); store.close(); } void markAsReserved( int id ) { PersistenceWindow window = acquireWindow( id, OperationType.WRITE ); try { markAsReserved( id, window ); } finally { releaseWindow( window ); } } public Collection<DynamicRecord> allocateTypeNameRecords( int startBlock, char src[] ) { return typeNameStore.allocateRecords( startBlock, src ); } public void updateRecord( RelationshipTypeRecord record, boolean recovered ) { assert recovered; setRecovered(); try { updateRecord( record ); } finally { unsetRecovered(); } } public void updateRecord( RelationshipTypeRecord record ) { PersistenceWindow window = acquireWindow( record.getId(), OperationType.WRITE ); try { updateRecord( record, window ); } finally { releaseWindow( window ); } for ( DynamicRecord typeRecord : record.getTypeRecords() ) { typeNameStore.updateRecord( typeRecord ); } } public RelationshipTypeRecord getRecord( int id ) { RelationshipTypeRecord record; PersistenceWindow window = acquireWindow( id, OperationType.READ ); try { record = getRecord( id, window ); } finally { releaseWindow( window ); } // } if ( record != null ) { Collection<DynamicRecord> nameRecords = typeNameStore.getRecords( record.getTypeBlock() ); for ( DynamicRecord nameRecord : nameRecords ) { record.addTypeRecord( nameRecord ); } } return record; } public RelationshipTypeData getRelationshipType( int id, boolean recovered ) { assert recovered; try { setRecovered(); RelationshipTypeRecord record = getRecord( id ); String name = getStringFor( record ); return new RelationshipTypeData( id, name ); } finally { unsetRecovered(); } } public RelationshipTypeData getRelationshipType( int id ) { RelationshipTypeRecord record = getRecord( id ); String name = getStringFor( record ); return new RelationshipTypeData( id, name ); } public RelationshipTypeData[] getRelationshipTypes() { LinkedList<RelationshipTypeData> typeDataList = new LinkedList<RelationshipTypeData>(); for ( int i = 0;; i++ ) { RelationshipTypeRecord record; try { record = getRecord( i ); } catch ( InvalidRecordException e ) { break; } if ( record != null && record.getTypeBlock() != Record.RESERVED.intValue() ) { String name = getStringFor( record ); typeDataList.add( new RelationshipTypeData( i, name ) ); } } return typeDataList.toArray( new RelationshipTypeData[typeDataList.size()] ); } public int nextBlockId() { return typeNameStore.nextBlockId(); } public void freeBlockId( int id ) { typeNameStore.freeBlockId( id ); } private void markAsReserved( int id, PersistenceWindow window ) { Buffer buffer = window.getOffsettedBuffer( id ); buffer.put( Record.IN_USE.byteValue() ).putInt( Record.RESERVED.intValue() ); } private RelationshipTypeRecord getRecord( int id, PersistenceWindow window ) { Buffer buffer = window.getOffsettedBuffer( id ); byte inUse = buffer.get(); if ( inUse == Record.NOT_IN_USE.byteValue() ) { return null; } if ( inUse != Record.IN_USE.byteValue() ) { throw new InvalidRecordException( "Record[" + id + "] unknown in use flag[" + inUse + "]" ); } RelationshipTypeRecord record = new RelationshipTypeRecord( id ); record.setInUse( true ); record.setTypeBlock( buffer.getInt() ); return record; } private void updateRecord( RelationshipTypeRecord record, PersistenceWindow window ) { int id = record.getId(); Buffer buffer = window.getOffsettedBuffer( id ); if ( record.inUse() ) { buffer.put( Record.IN_USE.byteValue() ).putInt( record.getTypeBlock() ); } else { buffer.put( Record.NOT_IN_USE.byteValue() ).putInt( 0 ); } } @Override protected void rebuildIdGenerator() { 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; int recordSize = getRecordSize(); try { long fileSize = fileChannel.size(); ByteBuffer byteBuffer = ByteBuffer.wrap( new byte[recordSize] ); for ( int i = 0; i * recordSize < fileSize; i++ ) { fileChannel.read( byteBuffer, i * recordSize ); byteBuffer.flip(); byte inUse = byteBuffer.get(); byteBuffer.flip(); if ( inUse != Record.IN_USE.byteValue() ) { // hole found, marking as reserved byteBuffer.clear(); byteBuffer.put( Record.IN_USE.byteValue() ).putInt( Record.RESERVED.intValue() ); byteBuffer.flip(); fileChannel.write( byteBuffer, i * recordSize ); byteBuffer.clear(); } else { highId = i; } nextId(); } highId++; fileChannel.truncate( highId * recordSize ); } catch ( IOException e ) { throw new UnderlyingStorageException( "Unable to rebuild id generator " + getStorageFileName(), e ); } setHighId( highId ); logger.fine( "[" + getStorageFileName() + "] high id=" + getHighId() ); closeIdGenerator(); openIdGenerator(); } public String getStringFor( RelationshipTypeRecord relTypeRecord ) { int recordToFind = relTypeRecord.getTypeBlock(); Iterator<DynamicRecord> records = relTypeRecord.getTypeRecords().iterator(); List<char[]> charList = new LinkedList<char[]>(); int totalSize = 0; while ( recordToFind != Record.NO_NEXT_BLOCK.intValue() && records.hasNext() ) { DynamicRecord record = records.next(); if ( record.inUse() && record.getId() == recordToFind ) { if ( record.isLight() ) { typeNameStore.makeHeavy( record ); } if ( !record.isCharData() ) { ByteBuffer buf = ByteBuffer.wrap( record.getData() ); char[] chars = new char[record.getData().length / 2]; totalSize += chars.length; buf.asCharBuffer().get( chars ); charList.add( chars ); } else { charList.add( record.getDataAsChar() ); } recordToFind = record.getNextBlock(); // TODO: optimize here, high chance next is right one records = relTypeRecord.getTypeRecords().iterator(); } } StringBuffer buf = new StringBuffer(); for ( char[] str : charList ) { buf.append( str ); } return buf.toString(); } @Override public void makeStoreOk() { typeNameStore.makeStoreOk(); super.makeStoreOk(); } @Override public void rebuildIdGenerators() { typeNameStore.rebuildIdGenerators(); super.rebuildIdGenerators(); } public void updateIdGenerators() { typeNameStore.updateHighId(); this.updateHighId(); } @Override protected boolean versionFound( String version ) { if ( !version.startsWith( "RelationshipTypeStore" ) ) { // non clean shutdown, need to do recover with right neo return false; } if ( version.equals( "RelationshipTypeStore v0.9.3" ) ) { rebuildIdGenerator(); closeIdGenerator(); return true; } throw new IllegalStoreVersionException( "Store version [" + version + "]. Please make sure you are not running old Neo4j kernel " + " towards a store that has been created by newer version " + " of Neo4j." ); } public List<WindowPoolStats> getAllWindowPoolStats() { List<WindowPoolStats> list = new ArrayList<WindowPoolStats>(); list.add( typeNameStore.getWindowPoolStats() ); list.add( getWindowPoolStats() ); return list; } }