/* * Licensed to "Neo Technology," Network Engine for Objects in Lund AB * (http://neotechnology.com) under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. Neo Technology licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at (http://www.apache.org/licenses/LICENSE-2.0). Unless required by * applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS * OF ANY KIND, either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.neo4j.neoclipse.graphdb; import java.io.File; import java.net.URISyntaxException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.osgi.service.datalocation.Location; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Transaction; import org.neo4j.kernel.EmbeddedGraphDatabase; import org.neo4j.kernel.EmbeddedReadOnlyGraphDatabase; import org.neo4j.neoclipse.Activator; import org.neo4j.neoclipse.preference.Preferences; import org.neo4j.neoclipse.view.ErrorMessage; import org.neo4j.neoclipse.view.UiHelper; import org.neo4j.remote.RemoteGraphDatabase; import org.neo4j.util.GraphDatabaseLifecycle; /** * This manager controls the neo4j service. * * @author Peter Hänsgen * @author Anders Nawroth */ public class GraphDbServiceManager { private static final String NEOCLIPSE_PACKAGE = "org.neo4j.neoclipse."; private static Logger logger = Logger.getLogger( GraphDbServiceManager.class.getName() ); static { logger.setUseParentHandlers( false ); logger.setLevel( Level.INFO ); ConsoleHandler handler = new ConsoleHandler(); handler.setLevel( Level.INFO ); logger.addHandler( handler ); } private class Tasks { final Runnable START = new Runnable() { public void run() { if ( lifecycle != null ) { throw new IllegalStateException( "Can't start new database: the old one isn't shutdown properly." ); } logInfo( "trying to start/connect ..." ); String dbLocation; GraphDatabaseService graphDb = null; switch ( serviceMode ) { case READ_WRITE_EMBEDDED: dbLocation = getDbLocation(); graphDb = new EmbeddedGraphDatabase( dbLocation ); logInfo( "connected to embedded neo4j" ); break; case READ_ONLY_EMBEDDED: dbLocation = getDbLocation(); graphDb = new EmbeddedReadOnlyGraphDatabase( dbLocation ); logInfo( "connected to embedded read-only neo4j" ); break; case REMOTE: try { graphDb = new RemoteGraphDatabase( getResourceUri() ); } catch ( URISyntaxException e ) { ErrorMessage.showDialog( "URI syntax error", e ); } logInfo( "connected to remote neo4j" ); break; } lifecycle = new GraphDatabaseLifecycle( graphDb ); logFine( "starting tx" ); tx = graphDb.beginTx(); fireServiceChangedEvent( GraphDbServiceStatus.STARTED ); } }; final Runnable STOP = new Runnable() { public void run() { logInfo( "stopping/disconnecting ..." ); if ( lifecycle == null ) { throw new IllegalStateException( "Can't stop the database: there is no running database." ); } fireServiceChangedEvent( GraphDbServiceStatus.STOPPING ); // TODO give the UI some time to deal with it here? try { tx.failure(); tx.finish(); } catch ( Exception e ) { e.printStackTrace(); } try { lifecycle.manualShutdown(); } finally { lifecycle = null; fireServiceChangedEvent( GraphDbServiceStatus.STOPPED ); } logInfo( "stopped/disconnected" ); } }; final Runnable SHUTDOWN = new Runnable() { public void run() { if ( lifecycle != null ) { STOP.run(); } } }; final Runnable COMMIT = new Runnable() { public void run() { if ( serviceMode == GraphDbServiceMode.READ_WRITE_EMBEDDED ) { tx.success(); } else { logFine( "Committing while not in write mode." ); } tx.finish(); tx = lifecycle.graphDb().beginTx(); fireServiceChangedEvent( GraphDbServiceStatus.COMMIT ); } }; final Runnable ROLLBACK = new Runnable() { public void run() { tx.finish(); tx = lifecycle.graphDb().beginTx(); fireServiceChangedEvent( GraphDbServiceStatus.ROLLBACK ); } }; } private class TaskWrapper<T> implements Callable<T> { private final GraphCallable<T> callable; public TaskWrapper( final GraphCallable<T> callable ) { this.callable = callable; } public T call() throws Exception { GraphDatabaseService graphDb = null; if ( lifecycle != null ) { graphDb = lifecycle.graphDb(); } return callable.call( graphDb ); } } private class RunnableWrapper implements Runnable { private final GraphRunnable runnable; private final String name; public RunnableWrapper( final GraphRunnable runnable, final String name ) { this.runnable = runnable; this.name = name; } public void run() { GraphDatabaseService graphDb = null; if ( lifecycle != null ) { graphDb = lifecycle.graphDb(); } logFine( "running: " + name ); runnable.run( graphDb ); logFine( "finished running: " + name ); } } private class DisplayRunnable implements Runnable { private final Runnable runnable; private final String name; public DisplayRunnable( final Runnable runnable, final String name ) { this.runnable = runnable; this.name = name; } public void run() { logFine( "sending display task: " + name ); UiHelper.asyncExec( runnable ); } } private final ExecutorService executor = Executors.newSingleThreadExecutor(); private final Tasks tasks = new Tasks(); /** * The service instance. */ protected GraphDbServiceMode serviceMode; protected GraphDatabaseLifecycle lifecycle = null; /** * The registered service change listeners. */ private final ListenerList listeners = new ListenerList(); private Transaction tx; private final IPreferenceStore preferenceStore = Activator.getDefault().getPreferenceStore(); /** * The constructor. */ public GraphDbServiceManager() { serviceMode = GraphDbServiceMode.valueOf( preferenceStore.getString( Preferences.CONNECTION_MODE ) ); logInfo( "Starting " + this.getClass().getSimpleName() ); } private void logFine( final String message ) { logger.fine( message ); } private void logInfo( final String message ) { logger.info( message ); } private Tasks tasks() { return tasks; } private void printTask( final Object task, final String type, final String info ) { String name = task.getClass().getName(); if ( name.startsWith( NEOCLIPSE_PACKAGE ) ) { name = name.substring( NEOCLIPSE_PACKAGE.length() ); } logFine( type + " -> " + name + ":\n" + info ); } public <T> Future<T> submitTask( final Callable<T> task, final String info ) { printTask( task, "C", info ); return executor.submit( task ); } public <T> Future<T> submitTask( final GraphCallable<T> callable, final String info ) { printTask( callable, "GC", info ); TaskWrapper<T> wrapped = new TaskWrapper<T>( callable ); return executor.submit( wrapped ); } public Future<?> submitTask( final Runnable runnable, final String info ) { printTask( runnable, "R", info ); return executor.submit( runnable ); } public Future<?> submitTask( final GraphRunnable runnable, final String info ) { printTask( runnable, "GR", info ); RunnableWrapper wrapped = new RunnableWrapper( runnable, info ); return executor.submit( wrapped ); } /** * Submit a task that should be performed by the UI thread after the tasks * in the execution queue have executed. * * @param runnable runnable to execute * @param info short discription of the task */ public void submitDisplayTask( final Runnable runnable, final String info ) { DisplayRunnable wrapped = new DisplayRunnable( runnable, info ); executor.submit( wrapped ); } public void executeTask( final GraphRunnable runnable, final String info ) { logFine( "starting: " + info ); runnable.run( lifecycle.graphDb() ); logFine( "finishing: " + info ); } public <T> T executeTask( final GraphCallable<T> callable, final String info ) { logFine( "calling: " + info ); return callable.call( lifecycle.graphDb() ); } public void stopExecutingTasks() { if ( !executor.isShutdown() ) { executor.shutdown(); } } public boolean isRunning() { return lifecycle != null && lifecycle.graphDb() != null; } public boolean isReadOnlyMode() { return serviceMode == GraphDbServiceMode.READ_ONLY_EMBEDDED; } public boolean isLocal() { return serviceMode == GraphDbServiceMode.READ_WRITE_EMBEDDED || serviceMode == GraphDbServiceMode.READ_ONLY_EMBEDDED; } public void setGraphServiceMode( final GraphDbServiceMode gdbServiceMode ) { serviceMode = gdbServiceMode; } /** * Starts the neo4j service. * * @return */ public Future<?> startGraphDbService() throws Exception { return submitTask( tasks().START, "start db" ); } /** * Stops the neo4j service. * * @return */ public Future<?> stopGraphDbService() { return submitTask( tasks().STOP, "stop db" ); } /** * Shuts down the Neo4j service if it's running. * * @return */ public Future<?> shutdownGraphDbService() { return submitTask( tasks().SHUTDOWN, "shutdown db" ); } /** * Commit transaction. * * @return */ public Future<?> commit() { return submitTask( tasks().COMMIT, "commit" ); } /** * Roll back transaction. * * @return */ public Future<?> rollback() { return submitTask( tasks().ROLLBACK, "rollback" ); } /** * Registers a service listener. */ public void addServiceEventListener( final GraphDbServiceEventListener listener ) { listeners.add( listener ); } /** * Unregisters a service listener. */ public void removeServiceEventListener( final GraphDbServiceEventListener listener ) { listeners.remove( listener ); } // determine the neo4j directory from the preferences private String getDbLocation() { String location = preferenceStore.getString( Preferences.DATABASE_LOCATION ); if ( ( location == null ) || ( location.trim().length() == 0 ) ) { // if there's really no db dir, create one in the workspace Location workspace = Platform.getInstanceLocation(); if ( workspace == null ) { throw new IllegalArgumentException( "The database location is not correctly set." ); } try { File dbDir = new File( workspace.getURL().toURI().getPath() + "/neo4j-db" ); if ( !dbDir.exists() ) { if ( !dbDir.mkdir() ) { throw new IllegalArgumentException( "Could not create a database directory." ); } logInfo( "created: " + dbDir.getAbsolutePath() ); } location = dbDir.getAbsolutePath(); preferenceStore.setValue( Preferences.DATABASE_LOCATION, location ); } catch ( URISyntaxException e ) { e.printStackTrace(); throw new IllegalArgumentException( "The database location is not correctly set." ); } } File dir = new File( location ); if ( !dir.exists() ) { throw new IllegalArgumentException( "The database location does not exist." ); } if ( !dir.isDirectory() ) { throw new IllegalArgumentException( "The database location is not a directory." ); } if ( !dir.canWrite() ) { throw new IllegalAccessError( "Writes are not allowed to the database location." ); } logFine( "using location: " + location ); return location; } private String getResourceUri() { String resourceUri = preferenceStore.getString( Preferences.DATABASE_RESOURCE_URI ); if ( resourceUri == null || resourceUri.trim().length() == 0 ) { throw new IllegalArgumentException( "There is no resource URI defined." ); } return resourceUri; } /** * Notifies all registered listeners about the new service status. Actually * just queues up the task so running tasks can finish first. */ protected void fireServiceChangedEvent( final GraphDbServiceStatus status ) { submitTask( new Runnable() { public void run() { fireTheServiceChangedEvent( status ); } }, "fire changed event" ); } private void fireTheServiceChangedEvent( final GraphDbServiceStatus status ) { Object[] changeListeners = listeners.getListeners(); if ( changeListeners.length > 0 ) { final GraphDbServiceEvent e = new GraphDbServiceEvent( this, status ); for ( Object changeListener : changeListeners ) { final GraphDbServiceEventListener l = (GraphDbServiceEventListener) changeListener; l.serviceChanged( e ); } } } }