| /* |
| * 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.Field; |
| 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.ArrayList; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.concurrent.CountDownLatch; |
| import javax.swing.ImageIcon; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteState; |
| import org.apache.ignite.IgniteSystemProperties; |
| import org.apache.ignite.IgnitionListener; |
| import org.apache.ignite.SystemProperty; |
| import org.apache.ignite.internal.processors.cache.ExchangeContext; |
| import org.apache.ignite.internal.processors.cache.GridCacheMapEntry; |
| import org.apache.ignite.internal.processors.cache.distributed.dht.topology.PartitionsEvictManager; |
| import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager; |
| import org.apache.ignite.internal.processors.cache.persistence.GridCacheOffheapManager; |
| import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList; |
| import org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryEventBuffer; |
| import org.apache.ignite.internal.processors.cache.query.continuous.CacheContinuousQueryHandler; |
| import org.apache.ignite.internal.util.GridConfigurationFinder; |
| import org.apache.ignite.internal.util.OffheapReadWriteLock; |
| 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.apache.ignite.spi.communication.tcp.internal.TcpCommunicationConfiguration; |
| import org.apache.ignite.spi.deployment.local.LocalDeploymentSpi; |
| import org.jetbrains.annotations.Nullable; |
| |
| import static java.lang.String.format; |
| import static org.apache.ignite.IgniteState.STARTED; |
| 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; |
| |
| /** Command to print Ignite system properties info. */ |
| static final String PRINT_PROPS_COMMAND = "-systemProps"; |
| |
| /** Classes with Ignite system properties. */ |
| static final List<Class<?>> PROPS_CLS = new ArrayList<>(Arrays.asList( |
| IgniteSystemProperties.class, |
| ExchangeContext.class, |
| GridCacheMapEntry.class, |
| LocalDeploymentSpi.class, |
| GridCacheDatabaseSharedManager.class, |
| PartitionsEvictManager.class, |
| PagesList.class, |
| PagesList.PagesCache.class, |
| GridCacheOffheapManager.class, |
| CacheContinuousQueryEventBuffer.class, |
| CacheContinuousQueryHandler.class, |
| OffheapReadWriteLock.class, |
| TcpCommunicationConfiguration.class |
| )); |
| |
| static { |
| String h2TreeCls = "org.apache.ignite.internal.processors.query.h2.database.H2Tree"; |
| String zkDiscoImpl = "org.apache.ignite.spi.discovery.zk.internal.ZookeeperDiscoveryImpl"; |
| String zkTcpDiscoIpFinder = "org.apache.ignite.spi.discovery.tcp.ipfinder.zk.TcpDiscoveryZookeeperIpFinder"; |
| |
| try { |
| if (U.inClassPath(h2TreeCls)) |
| PROPS_CLS.add(Class.forName(h2TreeCls)); |
| |
| if (U.inClassPath(zkDiscoImpl)) { |
| PROPS_CLS.add(Class.forName(zkDiscoImpl)); |
| PROPS_CLS.add(Class.forName(zkTcpDiscoIpFinder)); |
| } |
| } |
| catch (ClassNotFoundException ignored) { |
| // No-op. |
| } |
| } |
| |
| /** @see IgniteSystemProperties#IGNITE_PROG_NAME */ |
| public static final String DFLT_PROG_NAME = "ignite.{sh|bat}"; |
| |
| /** 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, DFLT_PROG_NAME); |
| |
| 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)", |
| " " + PRINT_PROPS_COMMAND + " - prints Ignite system properties info."); |
| |
| 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. |
| */ |
| public 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 && PRINT_PROPS_COMMAND.equalsIgnoreCase(args[0])) { |
| printSystemPropertiesInfo(); |
| |
| exit(null, false, 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 != STARTED) |
| 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); |
| } |
| |
| /** Prints properties info to console. */ |
| private static void printSystemPropertiesInfo() { |
| Map<String, Field> props = new TreeMap<>(); |
| int maxLength = 0; |
| |
| for (Class<?> cls : PROPS_CLS) { |
| for (Field field : cls.getFields()) { |
| SystemProperty ann = field.getAnnotation(SystemProperty.class); |
| |
| if (ann != null) { |
| try { |
| String name = U.staticField(cls, field.getName()); |
| |
| maxLength = Math.max(maxLength, name.length()); |
| |
| props.put(name, field); |
| } |
| catch (IgniteCheckedException ignored) { |
| // No-op. |
| } |
| } |
| } |
| } |
| |
| String fmt = "%-" + maxLength + "s - %s[%s] %s.%s"; |
| |
| props.forEach((name, field) -> { |
| String deprecated = field.isAnnotationPresent(Deprecated.class) ? "[Deprecated] " : ""; |
| |
| SystemProperty prop = field.getAnnotation(SystemProperty.class); |
| |
| String defaults = prop.defaults(); |
| |
| if (prop.type() == Boolean.class && defaults.isEmpty()) |
| defaults = " Default is false."; |
| else if (!defaults.isEmpty()) |
| defaults = " Default is " + defaults + '.'; |
| |
| X.println(format(fmt, name, deprecated, prop.type().getSimpleName(), prop.value(), defaults)); |
| }); |
| } |
| } |