Skip to content

Commit

Permalink
Refactor start-h2 logic (liquibase#2597)
Browse files Browse the repository at this point in the history
* Refactored startH2 command so the logic is contained in StartH2CommandStep and the old start-h2 standalone script is just a call to `liquibase init start-h2`
* Improved "init start-h2" shutdown messaging and busy-wait logic
* Updated docs to use `liquibase init start-h2` instead of `./start-h2`
  • Loading branch information
nvoxland authored May 12, 2022
1 parent d2bea1a commit 793c760
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 232 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Liquibase system requirements can be found on the [Download Liquibase](https://w
2. Make sure to add Liquibase to your PATH.
3. Copy the included `examples` directory to the needed location.
4. Open your CLI and navigate to your `examples/sql` or `examples/xml` directory.
5. Start the included H2 database with the `./start-h2` command.
5. Start the included H2 database with the `liquibase init start-h2` command.
6. Run the `liquibase update` command.
7. Optionally, follow the prompt for your email to register for [Liquibase Hub](https://hub.liquibase.com/).
8. Run the `liquibase history` command.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package liquibase.command.core;

import liquibase.Scope;
import liquibase.command.*;
import liquibase.configuration.ConfigurationValueObfuscator;

import java.sql.Connection;
import java.sql.DriverManager;

public class StartH2CommandStep extends AbstractCommandStep {

public static final String[] COMMAND_NAME = {"init", "startH2"};

public static final CommandArgumentDefinition<String> BIND_ARG;
public static final CommandArgumentDefinition<Integer> DB_PORT_ARG;
public static final CommandArgumentDefinition<Integer> WEB_PORT_ARG;
public static final CommandArgumentDefinition<String> USERNAME_ARG;
public static final CommandArgumentDefinition<String> PASSWORD_ARG;
public static final CommandArgumentDefinition<Boolean> LAUNCH_BROWSER_ARG;

static {
CommandBuilder builder = new CommandBuilder(COMMAND_NAME);
DB_PORT_ARG = builder.argument("dbPort", Integer.class)
.description("Port to run h2 database on")
.defaultValue(9090)
.build();

WEB_PORT_ARG = builder.argument("webPort", Integer.class)
.description("Port to run h2's web interface on")
.defaultValue(8080)
.build();

USERNAME_ARG = builder.argument("username", String.class)
.description("Username to create in h2")
.defaultValue("dbuser")
.build();

PASSWORD_ARG = builder.argument("password", String.class)
.description("Password to use for created h2 user")
.defaultValue("letmein")
.setValueObfuscator(ConfigurationValueObfuscator.STANDARD)
.build();

BIND_ARG = builder.argument("bindAddress", String.class)
.description("Network address to bind to")
.defaultValue("127.0.0.1")
.build();

LAUNCH_BROWSER_ARG = builder.argument("launchBrowser", Boolean.class)
.description("Whether to open a browser to the database's web interface")
.defaultValue(true)
.build();
}

@Override
public void run(CommandResultsBuilder resultsBuilder) throws Exception {
final CommandScope commandScope = resultsBuilder.getCommandScope();

System.setProperty("h2.bindAddress", commandScope.getConfiguredValue(BIND_ARG).getValue());

System.out.println("Starting Example H2 Database...");
System.out.println("NOTE: The database does not persist data, so stopping and restarting this process will reset it back to a blank database");
System.out.println();

try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
String msg = "ERROR: H2 was not configured properly. To use Liquibase and H2, you need to have the H2 JDBC driver jar file in liquibase/lib. Learn more at https://docs.liquibase.com/";
System.out.println(msg);
throw e;
}

final String username = commandScope.getConfiguredValue(USERNAME_ARG).getValue();
final String password = commandScope.getConfiguredValue(PASSWORD_ARG).getValue();
final Integer dbPort = commandScope.getConfiguredValue(DB_PORT_ARG).getValue();
final Integer webPort = commandScope.getConfiguredValue(WEB_PORT_ARG).getValue();

try (Connection devConnection = DriverManager.getConnection("jdbc:h2:mem:dev", username, password);
Connection intConnection = DriverManager.getConnection("jdbc:h2:mem:integration", username, password)) {

startTcpServer(dbPort);

Object webServer = startWebServer(webPort);
String devUrl = createWebSession(devConnection, webServer, commandScope.getConfiguredValue(LAUNCH_BROWSER_ARG).getValue());
String intUrl = createWebSession(intConnection, webServer, false);

System.out.println("Connection Information:" + System.lineSeparator() +
" Dev database: " + System.lineSeparator() +
" JDBC URL: jdbc:h2:tcp://localhost:" + dbPort + "/mem:dev" + System.lineSeparator() +
" Username: " + username + System.lineSeparator() +
" Password: " + password + System.lineSeparator() +
" Integration database: " + System.lineSeparator() +
" JDBC URL: jdbc:h2:tcp://localhost:" + dbPort + "/mem:integration" + System.lineSeparator() +
" Username: " + username + System.lineSeparator() +
" Password: " + password + System.lineSeparator() +
"" + System.lineSeparator() +
"Opening Database Console in Browser..." + System.lineSeparator() +
" Dev Web URL: " + devUrl + System.lineSeparator() +
" Integration Web URL: " + intUrl + System.lineSeparator());


Runtime.getRuntime().addShutdownHook(new Thread(() -> {
Scope.getCurrentScope().getUI().sendMessage("Shutting down H2 database...");
}));

Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
throw e;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}

resultsBuilder.addResult("statusCode", 0);
}

@Override
public String[][] defineCommandNames() {
return new String[][]{COMMAND_NAME};
}

@Override
public void adjustCommandDefinition(CommandDefinition commandDefinition) {
super.adjustCommandDefinition(commandDefinition);
commandDefinition.setShortDescription(
"Launches H2, an included open source in-memory database. This Java application is shipped with Liquibase, and is useful in the Getting Started experience and for testing out Liquibase commands.");
commandDefinition.setGroupShortDescription(new String[]{"init"}, "Init commands");
}

protected static void startTcpServer(Integer dbPort) throws Exception {
final Class<?> serverClass = Class.forName("org.h2.tools.Server");
final Object tcpServer = serverClass.getMethod("createTcpServer", String[].class)
.invoke(null, (Object) new String[]{"-tcpAllowOthers", "-tcpPort", dbPort.toString()});

tcpServer.getClass().getMethod("start")
.invoke(tcpServer);
}

protected static Object startWebServer(Integer webPort) throws Exception {
final Class<?> serverClass = Class.forName("org.h2.tools.Server");

final Object webServer = Class.forName("org.h2.server.web.WebServer").newInstance();
Object web = serverClass.getConstructor(Class.forName("org.h2.server.Service"), String[].class).newInstance(webServer, (Object) new String[]{"-webPort", webPort.toString()});
web.getClass().getMethod("start").invoke(web);

return webServer;
}

private static String createWebSession(Connection connection, Object webServer, boolean openBrowser) throws Exception {
final Class<?> serverClass = Class.forName("org.h2.tools.Server");

String url = (String) webServer.getClass().getMethod("addSession", Connection.class).invoke(webServer, connection);

if (openBrowser) {
try {
serverClass.getMethod("openBrowser", String.class).invoke(null, url);
} catch (Exception e) {
String message = e.getMessage();
if (message == null && e.getCause() != null) {
message = e.getCause().getMessage();
}
System.out.println("Cannot open browser: "+ message);
System.out.println("");
}
}

return url;
}
}
112 changes: 0 additions & 112 deletions liquibase-core/src/main/java/liquibase/example/StartH2Main.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ liquibase.command.core.RollbackToDateCommandStep
liquibase.command.core.RollbackToDateSqlCommandStep
liquibase.command.core.SnapshotCommandStep
liquibase.command.core.SnapshotReferenceCommandStep
liquibase.command.core.StartH2CommandStep
liquibase.command.core.StatusCommandStep
liquibase.command.core.SyncHubCommandStep
liquibase.command.core.TagCommandStep
Expand Down
27 changes: 1 addition & 26 deletions liquibase-core/src/main/resources/liquibase/examples/start-h2
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,4 @@ if [ -z "${LIQUIBASE_HOME}" ]; then
LIQUIBASE_HOME=$(dirname "$(which liquibase)")
fi

if [ -z "${JAVA_HOME}" ]; then
#JAVA_HOME not set, try to find a bundled version
if [ -d "${LIQUIBASE_HOME}/jre" ]; then
JAVA_HOME="$LIQUIBASE_HOME/jre"
elif [ -d "${LIQUIBASE_HOME}/.install4j/jre.bundle/Contents/Home" ]; then
JAVA_HOME="${LIQUIBASE_HOME}/.install4j/jre.bundle/Contents/Home"
fi
fi

if [ -z "${JAVA_HOME}" ]; then
JAVA_PATH="$(which java)"

if [ -z "${JAVA_PATH}" ]; then
echo "Cannot find java in your path. Install java or use the JAVA_HOME environment variable"

exit 1
fi
else
#Use path in JAVA_HOME
JAVA_PATH="${JAVA_HOME}/bin/java"
fi


# echo "${JAVA_PATH}" -cp "${LIQUIBASE_HOME}/lib/h2-1.4.200.jar:${LIQUIBASE_HOME}/liquibase.jar" liquibase.example.StartH2Main

"${JAVA_PATH}" -cp "${LIQUIBASE_HOME}/lib/h2-2.1.212.jar:${LIQUIBASE_HOME}/liquibase.jar" liquibase.example.StartH2Main
"${LIQUIBASE_HOME}/liquibase" init start-h2
18 changes: 2 additions & 16 deletions liquibase-core/src/main/resources/liquibase/examples/start-h2.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ setlocal enabledelayedexpansion
rem %~dp0 is expanded pathname of the current script under NT
rem %~p0 is the directory of the current script

if exist %~p0\..\liquibase.jar SET LIQUIBASE_HOME="%~p0\.."
if exist %~p0\..\liquibase.jar SET LIQUIBASE_HOME=%~p0..

if "%LIQUIBASE_HOME%"=="" (
FOR /F "tokens=* USEBACKQ" %%g IN (`where liquibase.bat`) do (SET "LIQUIBASE_HOME=%%~dpg")
Expand All @@ -17,18 +17,4 @@ if "%LIQUIBASE_HOME%"=="" (
exit /B 1
)

if "%JAVA_HOME%"=="" (

rem check for jre dir in liquibase_home
if NOT "%LIQUIBASE_HOME%"=="" if exist "%LIQUIBASE_HOME%\jre" (
set JAVA_HOME=%LIQUIBASE_HOME%\jre
)
)

if "%JAVA_HOME%"=="" (
set JAVA_PATH=java
) else (
set JAVA_PATH=%JAVA_HOME%\bin\java
)

"%JAVA_PATH%" -cp "%LIQUIBASE_HOME%\lib\h2-2.1.212.jar;%LIQUIBASE_HOME%\liquibase.jar" liquibase.example.StartH2Main
"%LIQUIBASE_HOME%\liquibase.bat" init start-h2
7 changes: 3 additions & 4 deletions liquibase-dist/src/main/archive/GETTING_STARTED.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,14 @@ complex database.

To start the example database:
* open your command line or Terminal app
* navigate to the directory where you placed "examples" directory
* run `examples/start-h2`.
* run `liquibase init start-h2`.

To stop the example database:
* run `ctrl-c' in the terminal running examples/start-h2
* run `ctrl-c' in the terminal running `liquibase init start-h2`

Notes

Running `examples/start-h2` starts local H2 databases that listen on port 9090, and
Running `liquibase init start-h2` starts local H2 databases that listen on port 9090, and
it opens a browser to the database console on the same port.
* The Example h2 databases do not store data and will reset when the start-h2
process ends via "ctrl-c".
Expand Down
Loading

0 comments on commit 793c760

Please sign in to comment.