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

com.oracle.bedrock.runtime.java.LocalJavaApplicationLauncher Maven / Gradle / Ivy

Go to download

Interfaces, classes and resources to construct, inspect and manage runtime processes.

There is a newer version: 7.0.3
Show newest version
/*
 * File: LocalJavaApplicationLauncher.java
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * The contents of this file are subject to the terms and conditions of 
 * the Common Development and Distribution License 1.0 (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License by consulting the LICENSE.txt file
 * distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
 *
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file LICENSE.txt.
 *
 * MODIFICATIONS:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 */

package com.oracle.bedrock.runtime.java;

import com.oracle.bedrock.Option;
import com.oracle.bedrock.OptionsByType;
import com.oracle.bedrock.Bedrock;
import com.oracle.bedrock.annotations.Internal;
import com.oracle.bedrock.deferred.AbstractDeferred;
import com.oracle.bedrock.deferred.PermanentlyUnavailableException;
import com.oracle.bedrock.deferred.TemporarilyUnavailableException;
import com.oracle.bedrock.lang.ExpressionEvaluator;
import com.oracle.bedrock.lang.StringHelper;
import com.oracle.bedrock.options.LaunchLogging;
import com.oracle.bedrock.options.Timeout;
import com.oracle.bedrock.options.Variable;
import com.oracle.bedrock.options.Variables;
import com.oracle.bedrock.runtime.ApplicationListener;
import com.oracle.bedrock.runtime.LocalApplicationProcess;
import com.oracle.bedrock.runtime.MetaClass;
import com.oracle.bedrock.runtime.Platform;
import com.oracle.bedrock.runtime.Profile;
import com.oracle.bedrock.runtime.Profiles;
import com.oracle.bedrock.runtime.Settings;
import com.oracle.bedrock.runtime.concurrent.ControllableRemoteChannel;
import com.oracle.bedrock.runtime.concurrent.RemoteCallable;
import com.oracle.bedrock.runtime.concurrent.RemoteChannel;
import com.oracle.bedrock.runtime.concurrent.RemoteEvent;
import com.oracle.bedrock.runtime.concurrent.RemoteEventListener;
import com.oracle.bedrock.runtime.concurrent.RemoteRunnable;
import com.oracle.bedrock.runtime.concurrent.socket.SocketBasedRemoteChannelServer;
import com.oracle.bedrock.runtime.java.features.JmxFeature;
import com.oracle.bedrock.runtime.java.options.BedrockRunner;
import com.oracle.bedrock.runtime.java.options.ClassName;
import com.oracle.bedrock.runtime.java.options.IPv4Preferred;
import com.oracle.bedrock.runtime.java.options.JavaHome;
import com.oracle.bedrock.runtime.java.options.JvmOption;
import com.oracle.bedrock.runtime.java.options.RemoteEvents;
import com.oracle.bedrock.runtime.java.options.SystemProperties;
import com.oracle.bedrock.runtime.java.options.JavaModules;
import com.oracle.bedrock.runtime.java.options.WaitToStart;
import com.oracle.bedrock.runtime.java.profiles.CommercialFeatures;
import com.oracle.bedrock.runtime.java.profiles.RemoteDebugging;
import com.oracle.bedrock.runtime.options.Arguments;
import com.oracle.bedrock.runtime.options.DisplayName;
import com.oracle.bedrock.runtime.options.EnvironmentVariables;
import com.oracle.bedrock.runtime.options.ErrorStreamRedirection;
import com.oracle.bedrock.runtime.options.Executable;
import com.oracle.bedrock.runtime.options.Orphanable;
import com.oracle.bedrock.runtime.options.WorkingDirectory;
import com.oracle.bedrock.table.Cell;
import com.oracle.bedrock.table.Table;
import com.oracle.bedrock.table.Tabularize;
import com.oracle.bedrock.util.ReflectionHelper;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.oracle.bedrock.deferred.DeferredHelper.ensure;
import static com.oracle.bedrock.deferred.DeferredHelper.within;

/**
 * A {@link JavaApplicationLauncher} that launches a {@link JavaApplication}s as
 * external, non-child local operating system processes, by default using the
 * environment variables of the current system process.
 * 

* Copyright (c) 2014. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates. * * @author Brian Oliver */ @Internal public class LocalJavaApplicationLauncher implements JavaApplicationLauncher { /** * The {@link java.util.logging.Logger} for this class. */ private static final Logger LOGGER = Logger.getLogger(LocalJavaApplicationLauncher.class.getName()); /** * Constructs a {@link LocalJavaApplicationLauncher}. * */ public LocalJavaApplicationLauncher() { } protected LocalProcessBuilder createProcessBuilder(String executable) { return new SimpleLocalProcessBuilder(executable); } @Override public A launch(Platform platform, MetaClass metaClass, OptionsByType optionsByType) { // establish the diagnostics output table Table diagnosticsTable = new Table(); diagnosticsTable.getOptions().add(Table.orderByColumn(0)); if (platform != null) { diagnosticsTable.addRow("Target Platform", platform.getName()); } // ----- establish the launch Options for the Application ----- // add the platform options OptionsByType launchOptions = OptionsByType.of(platform.getOptions().asArray()); // add the meta-class options metaClass.onLaunching(platform, launchOptions); // add the launch specific options launchOptions.addAll(optionsByType); // ----- establish an identity for the application ----- // add a unique runtime id for expression support launchOptions.add(Variable.with("bedrock.runtime.id", UUID.randomUUID())); // ----- establish default Profiles for this Platform (and Builder) ----- // java applications can automatically detect the following profiles launchOptions.get(RemoteDebugging.class); launchOptions.get(CommercialFeatures.class); // auto-detect and add externally defined profiles launchOptions.addAll(Profiles.getProfiles()); // ----- notify the Profiles that the application is about to be launched ----- for (Profile profile : launchOptions.getInstancesOf(Profile.class)) { profile.onLaunching(platform, metaClass, launchOptions); } // ----- give the MetaClass a last chance to manipulate any options ----- metaClass.onLaunch(platform, launchOptions); // ----- determine the display name for the application ----- DisplayName displayName = getDisplayName(launchOptions); // ----- establish the underlying ProcessBuilder ----- // determine the Executable, defaulting to "java" if not defined Executable executable = launchOptions.getOrSetDefault(Executable.class, Executable.named("java")); // we'll use the native operating system process builder to create // and manage the local application process LocalProcessBuilder processBuilder = createProcessBuilder(executable.getName()); // ----- establish the working directory ----- // set the working directory for the Process WorkingDirectory workingDirectory = launchOptions.getOrSetDefault(WorkingDirectory.class, WorkingDirectory.currentDirectory()); File directory = workingDirectory.resolve(platform, launchOptions); // set the resolved working directory back into the options launchOptions.add(WorkingDirectory.at(directory)); if (directory != null) { processBuilder.directory(directory); diagnosticsTable.addRow("Working Directory", directory.toString()); } // ----- establish environment variables ----- EnvironmentVariables environmentVariables = launchOptions.get(EnvironmentVariables.class); switch (environmentVariables.getSource()) { case Custom : processBuilder.environment().clear(); diagnosticsTable.addRow("Environment Variables", "(cleared)"); break; case ThisApplication : Map map = System.getenv(); processBuilder.environment().clear(); processBuilder.environment().putAll(map); diagnosticsTable.addRow("Environment Variables", "(based on parent process)"); break; case TargetPlatform : diagnosticsTable.addRow("Environment Variables", "(based on platform defaults)"); break; } // add the optionally defined environment variables Properties variables = environmentVariables.realize(platform, launchOptions.asArray()); for (String variableName : variables.stringPropertyNames()) { processBuilder.environment().put(variableName, variables.getProperty(variableName)); } if (variables.size() > 0) { Table table = Tabularize.tabularize(variables); diagnosticsTable.addRow("", table.toString()); } // ----- establish java specific environment variables ----- // by default we use the java home defined by the schema. if that's not // defined we'll attempt to use the java home defined by this builder. JavaHome javaHome = launchOptions.get(JavaHome.class); // when we still don't have a java home we use what this process defines // (using the system property) if (javaHome == null) { javaHome = JavaHome.at(System.getProperty("java.home", null)); } if (javaHome != null) { processBuilder.environment().put("JAVA_HOME", StringHelper.doubleQuoteIfNecessary(javaHome.get())); } // ----- establish the command to start java ----- String javaExecutable; if (javaHome == null) { // when we don't have a java home we just use the defined executable javaExecutable = StringHelper.doubleQuoteIfNecessary(executable.getName()); } else { // when we have a java home, we prefix the executable name with the java.home/bin/ String javaHomePath = javaHome.get(); javaHomePath = javaHomePath.trim(); diagnosticsTable.addRow("Java Home", javaHomePath); if (!javaHomePath.endsWith(File.separator)) { javaHomePath = javaHomePath + File.separator; } javaExecutable = StringHelper.doubleQuoteIfNecessary(javaHomePath + "bin" + File.separator + executable.getName()); } processBuilder.command(javaExecutable); diagnosticsTable.addRow("Java Executable", javaExecutable); // ----- establish the class path ----- JavaModules modular = launchOptions.get(JavaModules.class); boolean useModules = modular.isEnabled(); // determine the predefined class path based on the launch options ClassPath classPath = launchOptions.get(ClassPath.class); try { // include the ClassPath of the Platform classPath = new ClassPath(classPath, ClassPath.ofClass(platform.getClass())); // include the ClassPath of each of the Options for (Option option : launchOptions.getInstancesOf(Option.class)) { classPath = new ClassPath(classPath, ClassPath.ofClass(option.getClass())); } // include the application runner (if defined) BedrockRunner bedrockRunner = optionsByType.get(BedrockRunner.class); if (bedrockRunner != null && bedrockRunner.isEnabled()) { // include the JavaApplicationLauncher classPath = new ClassPath(classPath, ClassPath.ofClass(bedrockRunner.getClassOfRunner())); } // add the updated ClassPath back into the launch options launchOptions.add(classPath); } catch (IOException e) { throw new RuntimeException("Failed to locate required classes for the class path", e); } processBuilder.command().add(useModules ? "--module-path" : "-cp"); processBuilder.command().add(classPath.toString(launchOptions.asArray())); if (useModules) { Table modulePathTable = classPath.getTable(); modulePathTable.getOptions().add(Cell.Separator.of("")); diagnosticsTable.addRow("Module Path", modulePathTable.toString()); ClassPath path = modular.getClassPath(); if (path != null && !path.isEmpty()) { processBuilder.command().add("-cp"); processBuilder.command().add(path.toString(launchOptions.asArray())); Table classPathTable = path.getTable(); classPathTable.getOptions().add(Cell.Separator.of("")); diagnosticsTable.addRow("Class Path", classPathTable.toString()); } } else { Table classPathTable = classPath.getTable(); classPathTable.getOptions().add(Cell.Separator.of("")); diagnosticsTable.addRow("Class Path", classPathTable.toString()); } String applicationName = displayName.resolve(launchOptions); // ----- establish Bedrock specific system properties ----- // configure a server channel to communicate with the native process final SocketBasedRemoteChannelServer server = new SocketBasedRemoteChannelServer(applicationName); // register the defined RemoteEventListeners with the server so that when the application starts // the listeners can immediately start receiving RemoteEvents RemoteEvents remoteEvents = launchOptions.get(RemoteEvents.class); remoteEvents.forEach((remoteEventListener, listenerOptions) -> server.addListener(remoteEventListener, listenerOptions)); try { // NOTE: this listens on the wildcard address on an ephemeral port server.open(); } catch (IOException e) { throw new RuntimeException("Failed to create remote execution server for the application", e); } // add Bedrock specific System Properties // NOTE: the Bedrock parent address for locally created applications is always "loopback" as // i). they are always running locally, // ii). they only need to connect locally, and // iii). the "loopback" interface should work regardless of the network we're on. InetAddress parentAddress; IPv4Preferred iPv4Preferred = launchOptions.get(IPv4Preferred.class); if (iPv4Preferred.isPreferred()) { // we have to provide the schema with an IPv4 address! try { parentAddress = InetAddress.getByName("127.0.0.1"); } catch (UnknownHostException e) { // TODO: log that we couldn't determine the loopback address! parentAddress = InetAddress.getLoopbackAddress(); } } else { // when the schema doesn't care, we can default to what this platform chooses parentAddress = InetAddress.getLoopbackAddress(); } Table systemPropertiesTable = new Table(); systemPropertiesTable.getOptions().add(Table.orderByColumn(0)); systemPropertiesTable.getOptions().add(Cell.Separator.of("")); // establish the URI for this (parent) process String parentURI = "//" + parentAddress.getHostAddress() + ":" + server.getPort(); systemPropertiesTable.addRow(Settings.PARENT_URI, parentURI.toString()); processBuilder.command().add("-D" + Settings.PARENT_URI + "=" + parentURI); // add Orphanable configuration Orphanable orphanable = launchOptions.get(Orphanable.class); processBuilder.command().add("-D" + Settings.ORPHANABLE + "=" + orphanable.isOrphanable()); systemPropertiesTable.addRow(Settings.ORPHANABLE, Boolean.toString(orphanable.isOrphanable())); // ----- establish the system properties for the java application ----- // define the system properties based on those defined by the launch options Properties systemProperties = launchOptions.get(SystemProperties.class).resolve(platform, launchOptions); for (String propertyName : systemProperties.stringPropertyNames()) { String propertyValue = systemProperties.getProperty(propertyName); // filter out (don't set) system properties that start with "bedrock", unless it's a profile // (we don't want to have "parents" applications effect child applications if (propertyName.startsWith("bedrock.profile.") ||!propertyName.startsWith("bedrock")) { processBuilder.command().add("-D" + propertyName + (propertyValue.isEmpty() ? "" : "=" + propertyValue)); systemPropertiesTable.addRow(propertyName, propertyValue); } } diagnosticsTable.addRow("System Properties", systemPropertiesTable.toString()); // ----- establish Java Virtual Machine options ----- StringBuilder jvmOptions = new StringBuilder(); for (JvmOption jvmOption : launchOptions.getInstancesOf(JvmOption.class)) { for (String value : jvmOption.resolve(launchOptions)) { processBuilder.command().add(value); if (jvmOptions.length() > 0) { jvmOptions.append(" "); } jvmOptions.append(value); } } if (jvmOptions.length() > 0) { diagnosticsTable.addRow("Java Options", jvmOptions.toString()); } // ----- add bedrock.runtime.inherit.xxx values to the command ----- for (String propertyName : System.getProperties().stringPropertyNames()) { if (propertyName.startsWith("bedrock.runtime.inherit.")) { // resolve the property value String propertyValue = System.getProperty(propertyName); // evaluate any expressions in the property value ExpressionEvaluator evaluator = new ExpressionEvaluator(launchOptions.get(Variables.class)); propertyValue = evaluator.evaluate(propertyValue, String.class); processBuilder.command().add(propertyValue); } } // ----- establish the application command line to execute ----- // use the launcher to launch the application // (we don't start the application directly itself) String applicationLauncherClassName = JavaApplicationRunner.class.getName(); if (useModules) { applicationLauncherClassName = "com.oracle.bedrock.runtime/" + applicationLauncherClassName; processBuilder.command().add("-m"); processBuilder.command().add(applicationLauncherClassName); } else { processBuilder.command().add(applicationLauncherClassName); } // set the Java application class name we need to launch ClassName className = launchOptions.get(ClassName.class); if (className == null) { throw new IllegalArgumentException("Java Application ClassName not specified"); } String applicationClassName = className.getName(); processBuilder.command().add(applicationClassName); diagnosticsTable.addRow("Application Launcher", applicationLauncherClassName); diagnosticsTable.addRow("Application Class", applicationClassName); diagnosticsTable.addRow("Application", applicationName); // ----- included the java arguments to the command ----- List argList = launchOptions.get(Arguments.class).resolve(platform, launchOptions); // Set the actual arguments used back into the options launchOptions.add(Arguments.of(argList)); String arguments = ""; for (String argument : argList) { processBuilder.command().add(argument); arguments += argument + " "; } if (arguments.length() > 0) { diagnosticsTable.addRow("Application Arguments", arguments); } // should the standard error be redirected to the standard out? ErrorStreamRedirection redirection = launchOptions.get(ErrorStreamRedirection.class); processBuilder.redirectErrorStream(redirection.isEnabled()); diagnosticsTable.addRow("Standard Error Device", redirection.isEnabled() ? "stdout" : "stderr"); diagnosticsTable.addRow("Application Launch Time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); // TODO: add all of the Profile options to the table // ----- start the local process ----- boolean launchLogging = optionsByType.get(LaunchLogging.class).isEnabled(); if (launchLogging && LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, "Oracle Bedrock " + Bedrock.getVersion() + ": Starting Application...\n" + "------------------------------------------------------------------------\n" + diagnosticsTable.toString() + "\n" + "------------------------------------------------------------------------\n"); } // create and start the native process Process process; try { process = processBuilder.start(launchOptions); } catch (IOException e) { throw new RuntimeException("Failed to build the underlying native process for the application", e); } // ----- create the local process and application ----- // establish a LocalJavaProcess to represent the underlying Process LocalJavaApplicationProcess localJavaProcess = new LocalJavaApplicationProcess(process, server, systemProperties); // determine the application class that will represent the running application Class applicationClass = metaClass.getImplementationClass(platform, launchOptions); A application; try { // attempt to find a constructor(Platform, JavaApplicationProcess, Options) Constructor constructor = ReflectionHelper.getCompatibleConstructor(applicationClass, platform.getClass(), localJavaProcess.getClass(), launchOptions.getClass()); // create the application application = constructor.newInstance(platform, localJavaProcess, launchOptions); } catch (Exception e) { throw new RuntimeException("Failed to instantiate the Application class specified by the MetaClass:" + metaClass, e); } // ----- enhance the application with java-specific features ----- if (JmxFeature.isSupportedBy(application)) { application.add(new JmxFeature()); } // ----- wait for the application to start ----- // ensure that the launcher process connects back to the server to // know that the application has started WaitToStart waitToStart = launchOptions.get(WaitToStart.class); if (waitToStart.isEnabled()) { Timeout timeout = launchOptions.get(Timeout.class); ensure(new AbstractDeferred() { @Override public Boolean get() throws TemporarilyUnavailableException, PermanentlyUnavailableException { if (!server.getRemoteChannels().iterator().hasNext()) { throw new TemporarilyUnavailableException(this); } else { return true; } } },within(timeout)); } // ----- notify the MetaClass that the application has been launched ----- metaClass.onLaunched(platform, application, launchOptions); // ----- notify the Profiles that the application has been launched ----- for (Profile profile : launchOptions.getInstancesOf(Profile.class)) { profile.onLaunched(platform, application, launchOptions); } // ----- notify all of the application listeners ----- // notify the ApplicationListener-based Options that the application has been launched for (ApplicationListener listener : launchOptions.getInstancesOf(ApplicationListener.class)) { listener.onLaunched(application); } return application; } /** * A {@link LocalApplicationProcess} specifically for Java-based applications. */ public static class LocalJavaApplicationProcess extends LocalApplicationProcess implements JavaApplicationProcess { /** * The {@link RemoteChannel} for the {@link LocalJavaApplicationProcess}. */ private ControllableRemoteChannel remoteExecutor; /** * The resolved System {@link Properties} provided to the {@link JavaApplicationProcess} when it was launched. */ private Properties systemProperties; /** * Constructs a {@link LocalJavaApplicationProcess}. * * @param process the underlying operating system {@link Process} * @param remoteExecutor the {@link ControllableRemoteChannel} that may be used * to submit and control the process remotely * @param systemProperties the resolved System {@link Properties} provided to the {} */ public LocalJavaApplicationProcess(Process process, ControllableRemoteChannel remoteExecutor, Properties systemProperties) { super(process); this.remoteExecutor = remoteExecutor; this.systemProperties = systemProperties; } @Override public Properties getSystemProperties() { return systemProperties; } @Override public CompletableFuture submit(RemoteCallable callable, Option... options) throws IllegalStateException { return remoteExecutor.submit(callable, options); } @Override public CompletableFuture submit(RemoteRunnable runnable, Option... options) throws IllegalStateException { return remoteExecutor.submit(runnable, options); } @Override public void addListener(RemoteEventListener listener, Option... options) { remoteExecutor.addListener(listener, options); } @Override public void removeListener(RemoteEventListener listener, Option... options) { remoteExecutor.removeListener(listener, options); } @Override public CompletableFuture raise(RemoteEvent event, Option... options) { return remoteExecutor.raise(event, options); } @Override public void close() { super.close(); remoteExecutor.close(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy