/*
* 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.xa;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Collection;
import java.util.logging.Logger;
import org.neo4j.kernel.impl.nioneo.store.DynamicRecord;
import org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
import org.neo4j.kernel.impl.nioneo.store.NeoStore;
import org.neo4j.kernel.impl.nioneo.store.NodeRecord;
import org.neo4j.kernel.impl.nioneo.store.NodeStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyStore;
import org.neo4j.kernel.impl.nioneo.store.PropertyType;
import org.neo4j.kernel.impl.nioneo.store.Record;
import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeStore;
import org.neo4j.kernel.impl.transaction.xaframework.LogBuffer;
import org.neo4j.kernel.impl.transaction.xaframework.XaCommand;
/**
* Command implementations for all the commands that can be performed on a Neo
* store.
*/
abstract class Command extends XaCommand
{
static Logger logger = Logger.getLogger( Command.class.getName() );
private final int key;
Command( int key )
{
this.key = key;
}
@Override
protected void setRecovered()
{
super.setRecovered();
}
int getKey()
{
return key;
}
public int hashCode()
{
return key;
}
static void writeDynamicRecord( LogBuffer buffer, DynamicRecord record )
throws IOException
{
// id+type+in_use(byte)+prev_block(int)+nr_of_bytes(int)+next_block(int)
if ( record.inUse() )
{
byte inUse = Record.IN_USE.byteValue();
buffer.putInt( record.getId() ).putInt( record.getType() ).put(
inUse ).putInt( record.getPrevBlock() ).putInt(
record.getLength() ).putInt( record.getNextBlock() );
if ( !record.isLight() )
{
if ( !record.isCharData() )
{
byte[] data = record.getData();
buffer.put( data );
}
else
{
char[] chars = record.getDataAsChar();
buffer.put( chars );
}
}
}
else
{
byte inUse = Record.NOT_IN_USE.byteValue();
buffer.putInt( record.getId() ).putInt( record.getType() ).put(
inUse );
}
}
static DynamicRecord readDynamicRecord( ReadableByteChannel byteChannel,
ByteBuffer buffer ) throws IOException
{
// id+type+in_use(byte)+prev_block(int)+nr_of_bytes(int)+next_block(int)
buffer.clear();
buffer.limit( 9 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int id = buffer.getInt();
int type = buffer.getInt();
byte inUseFlag = buffer.get();
boolean inUse = false;
if ( inUseFlag == Record.IN_USE.byteValue() )
{
inUse = true;
buffer.clear();
buffer.limit( 12 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
}
else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
{
throw new IOException( "Illegal in use flag: " + inUseFlag );
}
DynamicRecord record = new DynamicRecord( id );
record.setInUse( inUse, type );
if ( inUse )
{
record.setPrevBlock( buffer.getInt() );
int nrOfBytes = buffer.getInt();
record.setNextBlock( buffer.getInt() );
buffer.clear();
buffer.limit( nrOfBytes );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
byte data[] = new byte[nrOfBytes];
buffer.get( data );
record.setData( data );
}
return record;
}
// means the first byte of the command record was only written but second
// (saying what type) did not get written but the file still got expanded
private static final byte NONE = (byte) 0;
private static final byte NODE_COMMAND = (byte) 1;
private static final byte PROP_COMMAND = (byte) 2;
private static final byte REL_COMMAND = (byte) 3;
private static final byte REL_TYPE_COMMAND = (byte) 4;
private static final byte PROP_INDEX_COMMAND = (byte) 5;
static class NodeCommand extends Command
{
private final NodeRecord record;
private final NodeStore store;
NodeCommand( NodeStore store, NodeRecord record )
{
super( record.getId() );
this.record = record;
this.store = store;
}
@Override
public void execute()
{
if ( isRecovered() )
{
logger.fine( this.toString() );
store.updateRecord( record, true );
}
else
{
store.updateRecord( record );
}
}
public String toString()
{
return "NodeCommand[" + record + "]";
}
@Override
public void writeToFile( LogBuffer buffer ) throws IOException
{
byte inUse = record.inUse() ? Record.IN_USE.byteValue()
: Record.NOT_IN_USE.byteValue();
buffer.put( NODE_COMMAND );
buffer.putInt( record.getId() );
buffer.put( inUse );
if ( record.inUse() )
{
buffer.putInt( record.getNextRel() ).putInt(
record.getNextProp() );
}
}
static Command readCommand( NeoStore neoStore,
ReadableByteChannel byteChannel, ByteBuffer buffer )
throws IOException
{
buffer.clear();
buffer.limit( 5 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int id = buffer.getInt();
byte inUseFlag = buffer.get();
boolean inUse = false;
if ( inUseFlag == Record.IN_USE.byteValue() )
{
inUse = true;
}
else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
{
throw new IOException( "Illegal in use flag: " + inUseFlag );
}
NodeRecord record = new NodeRecord( id );
record.setInUse( inUse );
if ( inUse )
{
buffer.clear();
buffer.limit( 8 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
record.setNextRel( buffer.getInt() );
record.setNextProp( buffer.getInt() );
}
return new NodeCommand( neoStore.getNodeStore(), record );
}
public boolean equals( Object o )
{
if ( !(o instanceof NodeCommand) )
{
return false;
}
return getKey() == ((Command) o).getKey();
}
}
static class RelationshipCommand extends Command
{
private final RelationshipRecord record;
private final RelationshipStore store;
RelationshipCommand( RelationshipStore store, RelationshipRecord record )
{
super( record.getId() );
this.record = record;
this.store = store;
}
@Override
public void execute()
{
if ( isRecovered() )
{
logger.fine( this.toString() );
store.updateRecord( record, true );
}
else
{
store.updateRecord( record );
}
}
@Override
public String toString()
{
return "RelationshipCommand[" + record + "]";
}
@Override
public void writeToFile( LogBuffer buffer ) throws IOException
{
byte inUse = record.inUse() ? Record.IN_USE.byteValue()
: Record.NOT_IN_USE.byteValue();
buffer.put( REL_COMMAND );
buffer.putInt( record.getId() );
buffer.put( inUse );
if ( record.inUse() )
{
buffer.putInt( record.getFirstNode() ).putInt(
record.getSecondNode() ).putInt( record.getType() ).putInt(
record.getFirstPrevRel() )
.putInt( record.getFirstNextRel() ).putInt(
record.getSecondPrevRel() ).putInt(
record.getSecondNextRel() ).putInt(
record.getNextProp() );
}
}
static Command readCommand( NeoStore neoStore,
ReadableByteChannel byteChannel, ByteBuffer buffer )
throws IOException
{
buffer.clear();
buffer.limit( 5 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int id = buffer.getInt();
byte inUseFlag = buffer.get();
boolean inUse = false;
if ( (inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE
.byteValue() )
{
inUse = true;
}
else if ( (inUseFlag & Record.IN_USE.byteValue()) != Record.NOT_IN_USE
.byteValue() )
{
throw new IOException( "Illegal in use flag: " + inUseFlag );
}
RelationshipRecord record;
if ( inUse )
{
buffer.clear();
buffer.limit( 32 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
record = new RelationshipRecord( id, buffer.getInt(), buffer
.getInt(), buffer.getInt() );
record.setInUse( inUse );
record.setFirstPrevRel( buffer.getInt() );
record.setFirstNextRel( buffer.getInt() );
record.setSecondPrevRel( buffer.getInt() );
record.setSecondNextRel( buffer.getInt() );
record.setNextProp( buffer.getInt() );
}
else
{
record = new RelationshipRecord( id, -1, -1, -1 );
record.setInUse( false );
}
return new RelationshipCommand( neoStore.getRelationshipStore(),
record );
}
@Override
public boolean equals( Object o )
{
if ( !(o instanceof RelationshipCommand) )
{
return false;
}
return getKey() == ((Command) o).getKey();
}
}
static class PropertyIndexCommand extends Command
{
private final PropertyIndexRecord record;
private final PropertyIndexStore store;
PropertyIndexCommand( PropertyIndexStore store,
PropertyIndexRecord record )
{
super( record.getId() );
this.record = record;
this.store = store;
}
@Override
public void execute()
{
if ( isRecovered() )
{
logger.fine( this.toString() );
store.updateRecord( record, true );
}
else
{
store.updateRecord( record );
}
}
@Override
public String toString()
{
return "PropertyIndexCommand[" + record + "]";
}
@Override
public void writeToFile( LogBuffer buffer ) throws IOException
{
// id+in_use(byte)+count(int)+key_blockId(int)+nr_key_records(int)
byte inUse = record.inUse() ? Record.IN_USE.byteValue()
: Record.NOT_IN_USE.byteValue();
buffer.put( PROP_INDEX_COMMAND );
buffer.putInt( record.getId() );
buffer.put( inUse );
buffer.putInt( record.getPropertyCount() ).putInt(
record.getKeyBlockId() );
if ( record.isLight() )
{
buffer.putInt( 0 );
}
else
{
Collection<DynamicRecord> keyRecords = record.getKeyRecords();
buffer.putInt( keyRecords.size() );
for ( DynamicRecord keyRecord : keyRecords )
{
writeDynamicRecord( buffer, keyRecord );
}
}
}
static Command readCommand( NeoStore neoStore, ReadableByteChannel byteChannel,
ByteBuffer buffer ) throws IOException
{
// id+in_use(byte)+count(int)+key_blockId(int)+nr_key_records(int)
buffer.clear();
buffer.limit( 17 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int id = buffer.getInt();
byte inUseFlag = buffer.get();
boolean inUse = false;
if ( (inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE
.byteValue() )
{
inUse = true;
}
else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
{
throw new IOException( "Illegal in use flag: " + inUseFlag );
}
PropertyIndexRecord record = new PropertyIndexRecord( id );
record.setInUse( inUse );
record.setPropertyCount( buffer.getInt() );
record.setKeyBlockId( buffer.getInt() );
int nrKeyRecords = buffer.getInt();
for ( int i = 0; i < nrKeyRecords; i++ )
{
DynamicRecord dr = readDynamicRecord( byteChannel, buffer );
if ( dr == null )
{
return null;
}
record.addKeyRecord( dr );
}
return new PropertyIndexCommand( neoStore.getPropertyStore()
.getIndexStore(), record );
}
@Override
public boolean equals( Object o )
{
if ( !(o instanceof PropertyIndexCommand) )
{
return false;
}
return getKey() == ((Command) o).getKey();
}
}
static class PropertyCommand extends Command
{
private final PropertyRecord record;
private final PropertyStore store;
PropertyCommand( PropertyStore store, PropertyRecord record )
{
super( record.getId() );
this.record = record;
this.store = store;
}
@Override
public void execute()
{
if ( isRecovered() )
{
logger.fine( this.toString() );
store.updateRecord( record, true );
}
else
{
store.updateRecord( record );
}
}
@Override
public String toString()
{
return "PropertyCommand[" + record + "]";
}
@Override
public void writeToFile( LogBuffer buffer ) throws IOException
{
// id+in_use(byte)+type(int)+key_indexId(int)+prop_blockId(long)+
// prev_prop_id(int)+next_prop_id(int)+nr_value_records(int)
byte inUse = record.inUse() ? Record.IN_USE.byteValue()
: Record.NOT_IN_USE.byteValue();
buffer.put( PROP_COMMAND );
buffer.putInt( record.getId() );
buffer.put( inUse );
if ( record.inUse() )
{
buffer.putInt( record.getType().intValue() ).putInt(
record.getKeyIndexId() ).putLong( record.getPropBlock() )
.putInt( record.getPrevProp() ).putInt(
record.getNextProp() );
}
if ( record.isLight() )
{
buffer.putInt( 0 );
}
else
{
Collection<DynamicRecord> valueRecords = record
.getValueRecords();
buffer.putInt( valueRecords.size() );
for ( DynamicRecord valueRecord : valueRecords )
{
writeDynamicRecord( buffer, valueRecord );
}
}
}
static Command readCommand( NeoStore neoStore,
ReadableByteChannel byteChannel, ByteBuffer buffer )
throws IOException
{
// id+in_use(byte)+type(int)+key_indexId(int)+prop_blockId(long)+
// prev_prop_id(int)+next_prop_id(int)+nr_value_records(int)
buffer.clear();
buffer.limit( 5 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int id = buffer.getInt();
byte inUseFlag = buffer.get();
boolean inUse = false;
if ( (inUseFlag & Record.IN_USE.byteValue()) == Record.IN_USE
.byteValue() )
{
inUse = true;
}
else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
{
throw new IOException( "Illegal in use flag: " + inUseFlag );
}
PropertyRecord record = new PropertyRecord( id );
if ( inUse )
{
buffer.clear();
buffer.limit( 24 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
PropertyType type = getType( buffer.getInt() );
if ( type == null )
{
return null;
}
record.setType( type );
record.setInUse( inUse );
record.setKeyIndexId( buffer.getInt() );
record.setPropBlock( buffer.getLong() );
record.setPrevProp( buffer.getInt() );
record.setNextProp( buffer.getInt() );
}
buffer.clear();
buffer.limit( 4 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int nrValueRecords = buffer.getInt();
for ( int i = 0; i < nrValueRecords; i++ )
{
DynamicRecord dr = readDynamicRecord( byteChannel, buffer );
if ( dr == null )
{
return null;
}
record.addValueRecord( dr );
}
return new PropertyCommand( neoStore.getPropertyStore(), record );
}
private static PropertyType getType( int type )
{
switch ( type )
{
case 1:
return PropertyType.INT;
case 2:
return PropertyType.STRING;
case 3:
return PropertyType.BOOL;
case 4:
return PropertyType.DOUBLE;
case 5:
return PropertyType.FLOAT;
case 6:
return PropertyType.LONG;
case 7:
return PropertyType.BYTE;
case 8:
return PropertyType.CHAR;
case 9:
return PropertyType.ARRAY;
case 10:
return PropertyType.SHORT;
case 0:
return null;
}
throw new InvalidRecordException( "Unknown property type:" + type );
}
@Override
public boolean equals( Object o )
{
if ( !(o instanceof PropertyCommand) )
{
return false;
}
return getKey() == ((Command) o).getKey();
}
}
static class RelationshipTypeCommand extends Command
{
private final RelationshipTypeRecord record;
private final RelationshipTypeStore store;
RelationshipTypeCommand( RelationshipTypeStore store,
RelationshipTypeRecord record )
{
super( record.getId() );
this.record = record;
this.store = store;
}
@Override
public void execute()
{
if ( isRecovered() )
{
logger.fine( this.toString() );
store.updateRecord( record, true );
}
else
{
store.updateRecord( record );
}
}
@Override
public String toString()
{
return "RelationshipTypeCommand[" + record + "]";
}
@Override
public void writeToFile( LogBuffer buffer ) throws IOException
{
// id+in_use(byte)+type_blockId(int)+nr_type_records(int)
byte inUse = record.inUse() ? Record.IN_USE.byteValue()
: Record.NOT_IN_USE.byteValue();
buffer.put( REL_TYPE_COMMAND );
buffer.putInt( record.getId() ).put( inUse ).putInt(
record.getTypeBlock() );
Collection<DynamicRecord> typeRecords = record.getTypeRecords();
buffer.putInt( typeRecords.size() );
for ( DynamicRecord typeRecord : typeRecords )
{
writeDynamicRecord( buffer, typeRecord );
}
}
static Command readCommand( NeoStore neoStore,
ReadableByteChannel byteChannel, ByteBuffer buffer )
throws IOException
{
// id+in_use(byte)+type_blockId(int)+nr_type_records(int)
buffer.clear();
buffer.limit( 13 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
int id = buffer.getInt();
byte inUseFlag = buffer.get();
boolean inUse = false;
if ( (inUseFlag & Record.IN_USE.byteValue()) ==
Record.IN_USE.byteValue() )
{
inUse = true;
}
else if ( inUseFlag != Record.NOT_IN_USE.byteValue() )
{
throw new IOException( "Illegal in use flag: " + inUseFlag );
}
RelationshipTypeRecord record = new RelationshipTypeRecord( id );
record.setInUse( inUse );
record.setTypeBlock( buffer.getInt() );
int nrTypeRecords = buffer.getInt();
for ( int i = 0; i < nrTypeRecords; i++ )
{
DynamicRecord dr = readDynamicRecord( byteChannel, buffer );
if ( dr == null )
{
return null;
}
record.addTypeRecord( dr );
}
return new RelationshipTypeCommand(
neoStore.getRelationshipTypeStore(), record );
}
@Override
public boolean equals( Object o )
{
if ( !(o instanceof RelationshipTypeCommand) )
{
return false;
}
return getKey() == ((Command) o).getKey();
}
}
static Command readCommand( NeoStore neoStore, ReadableByteChannel byteChannel,
ByteBuffer buffer ) throws IOException
{
buffer.clear();
buffer.limit( 1 );
if ( byteChannel.read( buffer ) != buffer.limit() )
{
return null;
}
buffer.flip();
byte commandType = buffer.get();
switch ( commandType )
{
case NODE_COMMAND:
return NodeCommand.readCommand( neoStore, byteChannel, buffer );
case PROP_COMMAND:
return PropertyCommand.readCommand( neoStore, byteChannel,
buffer );
case PROP_INDEX_COMMAND:
return PropertyIndexCommand.readCommand( neoStore, byteChannel,
buffer );
case REL_COMMAND:
return RelationshipCommand.readCommand( neoStore, byteChannel,
buffer );
case REL_TYPE_COMMAND:
return RelationshipTypeCommand.readCommand( neoStore,
byteChannel, buffer );
case NONE: return null;
default:
throw new IOException( "Unknown command type[" + commandType
+ "]" );
}
}
}