1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest.agent; 28 29 import java.io.IOException; 30 import java.io.PrintStream; 31 import java.lang.reflect.Constructor; 32 import java.lang.reflect.InvocationTargetException; 33 import java.util.HashMap; 34 35 import com.sun.javatest.JavaTestSecurityManager; 36 import com.sun.javatest.Status; 37 38 /** 39 * Start an agent, based on command line arguments. 40 * No GUI is used. To create an agent with a GUI, see 41 * AgentApplet or AgentFrame. 42 * 43 * @see Agent 44 * 45 **/ 46 public class AgentMain { 47 48 /** 49 * This exception is used to report bad command line arguments. 50 */ 51 public static class BadArgs extends Exception 52 { 53 /** 54 * Create a BadArgs exception. 55 * @param msg A detail message about an error that has been found. 56 */ 57 public BadArgs(String msg) { 58 this(new String[] { msg }); 59 } 60 61 /** 62 * Create a BadArgs object. 63 * @param msgs Detailed message about an error that has been found. 64 */ 65 public BadArgs(String[] msgs) { 66 super(msgs[0]); 67 this.msgs = msgs; 68 } 69 70 /** 71 * Get the detail messages. 72 * @return the messages given when this exception was created. 73 */ 74 public String[] getMessages() { 75 return msgs; 76 } 77 78 private String[] msgs; 79 } 80 81 /** 82 * This exception is used to report problems that occur while running. 83 */ 84 public static class Fault extends Exception 85 { 86 /** 87 * Create a Fault exception. 88 * @param msg A detail message about a fault that has occurred. 89 */ 90 public Fault(String msg) { 91 this(new String[] { msg }); 92 } 93 94 /** 95 * Create a Fault object. 96 * @param msgs A detail message about a fault that has been found. 97 */ 98 public Fault(String[] msgs) { 99 super(msgs[0]); 100 this.msgs = msgs; 101 } 102 103 /** 104 * Get the detail messages. 105 * @return the messages given when this exception was created. 106 */ 107 public String[] getMessages() { 108 return msgs; 109 } 110 111 private String[] msgs; 112 } 113 114 /** 115 * Create and start an Agent, based on the supplied command line arguments. 116 * 117 * @param args The command line arguments 118 * <table> 119 * <tr><td> -help <td> print a short summary of the command usage 120 * <tr><td> -usage <td> print a short summary of the command usage 121 * <tr><td> -active <td> set mode to be active 122 * <tr><td> -activeHost <em>hostname</em> <td> set the host for active connections (implies -active) 123 * <tr><td> -activePort <em>port</em> <td> set the port for active connections (implies -active) 124 * <tr><td> -passive <td> set mode to be passive 125 * <tr><td> -passivePort <em>port</em> <td> set the port for passive connections (implies -passive) 126 * <tr><td> -concurrency <em>number</em> <td> set the maximum number of simultaneous connections 127 * <tr><td> -map <em>file</em> <td> map file for translating arguments of incoming requests 128 * <tr><td> -trace <td> trace the execution of the agent 129 * <tr><td> -observer <em>classname</em> <td> add an observer to the agent that is used 130 * </table> 131 */ 132 public static void main(String[] args) { 133 AgentMain m = new AgentMain(); 134 m.runAndExit(args); 135 } 136 137 138 /** 139 * Create and start an Agent, based on the supplied command line arguments. 140 * This is for use by subtypes with their own main(String[]) entry point. 141 * @param args the command line arguments for the run 142 */ 143 protected void runAndExit(String[] args) { 144 // install our own permissive security manager, to prevent anyone else 145 // installing a less permissive one; moan if it can't be installed. 146 JavaTestSecurityManager.install(); 147 148 if (Boolean.getBoolean("javatest.trace.printargs") && 149 args != null && args.length > 0) { 150 StringBuffer fullCmd = new StringBuffer(); 151 StringBuffer incrementalCmd = new StringBuffer(); 152 153 for (int i = 0; i < args.length; i++) { 154 fullCmd.append(args[i]); 155 fullCmd.append(" "); 156 157 incrementalCmd.append("// "); 158 incrementalCmd.append(args[i]); 159 incrementalCmd.append("\n"); 160 } // for 161 162 System.out.println(fullCmd.toString().trim()); 163 System.out.println(incrementalCmd.toString()); 164 } 165 166 int rc; 167 try { 168 run(args); 169 rc = 0; 170 } 171 catch (BadArgs e) { 172 System.err.println("Error: Bad arguments"); 173 String[] msgs = e.getMessages(); 174 for (int i = 0; i < msgs.length; i++) 175 System.err.println(msgs[i]); 176 System.err.println(); 177 usage(System.err); 178 rc = 1; 179 } 180 catch (Fault e) { 181 String[] msgs = e.getMessages(); 182 for (int i = 0; i < msgs.length; i++) 183 System.err.println(msgs[i]); 184 rc = 2; 185 } 186 catch (Throwable t) { 187 t.printStackTrace(); 188 rc = 3; 189 } 190 191 // If the JT security manager is installed, it won't allow a call of 192 // System.exit unless we ask it nicely.. 193 SecurityManager sc = System.getSecurityManager(); 194 if (sc instanceof JavaTestSecurityManager) 195 ((JavaTestSecurityManager) sc).setAllowExit(true); 196 197 System.exit(rc); 198 } 199 200 /** 201 * Run with the specified command-lines options. 202 * This consists of the following steps: 203 * <ul> 204 * <li>The command line options are decoded with decodeAllArgs 205 * <li>Validity checks (such as option inter-dependencies) are done by validateArgs 206 * <li>An agent is created with createAgent 207 * <li>The agent is run. 208 * </ul> 209 * @param args An array of strings, typically provided via the command line 210 * @throws AgentMain.BadArgs if a problem is found in the arguments provided 211 * @throws AgentMain.Fault if a fault is found while running 212 * @see #main 213 * @see #decodeAllArgs 214 * @see #validateArgs 215 * @see #createAgent 216 */ 217 public void run(String[] args) throws BadArgs, Fault { 218 decodeAllArgs(args); 219 220 if (helpRequested) { 221 usage(System.err); 222 return; 223 } 224 225 validateArgs(); 226 227 Agent agent = createAgent(); 228 agent.addObserver(new ErrorObserver()); 229 agent.run(); 230 } 231 232 /** 233 * Decode an array of command line options, by calling decodeArg for 234 * successive options in the array. 235 * @param args the array of command line options 236 * @throws AgentMain.BadArgs if a problem is found decoding the args 237 * @throws AgentMain.Fault if the args can be decoded successfully but 238 * if there is a problem in their interpretation (e.g invalid port number) 239 */ 240 protected void decodeAllArgs(String[] args) throws BadArgs, Fault { 241 int i = 0; 242 while (i < args.length) { 243 int used = decodeArg(args, i); 244 if (used == 0 ) 245 throw new BadArgs("Unrecognised option: " + args[i]); 246 i += used; 247 } 248 } 249 250 /** 251 * Decode the next command line option in an array of options. 252 * @param args the array of command line options 253 * @param index the position of the next option to be decoded 254 * @return the number of elements consumed from the array 255 * @throws AgentMain.BadArgs if a problem is found decoding the args 256 * @throws AgentMain.Fault if the args can be decoded successfully but 257 * if there is a problem in their interpretation (e.g invalid port number) 258 */ 259 protected int decodeArg(String[] args, int index) throws BadArgs, Fault { 260 int i = index; 261 try { 262 if (args[i].equalsIgnoreCase("-active")) { 263 mode = ACTIVE; 264 modeCheck |= (1 << mode); 265 return 1; 266 } 267 else if (args[i].equalsIgnoreCase("-passive")) { 268 mode = PASSIVE; 269 modeCheck |= (1 << mode); 270 return 1; 271 } 272 else if (args[i].equalsIgnoreCase("-activeHost")) { 273 mode = ACTIVE; 274 modeCheck |= (1 << mode); 275 activeHost = args[++i]; 276 return 2; 277 } 278 else if (args[i].equalsIgnoreCase("-activePort")) { 279 mode = ACTIVE; 280 modeCheck |= (1 << mode); 281 activePort = Integer.parseInt(args[++i]); 282 return 2; 283 } 284 else if (args[i].equalsIgnoreCase("-passivePort")) { 285 mode = PASSIVE; 286 modeCheck |= (1 << mode); 287 passivePort = Integer.parseInt(args[++i]); 288 return 2; 289 } 290 else if (args[i].equalsIgnoreCase("-serialPort")) { 291 mode = SERIAL; 292 modeCheck |= (1 << mode); 293 serialPort = args[++i]; 294 return 2; 295 } 296 else if (args[i].equalsIgnoreCase("-concurrency")) { 297 concurrency = Integer.parseInt(args[++i]); 298 return 2; 299 } 300 else if (args[i].equalsIgnoreCase("-map")) { 301 mapFile = args[++i]; 302 return 2; 303 } else if (args[i].equalsIgnoreCase("-mapArg")) { 304 mappedArgs.put(args[++i], args[++i]); 305 return 3; 306 } 307 else if (args[i].equalsIgnoreCase("-trace")) { 308 tracing = true; 309 return 1; 310 } 311 else if ("-observer".equalsIgnoreCase(args[i]) && i < args.length - 1) { 312 if (observerClassName != null) 313 throw new BadArgs("duplicate use of -observer"); 314 observerClassName = args[++i]; 315 return 2; 316 } 317 else if (args[i].equalsIgnoreCase("-help") || args[i].equalsIgnoreCase("-usage") ) { 318 helpRequested = true; 319 return (args.length - index); // consume remaining args 320 } 321 else 322 return 0; // unrecognized 323 } 324 catch (ArrayIndexOutOfBoundsException e) { 325 throw new BadArgs("Missing argument for " + args[args.length - 1]); 326 } 327 catch (NumberFormatException e) { 328 throw new BadArgs("Number expected: " + args[i]); 329 } 330 } 331 332 /** 333 * Validate the values decoded by decodeAllArgs. 334 * @throws AgentMain.BadArgs if a problem is found validating the args that 335 * is likely caused by a misunderstanding of the command line options or syntax 336 * @throws AgentMain.Fault if there is some other problem with the args, such 337 * as a bad host name or a port not being available for use 338 */ 339 protected void validateArgs() throws BadArgs, Fault { 340 if (modeCheck == 0) 341 throw new BadArgs("No connection options given"); 342 343 if (modeCheck != (1 << mode)) 344 throw new BadArgs("Conflicting options for connection to JT Harness harness"); 345 346 switch (mode) { 347 case ACTIVE: 348 if (activeHost == null || activeHost.length() == 0) 349 throw new BadArgs("No active host specified"); 350 if (activePort <= 0) 351 throw new BadArgs("No active port specified"); 352 break; 353 354 case SERIAL: 355 if (serialPort == null) 356 throw new BadArgs("No serial port specified"); 357 } 358 359 if (!Agent.isValidConcurrency(concurrency)) { 360 throw new BadArgs("Bad value for concurrency: " + concurrency); 361 } 362 } 363 364 /** 365 * Create a connection factory based on the values decoded by decodeAllArgs 366 * Normally called from createAgent. 367 * @return a connection factory based on the values decoded by decodeAllArgs 368 * @throws AgentMain.Fault if there is a problem createing the factory 369 */ 370 protected ConnectionFactory createConnectionFactory() throws Fault { 371 String s = AgentMain.class.getName(); 372 String pkg = s.substring(0, s.lastIndexOf('.')); 373 374 switch (mode) { 375 case ACTIVE: 376 try { 377 Class<?> c = Class.forName(pkg + ".ActiveConnectionFactory"); 378 Constructor<?> m = c.getConstructor(new Class<?>[] {String.class, int.class}); 379 Object[] args = { activeHost, new Integer(activePort) }; 380 return (ConnectionFactory)(m.newInstance(args)); 381 } 382 catch (Throwable e) { 383 Throwable t = unwrapInvocationTargetException(e); 384 String[] msgs = { 385 "Error occurred while trying to start an active agent", 386 t.toString(), 387 "Are the java.net classes available?" 388 }; 389 throw new Fault(msgs); 390 } 391 392 case PASSIVE: 393 try { 394 Class<?> c = Class.forName(pkg + ".PassiveConnectionFactory"); 395 Constructor<?> m = c.getConstructor(new Class<?>[] {int.class, int.class}); 396 Object[] args = { new Integer(passivePort), new Integer(concurrency + 1) }; 397 return (ConnectionFactory)(m.newInstance(args)); 398 } 399 catch (Throwable e) { 400 Throwable t = unwrapInvocationTargetException(e); 401 if (t instanceof IOException) 402 throw new Fault("Cannot create socket on port " + passivePort); 403 else { 404 String[] msgs = { 405 "Error occurred while trying to start a passive agent", 406 t.toString(), 407 "Are the java.net classes available?" 408 }; 409 throw new Fault(msgs); 410 } 411 } 412 413 case SERIAL: 414 try { 415 Class<?> c = Class.forName(pkg + ".SerialPortConnectionFactory"); 416 Constructor<?> m = c.getConstructor(new Class<?>[] {String.class, String.class, int.class}); 417 Object[] args = {serialPort, Agent.PRODUCT_NAME, new Integer(10*1000)}; 418 return (ConnectionFactory)(m.newInstance(args)); 419 } 420 catch (InvocationTargetException e) { 421 Throwable t = e.getTargetException(); 422 if (t instanceof IllegalArgumentException || 423 t.getClass().getName().equals("gnu.io.NoSuchPortException")) { 424 throw new Fault(serialPort + " is not a valid port"); 425 } 426 else { 427 String[] msgs = { 428 "Error occurred while trying to access the communication ports", 429 t.toString(), 430 "Is the gnu.io extension installed?" 431 }; 432 throw new Fault(msgs); 433 } 434 } 435 catch (Throwable e) { 436 String[] msgs = { 437 "Error occurred while trying to access the communication ports", 438 e.toString(), 439 "Is the gnu.io extension installed?" 440 }; 441 throw new Fault(msgs); 442 } 443 444 default: 445 throw new Error("unexpected mode"); 446 } 447 } 448 449 /** 450 * Create an agent based on the command line options previously decoded. 451 * createConnectionFactory() is used to create the connection factory required 452 * by the agent. 453 * @return an agent based on the values decoded by decodeAllArgs 454 * @throws AgentMain.Fault if there is a problem createing the agent 455 */ 456 protected Agent createAgent() throws Fault { 457 ConnectionFactory cf = createConnectionFactory(); 458 Agent agent = new Agent(cf, concurrency); 459 agent.setTracing(tracing); 460 461 if (observerClassName != null) { 462 try { 463 Class<? extends Agent.Observer> observerClass = 464 Class.forName(observerClassName).asSubclass(Agent.Observer.class); 465 Agent.Observer observer = observerClass.getDeclaredConstructor().newInstance(); 466 agent.addObserver(observer); 467 } 468 catch (ClassCastException e) { 469 throw new Fault("observer is not of type " + 470 Agent.Observer.class.getName() + ": " + observerClassName); 471 } 472 catch (ClassNotFoundException e) { 473 throw new Fault("cannot find observer class: " + observerClassName); 474 } 475 catch (Exception e) { 476 throw new Fault("problem instantiating observer: " + e); 477 } 478 } 479 480 // for now, we only read a map file if one is explicitly specified; 481 // in JDK 1.1, it might be nice to check if <<HOSTNAME>>.jtm exists 482 // or something like that. In JDK 1.0.2, getting the HOSTNAME is 483 // problematic, because INetAddress fails the verifier. 484 if (mapFile != null) { 485 try { 486 agent.setMap(Map.readFileOrURL(mapFile)); 487 } 488 catch (IOException e) { 489 String[] msgs = {"Problem reading map file", e.toString()}; 490 throw new Fault(msgs); 491 } 492 } else if (mappedArgs.size() > 0) { 493 agent.setMap(new Map(mappedArgs)); 494 } 495 496 Integer delay = Integer.getInteger("agent.retry.delay"); 497 if (delay != null) 498 agent.setRetryDelay(delay.intValue()); 499 500 return agent; 501 } 502 503 /** 504 * Display the set of options recognized by main(String[] args). 505 * @param out a stream to which to write the information 506 */ 507 public void usage(PrintStream out) { 508 String className = getClass().getName(); 509 out.println("Usage:"); 510 out.println(" java " + className + " [options]"); 511 out.println(" -help print this message"); 512 out.println(" -usage print this message"); 513 out.println(" -active set mode to be active"); 514 out.println(" -activeHost host set the host for active connections (implies -active)"); 515 out.println(" -activePort port set the port for active connections (implies -active)"); 516 out.println(" -passive set mode to be passive"); 517 out.println(" -passivePort port set the port for passive connections (implies -passive)"); 518 out.println(" -serialPort port set the port for serial port connections"); 519 out.println(" -map file map file for translating arguments of incoming requests"); 520 out.println(" -mapArg from to map \"from\" arg to \"to\" arg for incoming requests"); 521 out.println(" -concurrency num set the maximum number of simultaneous connections"); 522 out.println(" -trace trace the execution of the agent"); 523 out.println(" -observer class add an observer to the agent"); 524 } 525 526 /** 527 * Unwrap an InvocationTargetException. 528 * @param t the exception to be unwrapped 529 * @return the argument's target exception if the argument is an 530 * InvocationTargetException; otherwise the argument itself is returned. 531 */ 532 protected static Throwable unwrapInvocationTargetException(Throwable t) { 533 if (t instanceof InvocationTargetException) 534 return ((InvocationTargetException) t).getTargetException(); 535 else 536 return t; 537 } 538 539 private boolean helpRequested = false; 540 private int mode = 0; 541 private int modeCheck = 0; 542 private String activeHost = null; 543 private int activePort = Agent.defaultActivePort; 544 private int passivePort = Agent.defaultPassivePort; 545 private String serialPort = null; 546 private int concurrency = 1; 547 private String mapFile = null; 548 private java.util.Map<String, String> mappedArgs = new HashMap<String, String>(); 549 private String observerClassName; 550 private boolean tracing; 551 552 private static final int ACTIVE = 1; 553 private static final int PASSIVE = 2; 554 private static final int SERIAL = 3; 555 556 private static final int max(int a, int b) { 557 return (a > b ? a : b); 558 } 559 560 private static class ErrorObserver implements Agent.Observer { 561 ErrorObserver() { 562 try { 563 connectExceptionClass = Class.forName("java.net.ConnectException", true, ClassLoader.getSystemClassLoader()); 564 } 565 catch (Throwable t) { 566 // ignore 567 } 568 569 try { 570 unknownHostExceptionClass = Class.forName("java.net.UnknownHostException", true, ClassLoader.getSystemClassLoader()); 571 } 572 catch (Throwable t) { 573 // ignore 574 } 575 } 576 577 public void started(Agent agent) { 578 } 579 580 public void errorOpeningConnection(Agent agent, Exception e) { 581 if (connectExceptionClass != null && connectExceptionClass.isInstance(e)) { 582 long now = System.currentTimeMillis(); 583 if (lastNotRespondMsgTime + lastNotRespondMsgInterval < now) { 584 System.err.println("host not responding: " + e.getMessage()); 585 lastNotRespondMsgTime = now; 586 } 587 } 588 else if (unknownHostExceptionClass != null && unknownHostExceptionClass.isInstance(e)) 589 System.err.println("unknown host: " + e.getMessage()); 590 else 591 System.err.println("error connecting to host: " + e); 592 } 593 594 private long lastNotRespondMsgTime = 0; 595 private int lastNotRespondMsgInterval = 596 max(Integer.getInteger("notResponding.message.interval", 60).intValue(), 10)*1000; 597 598 public void finished(Agent agent) { 599 } 600 601 public void openedConnection(Agent agent, Connection c) { 602 } 603 604 public void execTest(Agent agent, Connection c, String tag, String className, String[] args) { 605 } 606 607 public void execCommand(Agent agent, Connection c, String tag, String className, String[] args) { 608 } 609 610 public void execMain(Agent agent, Connection c, String tag, String className, String[] args) { 611 } 612 613 public void result(Agent agent, Connection c, Status result) { 614 } 615 616 public void exception(Agent agent, Connection c, Throwable e) { 617 } 618 619 public void completed(Agent agent, Connection c) { 620 } 621 622 private Class<?> connectExceptionClass; 623 private Class<?> unknownHostExceptionClass; 624 } 625 }