001    /*
002     * Copyright 2004-2005 Stephen J. McConnell.
003     * Copyright 2004 Apache Software Foundation
004     *
005     * Licensed  under the  Apache License,  Version 2.0  (the "License");
006     * you may not use  this file  except in  compliance with the License.
007     * 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
014     * implied.
015     *
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    package net.dpml.transit;
021    
022    import java.io.File;
023    import java.io.IOException;
024    import java.io.BufferedReader;
025    import java.io.InputStreamReader;
026    
027    import java.util.Properties;
028    import java.util.Enumeration;
029    
030    
031    /**
032     * Encapsulates operating system and shell specific access to environment
033     * variables.
034     *
035     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
036     * @version 1.0.3
037     */
038    public class Environment extends Properties
039    {
040       /**
041        * os.name System property
042        */
043        public static final String OSNAME = System.getProperty( "os.name" );
044    
045       /**
046        * user.name System property
047        */
048        public static final String USERNAME = System.getProperty( "user.name" );
049    
050       /**
051        * the user's platform specific shell executable
052        */
053        private static String m_SHELL = null;
054    
055       /**
056        * the last Env instance created
057        */
058        private static Environment m_INSTANCE = null;
059    
060       /**
061        * Creates a snapshot of the current shell environment variables for a user.
062        *
063        * @throws EnvironmentException if there is an error accessing the environment
064        */
065        public Environment() throws EnvironmentException
066        {
067            Properties properties = getEnvVariables();
068            Enumeration list = properties.propertyNames();
069            while ( list.hasMoreElements() )
070            {
071                String key = ( String ) list.nextElement();
072                setProperty( key, properties.getProperty( key ) );
073            }
074            m_INSTANCE = this;
075        }
076    
077        /**
078         * Gets a copy of the last Environment instance without parsing the user's shell
079         * environment.  Use this method if you do not want to reparse the
080         * environment every time an environment variable is accessed.  If an
081         * environment has not been created yet one is created then cloned
082         * and a copy is returned instead of returning null.
083         *
084         * @return a copy of the last Environment object created
085         * @throws EnvironmentException if there is an error accessing the environment
086         */
087        Environment getLastEnv() throws EnvironmentException
088        {
089            if( m_INSTANCE == null )
090            {
091                m_INSTANCE = new Environment();
092            }
093    
094            // return cloned copy so there is no cross interference
095            return ( Environment ) m_INSTANCE.clone();
096        }
097    
098    
099        /**
100         * Gets the value of a shell environment variable.
101         *
102         * @param name the name of variable
103         * @return the String representation of an environment variable value
104         * @throws EnvironmentException if there is a problem accessing the environment
105         */
106        public static String getEnvVariable( String name )
107            throws EnvironmentException
108        {
109            String osName = System.getProperty( "os.name" );
110    
111            if( isUnix() )
112            {
113                Properties properties = getUnixShellVariables();
114                return properties.getProperty( name );
115            }
116            else if( isWindows() )
117            {
118                return getWindowsShellVariable( name );
119            }
120    
121            throw new EnvironmentException( name,
122                "Unrecognized operating system: " + osName );
123        }
124    
125        /**
126         * Checks to see if the operating system is a UNIX variant.
127         *
128         * @return true of the OS is a UNIX variant, false otherwise
129         */
130        public static boolean isUnix()
131        {
132            if( -1 != OSNAME.indexOf( "Linux" )
133                   || -1 != OSNAME.indexOf( "SunOS" )
134                   || -1 != OSNAME.indexOf( "Solaris" )
135                   || -1 != OSNAME.indexOf( "MPE/iX" )
136                   || -1 != OSNAME.indexOf( "AIX" )
137                   || -1 != OSNAME.indexOf( "FreeBSD" )
138                   || -1 != OSNAME.indexOf( "Irix" )
139                   || -1 != OSNAME.indexOf( "Digital Unix" )
140                   || -1 != OSNAME.indexOf( "HP-UX" )
141                   || -1 != OSNAME.indexOf( "Mac OS X" ) )
142            {
143                return true;
144            }
145    
146            return false;
147        }
148    
149    
150        /**
151         * Checks to see if the operating system is a Windows variant.
152         *
153         * @return true of the OS is a Windows variant, false otherwise
154         */
155        public static boolean isWindows()
156        {
157            return ( -1 != OSNAME.indexOf( "Windows" ) );
158        }
159    
160        /**
161         * Checks to see if the operating system is NetWare.
162         *
163         * @return true of the OS is NetWare, false otherwise
164         */
165        public static boolean isNetWare()
166        {
167            return ( -1 != OSNAME.indexOf( "netware" ) );
168        }
169    
170        /**
171         * Checks to see if the operating system is OpenVMS.
172         *
173         * @return true of the OS is a NetWare variant, false otherwise
174         */
175        public static boolean isOpenVMS()
176        {
177            return ( -1 != OSNAME.indexOf( "openvms" ) );
178        }
179    
180        /**
181         * Gets all environment variables within a Properties instance where the
182         * key is the environment variable name and value is the value of the
183         * property.
184         *
185         * @return the environment variables and values as Properties
186         * @throws EnvironmentException if os is not recognized
187         */
188        public static Properties getEnvVariables() throws EnvironmentException
189        {
190            if( isUnix() )
191            {
192                return getUnixShellVariables();
193            }
194    
195            if( isWindows() )
196            {
197                return getWindowsShellVariables();
198            }
199    
200            throw new EnvironmentException(
201                new UnsupportedOperationException( "Environment operations not "
202                + "supported on unrecognized operatings system" ) );
203        }
204    
205    
206        /**
207         * Gets the user's shell executable.
208         *
209         * @return the shell executable for the user
210         * @throws EnvironmentException the there is a problem accessing shell
211         * information
212         */
213        public static String getUserShell() throws EnvironmentException
214        {
215            if( -1 != OSNAME.indexOf( "Mac OS X" ) )
216            {
217                return getMacUserShell();
218            }
219    
220            if( isWindows() )
221            {
222                return getWindowsUserShell();
223            }
224    
225            throw new EnvironmentException(
226                new UnsupportedOperationException( "Environment operations not "
227                    + "supported on unrecognized operatings system" ) );
228        }
229    
230        // ------------------------------------------------------------------------
231        // Private UNIX Shell Operations
232        // ------------------------------------------------------------------------
233    
234        /**
235         * Gets the default login shell used by a mac user.
236         *
237         * @return the Mac user's default shell as referenced by cmd:
238         *      'nidump passwd /'
239         * @throws EnvironmentException if os information is not resolvable
240         */
241        private static String getMacUserShell()
242            throws EnvironmentException
243        {
244            Process process = null;
245            BufferedReader reader = null;
246    
247            if( null != m_SHELL )
248            {
249                return m_SHELL;
250            }
251    
252            try
253            {
254                String entry = null;
255                String [] args = {"nidump", "passwd", "/"};
256                process = Runtime.getRuntime().exec( args );
257                reader = new BufferedReader(
258                        new InputStreamReader( process.getInputStream() ) );
259    
260                while ( null != ( entry = reader.readLine() ) )
261                {
262                    // Skip entries other than the one for this username
263                    if( !entry.startsWith( USERNAME ) )
264                    {
265                        continue;
266                    }
267    
268                    // Get the shell part of the passwd entry
269                    int index = entry.lastIndexOf( ':' );
270    
271                    if( index == -1 )
272                    {
273                        throw new EnvironmentException(
274                            "passwd database contains malformed user entry for "
275                            + USERNAME );
276                    }
277    
278                    m_SHELL = entry.substring( index + 1 );
279                    return m_SHELL;
280                }
281    
282                process.waitFor();
283                reader.close();
284            }
285            catch( Throwable t )
286            {
287                throw new EnvironmentException( t );
288            }
289            finally
290            {
291                if( process != null )
292                {
293                    process.destroy();
294                }
295    
296                try
297                {
298                    if( null != reader )
299                    {
300                        reader.close();
301                    }
302                }
303                catch( IOException e )
304                {
305                    // do nothing
306                    final boolean ignorable = true;
307                }
308            }
309    
310            throw new EnvironmentException( "User " + USERNAME
311                + " is not present in the passwd database" );
312        }
313    
314    
315       /**
316        * Adds a set of Windows variables to a set of properties.
317        * @return the environment properties
318        * @exception EnvironmentException if an error occurs
319        */
320        private static Properties getUnixShellVariables()
321            throws EnvironmentException
322        {
323            Process process = null;
324            Properties properties = new Properties();
325    
326            // Read from process here
327            BufferedReader reader = null;
328    
329            // fire up the shell and get echo'd results on stdout
330            try
331            {
332                String [] args = {getUnixEnv()};
333                process = Runtime.getRuntime().exec( args );
334                reader = new BufferedReader(
335                        new InputStreamReader( process.getInputStream() ) );
336    
337                String line = null;
338                while ( null != ( line = reader.readLine() ) )
339                {
340                    int index = line.indexOf( '=' );
341    
342                    if( -1 == index )
343                    {
344                        if( line.length() != 0 )
345                        {
346                            System.err.println(
347                              "Skipping line - could not find '=' in"
348                              + " line: '" + line + "'" );
349                        }
350                        continue;
351                    }
352    
353                    String name = line.substring( 0, index );
354                    String value = line.substring( index + 1, line.length() );
355                    properties.setProperty( name, value );
356                }
357    
358                process.waitFor();
359                reader.close();
360            }
361            catch( Throwable t )
362            {
363                throw new EnvironmentException( "NA", t );
364            }
365            finally
366            {
367                process.destroy();
368    
369                try
370                {
371                    if( null != reader )
372                    {
373                        reader.close();
374                    }
375                }
376                catch( IOException e )
377                {
378                    // ignore
379                    final boolean ignorable = true;
380                }
381            }
382    
383            // Check that we exited normally before returning an invalid output
384            if( 0 != process.exitValue() )
385            {
386                throw new EnvironmentException(
387                  "Environment process failed "
388                  + " with non-zero exit code of "
389                  + process.exitValue() );
390            }
391    
392            return properties;
393        }
394    
395    
396        /**
397         * Gets the UNIX env executable path.
398         *
399         * @return the absolute path to the env program
400         * @throws EnvironmentException if it cannot be found
401         */
402        private static String getUnixEnv() throws EnvironmentException
403        {
404            File env = new File( "/bin/env" );
405    
406            if( env.exists() && env.canRead() && env.isFile() )
407            {
408                return env.getAbsolutePath();
409            }
410    
411            env = new File( "/usr/bin/env" );
412            if( env.exists() && env.canRead() && env.isFile() )
413            {
414                return env.getAbsolutePath();
415            }
416    
417            throw new EnvironmentException(
418                    "Could not find the UNIX env executable" );
419        }
420    
421    
422        // ------------------------------------------------------------------------
423        // Private Windows Shell Operations
424        // ------------------------------------------------------------------------
425    
426    
427        /**
428         * Gets the shell used by the Windows user.
429         *
430         * @return the shell: cmd.exe or command.com.
431         */
432        private static String getWindowsUserShell()
433        {
434            if( null != m_SHELL )
435            {
436                return m_SHELL;
437            }
438    
439            if( -1 != OSNAME.indexOf( "98" )
440              || -1 != OSNAME.indexOf( "95" )
441              || -1 != OSNAME.indexOf( "Me" ) )
442            {
443                m_SHELL = "command.com";
444                return m_SHELL;
445            }
446    
447            m_SHELL = "cmd.exe";
448            return m_SHELL;
449        }
450    
451    
452        /**
453         * Adds a set of Windows variables to a set of properties.
454         * @return the environment properties
455         * @exception EnvironmentException if an error occurs
456         */
457        private static Properties getWindowsShellVariables()
458            throws EnvironmentException
459        {
460            String line = null;
461            Process process = null;
462            BufferedReader reader = null;
463            Properties properties = new Properties();
464    
465            // build the command based on the shell used: cmd.exe or command.com
466            StringBuffer buffer = new StringBuffer( getWindowsUserShell() );
467            buffer.append( " /C SET" );
468    
469            // fire up the shell and get echo'd results on stdout
470            try
471            {
472                process = Runtime.getRuntime().exec( buffer.toString() );
473                reader = new BufferedReader(
474                        new InputStreamReader( process.getInputStream() ) );
475                while ( null != ( line = reader.readLine() ) )
476                {
477                    int index = line.indexOf( '=' );
478    
479                    if( -1 == index )
480                    {
481                        System.err.println( "Skipping line - could not find '=' in"
482                                + " line: '" + line + "'" );
483                        continue;
484                    }
485    
486                    String name = line.substring( 0, index );
487                    String value = line.substring( index + 1, line.length() );
488                    properties.setProperty( name, value );
489                }
490    
491                process.waitFor();
492                reader.close();
493            }
494            catch( Throwable t )
495            {
496                throw new EnvironmentException( t );
497            }
498            finally
499            {
500                process.destroy();
501    
502                try
503                {
504                    if( null != reader )
505                    {
506                        reader.close();
507                    }
508                }
509                catch( IOException e )
510                {
511                    // ignore
512                    final boolean ignorable = true;
513                }
514            }
515    
516            if( 0 != process.exitValue() )
517            {
518                throw new EnvironmentException( "Environment process failed"
519                        + " with non-zero exit code of " + process.exitValue() );
520            }
521    
522            return properties;
523        }
524    
525    
526        /**
527         * Gets the value for a windows command shell environment variable.
528         *
529         * @param name the name of the variable
530         * @return the value of the variable
531         * @throws EnvironmentException if there is an error accessing the value
532         */
533        private static String getWindowsShellVariable( String name )
534            throws EnvironmentException
535        {
536            String value = null;
537            Process process = null;
538            BufferedReader reader = null;
539    
540            StringBuffer buffer = new StringBuffer( getWindowsUserShell() );
541            buffer.append( " /C echo %" );
542            buffer.append( name );
543            buffer.append( '%' );
544    
545            // fire up the shell and get echo'd results on stdout
546            try
547            {
548                process = Runtime.getRuntime().exec( buffer.toString() );
549                reader = new BufferedReader(
550                        new InputStreamReader( process.getInputStream() ) );
551                value = reader.readLine();
552                process.waitFor();
553                reader.close();
554            }
555            catch( Throwable t )
556            {
557                throw new EnvironmentException( name, t );
558            }
559            finally
560            {
561                process.destroy();
562    
563                try
564                {
565                    if( null != reader )
566                    {
567                        reader.close();
568                    }
569                }
570                catch( IOException e )
571                {
572                    // ignore
573                    final boolean ignorable = true;
574                }
575            }
576    
577            if( 0 == process.exitValue() )
578            {
579                // Handle situations where the env property does not exist.
580                if( value.startsWith( "%" ) && value.endsWith( "%" ) )
581                {
582                    return null;
583                }
584    
585                return value;
586            }
587    
588            throw new EnvironmentException(
589              name,
590              "Environment process failed"
591                + " with non-zero exit code of "
592                + process.exitValue() );
593        }
594    }