/* * Copyright (c) 2002-2010 "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; import java.io.File; import java.io.FileInputStream; import java.io.Serializable; import java.rmi.RemoteException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Properties; import java.util.Set; import java.util.Map.Entry; import java.util.logging.Logger; import javax.transaction.TransactionManager; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; import org.neo4j.kernel.ShellService.ShellNotAvailableException; import org.neo4j.kernel.impl.core.NodeManager; import org.neo4j.kernel.impl.transaction.TransactionFailureException; class EmbeddedGraphDbImpl { private static Logger log = Logger.getLogger( EmbeddedGraphDbImpl.class.getName() ); private ShellService shellService; private Transaction placeboTransaction = null; private final GraphDbInstance graphDbInstance; private final GraphDatabaseService graphDbService; private final NodeManager nodeManager; private final String storeDir; /** * Creates an embedded {@link GraphDatabaseService} with a store located in * <code>storeDir</code>, which will be created if it doesn't already exist. * * @param storeDir the store directory for the Neo4j db files */ public EmbeddedGraphDbImpl( String storeDir, GraphDatabaseService graphDbService ) { this.storeDir = storeDir; graphDbInstance = new GraphDbInstance( storeDir, true ); graphDbInstance.start(); nodeManager = graphDbInstance.getConfig().getNeoModule().getNodeManager(); this.graphDbService = graphDbService; } /** * A non-standard way of creating an embedded {@link GraphDatabaseService} * with a set of configuration parameters. Will most likely be removed in * future releases. * * @param storeDir the store directory for the db files * @param params configuration parameters */ public EmbeddedGraphDbImpl( String storeDir, Map<String,String> params, GraphDatabaseService graphDbService ) { this.storeDir = storeDir; graphDbInstance = new GraphDbInstance( storeDir, true ); graphDbInstance.start( params ); nodeManager = graphDbInstance.getConfig().getNeoModule().getNodeManager(); this.graphDbService = graphDbService; } /** * A non-standard Convenience method that loads a standard property file and * converts it into a generic <Code>Map<String,String></CODE>. Will most * likely be removed in future releases. * * @param file the property file to load * @return a map containing the properties from the file * @throws IllegalArgumentException if file does not exist */ public static Map<String,String> loadConfigurations( String file ) { Properties props = new Properties(); try { FileInputStream stream = new FileInputStream( new File( file ) ); try { props.load( stream ); } finally { stream.close(); } } catch ( Exception e ) { throw new IllegalArgumentException( "Unable to load " + file, e ); } Set<Entry<Object,Object>> entries = props.entrySet(); Map<String,String> stringProps = new HashMap<String,String>(); for ( Entry<Object,Object> entry : entries ) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); stringProps.put( key, value ); } return stringProps; } public Node createNode() { return nodeManager.createNode(); } public Node getNodeById( long id ) { if ( id < 0 || id > Integer.MAX_VALUE * 2l ) { throw new NotFoundException( "Node[" + id + "]" ); } return nodeManager.getNodeById( (int) id ); } public Relationship getRelationshipById( long id ) { if ( id < 0 || id > Integer.MAX_VALUE * 2l ) { throw new NotFoundException( "Relationship[" + id + "]" ); } return nodeManager.getRelationshipById( (int) id ); } public Node getReferenceNode() { return nodeManager.getReferenceNode(); } public void shutdown() { if ( this.shellService != null ) { try { this.shellService.shutdown(); } catch ( Throwable t ) { log.warning( "Error shutting down shell server: " + t ); } } graphDbInstance.shutdown(); } public boolean enableRemoteShell() { return this.enableRemoteShell( null ); } public boolean enableRemoteShell( final Map<String,Serializable> initialProperties ) { if ( shellService != null ) { throw new IllegalStateException( "Shell already enabled" ); } Map<String,Serializable> properties = initialProperties; if ( properties == null ) { properties = Collections.emptyMap(); } try { shellService = new ShellService( this.graphDbService, properties ); return true; } catch ( RemoteException e ) { throw new IllegalStateException( "Can't start remote Neo4j shell", e ); } catch ( ShellNotAvailableException e ) { log.info( "Shell library not available. Neo4j shell not " + "started. Please add the Neo4j shell jar to the classpath." ); e.printStackTrace(); return false; } } public Iterable<RelationshipType> getRelationshipTypes() { return graphDbInstance.getRelationshipTypes(); } /** * @throws TransactionFailureException if unable to start transaction */ public Transaction beginTx() { if ( graphDbInstance.transactionRunning() ) { if ( placeboTransaction == null ) { placeboTransaction = new PlaceboTransaction( graphDbInstance .getTransactionManager() ); } return placeboTransaction; } TransactionManager txManager = graphDbInstance.getTransactionManager(); try { txManager.begin(); } catch ( Exception e ) { throw new TransactionFailureException( "Unable to begin transaction", e ); } return new TransactionImpl( txManager ); } private static class PlaceboTransaction implements Transaction { private final TransactionManager transactionManager; PlaceboTransaction( TransactionManager transactionManager ) { // we should override all so null is ok this.transactionManager = transactionManager; } public void failure() { try { transactionManager.getTransaction().setRollbackOnly(); } catch ( Exception e ) { throw new TransactionFailureException( "Failed to mark transaction as rollback only.", e ); } } public void success() { } public void finish() { } } /** * Returns a non-standard configuration object. Will most likely be removed * in future releases. * * @return a configuration object */ public Config getConfig() { return graphDbInstance.getConfig(); } private static class TransactionImpl implements Transaction { private boolean success = false; private final TransactionManager transactionManager; TransactionImpl( TransactionManager transactionManager ) { this.transactionManager = transactionManager; } public void failure() { this.success = false; try { transactionManager.getTransaction().setRollbackOnly(); } catch ( Exception e ) { throw new TransactionFailureException( "Failed to mark transaction as rollback only.", e ); } } public void success() { success = true; } public void finish() { try { if ( success ) { if ( transactionManager.getTransaction() != null ) { transactionManager.getTransaction().commit(); } } else { if ( transactionManager.getTransaction() != null ) { transactionManager.getTransaction().rollback(); } } } catch ( Exception e ) { if ( success ) { throw new TransactionFailureException( "Unable to commit transaction", e ); } else { throw new TransactionFailureException( "Unable to rollback transaction", e ); } } } } @Override public String toString() { return super.toString() + " [" + storeDir + "]"; } public String getStoreDir() { return storeDir; } public Iterable<Node> getAllNodes() { return new Iterable<Node>() { public Iterator<Node> iterator() { long highId = (nodeManager.getHighestPossibleIdInUse( Node.class ) & 0xFFFFFFFFL); return new AllNodesIterator( highId ); } }; } // TODO: temporary all nodes getter, fix this with better implementation // (no NotFoundException to control flow) private class AllNodesIterator implements Iterator<Node> { private final long highId; private long currentNodeId = 0; private Node currentNode = null; AllNodesIterator( long highId ) { this.highId = highId; } public synchronized boolean hasNext() { while ( currentNode == null && currentNodeId <= highId ) { try { currentNode = getNodeById( currentNodeId++ ); } catch ( NotFoundException e ) { // ok we try next } } return currentNode != null; } public synchronized Node next() { if ( !hasNext() ) { throw new NoSuchElementException(); } Node nextNode = currentNode; currentNode = null; return nextNode; } public void remove() { throw new UnsupportedOperationException(); } } }