    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019
    020package org.apache.commons.exec.launcher;
    021
    022import java.io.File;
    023import java.io.IOException;
    024import java.io.PrintWriter;
    025import java.nio.charset.Charset;
    026import java.nio.file.Files;
    027import java.nio.file.Path;
    028import java.util.Map;
    029import java.util.Map.Entry;
    030import java.util.Set;
    031
    032import org.apache.commons.exec.CommandLine;
    033import org.apache.commons.exec.util.StringUtils;
    034
    035/**
    036 * A command launcher for VMS that writes the command to a temporary DCL script before launching commands. This is due to limitations of both the DCL
    037 * interpreter and the Java VM implementation.
    038 */
    039public class VmsCommandLauncher extends Java13CommandLauncher {
    040
    041    /**
    042     * Constructs a new instance.
    043     */
    044    public VmsCommandLauncher() {
    045        // empty
    046    }
    047
    048    /**
    049     * Writes the command into a temporary DCL script and returns the corresponding File object. The script will be deleted on exit.
    050     */
    051    File createCommandFile(final CommandLine cmd, final Map<String, String> env) throws IOException {
    052        final Path path = Files.createTempFile("EXEC", ".TMP");
    053        final File script = path.toFile();
    054        script.deleteOnExit();
    055        try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path, Charset.defaultCharset()))) {
    056            // add the environment as global symbols for the DCL script
    057            if (env != null) {
    058                final Set<Entry<String, String>> entries = env.entrySet();
    059                for (final Entry<String, String> entry : entries) {
    060                    writer.print("$ ");
    061                    writer.print(entry.getKey());
    062                    writer.print(" == "); // define as global symbol
    063                    writer.println('\"');
    064                    String value = entry.getValue();
    065                    // Any embedded " values need to be doubled
    066                    if (value.indexOf('\"') > 0) {
    067                        final StringBuilder sb = new StringBuilder();
    068                        for (int i = 0; i < value.length(); i++) {
    069                            final char c = value.charAt(i);
    070                            if (c == '\"') {
    071                                sb.append('\"');
    072                            }
    073                            sb.append(c);
    074                        }
    075                        value = sb.toString();
    076                    }
    077                    writer.print(value);
    078                    writer.println('\"');
    079                }
    080            }
    081            final String command = cmd.getExecutable();
    082            if (cmd.isFile()) {
    083                // We assume it is it a script file
    084                writer.print("$ @");
    085                // This is a bit crude, but seems to work
    086                final String[] parts = StringUtils.split(command, "/");
    087                writer.print(parts[0]); // device
    088                writer.print(":[");
    089                writer.print(parts[1]); // top level directory
    090                final int lastPart = parts.length - 1;
    091                for (int i = 2; i < lastPart; i++) {
    092                    writer.print(".");
    093                    writer.print(parts[i]);
    094                }
    095                writer.print("]");
    096                writer.print(parts[lastPart]);
    097            } else {
    098                writer.print("$ ");
    099                writer.print(command);
    100            }
    101            final String[] args = cmd.getArguments();
    102            for (final String arg : args) {
    103                writer.println(" -");
    104                writer.print(arg);
    105            }
    106            writer.println();
    107        }
    108        return script;
    109    }
    110
    111    /**
    112     * Launches the given command in a new process.
    113     */
    114    @Override
    115    public Process exec(final CommandLine cmd, final Map<String, String> env) throws IOException {
    116        return super.exec(new CommandLine(createCommandFile(cmd, env).getPath()), env);
    117    }
    118
    119    /**
    120     * Launches the given command in a new process, in the given working directory. Note that under Java 1.3.1, 1.4.0 and 1.4.1 on VMS this method only works if
    121     * {@code workingDir} is null or the logical JAVA$FORK_SUPPORT_CHDIR needs to be set to TRUE.
    122     */
    123    @Override
    124    public Process exec(final CommandLine cmd, final Map<String, String> env, final File workingDir) throws IOException {
    125        return super.exec(new CommandLine(createCommandFile(cmd, env).getPath()), env, workingDir);
    126    }
    127
    128    /** @see org.apache.commons.exec.launcher.CommandLauncher#isFailure(int) */
    129    @Override
    130    public boolean isFailure(final int exitValue) {
    131        // even exit value signals failure
    132        return exitValue % 2 == 0;
    133    }
    134}