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    }