/**
* Copyright (c) 2002-2011 "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.ha;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.LengthFieldPrepender;
import org.neo4j.helpers.Pair;
import org.neo4j.helpers.Triplet;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.kernel.IdType;
import org.neo4j.kernel.impl.nioneo.store.IdRange;
public abstract class CommunicationProtocol
{
public static final int PORT = 8901;
private static final int MEGA = 1024 * 1024;
static final int MAX_FRAME_LENGTH = 16*MEGA;
static final ObjectSerializer<Integer> INTEGER_SERIALIZER = new ObjectSerializer<Integer>()
{
@SuppressWarnings( "boxing" )
public void write( Integer responseObject, ChannelBuffer result ) throws IOException
{
result.writeInt( responseObject );
}
};
static final ObjectSerializer<Long> LONG_SERIALIZER = new ObjectSerializer<Long>()
{
@SuppressWarnings( "boxing" )
public void write( Long responseObject, ChannelBuffer result ) throws IOException
{
result.writeLong( responseObject );
}
};
static final ObjectSerializer<Void> VOID_SERIALIZER = new ObjectSerializer<Void>()
{
public void write( Void responseObject, ChannelBuffer result ) throws IOException
{
}
};
static final ObjectSerializer<LockResult> LOCK_SERIALIZER = new ObjectSerializer<LockResult>()
{
public void write( LockResult responseObject, ChannelBuffer result ) throws IOException
{
result.writeByte( responseObject.getStatus().ordinal() );
if ( responseObject.getStatus().hasMessage() )
{
writeString( result, responseObject.getDeadlockMessage() );
}
}
};
protected static final Deserializer<LockResult> LOCK_RESULT_DESERIALIZER = new Deserializer<LockResult>()
{
public LockResult read( ChannelBuffer buffer ) throws IOException
{
LockStatus status = LockStatus.values()[buffer.readByte()];
return status.hasMessage() ? new LockResult( readString( buffer ) ) : new LockResult(
status );
}
};
protected static final Deserializer<Integer> INTEGER_DESERIALIZER = new Deserializer<Integer>()
{
public Integer read( ChannelBuffer buffer ) throws IOException
{
return buffer.readInt();
}
};
protected static final Deserializer<Void> VOID_DESERIALIZER = new Deserializer<Void>()
{
public Void read( ChannelBuffer buffer ) throws IOException
{
return null;
}
};
protected static final Serializer EMPTY_SERIALIZER = new Serializer()
{
public void write( ChannelBuffer buffer, ByteBuffer readBuffer ) throws IOException
{
}
};
public static enum RequestType
{
ALLOCATE_IDS( new MasterCaller<IdAllocation>()
{
public Response<IdAllocation> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
IdType idType = IdType.values()[input.readByte()];
return Response.wrapResponseObjectOnly( master.allocateIds( idType ) );
}
}, new ObjectSerializer<IdAllocation>()
{
public void write( IdAllocation idAllocation, ChannelBuffer result ) throws IOException
{
IdRange idRange = idAllocation.getIdRange();
result.writeInt( idRange.getDefragIds().length );
for ( long id : idRange.getDefragIds() )
{
result.writeLong( id );
}
result.writeLong( idRange.getRangeStart() );
result.writeInt( idRange.getRangeLength() );
result.writeLong( idAllocation.getHighestIdInUse() );
result.writeLong( idAllocation.getDefragCount() );
}
}, false ),
CREATE_RELATIONSHIP_TYPE( new MasterCaller<Integer>()
{
public Response<Integer> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
return master.createRelationshipType( context, readString( input ) );
}
}, INTEGER_SERIALIZER ),
ACQUIRE_NODE_WRITE_LOCK( new AquireLockCall()
{
@Override
Response<LockResult> lock( Master master, SlaveContext context, long... ids )
{
return master.acquireNodeWriteLock( context, ids );
}
}, LOCK_SERIALIZER ),
ACQUIRE_NODE_READ_LOCK( new AquireLockCall()
{
@Override
Response<LockResult> lock( Master master, SlaveContext context, long... ids )
{
return master.acquireNodeReadLock( context, ids );
}
}, LOCK_SERIALIZER ),
ACQUIRE_RELATIONSHIP_WRITE_LOCK( new AquireLockCall()
{
@Override
Response<LockResult> lock( Master master, SlaveContext context, long... ids )
{
return master.acquireRelationshipWriteLock( context, ids );
}
}, LOCK_SERIALIZER ),
ACQUIRE_RELATIONSHIP_READ_LOCK( new AquireLockCall()
{
@Override
Response<LockResult> lock( Master master, SlaveContext context, long... ids )
{
return master.acquireRelationshipReadLock( context, ids );
}
}, LOCK_SERIALIZER ),
COMMIT( new MasterCaller<Long>()
{
public Response<Long> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
String resource = readString( input );
final ReadableByteChannel reader = new BlockLogReader( input );
return master.commitSingleResourceTransaction( context, resource,
TxExtractor.create( reader ) );
}
}, LONG_SERIALIZER ),
PULL_UPDATES( new MasterCaller<Void>()
{
public Response<Void> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
return master.pullUpdates( context );
}
}, VOID_SERIALIZER ),
FINISH( new MasterCaller<Void>()
{
public Response<Void> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
return master.finishTransaction( context );
}
}, VOID_SERIALIZER ),
GET_MASTER_ID_FOR_TX( new MasterCaller<Integer>()
{
public Response<Integer> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
int masterId = master.getMasterIdForCommittedTx( input.readLong() );
return Response.wrapResponseObjectOnly( masterId );
}
}, INTEGER_SERIALIZER, false ),
COPY_STORE( new MasterCaller<Void>()
{
public Response<Void> callMaster( Master master, SlaveContext context,
ChannelBuffer input, final ChannelBuffer target )
{
return master.copyStore( context, new StoreWriter()
{
public void write( String path, ReadableByteChannel data, boolean hasData ) throws IOException
{
char[] chars = path.toCharArray();
target.writeShort( chars.length );
writeChars( target, chars );
target.writeByte( hasData ? 1 : 0 );
BlockLogBuffer buffer = new BlockLogBuffer( target );
if ( hasData )
{
buffer.write( data );
buffer.done();
}
}
public void done()
{
target.writeShort( 0 );
}
} );
}
}, VOID_SERIALIZER );
@SuppressWarnings( "rawtypes" )
final MasterCaller caller;
@SuppressWarnings( "rawtypes" )
final ObjectSerializer serializer;
private final boolean includesSlaveContext;
private <T> RequestType( MasterCaller<T> caller, ObjectSerializer<T> serializer,
boolean includesSlaveContext )
{
this.caller = caller;
this.serializer = serializer;
this.includesSlaveContext = includesSlaveContext;
}
private <T> RequestType( MasterCaller<T> caller, ObjectSerializer<T> serializer )
{
this( caller, serializer, true );
}
public boolean includesSlaveContext()
{
return this.includesSlaveContext;
}
}
static void addLengthFieldPipes( ChannelPipeline pipeline )
{
pipeline.addLast( "frameDecoder",
new LengthFieldBasedFrameDecoder( MAX_FRAME_LENGTH+4, 0, 4, 0, 4 ) );
pipeline.addLast( "frameEncoder", new LengthFieldPrepender( 4 ) );
}
static <T> void writeTransactionStreams( TransactionStream txStream,
ChannelBuffer buffer, ByteBuffer readBuffer ) throws IOException
{
String[] datasources = txStream.dataSourceNames();
assert datasources.length <= 255 : "too many data sources";
buffer.writeByte( datasources.length );
Map<String, Integer> datasourceId = new HashMap<String, Integer>();
for ( int i = 0; i < datasources.length; i++ )
{
String datasource = datasources[i];
writeString( buffer, datasource );
datasourceId.put( datasource, i + 1/*0 means "no more transactions"*/);
}
for ( Triplet<String, Long, TxExtractor> tx : IteratorUtil.asIterable( txStream ) )
{
buffer.writeByte( datasourceId.get( tx.first() ) );
buffer.writeLong( tx.second() );
BlockLogBuffer blockBuffer = new BlockLogBuffer( buffer );
tx.third().extract( blockBuffer );
blockBuffer.done();
}
buffer.writeByte( 0/*no more transactions*/);
}
protected static TransactionStream readTransactionStreams( final ChannelBuffer buffer )
{
final String[] datasources = readTransactionStreamHeader( buffer );
return new TransactionStream()
{
@Override
protected Triplet<String, Long, TxExtractor> fetchNextOrNull()
{
makeSureNextTransactionIsFullyFetched( buffer );
String datasource = datasources[buffer.readUnsignedByte()];
if ( datasource == null ) return null;
long txId = buffer.readLong();
TxExtractor extractor = TxExtractor.create( new BlockLogReader( buffer ) );
return Triplet.of( datasource, txId, extractor );
}
@Override
public String[] dataSourceNames()
{
return Arrays.copyOfRange( datasources, 1, datasources.length );
}
};
}
private static void makeSureNextTransactionIsFullyFetched( ChannelBuffer buffer )
{
buffer.markReaderIndex();
try
{
if ( buffer.readUnsignedByte() > 0 /* datasource id */ )
{
buffer.skipBytes( 8 ); // tx id
int blockSize = 0;
while ( (blockSize = buffer.readUnsignedByte()) == 0 )
{
buffer.skipBytes( BlockLogBuffer.DATA_SIZE );
}
buffer.skipBytes( blockSize );
}
}
finally
{
buffer.resetReaderIndex();
}
}
protected static String[] readTransactionStreamHeader( ChannelBuffer buffer )
{
final String[] datasources = new String[buffer.readUnsignedByte() + 1];
datasources[0] = null; // identifier for "no more transactions"
for ( int i = 1; i < datasources.length; i++ )
{
datasources[i] = readString( buffer );
}
return datasources;
}
protected static class AcquireLockSerializer implements Serializer
{
private final long[] entities;
AcquireLockSerializer( long... entities )
{
this.entities = entities;
}
public void write( ChannelBuffer buffer, ByteBuffer readBuffer ) throws IOException
{
buffer.writeInt( entities.length );
for ( long entity : entities )
{
buffer.writeLong( entity );
}
}
}
static abstract class AquireLockCall implements MasterCaller<LockResult>
{
public Response<LockResult> callMaster( Master master, SlaveContext context,
ChannelBuffer input, ChannelBuffer target )
{
long[] ids = new long[input.readInt()];
for ( int i = 0; i < ids.length; i++ )
{
ids[i] = input.readLong();
}
return lock( master, context, ids );
}
abstract Response<LockResult> lock( Master master, SlaveContext context, long... ids );
}
protected static interface Serializer
{
void write( ChannelBuffer buffer, ByteBuffer readBuffer ) throws IOException;
}
protected static interface Deserializer<T>
{
T read( ChannelBuffer buffer ) throws IOException;
}
protected interface ObjectSerializer<T>
{
void write( T responseObject, ChannelBuffer result ) throws IOException;
}
protected interface MasterCaller<T>
{
Response<T> callMaster( Master master, SlaveContext context, ChannelBuffer input,
ChannelBuffer target );
}
protected static IdAllocation readIdAllocation( ChannelBuffer buffer )
{
int numberOfDefragIds = buffer.readInt();
long[] defragIds = new long[numberOfDefragIds];
for ( int i = 0; i < numberOfDefragIds; i++ )
{
defragIds[i] = buffer.readLong();
}
long rangeStart = buffer.readLong();
int rangeLength = buffer.readInt();
long highId = buffer.readLong();
long defragCount = buffer.readLong();
return new IdAllocation( new IdRange( defragIds, rangeStart, rangeLength ),
highId, defragCount );
}
protected static void writeString( ChannelBuffer buffer, String name )
{
char[] chars = name.toCharArray();
buffer.writeInt( chars.length );
writeChars( buffer, chars );
}
private static void writeChars( ChannelBuffer buffer, char[] chars )
{
// TODO optimize?
for ( char ch : chars )
{
buffer.writeChar( ch );
}
}
protected static String readString( ChannelBuffer buffer )
{
return readString( buffer, buffer.readInt() );
}
protected static String readString( ChannelBuffer buffer, int length )
{
char[] chars = new char[length];
for ( int i = 0; i < length; i++ )
{
chars[i] = buffer.readChar();
}
return new String( chars );
}
@SuppressWarnings( "boxing" )
protected static void writeSlaveContext( ChannelBuffer buffer, SlaveContext context )
{
buffer.writeInt( context.machineId() );
buffer.writeInt( context.getEventIdentifier() );
Pair<String, Long>[] txs = context.lastAppliedTransactions();
buffer.writeByte( txs.length );
for ( Pair<String, Long> tx : txs )
{
writeString( buffer, tx.first() );
buffer.writeLong( tx.other() );
}
}
@SuppressWarnings( "boxing" )
static SlaveContext readSlaveContext( ChannelBuffer buffer )
{
int machineId = buffer.readInt();
int eventIdentifier = buffer.readInt();
int txsSize = buffer.readByte();
@SuppressWarnings( "unchecked" )
Pair<String, Long>[] lastAppliedTransactions = new Pair[txsSize];
for ( int i = 0; i < txsSize; i++ )
{
lastAppliedTransactions[i] = Pair.of( readString( buffer ), buffer.readLong() );
}
return new SlaveContext( machineId, eventIdentifier, lastAppliedTransactions );
}
}