001    /*
002     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003     *
004     * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
005     *
006     * The contents of this file are subject to the terms of either the GNU
007     * General Public License Version 2 only ("GPL") or the Common Development
008     * and Distribution License("CDDL") (collectively, the "License").  You
009     * may not use this file except in compliance with the License.  You can
010     * obtain a copy of the License at
011     * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012     * or packager/legal/LICENSE.txt.  See the License for the specific
013     * language governing permissions and limitations under the License.
014     *
015     * When distributing the software, include this License Header Notice in each
016     * file and include the License file at packager/legal/LICENSE.txt.
017     *
018     * GPL Classpath Exception:
019     * Oracle designates this particular file as subject to the "Classpath"
020     * exception as provided by Oracle in the GPL Version 2 section of the License
021     * file that accompanied this code.
022     *
023     * Modifications:
024     * If applicable, add the following below the License Header, with the fields
025     * enclosed by brackets [] replaced by your own identifying information:
026     * "Portions Copyright [year] [name of copyright owner]"
027     *
028     * Contributor(s):
029     * If you wish your version of this file to be governed by only the CDDL or
030     * only the GPL Version 2, indicate your decision by adding "[Contributor]
031     * elects to include this software in this distribution under the [CDDL or GPL
032     * Version 2] license."  If you don't indicate a single choice of license, a
033     * recipient has the option to distribute your version of this file under
034     * either the CDDL, the GPL Version 2 or to extend the choice of license to
035     * its licensees as provided above.  However, if you add GPL Version 2 code
036     * and therefore, elected the GPL Version 2 license, then the option applies
037     * only if the new code is made subject to such option by the copyright
038     * holder.
039     */
040    
041    package com.sun.enterprise.admin.cli;
042    
043    import java.io.*;
044    import java.util.*;
045    import java.text.*;
046    import org.jvnet.hk2.component.*;
047    import org.glassfish.api.admin.*;
048    import com.sun.enterprise.admin.cli.remote.RemoteCommand;
049    import com.sun.enterprise.universal.i18n.LocalStringsImpl;
050    import java.util.logging.Logger;
051    
052    /**
053     *  CLI Utility class
054     */
055    public class CLIUtil {
056    
057        private static final int MAX_COMMANDS_TO_DISPLAY = 75;
058    
059        private static final LocalStringsImpl strings =
060                                    new LocalStringsImpl(CLIUtil.class);
061        
062        /**
063         *   Read passwords from the password file and save them in a java.util.Map.
064         *   @param passwordFileName  password file name
065         *   @param withPrefix decides whether prefix should be taken into account
066         *   @return Map of the password name and value
067         */
068        public static Map<String, String> readPasswordFileOptions(
069                            final String passwordFileName, boolean withPrefix) 
070                            throws CommandException {
071    
072            Map<String, String> passwordOptions = new HashMap<String, String>();
073            boolean readStdin = passwordFileName.equals("-");
074            InputStream is = null;
075            try {
076                is = new BufferedInputStream(
077                    readStdin ? System.in : new FileInputStream(passwordFileName));
078                final Properties prop = new Properties();
079                prop.load(is);
080                for (Object key : prop.keySet()) {
081                    final String entry = (String)key;
082                    if (entry.startsWith(Environment.AS_ADMIN_ENV_PREFIX)) {
083                        final String optionName = withPrefix ? entry :
084                          entry.substring(Environment.AS_ADMIN_ENV_PREFIX.length()).
085                                toLowerCase(Locale.ENGLISH);
086                        final String optionValue = prop.getProperty(entry);
087                        passwordOptions.put(optionName, optionValue);
088                    }
089                }
090            } catch (final Exception e) {
091                throw new CommandException(e);
092            } finally {
093                try {
094                    if (!readStdin && is != null)
095                        is.close();
096                } catch (final Exception ignore) { }
097            }
098            return passwordOptions;
099        }
100    
101        /**
102         * Display the commands from the list that are the closest match
103         * to the specified command.
104         */
105        public static void displayClosestMatch(final String commandName,
106                                   String[] commands, final String msg,
107                                   final Logger logger)
108                                   throws InvalidCommandException {
109            try {
110                // remove leading "*" and ending "*" chars
111                int beginIndex = 0;
112                int endIndex = commandName.length();
113                if (commandName.startsWith("*"))
114                    beginIndex = 1;
115                if (commandName.endsWith("*"))
116                    endIndex = commandName.length() - 1;
117                final String trimmedCommandName =
118                        commandName.substring(beginIndex, endIndex);
119    
120                // if pattern doesn't start with "_", remove hidden commands
121                if (!trimmedCommandName.startsWith("_")) {
122                    List<String> ncl = new ArrayList<String>();
123                    for (String cmd : Arrays.asList(commands))
124                        if (!cmd.startsWith("_"))
125                            ncl.add(cmd);
126                    commands = ncl.toArray(new String[ncl.size()]);
127                }
128    
129                // sort commands in alphabetical order
130                Arrays.sort(commands);
131    
132                // add all matches to the search String since we want
133                // to search all the commands that match the string
134                final String[] matchedCommands =
135                        getMatchedCommands(trimmedCommandName, commands);
136                        //".*"+trimmedCommandName+".*", commands);
137                // don't want to display more than 50 commands
138                StringWriter sw = new StringWriter();
139                PrintWriter pw = new PrintWriter(sw);
140                if (matchedCommands.length > 0 &&
141                        matchedCommands.length < MAX_COMMANDS_TO_DISPLAY) {
142                    pw.println(msg != null ? msg :
143                                       strings.get("ClosestMatchedCommands"));
144                    for (String eachCommand : matchedCommands)
145                        pw.println("    " + eachCommand);
146                } else {
147                    // find the closest distance
148                    final String nearestString = StringEditDistance.findNearest(
149                            trimmedCommandName, commands);
150                    // don't display the string if the edit distance is too large
151                    if (StringEditDistance.editDistance(
152                            trimmedCommandName, nearestString) < 5) {
153                        pw.println(msg != null? msg :
154                                           strings.get("ClosestMatchedCommands"));
155                        pw.println("    " + nearestString);
156                    } else
157                        throw new InvalidCommandException(commandName);
158                }
159                pw.flush();
160                logger.severe(sw.toString());
161            } catch (Exception e) {
162                throw new InvalidCommandException(commandName);
163            }
164        }
165    
166        /**
167         * Return all the commands that include pattern (just a literal
168         * string, not really a pattern) as a substring.
169         */
170        private static String[] getMatchedCommands(final String pattern,
171                                    final String[] commands) {
172            List<String> matchedCommands = new ArrayList<String>();
173            for (int i = 0; i < commands.length; i++) {
174                if (commands[i].indexOf(pattern) >= 0)
175                    matchedCommands.add(commands[i]);
176            }
177            return matchedCommands.toArray(new String[matchedCommands.size()]);
178        }
179    
180        /**
181         * Return all commands, local and remote.
182         *
183         * @return the commands as a String array, sorted
184         */
185        public static String[] getAllCommands(Habitat habitat, ProgramOptions po,
186                                    Environment env) {
187            try {
188                String[] remoteCommands = getRemoteCommands(habitat, po, env);
189                String[] localCommands = getLocalCommands(habitat);
190                String[] allCommands =
191                        new String[localCommands.length + remoteCommands.length];
192                System.arraycopy(localCommands, 0,
193                    allCommands, 0, localCommands.length);
194                System.arraycopy(remoteCommands, 0,
195                    allCommands, localCommands.length, remoteCommands.length);
196                Arrays.sort(allCommands);
197                return allCommands;
198            } catch (CommandValidationException cve) {
199                return null;
200            } catch (CommandException ce) {
201                return null;
202            }
203        }
204    
205        /**
206         * Get all the known local commands.
207         *
208         * @return the commands as a String array, sorted
209         */
210        public static String[] getLocalCommands(Habitat habitat) {
211            List<String> names = new ArrayList<String>();
212    
213            String cname = CLICommand.class.getName();
214            for (Inhabitant<?> command : habitat.getInhabitantsByContract(cname)) {
215                for (String name : Inhabitants.getNamesFor(command, cname))
216                    names.add(name);
217            }
218            String[] localCommands = names.toArray(new String[names.size()]);
219            Arrays.sort(localCommands);
220            return localCommands;
221        }
222    
223        /**
224         * Get the list of commands from the remote server.
225         *
226         * @return the commands as a String array, sorted
227         */
228        public static String[] getRemoteCommands(Habitat habitat,
229                ProgramOptions po, Environment env)
230                throws CommandException, CommandValidationException {
231            /*
232             * In order to eliminate all local command names from the list
233             * of remote commands, we collect the local command names into
234             * a HashSet that we check later when collecting remote command
235             * names.
236             */
237            Set<String> localnames = new HashSet<String>();
238            String cname = CLICommand.class.getName();
239            for (Inhabitant<?> command : habitat.getInhabitantsByContract(cname)) {
240                for (String name : Inhabitants.getNamesFor(command, cname))
241                    localnames.add(name);
242            }
243    
244            /*
245             * Now get the list of remote commands.
246             */
247            RemoteCommand cmd =
248                new RemoteCommand("list-commands", po, env);
249            String cmds = cmd.executeAndReturnOutput("list-commands");
250            List<String> rcmds = new ArrayList<String>();
251            BufferedReader r = new BufferedReader(new StringReader(cmds));
252            String line;
253    
254            /*
255             * The output of the remote list-commands command is a bunch of
256             * lines of the form:
257             * Command : cmd-name
258             * We extract the command name from each such line.
259             * XXX - depending on this output format is gross;
260             * should be able to send --terse to remote command
261             * to cause it to produce exactly the output we want.
262             */
263            try {
264                while ((line = r.readLine()) != null) {
265                    if (line.startsWith("Command")) {
266                        int i = line.indexOf(':');
267                        if (i < 0)
268                            continue;
269                        String s = line.substring(i + 1).trim();
270                        // add it if it's not a local command
271                        if (!localnames.contains(s))
272                            rcmds.add(s);
273                    }
274                }
275            } catch (IOException ioex) {
276                // ignore it, will never happen
277            }
278            Collections.sort(rcmds);
279            String[] remoteCommands = rcmds.toArray(new String[rcmds.size()]);
280            Arrays.sort(remoteCommands);
281            return remoteCommands;
282        }
283    
284        /**
285         * Log the command, for debugging.
286         */
287        public static void writeCommandToDebugLog(String[] args, int exit) {
288            File log = getDebugLogfile();
289    
290            if (log == null)
291                return;
292    
293            BufferedWriter out = null;
294            try {
295                out = new BufferedWriter(new FileWriter(log, true));
296                DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
297                Date date = new Date();
298                out.write(dateFormat.format(date));
299                out.write(" EXIT: " + exit);
300    
301                out.write(" asadmin ");
302    
303                if (args != null) {
304                   for (int i = 0; args != null && i < args.length; ++i) {
305                        out.write(args[i] + " ");
306                   }
307                }
308            } catch (IOException e) {
309                // It is just a debug file.
310            } finally {
311                if (out != null) {
312                    try {
313                        out.write("\n");
314                        out.close();
315                    } catch (Exception e) {
316                        // ignore
317                    }
318                }
319            }
320        }
321    
322        private static File getDebugLogfile() {
323            // System Prop trumps environmental variable
324            String fname =
325                System.getProperty(CLIConstants.CLI_RECORD_ALL_COMMANDS_PROP);
326            if (fname == null)
327                fname = System.getenv(CLIConstants.CLI_RECORD_ALL_COMMANDS_PROP);
328            if (fname == null)
329                return null;
330    
331            File f = new File(fname);
332    
333            if (!f.exists()) {
334                try {
335                    f.createNewFile();
336                } catch (IOException e) { /* ignore */ }
337            }
338    
339            if (f.isFile() && f.canWrite())
340                return f;
341            else
342                return null;
343        }
344    }