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 }