       1 /*
       2  * $Id$
       3  *
       4  * Copyright (c) 1996, 2016, 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 }