/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Licensed 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.driver.v1.util; import java.io.File; import java.io.IOException; import java.net.StandardSocketOptions; import java.net.URI; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.neo4j.driver.internal.net.BoltServerAddress; import org.neo4j.driver.v1.AuthToken; import org.neo4j.driver.v1.Driver; import org.neo4j.driver.v1.GraphDatabase; import static java.util.Arrays.asList; import static org.junit.Assume.assumeTrue; import static org.neo4j.driver.v1.AuthTokens.basic; import static org.neo4j.driver.v1.ConfigTest.deleteDefaultKnownCertFileIfExists; import static org.neo4j.driver.v1.util.FileTools.moveFile; import static org.neo4j.driver.v1.util.FileTools.updateProperties; import static org.neo4j.driver.v1.util.cc.CommandLineUtil.boltKitAvailable; import static org.neo4j.driver.v1.util.cc.CommandLineUtil.executeCommand; /** * This class wraps the neo4j stand-alone jar in some code to help pulling it in from a remote URL and then launching * it in a separate process. */ public class Neo4jRunner { private static Neo4jRunner globalInstance; private static final boolean debug = true; private static final String DEFAULT_NEOCTRL_ARGS = "-e 3.1.2"; public static final String NEOCTRL_ARGS = System.getProperty( "neoctrl.args", DEFAULT_NEOCTRL_ARGS ); public static final URI DEFAULT_URI = URI.create( "bolt://localhost:7687" ); public static final BoltServerAddress DEFAULT_ADDRESS = BoltServerAddress.from( DEFAULT_URI ); public static final String USER = "neo4j"; public static final String PASSWORD = "password"; public static final AuthToken DEFAULT_AUTH_TOKEN = basic( USER, PASSWORD ); private Neo4jSettings currentSettings = Neo4jSettings.TEST_SETTINGS; public static final String TARGET_DIR = new File( "../target" ).getAbsolutePath(); private static final String NEO4J_DIR = new File( TARGET_DIR, "test-server" ).getAbsolutePath(); public static final String HOME_DIR = new File( NEO4J_DIR, "neo4jHome" ).getAbsolutePath(); private Driver driver; /** Global runner controlling a single server, used to avoid having to restart the server between tests */ public static synchronized Neo4jRunner getOrCreateGlobalRunner() throws IOException { assumeTrue( "BoltKit support unavailable", boltKitAvailable() ); if ( globalInstance == null ) { globalInstance = new Neo4jRunner(); } return globalInstance; } private Neo4jRunner() throws IOException { try { installNeo4j(); startNeo4j(); } finally { // Make sure we stop on JVM exit even if start failed installShutdownHook(); } } public void ensureRunning( Neo4jSettings neo4jSettings ) throws IOException, InterruptedException { ServerStatus status = serverStatus(); switch( status ) { case OFFLINE: updateServerSettings( neo4jSettings ); startNeo4j(); break; case ONLINE: restartNeo4j( neo4jSettings ); break; } } public Driver driver() { if ( driver == null ) { driver = GraphDatabase.driver( DEFAULT_URI, DEFAULT_AUTH_TOKEN ); } return driver; } private void installNeo4j() throws IOException { // this is required for windows as python scripts cannot delete the file when it is used by driver tests deleteDefaultKnownCertFileIfExists(); // Remove this once TrustOnFirstUse is removed. File targetHomeFile = new File( HOME_DIR ); if( targetHomeFile.exists() ) { debug( "Found and using server installed at `%s`. ", HOME_DIR ); } else { List<String> commands = new ArrayList<>(); commands.add( "neoctrl-install" ); String[] split = NEOCTRL_ARGS.trim().split( "\\s+" ); commands.addAll( asList( split ) ); commands.add( NEO4J_DIR ); String tempHomeDir = executeCommand( commands ).trim(); debug( "Downloaded server at `%s`, now renaming to `%s`.", tempHomeDir, HOME_DIR ); moveFile( new File( tempHomeDir ), targetHomeFile ); debug( "Installed server at `%s`.", HOME_DIR ); } updateServerSettingsFile(); } public void startNeo4j() throws IOException { debug( "Starting server..." ); executeCommand( "neoctrl-create-user", HOME_DIR, USER, PASSWORD ); executeCommand( "neoctrl-start", HOME_DIR ); debug( "Server started." ); } public synchronized void stopNeo4j() throws IOException { if( serverStatus() == ServerStatus.OFFLINE ) { return; } if( driver != null ) { driver.close(); driver = null; } debug( "Stopping server..." ); executeCommand( "neoctrl-stop", HOME_DIR ); debug( "Server stopped." ); } public void forceToRestart() throws IOException { stopNeo4j(); startNeo4j(); } /** * Restart the server with default testing server configuration * @throws IOException */ public void restartNeo4j() throws IOException { restartNeo4j( Neo4jSettings.TEST_SETTINGS ); } /** * Will only restart the server if any configuration changes happens * @param neo4jSettings * @throws IOException */ public void restartNeo4j( Neo4jSettings neo4jSettings ) throws IOException { if( updateServerSettings( neo4jSettings ) ) // needs to update server setting files { forceToRestart(); } } private enum ServerStatus { ONLINE, OFFLINE } private ServerStatus serverStatus() { try { SocketChannel soChannel = SocketChannel.open(); soChannel.setOption( StandardSocketOptions.SO_REUSEADDR, true ); soChannel.connect( DEFAULT_ADDRESS.toSocketAddress() ); soChannel.close(); return ServerStatus.ONLINE; } catch ( IOException e ) { return ServerStatus.OFFLINE; } } private boolean updateServerSettings( Neo4jSettings settingsUpdate ) { Neo4jSettings updatedSettings = currentSettings.updateWith( settingsUpdate ); if ( currentSettings.equals( updatedSettings ) ) { return false; } else { currentSettings = updatedSettings; } updateServerSettingsFile(); return true; } /** * Write updated neo4j settings into neo4j-server.properties for use by the next start */ private void updateServerSettingsFile() { Map<String, String> propertiesMap = currentSettings.propertiesMap(); if ( propertiesMap.isEmpty() ) { return; } File oldFile = new File( HOME_DIR, "conf/neo4j.conf" ); try { debug( "Changing server properties file (for next start): %s", oldFile.getCanonicalPath() ); for ( Map.Entry<String, String> property : propertiesMap.entrySet() ) { String name = property.getKey(); Object value = property.getValue(); debug( "%s=%s", name, value ); } updateProperties( oldFile, propertiesMap, currentSettings.excludes() ); } catch ( Exception e ) { throw new RuntimeException( e ); } } private void installShutdownHook() { Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() { @Override public void run() { try { debug("Starting shutdown hook"); stopNeo4j(); debug("Finished shutdown hook"); } catch ( Exception e ) { e.printStackTrace(); } } } ) ); } public static void debug( String text, Object... args ) { if ( debug ) { System.out.println( String.format( text, args ) ); } } }