/*
* Copyright (c) 2008-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.remote;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.neo4j.remote.RemoteResponse.ResponseBuilder;
import org.neo4j.index.IndexHits;
import org.neo4j.index.IndexService;
/**
* A Basic implementation of a Server for a remote graph database. This implementation relies
* on the {@link GraphDatabaseService} API to perform the actions of the remote graph database
* communication protocol.
*
* To make a concrete implementation the subclass needs to implement the two
* abstract methods that provide a {@link GraphDatabaseService} implementation upon
* connection. One for authenticated connection and one for unauthenticated
* connection. One also needs to provide the transaction manager used by the
* {@link GraphDatabaseService} to the constructor of the server.
*
* @author Tobias Ivarsson
*/
public abstract class BasicGraphDatabaseServer implements ConnectionTarget
{
private static final int DEFAULT_BATCH_SIZE = 10;
private final TransactionManager txManager;
private IndexSpec[] indexes = {};
/**
* Create a new server for a remote graph database.
* @param txManager
* The transaction manager to use on the server.
*/
protected BasicGraphDatabaseServer( TransactionManager txManager )
{
if ( txManager == null )
{
throw new NullPointerException(
"No transaction manager was supplied." );
}
this.txManager = txManager;
}
/**
* Create an unauthenticated connection.
* @return The {@link GraphDatabaseService} implementation to use for the connection.
*/
protected abstract GraphDatabaseService connectGraphDatabase();
/**
* Create an authenticated connection.
* @param username
* The name of the authenticating user.
* @param password
* The password for the authenticating user.
* @return The {@link GraphDatabaseService} implementation to use for the connection.
*/
protected abstract GraphDatabaseService connectGraphDatabase( String username, String password );
/**
* Get the size of the next batch of {@link Node}s sent to the client in an
* iteration.
*
* Override to change the default batch size or create a smarter batching
* scheme.
* @param returned
* The number of previously returned elements in the iteration.
* @return The size of the next batch of elements to send to the client.
*/
protected int getNodesBatchSize( int returned )
{
return DEFAULT_BATCH_SIZE;
}
int nodesBatchSize( int returned )
{
int result;
try
{
result = getNodesBatchSize( returned );
}
catch ( Exception ex )
{
result = DEFAULT_BATCH_SIZE;
}
return ( result < 1 ) ? 1 : result;
}
/**
* Get the size of the next batch of {@link RelationshipType}s sent to the
* client in an iteration.
*
* Override to change the default batch size or create a smarter batching
* scheme.
* @param returned
* The number of previously returned elements in the iteration.
* @return The size of the next batch of elements to send to the client.
*/
protected int getTypesBatchSize( int returned )
{
return DEFAULT_BATCH_SIZE;
}
final int typesBatchSize( int returned )
{
int result;
try
{
result = getTypesBatchSize( returned );
}
catch ( Exception ex )
{
result = DEFAULT_BATCH_SIZE;
}
return ( result < 1 ) ? 1 : result;
}
/**
* Get the size of the next batch of {@link Relationship}s sent to the
* client in an iteration.
*
* Override to change the default batch size or create a smarter batching
* scheme.
* @param returned
* The number of previously returned elements in the iteration.
* @return The size of the next batch of elements to send to the client.
*/
protected int getRelationshipsBatchSize( int returned )
{
return DEFAULT_BATCH_SIZE;
}
final int relationshipsBatchSize( int returned )
{
int result;
try
{
result = getRelationshipsBatchSize( returned );
}
catch ( Exception ex )
{
result = DEFAULT_BATCH_SIZE;
}
return ( result < 1 ) ? 1 : result;
}
/**
* Get the size of the next batch of property keys sent to the client in an
* iteration.
*
* Override to change the default batch size or create a smarter batching
* scheme.
* @param returned
* The number of previously returned elements in the iteration.
* @return The size of the next batch of elements to send to the client.
*/
protected int getKeysBatchSize( int returned )
{
return DEFAULT_BATCH_SIZE;
}
final int keysBatchSize( int returned )
{
int result;
try
{
result = getKeysBatchSize( returned );
}
catch ( Exception ex )
{
result = DEFAULT_BATCH_SIZE;
}
return ( result < 1 ) ? 1 : result;
}
/**
* Register a server side index service implementation.
* @param name
* A name that identifies the index service implementation.
* @param index
* The index service implementation to register.
*/
public void registerIndexService( String name, IndexService index )
{
synchronized ( this )
{
IndexSpec[] new_indexes = new IndexSpec[ indexes.length + 1 ];
for ( int i = 0; i < indexes.length; i++ )
{
if ( indexes[ i ].name.equals( name ) )
{
throw new IllegalArgumentException( "IndexService \""
+ name + "\" is already registered." );
}
new_indexes[ i ] = indexes[ i ];
}
new_indexes[ indexes.length ] = new IndexSpec( name, index );
this.indexes = new_indexes;
}
}
private static class IndexSpec
{
final String name;
final IndexService index;
IndexSpec( String name, IndexService index )
{
this.name = name;
this.index = index;
}
}
void buildResponse( GraphDatabaseService neo, ResponseBuilder builder )
{
// TODO: implement this. Might need redefined interface.
// This is where extra information for the caches is sent to the client.
}
void buildResponse( GraphDatabaseService neo, Object transactionId,
ResponseVisitor responseState )
{
// TODO: implement this. Might need redefined interface.
// This is where extra information for the caches is sent to the client.
}
public final RemoteConnection connect()
{
return new BasicConnection( this, connectGraphDatabase() );
}
public RemoteConnection connect( String username, String password )
{
return new BasicConnection( this, connectGraphDatabase( username, password ) );
}
private void suspendTransaction()
{
try
{
if ( txManager.getTransaction() != null )
{
txManager.suspend();
}
}
catch ( SystemException ex )
{
throw new RuntimeException(
"TODO: better exception. SystemException in tx suspend.", ex );
}
}
void resumeTransaction( Transaction transaction )
{
suspendTransaction();
try
{
txManager.resume( transaction );
}
catch ( InvalidTransactionException ex )
{
throw new RuntimeException(
"TODO: better exception. InvalidTransactionException in tx resume.",
ex );
}
catch ( IllegalStateException ex )
{
throw new RuntimeException(
"TODO: better exception. IllegalStateException in tx resume.",
ex );
}
catch ( SystemException ex )
{
throw new RuntimeException(
"TODO: better exception. SystemException in tx resume.", ex );
}
}
BasicServerTransaction beginTransaction( BasicConnection connection )
{
suspendTransaction();
final Transaction tx;
try
{
txManager.begin();
tx = txManager.getTransaction();
}
catch ( SystemException ex )
{
throw new RuntimeException(
"TODO: better exception. SystemException in tx begin.", ex );
}
catch ( NotSupportedException ex )
{
throw new RuntimeException(
"TODO: better exceptio. txManager.begin() not supported.", ex );
}
return new BasicServerTransaction( this, connection, tx );
}
// Graph database actions
SimpleIterator<String> getRelationshipTypes( GraphDatabaseService neo )
{
final Iterator<RelationshipType> types;
if ( neo instanceof EmbeddedGraphDatabase )
{
types = ( ( EmbeddedGraphDatabase ) neo ).getRelationshipTypes().iterator();
}
else
{
throw new UnsupportedOperationException(
"Cannot get the relationship types from this graph database server." );
}
return new SimpleIterator<String>()
{
@Override
boolean hasNext()
{
return types.hasNext();
}
@Override
String getNext()
{
return types.next().name();
}
};
}
long createNode( GraphDatabaseService neo )
{
return neo.createNode().getId();
}
long getReferenceNode( GraphDatabaseService neo )
{
return neo.getReferenceNode().getId();
}
SimpleIterator<NodeSpecification> getAllNodes( GraphDatabaseService neo )
{
final Iterator<Node> nodes = neo.getAllNodes().iterator();
return new SimpleIterator<NodeSpecification>()
{
@Override
boolean hasNext()
{
return nodes.hasNext();
}
@Override
NodeSpecification getNext()
{
return new NodeSpecification( nodes.next().getId() );
}
};
}
boolean hasNodeWithId( GraphDatabaseService neo, long nodeId )
{
Node node = null;
try
{
node = neo.getNodeById( nodeId );
}
catch ( Exception ex )
{
};
return node != null;
}
void deleteNode( GraphDatabaseService neo, long nodeId )
{
neo.getNodeById( nodeId ).delete();
}
long createRelationship( GraphDatabaseService neo, String relationshipTypeName,
long startNodeId, long endNodeId )
{
return neo.getNodeById( startNodeId ).createRelationshipTo(
neo.getNodeById( endNodeId ), new RelType( relationshipTypeName ) )
.getId();
}
RelationshipSpecification getRelationshipById( GraphDatabaseService neo,
long relationshipId )
{
return new RelationshipSpecification( neo
.getRelationshipById( relationshipId ) );
}
SimpleIterator<RelationshipSpecification> getAllRelationships(
GraphDatabaseService neo, long nodeId, Direction direction )
{
final Iterator<Relationship> relationships = neo.getNodeById( nodeId )
.getRelationships( direction ).iterator();
return new SimpleIterator<RelationshipSpecification>()
{
@Override
boolean hasNext()
{
return relationships.hasNext();
}
@Override
RelationshipSpecification getNext()
{
return new RelationshipSpecification( relationships.next() );
}
};
}
SimpleIterator<RelationshipSpecification> getRelationships( GraphDatabaseService neo,
final long nodeId, final Direction direction,
String[] relationshipTypeNames )
{
RelationshipType[] types = new RelationshipType[ relationshipTypeNames.length ];
for ( int i = 0; i < types.length; i++ )
{
types[ i ] = new RelType( relationshipTypeNames[ i ] );
}
final Iterator<Relationship> relationships = neo.getNodeById( nodeId )
.getRelationships( types ).iterator();
return new SimpleIterator<RelationshipSpecification>()
{
Relationship next = null;
@Override
boolean hasNext()
{
while ( next == null && relationships.hasNext() )
{
Relationship candidate = relationships.next();
switch ( direction )
{
case OUTGOING:
if ( candidate.getStartNode().getId() == nodeId )
{
next = candidate;
return true;
}
break;
case INCOMING:
if ( candidate.getEndNode().getId() == nodeId )
{
next = candidate;
return true;
}
break;
case BOTH:
next = candidate;
return true;
default:
throw new IllegalArgumentException();
}
}
return next != null;
}
@Override
RelationshipSpecification getNext()
{
try
{
return new RelationshipSpecification( next );
}
finally
{
next = null;
}
}
};
}
void deleteRelationship( GraphDatabaseService neo, long relationshipId )
{
neo.getRelationshipById( relationshipId ).delete();
}
Object getNodeProperty( GraphDatabaseService neo, long nodeId, String key )
{
return neo.getNodeById( nodeId ).getProperty( key, null );
}
Object getRelationshipProperty( GraphDatabaseService neo, long relationshipId,
String key )
{
return neo.getRelationshipById( relationshipId )
.getProperty( key, null );
}
Object setNodeProperty( GraphDatabaseService neo, long nodeId, String key,
Object value )
{
neo.getNodeById( nodeId ).setProperty( key, value );
return null;
}
Object setRelationshipProperty( GraphDatabaseService neo, long relationshipId,
String key, Object value )
{
neo.getRelationshipById( relationshipId ).setProperty( key, value );
return null;
}
SimpleIterator<String> getNodePropertyKeys( GraphDatabaseService neo, long nodeId )
{
final Iterator<String> keys = neo.getNodeById( nodeId )
.getPropertyKeys().iterator();
return new SimpleIterator<String>()
{
@Override
boolean hasNext()
{
return keys.hasNext();
}
@Override
String getNext()
{
return keys.next();
}
};
}
SimpleIterator<String> getRelationshipPropertyKeys( GraphDatabaseService neo,
long relationshipId )
{
final Iterator<String> keys = neo.getRelationshipById( relationshipId )
.getPropertyKeys().iterator();
return new SimpleIterator<String>()
{
@Override
boolean hasNext()
{
return keys.hasNext();
}
@Override
String getNext()
{
return keys.next();
}
};
}
boolean hasNodeProperty( GraphDatabaseService neo, long nodeId, String key )
{
return neo.getNodeById( nodeId ).hasProperty( key );
}
boolean hasRelationshipProperty( GraphDatabaseService neo, long relationshiId,
String key )
{
return neo.getRelationshipById( relationshiId ).hasProperty( key );
}
Object removeNodeProperty( GraphDatabaseService neo, long nodeId, String key )
{
return neo.getNodeById( nodeId ).removeProperty( key );
}
Object removeRelationshipProperty( GraphDatabaseService neo, long relationshipId,
String key )
{
return neo.getRelationshipById( relationshipId ).removeProperty( key );
}
int getIndexId( String indexName )
{
IndexSpec[] indexes = this.indexes;
for ( int i = 0; i < indexes.length; i++ )
{
if ( indexes[ i ].name.equals( indexName ) )
{
return i;
}
}
throw new NoSuchElementException( "No index with the name \""
+ indexName + "\" registered." );
}
SimpleIterator<NodeSpecification> getIndexNodes( GraphDatabaseService neo,
int indexId, String key, Object value )
{
final IndexHits<Node> nodes = indexes[ indexId ].index.getNodes( key,
value );
return new SimpleIterator<NodeSpecification>( nodes.size() )
{
Iterator<Node> iter = nodes.iterator();
@Override
NodeSpecification getNext()
{
return new NodeSpecification( iter.next().getId() );
}
@Override
boolean hasNext()
{
return iter.hasNext();
}
};
}
void indexNode( GraphDatabaseService neo, int indexId, long nodeId, String key,
Object value )
{
indexes[ indexId ].index.index( neo.getNodeById( nodeId ), key, value );
}
void removeIndexNode( GraphDatabaseService neo, int indexId, long nodeId, String key,
Object value )
{
indexes[ indexId ].index.removeIndex( neo.getNodeById( nodeId ), key,
value );
}
public long getTotalNumberOfNodes( GraphDatabaseService neo )
{
if ( neo instanceof EmbeddedGraphDatabase )
{
EmbeddedGraphDatabase embedded = ( EmbeddedGraphDatabase ) neo;
return embedded.getConfig().getNeoModule().getNodeManager()
.getNumberOfIdsInUse( Node.class );
}
else
{
return -1;
}
}
}