View Javadoc

1   /* 
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.exec.environment;
19  
20  import java.io.BufferedReader;
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.util.Comparator;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.TreeMap;
31  
32  import org.apache.commons.exec.CommandLine;
33  import org.apache.commons.exec.DefaultExecutor;
34  import org.apache.commons.exec.Executor;
35  import org.apache.commons.exec.OS;
36  import org.apache.commons.exec.PumpStreamHandler;
37  
38  /**
39   * Helper class to determine the environment variable
40   * for the OS. Depending on the JDK the environment
41   * variables can be either retrieved directly from the
42   * JVM or requires starting a process to get them running
43   * an OS command line. 
44   */
45  public class DefaultProcessingEnvironment {
46  
47      /** the line separator of the system */
48      private static final String LINE_SEPARATOR = System.getProperty("line.separator");
49  
50      /** the environment variables of the process */
51      protected Map procEnvironment;
52  
53      /**
54       * Find the list of environment variables for this process.
55       *
56       * @return a map containing the environment variables
57       * @throws IOException obtaining the environment variables failed
58       */
59      public synchronized Map getProcEnvironment() throws IOException {
60  
61          if(procEnvironment == null) {
62              procEnvironment = this.createProcEnvironment();
63          }
64  
65          // create a copy of the map just in case that
66          // anyone is going to modifiy it, e.g. removing
67          // or setting an evironment variable
68          Map copy = createEnvironmentMap();
69          copy.putAll(procEnvironment);
70          return copy;
71      }
72  
73      /**
74       * Find the list of environment variables for this process.
75       *
76       * @return a amp containing the environment variables
77       * @throws IOException the operation failed 
78       */
79      protected Map createProcEnvironment() throws IOException {
80          if (procEnvironment == null) {
81              try {
82                  Method getenvs = System.class.getMethod( "getenv", (java.lang.Class[]) null );
83                  Map env = (Map) getenvs.invoke( null, (java.lang.Object[]) null );
84                  procEnvironment = createEnvironmentMap();
85                  procEnvironment.putAll(env);
86              } catch ( NoSuchMethodException e ) {
87                  // ok, just not on JDK 1.5
88              } catch ( IllegalAccessException e ) {
89                  // Unexpected error obtaining environment - using JDK 1.4 method
90              } catch ( InvocationTargetException e ) {
91                  // Unexpected error obtaining environment - using JDK 1.4 method
92              }
93          }
94  
95          if(procEnvironment == null) {
96              procEnvironment = createEnvironmentMap();
97              BufferedReader in = runProcEnvCommand();
98  
99              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 }