001 /*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 1997-2011 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.net.URISyntaxException;
045 import java.text.*;
046 import java.util.*;
047 import java.util.logging.*;
048 import org.glassfish.common.util.admin.AsadminInput;
049
050 import org.jvnet.hk2.annotations.Contract;
051 import org.jvnet.hk2.component.*;
052 import org.glassfish.api.admin.*;
053 import com.sun.enterprise.module.*;
054 import com.sun.enterprise.module.single.StaticModulesRegistry;
055
056 import com.sun.enterprise.admin.cli.remote.*;
057 import com.sun.enterprise.universal.i18n.LocalStringsImpl;
058 import com.sun.enterprise.universal.io.SmartFile;
059 import com.sun.enterprise.universal.glassfish.ASenvPropertyReader;
060 import com.sun.enterprise.util.JDK;
061 import com.sun.enterprise.util.SystemPropertyConstants;
062
063 /**
064 * The asadmin main program.
065 */
066 public class AsadminMain {
067
068 private static String classPath;
069 private static String className;
070 private static String command;
071 private static ProgramOptions po;
072 private static Habitat habitat;
073 private static Logger logger;
074
075 private final static int ERROR = 1;
076 private final static int CONNECTION_ERROR = 2;
077 private final static int INVALID_COMMAND_ERROR = 3;
078 private final static int SUCCESS = 0;
079 private final static int WARNING = 4;
080
081 // XXX - move this to LogDomains?
082 private final static String ADMIN_CLI_LOGGER =
083 "com.sun.enterprise.admin.cli";
084
085 private final static String DEBUG_FLAG = "Debug";
086 private final static String ENV_DEBUG_FLAG = "AS_DEBUG";
087
088 private static final String[] copyProps = {
089 SystemPropertyConstants.INSTALL_ROOT_PROPERTY,
090 SystemPropertyConstants.CONFIG_ROOT_PROPERTY,
091 SystemPropertyConstants.PRODUCT_ROOT_PROPERTY
092 };
093
094 private static final LocalStringsImpl strings =
095 new LocalStringsImpl(AsadminMain.class);
096
097 static {
098 Map<String, String> systemProps = new ASenvPropertyReader().getProps();
099 for (String prop : copyProps) {
100 String val = systemProps.get(prop);
101 if (ok(val))
102 System.setProperty(prop, val);
103 }
104 }
105
106 /**
107 * A ConsoleHandler that prints all non-SEVERE messages to System.out
108 * and all SEVERE messages to System.err.
109 */
110 private static class CLILoggerHandler extends ConsoleHandler {
111
112 private CLILoggerHandler() {
113 setFormatter(new CLILoggerFormatter());
114 }
115
116 @Override
117 public void publish(java.util.logging.LogRecord logRecord) {
118 if (!isLoggable(logRecord))
119 return;
120 final PrintStream ps = (logRecord.getLevel() == Level.SEVERE) ? System.err : System.out;
121 ps.println(getFormatter().format(logRecord));
122 }
123 }
124
125 private static class CLILoggerFormatter extends SimpleFormatter {
126
127 @Override
128 public synchronized String format(LogRecord record) {
129 return formatMessage(record);
130 }
131 }
132
133 public static void main(String[] args) {
134 int minor = JDK.getMinor();
135
136 if (minor < 6) {
137 System.err.println(strings.get("OldJdk", "" + minor));
138 System.exit(ERROR);
139 }
140
141 // bnevins 4-18-08 A quickly added trace. should clean up later.
142 // TODO TODO TODO TODO
143
144 // System Prop just needs to exist
145 // Env Var. needs to be set to "true"
146 String sys = System.getProperty(DEBUG_FLAG);
147 boolean env = Boolean.parseBoolean(System.getenv(ENV_DEBUG_FLAG));
148 boolean trace = Boolean.parseBoolean(System.getenv("AS_TRACE"));
149 boolean debug = sys != null || env;
150
151 /*
152 * Use a logger associated with the top-most package that we
153 * expect all admin commands to share. Only this logger and
154 * its children obey the conventions that map terse=false to
155 * the INFO level and terse=true to the FINE level.
156 */
157 logger = Logger.getLogger(ADMIN_CLI_LOGGER);
158 if (trace)
159 logger.setLevel(Level.FINEST);
160 else if (debug)
161 logger.setLevel(Level.FINER);
162 else {
163 logger.setLevel(Level.FINE);
164 }
165 logger.setUseParentHandlers(false);
166 Handler h = new CLILoggerHandler();
167 h.setLevel(logger.getLevel());
168 logger.addHandler(h);
169
170 // make sure the root logger uses our handler as well
171 Logger rlogger = Logger.getLogger("");
172 rlogger.setUseParentHandlers(false);
173 for (Handler lh : rlogger.getHandlers())
174 rlogger.removeHandler(lh);
175 rlogger.addHandler(h);
176
177 if (CLIConstants.debugMode) {
178 System.setProperty(CLIConstants.WALL_CLOCK_START_PROP,
179 "" + System.currentTimeMillis());
180 logger.finer("CLASSPATH= " +
181 System.getProperty("java.class.path") +
182 "\nCommands: " + Arrays.toString(args));
183 }
184
185 /*
186 * Create a ClassLoader to load from all the jar files in the
187 * lib/asadmin directory. This directory can contain extension
188 * jar files with new local asadmin commands.
189 */
190 ClassLoader ecl = AsadminMain.class.getClassLoader();
191 try {
192 File inst = new File(System.getProperty(
193 SystemPropertyConstants.INSTALL_ROOT_PROPERTY));
194 File ext = new File(new File(inst, "lib"), "asadmin");
195 logger.finer(
196 "asadmin extension directory: " + ext);
197 if (ext.isDirectory())
198 ecl = new DirectoryClassLoader(ext, ecl);
199 else
200 logger.info(
201 strings.get("ExtDirMissing", ext));
202 } catch (IOException ex) {
203 // any failure here is fatal
204 logger.info(
205 strings.get("ExtDirFailed", ex));
206 System.exit(1);
207 }
208
209 /*
210 * Set the thread's context class laoder so that everyone can
211 * load from our extension directory.
212 */
213 Thread.currentThread().setContextClassLoader(ecl);
214
215 /*
216 * Create a habitat that can load from the extension directory.
217 */
218 ModulesRegistry registry = new StaticModulesRegistry(ecl);
219 habitat = registry.createHabitat("default");
220
221 classPath =
222 SmartFile.sanitizePaths(System.getProperty("java.class.path"));
223 className = AsadminMain.class.getName();
224
225 /*
226 * Special case: no arguments is the same as "multimode".
227 */
228 if (args.length == 0)
229 args = new String[] { "multimode" };
230
231 /*
232 * Special case: -V argument is the same as "version".
233 */
234 if (args[0].equals("-V"))
235 args = new String[] { "version" };
236
237 command = args[0];
238 int exitCode = executeCommand(args);
239
240 switch (exitCode) {
241 case SUCCESS:
242 if (!po.isTerse())
243 logger.fine(
244 strings.get("CommandSuccessful", command));
245 break;
246
247 case WARNING:
248 logger.fine(
249 strings.get("CommandSuccessfulWithWarnings", command));
250 exitCode = SUCCESS;
251 break;
252
253 case ERROR:
254 logger.fine(
255 strings.get("CommandUnSuccessful", command));
256 break;
257
258 case INVALID_COMMAND_ERROR:
259 logger.fine(
260 strings.get("CommandUnSuccessful", command));
261 break;
262
263 case CONNECTION_ERROR:
264 logger.fine(
265 strings.get("CommandUnSuccessful", command));
266 break;
267 }
268 CLIUtil.writeCommandToDebugLog(args, exitCode);
269 System.exit(exitCode);
270 }
271
272 public static int executeCommand(String[] argv) {
273 CLICommand cmd = null;
274 Environment env = new Environment();
275 try {
276
277 // if the first argument is an option, we're using the new form
278 if (argv.length > 0 && argv[0].startsWith("-")) {
279 /*
280 * Parse all the asadmin options, stopping at the first
281 * non-option, which is the command name.
282 */
283 Parser rcp = new Parser(argv, 0,
284 ProgramOptions.getValidOptions(), false);
285 ParameterMap params = rcp.getOptions();
286 po = new ProgramOptions(params, env);
287 readAndMergeOptionsFromAuxInput(po);
288 List<String> operands = rcp.getOperands();
289 argv = operands.toArray(new String[operands.size()]);
290 } else
291 po = new ProgramOptions(env);
292 po.toEnvironment(env);
293 po.setClassPath(classPath);
294 po.setClassName(className);
295 if (argv.length == 0) {
296 if (po.isHelp())
297 argv = new String[] { "help" };
298 else
299 argv = new String[] { "multimode" };
300 }
301 command = argv[0];
302
303 habitat.addComponent("environment", env);
304 habitat.addComponent("program-options", po);
305 cmd = CLICommand.getCommand(habitat, command);
306 return cmd.execute(argv);
307 } catch (CommandValidationException cve) {
308 logger.severe(cve.getMessage());
309 if (cmd == null) // error parsing program options
310 printUsage();
311 else
312 logger.severe(cmd.getUsage());
313 return ERROR;
314 } catch (InvalidCommandException ice) {
315 // find closest match with local or remote commands
316 logger.severe(ice.getMessage());
317 try {
318 CLIUtil.displayClosestMatch(command,
319 CLIUtil.getAllCommands(habitat, po, env),
320 strings.get("ClosestMatchedLocalAndRemoteCommands"), logger);
321 } catch (InvalidCommandException e) {
322 // not a big deal if we cannot help
323 }
324 return ERROR;
325 } catch (CommandException ce) {
326 if (ce.getCause() instanceof java.net.ConnectException) {
327 // find closest match with local commands
328 logger.severe(ce.getMessage());
329 try {
330 CLIUtil.displayClosestMatch(command,
331 CLIUtil.getLocalCommands(habitat),
332 strings.get("ClosestMatchedLocalCommands"), logger);
333 } catch (InvalidCommandException e) {
334 logger.info(
335 strings.get("InvalidRemoteCommand", command));
336 }
337 } else
338 logger.severe(ce.getMessage());
339 return ERROR;
340 }
341 }
342
343 private static void readAndMergeOptionsFromAuxInput(final ProgramOptions progOpts)
344 throws CommandException {
345 final String auxInput = progOpts.getAuxInput();
346 if (auxInput == null || auxInput.length() == 0) {
347 return;
348 }
349 final ParameterMap newParamMap = new ParameterMap();
350 /*
351 * We will place the options passed via the aux. input on the command
352 * line and we do not want to repeat the read from stdin again, so
353 * remove the aux input setting.
354 */
355 progOpts.setAuxInput(null);
356 try {
357 final AsadminInput.InputReader reader = AsadminInput.reader(auxInput);
358 final Properties newOptions = reader.settings().get("option");
359 for (String propName : newOptions.stringPropertyNames()) {
360 newParamMap.add(propName, newOptions.getProperty(propName));
361 }
362 progOpts.updateOptions(newParamMap);
363 } catch (Exception ex) {
364 throw new RuntimeException(ex);
365 }
366 }
367
368 /**
369 * Print usage message for asadmin command.
370 * XXX - should be derived from ProgramOptions.
371 */
372 private static void printUsage() {
373 logger.severe(strings.get("Asadmin.usage"));
374 }
375
376 private static boolean ok(String s) {
377 return s!= null && s.length() > 0;
378 }
379
380 /** Turned off for now -- it takes ~200 msec on a laptop!
381 private final static boolean foundClass(String s) {
382 try {
383 Class.forName(s);
384 return true;
385 } catch (Throwable t) {
386 System.out.println("Can not find class: " + s);
387 return false;
388 }
389 }
390
391 private final static String[] requiredClassnames = {
392 // one from launcher jar
393 "com.sun.enterprise.admin.launcher.GFLauncher",
394 // one from universal jar
395 "com.sun.enterprise.universal.xml.MiniXmlParser",
396 // one from glassfish bootstrap jar
397 "com.sun.enterprise.glassfish.bootstrap.ASMain",
398 // one from stax-api
399 "javax.xml.stream.XMLInputFactory",
400 // one from server-mgmt
401 "com.sun.enterprise.admin.servermgmt.RepositoryException",
402 // one from common-utils
403 "com.sun.enterprise.util.net.NetUtils",
404 // one from admin/util
405 "com.sun.enterprise.admin.util.TokenValueSet",
406 // here's one that server-mgmt is dependent on
407 "com.sun.enterprise.security.auth.realm.file.FileRealm",
408 // dol
409 "com.sun.enterprise.deployment.PrincipalImpl",
410 // kernel
411 //"com.sun.appserv.server.util.Version",
412 };
413
414 static {
415 // check RIGHT NOW to make sure all the classes we need are
416 // available
417 long start = System.currentTimeMillis();
418 boolean gotError = false;
419 for (String s : requiredClassnames) {
420 if (!foundClass(s))
421 gotError = true;
422 }
423 // final test -- see if sjsxp is available
424 try {
425 javax.xml.stream.XMLInputFactory.newInstance().getXMLReporter();
426 } catch(Throwable t) {
427 gotError = true;
428 System.out.println("Can't access STAX classes");
429 }
430 if (gotError) {
431 // messages already sent to stdout...
432 System.exit(1);
433 }
434 long stop = System.currentTimeMillis();
435 System.out.println("Time to pre-load classes = " + (stop-start) + " msec");
436 }
437 */
438 }