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 }