/*
* 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.nio.ByteBuffer;
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 node store.
*/
public class PropertyIndexStore extends AbstractStore implements Store
{
// store version, should end with this string (byte encoded)
private static final String VERSION = "PropertyIndex v0.9.5";
private static final int KEY_STORE_BLOCK_SIZE = 30;
// in_use(byte)+prop_count(int)+key_block_id(int)
private static final int RECORD_SIZE = 9;
private DynamicStringStore keyPropertyStore;
public PropertyIndexStore( String fileName, Map<?,?> config )
{
super( fileName, config );
}
public PropertyIndexStore( String fileName )
{
super( fileName );
}
protected void initStorage()
{
keyPropertyStore = new DynamicStringStore( getStorageFileName()
+ ".keys", getConfig() );
}
public String getTypeAndVersionDescriptor()
{
return VERSION;
}
public int getRecordSize()
{
return RECORD_SIZE;
}
@Override
protected void setRecovered()
{
super.setRecovered();
keyPropertyStore.setRecovered();
}
@Override
protected void unsetRecovered()
{
super.unsetRecovered();
keyPropertyStore.unsetRecovered();
}
@Override
public void makeStoreOk()
{
keyPropertyStore.makeStoreOk();
super.makeStoreOk();
}
@Override
public void rebuildIdGenerators()
{
keyPropertyStore.rebuildIdGenerators();
super.rebuildIdGenerators();
}
public void updateIdGenerators()
{
keyPropertyStore.updateHighId();
this.updateHighId();
}
public void freeBlockId( int id )
{
keyPropertyStore.freeBlockId( id );
}
@Override
protected void closeStorage()
{
keyPropertyStore.close();
keyPropertyStore = null;
}
@Override
public void flushAll()
{
keyPropertyStore.flushAll();
super.flushAll();
}
public static void createStore( String fileName )
{
createEmptyStore( fileName, VERSION );
DynamicStringStore.createStore( fileName + ".keys",
KEY_STORE_BLOCK_SIZE );
}
public PropertyIndexData[] getPropertyIndexes( int count )
{
LinkedList<PropertyIndexData> indexList =
new LinkedList<PropertyIndexData>();
long maxIdInUse = getHighestPossibleIdInUse();
int found = 0;
for ( int i = 0; i <= maxIdInUse && found < count; i++ )
{
PropertyIndexRecord record;
try
{
record = getRecord( i );
}
catch ( InvalidRecordException t )
{
continue;
}
found++;
indexList.add( new PropertyIndexData( record.getId(),
getStringFor( record ) ) );
}
return indexList.toArray( new PropertyIndexData[indexList.size()] );
}
public PropertyIndexData getPropertyIndex( int id )
{
PropertyIndexRecord record = getRecord( id );
return new PropertyIndexData( record.getId(),
getStringFor( record ) );
}
public PropertyIndexData getPropertyIndex( int id, boolean recovered )
{
assert recovered;
try
{
setRecovered();
PropertyIndexRecord record = getRecord( id );
return new PropertyIndexData( record.getId(),
getStringFor( record ) );
}
finally
{
unsetRecovered();
}
}
public PropertyIndexRecord getRecord( int id )
{
PropertyIndexRecord record;
PersistenceWindow window = acquireWindow( id, OperationType.READ );
try
{
record = getRecord( id, window );
}
finally
{
releaseWindow( window );
}
Collection<DynamicRecord> keyRecords =
keyPropertyStore.getLightRecords( record.getKeyBlockId() );
for ( DynamicRecord keyRecord : keyRecords )
{
record.addKeyRecord( keyRecord );
}
return record;
}
public PropertyIndexRecord getLightRecord( int id )
{
PersistenceWindow window = acquireWindow( id, OperationType.READ );
try
{
PropertyIndexRecord record = getRecord( id, window );
record.setIsLight( true );
return record;
}
finally
{
releaseWindow( window );
}
}
public void updateRecord( PropertyIndexRecord record, boolean recovered )
{
assert recovered;
setRecovered();
try
{
updateRecord( record );
}
finally
{
unsetRecovered();
}
}
public void updateRecord( PropertyIndexRecord record )
{
PersistenceWindow window = acquireWindow( record.getId(),
OperationType.WRITE );
try
{
updateRecord( record, window );
}
finally
{
releaseWindow( window );
}
if ( !record.isLight() )
{
for ( DynamicRecord keyRecord : record.getKeyRecords() )
{
keyPropertyStore.updateRecord( keyRecord );
}
}
}
public Collection<DynamicRecord> allocateKeyRecords( int keyBlockId,
char[] chars )
{
return keyPropertyStore.allocateRecords( keyBlockId, chars );
}
public int nextKeyBlockId()
{
return keyPropertyStore.nextBlockId();
}
private PropertyIndexRecord getRecord( int id, PersistenceWindow window )
{
Buffer buffer = window.getOffsettedBuffer( id );
boolean inUse = (buffer.get() == Record.IN_USE.byteValue());
if ( !inUse )
{
throw new InvalidRecordException( "Record[" + id + "] not in use" );
}
PropertyIndexRecord record = new PropertyIndexRecord( id );
record.setInUse( inUse );
record.setPropertyCount( buffer.getInt() );
record.setKeyBlockId( buffer.getInt() );
return record;
}
private void updateRecord( PropertyIndexRecord record,
PersistenceWindow window )
{
int id = record.getId();
Buffer buffer = window.getOffsettedBuffer( id );
if ( record.inUse() )
{
buffer.put( Record.IN_USE.byteValue() ).putInt(
record.getPropertyCount() ).putInt( record.getKeyBlockId() );
}
else
{
buffer.put( Record.NOT_IN_USE.byteValue() );
if ( !isInRecoveryMode() )
{
freeId( id );
}
}
}
public void makeHeavy( PropertyIndexRecord record )
{
record.setIsLight( false );
Collection<DynamicRecord> keyRecords = keyPropertyStore.getRecords(
record.getKeyBlockId() );
for ( DynamicRecord keyRecord : keyRecords )
{
record.addKeyRecord( keyRecord );
}
}
public String getStringFor( PropertyIndexRecord propRecord )
{
int recordToFind = propRecord.getKeyBlockId();
Iterator<DynamicRecord> records = propRecord.getKeyRecords().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() )
{
keyPropertyStore.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 = propRecord.getKeyRecords().iterator();
}
}
StringBuffer buf = new StringBuffer();
for ( char[] str : charList )
{
buf.append( str );
}
return buf.toString();
}
public String toString()
{
return "PropertyIndexStore";
}
@Override
protected boolean versionFound( String version )
{
if ( !version.startsWith( "PropertyIndex" ) )
{
// non clean shutdown, need to do recover with right neo
return false;
}
if ( version.equals( "PropertyIndex 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( keyPropertyStore.getWindowPoolStats() );
list.add( getWindowPoolStats() );
return list;
}
}