    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.exec.environment;
    019
    020    import java.io.BufferedReader;
    021    import java.io.ByteArrayOutputStream;
    022    import java.io.File;
    023    import java.io.IOException;
    024    import java.io.StringReader;
    025    import java.lang.reflect.InvocationTargetException;
    026    import java.lang.reflect.Method;
    027    import java.util.Comparator;
    028    import java.util.HashMap;
    029    import java.util.Map;
    030    import java.util.TreeMap;
    031
    032    import org.apache.commons.exec.CommandLine;
    033    import org.apache.commons.exec.DefaultExecutor;
    034    import org.apache.commons.exec.Executor;
    035    import org.apache.commons.exec.OS;
    036    import org.apache.commons.exec.PumpStreamHandler;
    037
    038    /**
    039     * Helper class to determine the environment variable
    040     * for the OS. Depending on the JDK the environment
    041     * variables can be either retrieved directly from the
    042     * JVM or requires starting a process to get them running
    043     * an OS command line.
    044     */
    045    public class DefaultProcessingEnvironment {
    046
    047        /** the line separator of the system */
    048        private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    049
    050        /** the environment variables of the process */
    051        protected Map procEnvironment;
    052
    053        /**
    054         * Find the list of environment variables for this process.
    055         *
    056         * @return a map containing the environment variables
    057         * @throws IOException obtaining the environment variables failed
    058         */
    059        public synchronized Map getProcEnvironment() throws IOException {
    060
    061            if(procEnvironment == null) {
    062                procEnvironment = this.createProcEnvironment();
    063            }
    064
    065            // create a copy of the map just in case that
    066            // anyone is going to modifiy it, e.g. removing
    067            // or setting an evironment variable
    068            Map copy = createEnvironmentMap();
    069            copy.putAll(procEnvironment);
    070            return copy;
    071        }
    072
    073        /**
    074         * Find the list of environment variables for this process.
    075         *
    076         * @return a amp containing the environment variables
    077         * @throws IOException the operation failed
    078         */
    079        protected Map createProcEnvironment() throws IOException {
    080            if (procEnvironment == null) {
    081                try {
    082                    Method getenvs = System.class.getMethod( "getenv", (java.lang.Class[]) null );
    083                    Map env = (Map) getenvs.invoke( null, (java.lang.Object[]) null );
    084                    procEnvironment = createEnvironmentMap();
    085                    procEnvironment.putAll(env);
    086                } catch ( NoSuchMethodException e ) {
    087                    // ok, just not on JDK 1.5
    088                } catch ( IllegalAccessException e ) {
    089                    // Unexpected error obtaining environment - using JDK 1.4 method
    090                } catch ( InvocationTargetException e ) {
    091                    // Unexpected error obtaining environment - using JDK 1.4 method
    092                }
    093            }
    094
    095            if(procEnvironment == null) {
    096                procEnvironment = createEnvironmentMap();
    097                BufferedReader in = runProcEnvCommand();
    098
    099                String var = null;
    100                String line;
    101                while ((line = in.readLine()) != null) {
    102                    if (line.indexOf('=') == -1) {
    103                        // Chunk part of previous env var (UNIX env vars can
    104                        // contain embedded new lines).
    105                        if (var == null) {
    106                            var = LINE_SEPARATOR + line;
    107                        } else {
    108                            var += LINE_SEPARATOR + line;
    109                        }
    110                    } else {
    111                        // New env var...append the previous one if we have it.
    112                        if (var != null) {
    113                            EnvironmentUtils.addVariableToEnvironment(procEnvironment, var);
    114                        }
    115                        var = line;
    116                    }
    117                }
    118                // Since we "look ahead" before adding, there's one last env var.
    119                if (var != null) {
    120                    EnvironmentUtils.addVariableToEnvironment(procEnvironment, var);
    121                }
    122            }
    123            return procEnvironment;
    124        }
    125
    126        /**
    127         * Start a process to list the environment variables.
    128         *
    129         * @return a reader containing the output of the process
    130         * @throws IOException starting the process failed
    131         */
    132        protected BufferedReader runProcEnvCommand() throws IOException {
    133            ByteArrayOutputStream out = new ByteArrayOutputStream();
    134            Executor exe = new DefaultExecutor();
    135            exe.setStreamHandler(new PumpStreamHandler(out));
    136            // ignore the exit value - Just try to use what we got
    137            exe.execute(getProcEnvCommand());
    138            return new BufferedReader(new StringReader(toString(out)));
    139        }
    140
    141        /**
    142         * Determine the OS specific command line to get a list of environment
    143         * variables.
    144         *
    145         * @return the command line
    146         */
    147        protected CommandLine getProcEnvCommand() {
    148            String executable;
    149            String[] arguments = null;
    150            if (OS.isFamilyOS2()) {
    151                // OS/2 - use same mechanism as Windows 2000
    152                executable = "cmd";
    153
    154                arguments = new String[] {"/c", "set"};
    155            } else if (OS.isFamilyWindows()) {
    156                // Determine if we're running under XP/2000/NT or 98/95
    157                if (OS.isFamilyWin9x()) {
    158                    executable = "command.com";
    159                    // Windows 98/95
    160                } else {
    161                    executable = "cmd";
    162                    // Windows XP/2000/NT/2003
    163                }
    164                arguments = new String[] {"/c", "set"};
    165            } else if (OS.isFamilyZOS() || OS.isFamilyUnix()) {
    166                // On most systems one could use: /bin/sh -c env
    167
    168                // Some systems have /bin/env, others /usr/bin/env, just try
    169                if (new File("/bin/env").canRead()) {
    170                    executable = "/bin/env";
    171                } else if (new File("/usr/bin/env").canRead()) {
    172                    executable = "/usr/bin/env";
    173                } else {
    174                    // rely on PATH
    175                    executable = "env";
    176                }
    177            } else if (OS.isFamilyNetware() || OS.isFamilyOS400()) {
    178                // rely on PATH
    179                executable = "env";
    180            } else {
    181                // MAC OS 9 and previous
    182                // TODO: I have no idea how to get it, someone must fix it
    183                executable = null;
    184            }
    185            CommandLine commandLine = null;
    186            if(executable != null) {
    187                commandLine = new CommandLine(executable);
    188                commandLine.addArguments(arguments);
    189            }
    190            return commandLine;
    191        }
    192
    193        /**
    194         * ByteArrayOutputStream#toString doesn't seem to work reliably on OS/390,
    195         * at least not the way we use it in the execution context.
    196         *
    197         * @param bos
    198         *            the output stream that one wants to read
    199         * @return the output stream as a string, read with special encodings in the
    200         *         case of z/os and os/400
    201         */
    202        private String toString(final ByteArrayOutputStream bos) {
    203            if (OS.isFamilyZOS()) {
    204                try {
    205                    return bos.toString("Cp1047");
    206                } catch (java.io.UnsupportedEncodingException e) {
    207                    // noop default encoding used
    208                }
    209            } else if (OS.isFamilyOS400()) {
    210                try {
    211                    return bos.toString("Cp500");
    212                } catch (java.io.UnsupportedEncodingException e) {
    213                    // noop default encoding used
    214                }
    215            }
    216            return bos.toString();
    217        }
    218
    219        /**
    220         * Creates a map that obeys the casing rules of the current platform for key
    221         * lookup. E.g. on a Windows platform, the map keys will be
    222         * case-insensitive.
    223         *
    224         * @return The map for storage of environment variables, never
    225         *         <code>null</code>.
    226         */
    227        private Map createEnvironmentMap() {
    228            if (OS.isFamilyWindows()) {
    229                return new TreeMap(new Comparator() {
    230                    public int compare(Object arg0, Object arg1) {
    231                        String key0 = (String) arg0;
    232                        String key1 = (String) arg1;
    233                        return key0.compareToIgnoreCase(key1);
    234                    }
    235                });
    236            } else {
    237                return new HashMap();
    238            }
    239        }
    240
    241    }