All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.server.startup.Bootloader Maven / Gradle / Ivy

/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.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 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.server.startup;

import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;

import org.neo4j.configuration.BootloaderSettings;
import org.neo4j.configuration.Config;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.configuration.connectors.HttpConnector;
import org.neo4j.configuration.connectors.HttpsConnector;
import org.neo4j.graphdb.config.Configuration;
import org.neo4j.time.Stopwatch;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.neo4j.configuration.SettingValueParsers.INT;
import static org.neo4j.server.startup.BootloaderOsAbstraction.UNKNOWN_PID;

class Bootloader
{
    static final int EXIT_CODE_OK = 0;
    static final int EXIT_CODE_RUNNING = 1;
    static final int EXIT_CODE_NOT_RUNNING = 3;

    static final String ENV_NEO4J_HOME = "NEO4J_HOME";
    static final String ENV_NEO4J_CONF = "NEO4J_CONF";
    static final String ENV_NEO4J_SHUTDOWN_TIMEOUT = "NEO4J_SHUTDOWN_TIMEOUT";
    static final String ENV_NEO4J_START_WAIT = "NEO4J_START_WAIT";
    static final String ENV_HEAP_SIZE = "HEAP_SIZE";
    static final String ENV_JAVA_OPTS = "JAVA_OPTS";
    static final String PROP_JAVA_CP = "java.class.path";
    static final String PROP_VM_NAME = "java.vm.name";
    static final String PROP_VM_VENDOR = "java.vm.vendor";
    static final String PROP_BASEDIR = "basedir";

    static final String ARG_EXPAND_COMMANDS = "--expand-commands";

    static final Path DEFAULT_CONFIG_LOCATION = Path.of( Config.DEFAULT_CONFIG_DIR_NAME );
    static final int DEFAULT_NEO4J_SHUTDOWN_TIMEOUT = 120;

    private final BootloaderContext ctx;

    Bootloader( BootloaderContext ctx )
    {
        this.ctx = ctx;
    }

    int start()
    {
        BootloaderOsAbstraction os = ctx.os();
        ctx.validateConfig();
        Long pid = os.getPidIfRunning();
        if ( pid != null )
        {
            ctx.out.printf( "Neo4j is already running%s.%n", pidIfKnown( pid ) );
            return EXIT_CODE_RUNNING;
        }
        printDirectories();
        ctx.out.println( "Starting Neo4j." );
        try
        {
            pid = os.start();
        }
        catch ( BootFailureException e )
        {
            ctx.out.println( "Unable to start. See user log for details." );
            throw e;
        }

        String serverLocation;
        Configuration config = ctx.config();
        if ( config.get( HttpsConnector.enabled ) )
        {
            serverLocation = "It is available at https://" + config.get( HttpsConnector.listen_address );
        }
        else if ( config.get( HttpConnector.enabled ) )
        {
            serverLocation = "It is available at http://" + config.get( HttpConnector.listen_address );
        }
        else
        {
            serverLocation = "Both http & https are disabled.";
        }
        ctx.out.printf( "Started neo4j%s. %s%n", pidIfKnown( pid ), serverLocation );
        ctx.out.println( "There may be a short delay until the server is ready." );
        return EXIT_CODE_OK;
    }

    private void printDirectories()
    {
        Configuration config = ctx.config();

        ctx.out.println( "Directories in use:" );
        ctx.out.println( "home:         " + ctx.home().toAbsolutePath() );
        ctx.out.println( "config:       " + ctx.confDir().toAbsolutePath() );
        ctx.out.println( "logs:         " + config.get( GraphDatabaseSettings.logs_directory ).toAbsolutePath() );
        ctx.out.println( "plugins:      " + config.get( GraphDatabaseSettings.plugin_dir ).toAbsolutePath() );
        ctx.out.println( "import:       " + config.get( GraphDatabaseSettings.load_csv_file_url_root ).toAbsolutePath() );
        ctx.out.println( "data:         " + config.get( GraphDatabaseSettings.data_directory ).toAbsolutePath() );
        ctx.out.println( "certificates: " + ctx.home().resolve( "certificates" ).toAbsolutePath() ); //this is no longer an individual setting
        ctx.out.println( "licenses:     " + config.get( GraphDatabaseSettings.licenses_directory ).toAbsolutePath() );
        ctx.out.println( "run:          " + config.get( BootloaderSettings.run_directory ).toAbsolutePath() );
    }

    int console( boolean dryRun )
    {
        BootloaderOsAbstraction os = ctx.os();
        ctx.validateConfig();

        Long pid = os.getPidIfRunning();
        boolean alreadyRunning = pid != null;
        if ( alreadyRunning )
        {
            ctx.out.printf( "Neo4j is already running%s.%n", pidIfKnown( pid ) );
        }

        if ( dryRun )
        {
            List args = os.buildStandardStartArguments();
            String cmd = args.stream().map( Bootloader::quoteArgument ).collect( Collectors.joining( " " ) );
            ctx.out.println( cmd );
            return alreadyRunning ? EXIT_CODE_RUNNING : EXIT_CODE_OK;
        }

        if ( alreadyRunning )
        {
            return EXIT_CODE_RUNNING;
        }

        printDirectories();
        ctx.out.println( "Starting Neo4j." );
        os.console();
        return EXIT_CODE_OK;
    }

    private static String pidIfKnown( long pid )
    {
        return pid != UNKNOWN_PID ? " (pid:" + pid + ")" : "";
    }

    int stop()
    {
        BootloaderOsAbstraction os = ctx.os();
        Long pid = os.getPidIfRunning();
        if ( pid == null )
        {
            ctx.out.println( "Neo4j is not running." );
            return EXIT_CODE_OK;
        }
        ctx.out.print( "Stopping Neo4j." );
        int timeout = ctx.getEnv( ENV_NEO4J_SHUTDOWN_TIMEOUT, DEFAULT_NEO4J_SHUTDOWN_TIMEOUT, INT );
        Stopwatch stopwatch = Stopwatch.start();
        os.stop( pid );
        int printCount = 0;
        do
        {
            if ( !os.isRunning( pid ) )
            {
                ctx.out.println( " stopped." );
                return EXIT_CODE_OK;
            }

            if ( stopwatch.hasTimedOut( printCount, SECONDS ) )
            {
                printCount++;
                ctx.out.print( "." );
            }
            try
            {
                Thread.sleep( 50 );
            }
            catch ( InterruptedException e )
            {
                Thread.currentThread().interrupt();
                break;
            }
        }
        while ( !stopwatch.hasTimedOut( timeout, SECONDS ) );

        ctx.out.println( " failed to stop." );
        ctx.out.printf( "Neo4j%s took more than %d seconds to stop.%n", pidIfKnown( pid ), stopwatch.elapsed( SECONDS ) );
        ctx.out.printf( "Please see %s for details.%n", ctx.config().get( GraphDatabaseSettings.store_user_log_path ) );
        return EXIT_CODE_RUNNING;
    }

    int restart()
    {
        int stopCode = stop();
        if ( stopCode != EXIT_CODE_OK )
        {
            return stopCode;
        }
        return start();
    }

    int status()
    {
        Long pid = ctx.os().getPidIfRunning();
        if ( pid == null )
        {
            ctx.out.println( "Neo4j is not running." );
            return EXIT_CODE_NOT_RUNNING;
        }
        ctx.out.printf( "Neo4j is running%s%n", pid != UNKNOWN_PID ? " at pid " + pid : "" );
        return EXIT_CODE_OK;
    }

    int installService()
    {
        ctx.validateConfig();
        if ( ctx.os().serviceInstalled() )
        {
            ctx.out.println( "Neo4j service is already installed" );
            return EXIT_CODE_RUNNING;
        }
        ctx.os().installService();
        ctx.out.println( "Neo4j service installed." );
        return EXIT_CODE_OK;
    }

    int uninstallService()
    {
        if ( !ctx.os().serviceInstalled() )
        {
            ctx.out.println( "Neo4j service is not installed" );
            return EXIT_CODE_OK;
        }
        ctx.os().uninstallService();
        ctx.out.println( "Neo4j service uninstalled." );
        return EXIT_CODE_OK;
    }

    int updateService()
    {
        ctx.validateConfig();
        if ( !ctx.os().serviceInstalled() )
        {
            ctx.out.println( "Neo4j service is not installed" );
            return EXIT_CODE_RUNNING;
        }
        ctx.os().updateService();
        ctx.out.println( "Neo4j service updated." );
        return EXIT_CODE_OK;
    }

    int admin()
    {
        ctx.err.printf( "Selecting JVM - Version:%s, Name:%s, Vendor:%s%n", ctx.version(), ctx.getProp( PROP_VM_NAME ), ctx.getProp( PROP_VM_VENDOR ) );
        try
        {
            ctx.validateConfig();
            ctx.os().admin();
            return EXIT_CODE_OK;
        }
        catch ( BootProcessFailureException e )
        {
            return e.getExitCode(); //NOTE! This is not the generic BootFailureException, it indicates a process non-zero exit, not bootloader failure.
        }
    }

    /**
     *  This is written with inspiration from apache commons-exec `StringUtils.quoteArgument()`.
     *  That implementation contains a bug (fixed here) that could trim away quotes erroneously in the start or end of string
     *  E.g turn "a partly 'quoted string'" to " a partly 'quoted string" missing the last single quote.
     */
    private static String quoteArgument( String arg )
    {

        final String singleQuote = "'";
        final String doubleQuote = "\"";
        arg = arg.trim();
        while ( arg.length() > 2 &&
                (arg.startsWith( singleQuote ) && arg.endsWith( singleQuote ) || arg.startsWith( doubleQuote ) && arg.endsWith( doubleQuote )) )
        {
            arg = arg.substring( 1, arg.length() - 1 );
        }

        if ( arg.contains( doubleQuote ) )
        {
            if ( arg.contains( singleQuote ) )
            {
                throw new BootFailureException( "`" + arg + "` contains both single and double quotes. Can not be correctly quoted for commandline." );
            }
            arg = singleQuote + arg + singleQuote;
        }
        else if ( arg.contains( singleQuote ) || arg.contains( " " ) )
        {
            arg = doubleQuote + arg + doubleQuote;
        }
        return arg;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy