/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.ignite.startup.cmdline;
import java.awt.Image;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import javax.swing.ImageIcon;
import org.apache.ignite.IgniteState;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.IgnitionListener;
import org.apache.ignite.internal.util.GridConfigurationFinder;
import org.apache.ignite.internal.util.lang.GridTuple3;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.IgniteState.STOPPED;
import static org.apache.ignite.IgniteState.STOPPED_ON_SEGMENTATION;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_PROG_NAME;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_RESTART_CODE;
import static org.apache.ignite.internal.IgniteVersionUtils.ACK_VER_STR;
import static org.apache.ignite.internal.IgniteVersionUtils.COPYRIGHT;
import static org.apache.ignite.internal.IgniteVersionUtils.RELEASE_DATE_STR;
import static org.apache.ignite.internal.IgniteVersionUtils.VER_STR;
/**
* This class defines command-line Ignite startup. This startup can be used to start Ignite
* outside of any hosting environment from command line. This startup is a Java application with
* {@link #main(String[])} method that accepts command line arguments. It accepts just one
* parameter which is Spring XML configuration file path. You can run this class from command
* line without parameters to get help message.
* <p>
* Note that scripts {@code ${IGNITE_HOME}/bin/ignite.{sh|bat}} shipped with Ignite use
* this startup and you can use them as an example.
*/
@SuppressWarnings({"CallToSystemExit"})
public final class CommandLineStartup {
/** Quite log flag. */
private static final boolean QUITE;
/** Build date. */
private static Date releaseDate;
/**
* Static initializer.
*/
static {
String quiteStr = System.getProperty(IgniteSystemProperties.IGNITE_QUIET);
boolean quite = true;
if (quiteStr != null) {
quiteStr = quiteStr.trim();
if (!quiteStr.isEmpty()) {
if ("false".equalsIgnoreCase(quiteStr))
quite = false;
else if (!"true".equalsIgnoreCase(quiteStr)) {
System.err.println("Invalid value for '" + IgniteSystemProperties.IGNITE_QUIET +
"' VM parameter (must be {true|false}): " + quiteStr);
quite = false;
}
}
}
QUITE = quite;
// Mac OS specific customizations: app icon and about dialog.
try {
releaseDate = new SimpleDateFormat("ddMMyyyy", Locale.US).parse(RELEASE_DATE_STR);
Class<?> appCls = Class.forName("com.apple.eawt.Application");
Object osxApp = appCls.getDeclaredMethod("getApplication").invoke(null);
String icoPath = "logo_ignite_128x128.png";
URL url = CommandLineStartup.class.getResource(icoPath);
assert url != null : "Unknown icon path: " + icoPath;
ImageIcon ico = new ImageIcon(url);
appCls.getDeclaredMethod("setDockIconImage", Image.class).invoke(osxApp, ico.getImage());
// Setting Up about dialog
Class<?> aboutHndCls = Class.forName("com.apple.eawt.AboutHandler");
final URL bannerUrl = CommandLineStartup.class.getResource("logo_ignite_48x48.png");
Object aboutHndProxy = Proxy.newProxyInstance(
appCls.getClassLoader(),
new Class<?>[] {aboutHndCls},
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method mtd, Object[] args) throws Throwable {
AboutDialog.centerShow("Ignite Node", bannerUrl.toExternalForm(), VER_STR,
releaseDate, COPYRIGHT);
return null;
}
}
);
appCls.getDeclaredMethod("setAboutHandler", aboutHndCls).invoke(osxApp, aboutHndProxy);
}
catch (Exception ignore) {
// Ignore.
}
}
/**
* Enforces singleton.
*/
private CommandLineStartup() {
// No-op.
}
/**
* Exists with optional error message, usage show and exit code.
*
* @param errMsg Optional error message.
* @param showUsage Whether or not to show usage information.
* @param exitCode Exit code.
*/
private static void exit(@Nullable String errMsg, boolean showUsage, int exitCode) {
if (errMsg != null)
X.error(errMsg);
String runner = System.getProperty(IGNITE_PROG_NAME, "ignite.{sh|bat}");
int space = runner.indexOf(' ');
runner = runner.substring(0, space == -1 ? runner.length() : space);
if (showUsage) {
boolean ignite = runner.contains("ignite.");
X.error(
"Usage:",
" " + runner + (ignite ? " [?]|[path {-v}{-np}]|[-i]" : " [?]|[-v]"),
" Where:",
" ?, /help, -help, - show this message.",
" -v - verbose mode (quiet by default).",
" -np - no pause on exit (pause by default)",
" -nojmx - disable JMX monitoring (enabled by default)");
if (ignite) {
X.error(
" -i - interactive mode (choose configuration file from list).",
" path - path to Spring XML configuration file.",
" Path can be absolute or relative to IGNITE_HOME.",
" ",
"Spring file should contain one bean definition of Java type",
"'org.apache.ignite.configuration.IgniteConfiguration'. Note that bean will be",
"fetched by the type and its ID is not used.");
}
}
System.exit(exitCode);
}
/**
* Tests whether argument is help argument.
*
* @param arg Command line argument.
* @return {@code true} if given argument is a help argument, {@code false} otherwise.
*/
@SuppressWarnings({"IfMayBeConditional"})
private static boolean isHelp(String arg) {
String s;
if (arg.startsWith("--"))
s = arg.substring(2);
else if (arg.startsWith("-") || arg.startsWith("/") || arg.startsWith("\\"))
s = arg.substring(1);
else
s = arg;
return "?".equals(s) || "help".equalsIgnoreCase(s) || "h".equalsIgnoreCase(s);
}
/**
* Interactively asks for configuration file path.
*
* @return Configuration file path. {@code null} if operation was cancelled.
* @throws IOException In case of error.
*/
@Nullable private static String askConfigFile() throws IOException {
List<GridTuple3<String, Long, File>> files = GridConfigurationFinder.getConfigFiles();
String title = "Available configuration files:";
X.println(title);
X.println(U.dash(title.length()));
for (int i = 0; i < files.size(); i++)
System.out.println(i + ":\t" + files.get(i).get1());
X.print("\nChoose configuration file ('c' to cancel) [0]: ");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line = reader.readLine();
if ("c".equalsIgnoreCase(line)) {
System.out.println("\nOperation cancelled.");
return null;
}
if (line != null && line.isEmpty())
line = "0";
try {
GridTuple3<String, Long, File> file = files.get(Integer.valueOf(line));
X.println("\nUsing configuration: " + file.get1() + "\n");
return file.get3().getAbsolutePath();
}
catch (Exception ignored) {
X.error("\nInvalid selection: " + line);
return null;
}
}
/**
* Main entry point.
*
* @param args Command line arguments.
*/
public static void main(String[] args) {
if (!QUITE) {
X.println("Ignite Command Line Startup, ver. " + ACK_VER_STR);
X.println(COPYRIGHT);
X.println();
}
if (args.length > 1)
exit("Too many arguments.", true, -1);
if (args.length > 0 && isHelp(args[0]))
exit(null, true, 0);
if (args.length > 0 && args[0].isEmpty())
exit("Empty argument.", true, 1);
if (args.length > 0 && args[0].charAt(0) == '-')
exit("Invalid arguments: " + args[0], true, -1);
String cfg = null;
if (args.length > 0)
cfg = args[0];
else {
try {
cfg = askConfigFile();
if (cfg == null)
exit(null, false, 0);
}
catch (IOException e) {
exit("Failed to run interactive mode: " + e.getMessage(), false, -1);
}
}
// Name of the grid loaded from the command line (unique in JVM).
final String igniteInstanceName;
try {
igniteInstanceName = G.start(cfg).name();
}
catch (Throwable e) {
e.printStackTrace();
String note = "";
if (X.hasCause(e, ClassNotFoundException.class))
note = "\nNote! You may use 'USER_LIBS' environment variable to specify your classpath.";
exit("Failed to start grid: " + e.getMessage() + note, false, -1);
if (e instanceof Error)
throw e;
return;
}
// Exit latch for the grid loaded from the command line.
final CountDownLatch latch = new CountDownLatch(1);
G.addListener(new IgnitionListener() {
@Override public void onStateChange(String name, IgniteState state) {
// Skip all grids except loaded from the command line.
if (!F.eq(igniteInstanceName, name))
return;
if (state == STOPPED || state == STOPPED_ON_SEGMENTATION)
latch.countDown();
}
});
try {
latch.await();
}
catch (InterruptedException e) {
X.error("Start was interrupted (exiting): " + e.getMessage());
}
String code = System.getProperty(IGNITE_RESTART_CODE);
if (code != null)
try {
System.exit(Integer.parseInt(code));
}
catch (NumberFormatException ignore) {
System.exit(0);
}
else
System.exit(0);
}
}