/**
* 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.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.neo4j.kernel.IdGeneratorFactory;
import org.neo4j.kernel.IdType;
import org.neo4j.kernel.impl.core.LastCommittedTxIdSetter;
import org.neo4j.kernel.impl.storemigration.ConfigMapUpgradeConfiguration;
import org.neo4j.kernel.impl.storemigration.DatabaseFiles;
import org.neo4j.kernel.impl.storemigration.StoreMigrator;
import org.neo4j.kernel.impl.storemigration.StoreUpgrader;
import org.neo4j.kernel.impl.storemigration.UpgradableDatabase;
import org.neo4j.kernel.impl.storemigration.monitoring.VisibleMigrationProgressMonitor;
/**
* This class contains the references to the "NodeStore,RelationshipStore,
* PropertyStore and RelationshipTypeStore". NeoStore doesn't actually "store"
* anything but extends the AbstractStore for the "type and version" validation
* performed in there.
*/
public class NeoStore extends AbstractStore
{
public static final String TYPE_DESCRIPTOR = "NeoStore";
// 4 longs in header (long + in use), time | random | version | txid
private static final int RECORD_SIZE = 9;
private static final int DEFAULT_REL_GRAB_SIZE = 100;
public static final String DEFAULT_NAME = "neostore";
private NodeStore nodeStore;
private PropertyStore propStore;
private RelationshipStore relStore;
private RelationshipTypeStore relTypeStore;
private final LastCommittedTxIdSetter lastCommittedTxIdSetter;
private final IdGeneratorFactory idGeneratorFactory;
private boolean isStarted;
private long lastCommittedTx = -1;
private final int REL_GRAB_SIZE;
public NeoStore( Map<?,?> config )
{
super( (String) config.get( "neo_store" ), config, IdType.NEOSTORE_BLOCK );
int relGrabSize = DEFAULT_REL_GRAB_SIZE;
if ( getConfig() != null )
{
String grabSize = (String) getConfig().get( "relationship_grab_size" );
if ( grabSize != null )
{
relGrabSize = Integer.parseInt( grabSize );
}
}
REL_GRAB_SIZE = relGrabSize;
lastCommittedTxIdSetter = (LastCommittedTxIdSetter)
config.get( LastCommittedTxIdSetter.class );
idGeneratorFactory = (IdGeneratorFactory) config.get( IdGeneratorFactory.class );
}
@Override
protected void checkVersion()
{
try
{
verifyCorrectTypeDescriptorAndVersion();
}
catch ( NotCurrentStoreVersionException e )
{
releaseFileLockAndCloseFileChannel();
tryToUpgradeStores();
checkStorage();
}
catch ( IOException e )
{
throw new UnderlyingStorageException( "Unable to check version "
+ getStorageFileName(), e );
}
}
@Override
protected void initStorage()
{
instantiateChildStores();
}
/**
* Initializes the node,relationship,property and relationship type stores.
*/
private void instantiateChildStores()
{
relTypeStore = new RelationshipTypeStore( getStorageFileName()
+ ".relationshiptypestore.db", getConfig(), IdType.RELATIONSHIP_TYPE );
propStore = new PropertyStore( getStorageFileName()
+ ".propertystore.db", getConfig() );
relStore = new RelationshipStore( getStorageFileName()
+ ".relationshipstore.db", getConfig() );
nodeStore = new NodeStore( getStorageFileName() + ".nodestore.db",
getConfig() );
}
private void tryToUpgradeStores()
{
new StoreUpgrader( getConfig(), new ConfigMapUpgradeConfiguration(getConfig()),
new UpgradableDatabase(), new StoreMigrator( new VisibleMigrationProgressMonitor( System.out ) ),
new DatabaseFiles() ).attemptUpgrade( getStorageFileName() );
}
/**
* Closes the node,relationship,property and relationship type stores.
*/
@Override
protected void closeStorage()
{
if ( lastCommittedTxIdSetter != null ) lastCommittedTxIdSetter.close();
if ( relTypeStore != null )
{
relTypeStore.close();
relTypeStore = null;
}
if ( propStore != null )
{
propStore.close();
propStore = null;
}
if ( relStore != null )
{
relStore.close();
relStore = null;
}
if ( nodeStore != null )
{
nodeStore.close();
nodeStore = null;
}
}
@Override
public void flushAll()
{
if ( relTypeStore == null || propStore == null || relStore == null ||
nodeStore == null )
{
return;
}
relTypeStore.flushAll();
propStore.flushAll();
relStore.flushAll();
nodeStore.flushAll();
}
@Override
public String getTypeDescriptor()
{
return TYPE_DESCRIPTOR;
}
public IdGeneratorFactory getIdGeneratorFactory()
{
return idGeneratorFactory;
}
@Override
public int getRecordSize()
{
return RECORD_SIZE;
}
/**
* Creates the neo,node,relationship,property and relationship type stores.
*
* @param fileName
* The name of store
* @param config
* Map of configuration parameters
*/
public static void createStore( String fileName, Map<?,?> config )
{
IdGeneratorFactory idGeneratorFactory = (IdGeneratorFactory) config.get(
IdGeneratorFactory.class );
StoreId storeId = (StoreId) config.get( StoreId.class );
if ( storeId == null ) storeId = new StoreId();
createEmptyStore( fileName, buildTypeDescriptorAndVersion( TYPE_DESCRIPTOR ), idGeneratorFactory );
NodeStore.createStore( fileName + ".nodestore.db", config );
RelationshipStore.createStore( fileName + ".relationshipstore.db", idGeneratorFactory );
PropertyStore.createStore( fileName + ".propertystore.db", config );
RelationshipTypeStore.createStore( fileName
+ ".relationshiptypestore.db", config );
if ( !config.containsKey( "neo_store" ) )
{
// TODO Ugly
Map<Object, Object> newConfig = new HashMap<Object, Object>( config );
newConfig.put( "neo_store", fileName );
config = newConfig;
}
NeoStore neoStore = new NeoStore( config );
// created time | random long | backup version | tx id
neoStore.nextId(); neoStore.nextId(); neoStore.nextId(); neoStore.nextId();
neoStore.setCreationTime( storeId.getCreationTime() );
neoStore.setRandomNumber( storeId.getRandomId() );
neoStore.setVersion( 0 );
neoStore.setLastCommittedTx( 1 );
neoStore.close();
}
/**
* Sets the version for the given neostore file in {@code storeDir}.
* @param storeDir the store dir to locate the neostore file in.
* @param version the version to set.
* @return the previous version before writing.
*/
public static long setVersion( String storeDir, long version )
{
RandomAccessFile file = null;
try
{
file = new RandomAccessFile( new File( storeDir, NeoStore.DEFAULT_NAME ), "rw" );
FileChannel channel = file.getChannel();
channel.position( RECORD_SIZE*2+1/*inUse*/ );
ByteBuffer buffer = ByteBuffer.allocate( 8 );
channel.read( buffer );
buffer.flip();
long previous = buffer.getLong();
channel.position( RECORD_SIZE*2+1/*inUse*/ );
buffer.clear();
buffer.putLong( version ).flip();
channel.write( buffer );
return previous;
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
finally
{
try
{
if ( file != null ) file.close();
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
}
public StoreId getStoreId()
{
return new StoreId( getCreationTime(), getRandomNumber() );
}
public long getCreationTime()
{
return getRecord( 0 );
}
public void setCreationTime( long time )
{
setRecord( 0, time );
}
public long getRandomNumber()
{
return getRecord( 1 );
}
public void setRandomNumber( long nr )
{
setRecord( 1, nr );
}
public void setRecoveredStatus( boolean status )
{
if ( status )
{
setRecovered();
}
else
{
unsetRecovered();
}
}
public long getVersion()
{
return getRecord( 2 );
}
public void setVersion( long version )
{
setRecord( 2, version );
}
public synchronized void setLastCommittedTx( long txId )
{
long current = getRecord( 3 );
if ( (current + 1) != txId && !isInRecoveryMode() )
{
throw new InvalidRecordException( "Could not set tx commit id[" +
txId + "] since the current one is[" + current + "]" );
}
setRecord( 3, txId );
// TODO Why check null here? because I have no time to fix the tests
// And the update to zookeeper or whatever should probably be moved from
// here and be async since if it fails tx will get exception in committing
// state and shutdown... that is wrong since the tx did not fail
// - zookeeper is only used for master election, tx state there is not critical
if ( isStarted && lastCommittedTxIdSetter != null && txId != lastCommittedTx )
{
try
{
lastCommittedTxIdSetter.setLastCommittedTxId( txId );
}
catch ( RuntimeException e )
{
logger.log( Level.WARNING, "Could not set last committed tx id", e );
}
}
lastCommittedTx = txId;
}
public synchronized long getLastCommittedTx()
{
if ( lastCommittedTx == -1 )
{
lastCommittedTx = getRecord( 3 );
}
return lastCommittedTx;
}
public long incrementVersion()
{
long current = getVersion();
setVersion( current + 1 );
return current;
}
private long getRecord( long id )
{
PersistenceWindow window = acquireWindow( id, OperationType.READ );
try
{
Buffer buffer = window.getOffsettedBuffer( id );
buffer.get();
return buffer.getLong();
}
finally
{
releaseWindow( window );
}
}
private void setRecord( long id, long value )
{
PersistenceWindow window = acquireWindow( id, OperationType.WRITE );
try
{
Buffer buffer = window.getOffsettedBuffer( id );
buffer.put( Record.IN_USE.byteValue() ).putLong( value );
}
finally
{
releaseWindow( window );
}
}
/**
* Returns the node store.
*
* @return The node store
*/
public NodeStore getNodeStore()
{
return nodeStore;
}
/**
* The relationship store.
*
* @return The relationship store
*/
public RelationshipStore getRelationshipStore()
{
return relStore;
}
/**
* Returns the relationship type store.
*
* @return The relationship type store
*/
public RelationshipTypeStore getRelationshipTypeStore()
{
return relTypeStore;
}
/**
* Returns the property store.
*
* @return The property store
*/
public PropertyStore getPropertyStore()
{
return propStore;
}
@Override
public void makeStoreOk()
{
relTypeStore.makeStoreOk();
propStore.makeStoreOk();
relStore.makeStoreOk();
nodeStore.makeStoreOk();
super.makeStoreOk();
isStarted = true;
}
@Override
public void rebuildIdGenerators()
{
relTypeStore.rebuildIdGenerators();
propStore.rebuildIdGenerators();
relStore.rebuildIdGenerators();
nodeStore.rebuildIdGenerators();
super.rebuildIdGenerators();
}
public void updateIdGenerators()
{
this.updateHighId();
relTypeStore.updateIdGenerators();
propStore.updateIdGenerators();
relStore.updateHighId();
nodeStore.updateHighId();
}
public int getRelationshipGrabSize()
{
return REL_GRAB_SIZE;
}
@Override
public List<WindowPoolStats> getAllWindowPoolStats()
{
List<WindowPoolStats> list = new ArrayList<WindowPoolStats>();
list.addAll( nodeStore.getAllWindowPoolStats() );
list.addAll( propStore.getAllWindowPoolStats() );
list.addAll( relStore.getAllWindowPoolStats() );
list.addAll( relTypeStore.getAllWindowPoolStats() );
return list;
}
public boolean isStoreOk()
{
return getStoreOk() && relTypeStore.getStoreOk() &&
propStore.getStoreOk() && relStore.getStoreOk() && nodeStore.getStoreOk();
}
}