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.productName, 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 observerClass = Class.forName(observerClassName);
 464                 Agent.Observer observer = (Agent.Observer)(observerClass.newInstance());
 465                 agent.addObserver(observer);
 466             }
 467             catch (ClassCastException e) {
 468                 throw new Fault("observer is not of type " +
 469                         Agent.Observer.class.getName() + ": " + observerClassName);
 470             }
 471             catch (ClassNotFoundException e) {
 472                 throw new Fault("cannot find observer class: " + observerClassName);
 473             }
 474             catch (IllegalAccessException e) {
 475                 throw new Fault("problem instantiating observer: " + e);
 476             }
 477             catch (InstantiationException e) {
 478                 throw new Fault("problem instantiating observer: " + e);
 479             }
 480         }
 481 
 482         // for now, we only read a map file if one is explicitly specified;
 483         // in JDK 1.1, it might be nice to check if <<HOSTNAME>>.jtm exists
 484         // or something like that. In JDK 1.0.2, getting the HOSTNAME is
 485         // problematic, because INetAddress fails the verifier.
 486         if (mapFile != null) {
 487             try {
 488                 agent.setMap(Map.readFileOrURL(mapFile));
 489             }
 490             catch (IOException e) {
 491                 String[] msgs = {"Problem reading map file", e.toString()};
 492                 throw new Fault(msgs);
 493             }
 494         } else if (mappedArgs.size() > 0) {
 495             agent.setMap(new Map(mappedArgs));
 496         }
 497 
 498         Integer delay = Integer.getInteger("agent.retry.delay");
 499         if (delay != null)
 500             agent.setRetryDelay(delay.intValue());
 501 
 502         return agent;
 503     }
 504 
 505     /**
 506      * Display the set of options recognized by main(String[] args).
 507      * @param out a stream to which to write the information
 508      */
 509     public void usage(PrintStream out) {
 510         String className = getClass().getName();
 511         out.println("Usage:");
 512         out.println("    java " + className + " [options]");
 513         out.println("        -help             print this message");
 514         out.println("        -usage            print this message");
 515         out.println("        -active           set mode to be active");
 516         out.println("        -activeHost host  set the host for active connections (implies -active)");
 517         out.println("        -activePort port  set the port for active connections (implies -active)");
 518         out.println("        -passive          set mode to be passive");
 519         out.println("        -passivePort port set the port for passive connections (implies -passive)");
 520         out.println("        -serialPort port  set the port for serial port connections");
 521         out.println("        -map file         map file for translating arguments of incoming requests");
 522         out.println("        -mapArg from to   map \"from\" arg to \"to\" arg for incoming requests");
 523         out.println("        -concurrency num  set the maximum number of simultaneous connections");
 524         out.println("        -trace            trace the execution of the agent");
 525         out.println("        -observer class   add an observer to the agent");
 526     }
 527 
 528     /**
 529      * Unwrap an InvocationTargetException.
 530      * @param t the exception to be unwrapped
 531      * @return the argument's target exception if the argument is an
 532      * InvocationTargetException; otherwise the argument itself is returned.
 533      */
 534     protected static Throwable unwrapInvocationTargetException(Throwable t) {
 535         if (t instanceof InvocationTargetException)
 536             return ((InvocationTargetException) t).getTargetException();
 537         else
 538             return t;
 539     }
 540 
 541     private boolean helpRequested = false;
 542     private int mode = 0;
 543     private int modeCheck = 0;
 544     private String activeHost = null;
 545     private int activePort = Agent.defaultActivePort;
 546     private int passivePort = Agent.defaultPassivePort;
 547     private String serialPort = null;
 548     private int concurrency = 1;
 549     private String mapFile = null;
 550     private java.util.Map<String, String> mappedArgs = new HashMap<String, String>();
 551     private String observerClassName;
 552     private boolean tracing;
 553 
 554     private static final int ACTIVE = 1;
 555     private static final int PASSIVE = 2;
 556     private static final int SERIAL = 3;
 557 
 558     private static final int max(int a, int b) {
 559         return (a > b ? a : b);
 560     }
 561 
 562     private static class ErrorObserver implements Agent.Observer {
 563         ErrorObserver() {
 564             try {
 565                 connectExceptionClass = Class.forName("java.net.ConnectException", true, ClassLoader.getSystemClassLoader());
 566             }
 567             catch (Throwable t) {
 568                 // ignore
 569             }
 570 
 571             try {
 572                 unknownHostExceptionClass = Class.forName("java.net.UnknownHostException", true, ClassLoader.getSystemClassLoader());
 573             }
 574             catch (Throwable t) {
 575                 // ignore
 576             }
 577         }
 578 
 579         public void started(Agent agent) {
 580         }
 581 
 582         public void errorOpeningConnection(Agent agent, Exception e) {
 583             if (connectExceptionClass != null && connectExceptionClass.isInstance(e)) {
 584                 long now = System.currentTimeMillis();
 585                 if (lastNotRespondMsgTime + lastNotRespondMsgInterval < now) {
 586                     System.err.println("host not responding: " + e.getMessage());
 587                     lastNotRespondMsgTime = now;
 588                 }
 589             }
 590             else if (unknownHostExceptionClass != null && unknownHostExceptionClass.isInstance(e))
 591                 System.err.println("unknown host: " + e.getMessage());
 592             else
 593                 System.err.println("error connecting to host: " + e);
 594         }
 595 
 596         private long lastNotRespondMsgTime = 0;
 597         private int lastNotRespondMsgInterval =
 598             max(Integer.getInteger("notResponding.message.interval", 60).intValue(), 10)*1000;
 599 
 600         public void finished(Agent agent) {
 601         }
 602 
 603         public void openedConnection(Agent agent, Connection c) {
 604         }
 605 
 606         public void execTest(Agent agent, Connection c, String tag, String className, String[] args) {
 607         }
 608 
 609         public void execCommand(Agent agent, Connection c, String tag, String className, String[] args) {
 610         }
 611 
 612         public void execMain(Agent agent, Connection c, String tag, String className, String[] args) {
 613         }
 614 
 615         public void result(Agent agent, Connection c, Status result) {
 616         }
 617 
 618         public void exception(Agent agent, Connection c, Throwable e) {
 619         }
 620 
 621         public void completed(Agent agent, Connection c) {
 622         }
 623 
 624         private Class connectExceptionClass;
 625         private Class unknownHostExceptionClass;
 626     }
 627 }