/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.graalvm.polyglot.io;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.graalvm.polyglot.Context.Builder;
Service-provider for guest languages process builder. This interface allows embedder to intercept
subprocess creation done by guest languages.
Since: 19.1.0
/**
* Service-provider for guest languages process builder. This interface allows embedder to intercept
* subprocess creation done by guest languages.
*
* @since 19.1.0
*/
public interface ProcessHandler {
A request to start a new subprocess with given attributes.
The default implementation uses ProcessBuilder
to create the new subprocess. The subprocess current working directory is set to ProcessCommand.getDirectory()
. The ProcessCommand.getDirectory()
value was either explicitely set by the guest language or the FileSystem
's current working directory is used. The subprocess environment is set to ProcessCommand.getEnvironment()
, the initial value of ProcessBuilder.environment()
is cleaned. The ProcessCommand.getEnvironment()
contains the environment variables set by guest language and possibly also the JVM process environment depending on value of Builder.allowEnvironmentAccess(EnvironmentAccess)
.
Implementation example: ProcessHandlerSnippets.example
Params: - command – the subprocess attributes
Throws: - SecurityException – if the process creation was forbidden by this handler
- IOException – if the process fails to execute
Since: 19.1.0
/**
* A request to start a new subprocess with given attributes.
* <p>
* The default implementation uses {@link ProcessBuilder} to create the new subprocess. The
* subprocess current working directory is set to {@link ProcessCommand#getDirectory()}. The
* {@link ProcessCommand#getDirectory()} value was either explicitely set by the guest language
* or the {@link FileSystem}'s current working directory is used. The subprocess environment is
* set to {@link ProcessCommand#getEnvironment()}, the initial value of
* {@link ProcessBuilder#environment()} is cleaned. The {@link ProcessCommand#getEnvironment()}
* contains the environment variables set by guest language and possibly also the JVM process
* environment depending on value of
* {@link Builder#allowEnvironmentAccess(org.graalvm.polyglot.EnvironmentAccess)}.
* <p>
* Implementation example: {@link ProcessHandlerSnippets#example}
*
* @param command the subprocess attributes
* @throws SecurityException if the process creation was forbidden by this handler
* @throws IOException if the process fails to execute
* @since 19.1.0
*/
Process start(ProcessCommand command) throws IOException;
Subprocess attributes passed to start
method. Since: 19.1.0
/**
* Subprocess attributes passed to
* {@link #start(org.graalvm.polyglot.io.ProcessHandler.ProcessCommand) start} method.
*
* @since 19.1.0
*/
final class ProcessCommand {
private List<String> cmd;
private String cwd;
private Map<String, String> environment;
private boolean redirectErrorStream;
private Redirect inputRedirect;
private Redirect outputRedirect;
private Redirect errorRedirect;
ProcessCommand(List<String> command, String cwd, Map<String, String> environment, boolean redirectErrorStream,
Redirect inputRedirect, Redirect outputRedirect, Redirect errorRedirect) {
Objects.requireNonNull(command, "Command must be non null.");
Objects.requireNonNull(environment, "Environment must be non null.");
Objects.requireNonNull(inputRedirect, "InputRedirect must be non null.");
Objects.requireNonNull(outputRedirect, "OutputRedirect must be non null.");
Objects.requireNonNull(errorRedirect, "ErrorRedirect must be non null.");
this.cmd = Collections.unmodifiableList(new ArrayList<>(command));
this.cwd = cwd;
this.environment = Collections.unmodifiableMap(new HashMap<>(environment));
this.redirectErrorStream = redirectErrorStream;
this.inputRedirect = inputRedirect;
this.outputRedirect = outputRedirect;
this.errorRedirect = errorRedirect;
}
Returns the subprocess executable and arguments as an immutable list.
Since: 19.1.0
/**
* Returns the subprocess executable and arguments as an immutable list.
*
* @since 19.1.0
*/
public List<String> getCommand() {
return cmd;
}
Returns the subprocess working directory.
Since: 19.1.0
/**
* Returns the subprocess working directory.
*
* @since 19.1.0
*/
public String getDirectory() {
return cwd;
}
Returns the subprocess environment as an immutable map.
Since: 19.1.0
/**
* Returns the subprocess environment as an immutable map.
*
* @since 19.1.0
*/
public Map<String, String> getEnvironment() {
return environment;
}
Return whether the standard error output should be merged into standard output.
Since: 19.1.0
/**
* Return whether the standard error output should be merged into standard output.
*
* @since 19.1.0
*/
public boolean isRedirectErrorStream() {
return redirectErrorStream;
}
Returns the standard input source.
Since: 19.1.0
/**
* Returns the standard input source.
*
* @since 19.1.0
*/
public Redirect getInputRedirect() {
return inputRedirect;
}
Returns the standard output destination.
Since: 19.1.0
/**
* Returns the standard output destination.
*
* @since 19.1.0
*/
public Redirect getOutputRedirect() {
return outputRedirect;
}
Returns the standard error output destination.
Since: 19.1.0
/**
* Returns the standard error output destination.
*
* @since 19.1.0
*/
public Redirect getErrorRedirect() {
return errorRedirect;
}
}
Represents a source of subprocess input or a destination of subprocess output.
Since: 19.1.0
/**
* Represents a source of subprocess input or a destination of subprocess output.
*
* @since 19.1.0
*/
final class Redirect {
Indicates that subprocess I/O will be connected to the current Java process using a pipe.
Since: 19.1.0
/**
* Indicates that subprocess I/O will be connected to the current Java process using a pipe.
*
* @since 19.1.0
*/
public static final Redirect PIPE = new Redirect(Type.PIPE, null);
Indicates that subprocess I/O source or destination will be the same as those of the
current process.
Since: 19.1.0
/**
* Indicates that subprocess I/O source or destination will be the same as those of the
* current process.
*
* @since 19.1.0
*/
public static final Redirect INHERIT = new Redirect(Type.INHERIT, null);
private final Type type;
private final OutputStream stream;
Redirect(Type type, OutputStream stream) {
Objects.requireNonNull(type, "Type must be non null.");
this.type = type;
this.stream = stream;
}
OutputStream getOutputStream() {
return stream;
}
{@inheritDoc}
Since: 19.1.0
/**
* {@inheritDoc}
*
* @since 19.1.0
*/
@Override
public String toString() {
return type.toString();
}
{@inheritDoc}
Since: 19.1.0
/**
* {@inheritDoc}
*
* @since 19.1.0
*/
@Override
public int hashCode() {
return type.hashCode();
}
{@inheritDoc}
Since: 19.1.0
/**
* {@inheritDoc}
*
* @since 19.1.0
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != Redirect.class) {
return false;
}
return type.equals(((Redirect) obj).type);
}
enum Type {
The type of Redirect.PIPE
. /**
* The type of {@link Redirect#PIPE Redirect.PIPE}.
*/
PIPE,
The type of Redirect.INHERIT
. /**
* The type of {@link Redirect#INHERIT Redirect.INHERIT}.
*/
INHERIT,
The type of Redirect.stream
. /**
* The type of {@link Redirect#stream(java.io.OutputStream) Redirect.stream}.
*/
STREAM
}
}
}
final class ProcessHandlerSnippets implements ProcessHandler {
@Override
// @formatter:off
// BEGIN: ProcessHandlerSnippets#example
public Process start(ProcessCommand command) throws IOException {
ProcessBuilder builder = new ProcessBuilder(command.getCommand())
.redirectErrorStream(command.isRedirectErrorStream())
.redirectInput(asProcessBuilderRedirect(command.getInputRedirect()))
.redirectOutput(asProcessBuilderRedirect(command.getOutputRedirect()))
.redirectError(asProcessBuilderRedirect(command.getErrorRedirect()));
Map<String, String> env = builder.environment();
env.clear();
env.putAll(command.getEnvironment());
String cwd = command.getDirectory();
if (cwd != null) {
builder.directory(Paths.get(cwd).toFile());
}
return builder.start();
}
// END: ProcessHandlerSnippets#example
// @formatter:on
private static java.lang.ProcessBuilder.Redirect asProcessBuilderRedirect(ProcessHandler.Redirect redirect) {
if (redirect == ProcessHandler.Redirect.PIPE) {
return java.lang.ProcessBuilder.Redirect.PIPE;
} else if (redirect == ProcessHandler.Redirect.INHERIT) {
return java.lang.ProcessBuilder.Redirect.INHERIT;
} else {
throw new IllegalStateException("Unsupported redirect: " + redirect);
}
}
}