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 }