001    package org.hackystat.sensorshell;
002    
003    import java.io.File;
004    import java.util.Map;
005    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
006    
007    
008    /**
009     * Provides "middleware" for accumulating and sending notification (sensor)
010     * data to Hackystat. SensorShell has two modes of interaction: command line and
011     * programmatic. 
012     * <p> 
013     * Command line mode is entered by invoking the main() method, and
014     * is intended to be used as a kind of subshell to which commands to add and
015     * send notification data of various types can be sent. The SensorShell can be invoked
016     * without any additional arguments as follows:
017     * 
018     * <pre>java -jar sensorshell.jar</pre>
019     * 
020     * Or you can invoke it with one, two, or three additional arguments:
021     * 
022     * <pre>java -jar sensorshell.jar [tool] [sensorshell.properties] [command file]</pre>
023     * <p>
024     * Programmatic mode involves creating an instance of SensorShell, retrieving the 
025     * appropriate command instance (Ping, Add, etc.) and invoking the appropriate method.
026     *
027     * @author    Philip M. Johnson
028     */
029    public class SensorShell implements Shell {
030    
031      /** The underlying SingleSensorShell or MultiSensorShell. */
032      private Shell shell = null;
033      
034      /**
035       * Constructs a new SensorShell instance that can be provided with
036       * notification data to be sent eventually to a specific user key and host.
037       * The toolName field in the log file name is set to "interactive" if the tool
038       * is invoked interactively and "tool" if it is invoked programmatically.
039       *
040       * @param properties  The sensor properties instance for this run.
041       * @param isInteractive     Whether this SensorShell is being interactively invoked or not.
042       */
043      public SensorShell(SensorShellProperties properties, boolean isInteractive) {
044        this(properties, isInteractive, (isInteractive) ? "interactive" : "tool", null);
045      }
046    
047    
048      /**
049       * Constructs a new SensorShell instance that can be provided with
050       * notification data to be sent eventually to a specific user key and host.
051       *
052       * @param properties  The sensor properties instance for this run.
053       * @param isInteractive     Whether this SensorShell is being interactively invoked or not.
054       * @param tool          Indicates the invoking tool that is added to the log file name.
055       */
056      public SensorShell(SensorShellProperties properties, boolean isInteractive, String tool) {
057        this(properties, isInteractive, tool, null);
058      }
059    
060    
061      /**
062       * Constructs a new SensorShell instance that can be provided with
063       * notification data to be sent eventually to a specific user key and host.
064       *
065       * @param properties   The sensor properties instance for this run.
066       * @param isInteractive Whether this SensorShell is being interactively invoked or not.
067       * @param toolName  The invoking tool that is added to the log file name.
068       * @param commandFile A file containing shell commands, or null if none provided.
069       */
070      public SensorShell(SensorShellProperties properties, boolean isInteractive, String toolName,
071          File commandFile) {
072        if (properties.isMultiShellEnabled()) {
073          // Note that isInteractive, commandFile are ignored if MultiSensorShell is specified.
074          shell = new MultiSensorShell(properties, toolName);
075        }
076        else {
077          shell = new SingleSensorShell(properties, isInteractive, toolName, commandFile);
078        }
079      }
080      
081      /** {@inheritDoc} */
082      public void add(Map<String, String> keyValMap) throws SensorShellException {
083        this.shell.add(keyValMap);
084      }
085      
086      /** {@inheritDoc} */
087      public void add(SensorData sensorData) throws SensorShellException {
088        this.shell.add(sensorData);
089      }
090      
091      /** {@inheritDoc} */
092      public int send() throws SensorShellException {
093        return this.shell.send();
094      }
095      
096      /** {@inheritDoc} */
097      public void quit() throws SensorShellException {
098        this.shell.quit();
099      }
100      
101      /** {@inheritDoc} */
102      public boolean hasOfflineData() {
103        return this.shell.hasOfflineData();
104      }
105      
106      /** {@inheritDoc} */
107      public boolean ping() {
108        return this.shell.ping();
109      }
110      
111      /** {@inheritDoc} */
112      public SensorShellProperties getProperties() {
113        return this.shell.getProperties();
114      }
115      
116      /** {@inheritDoc} */
117      public void statechange(long resourceCheckSum, Map<String, String> keyValMap) throws Exception {
118        this.shell.statechange(resourceCheckSum, keyValMap);
119      }
120      
121      /**
122       * The command line shell interface for invoking a single sensor shell interactively.
123       * <ul>
124       *   <li> If invoked with no arguments, then the default sensorshell.properties
125       *   file is used and the toolName field in the sensor log file name is
126       *   "interactive".
127       *   <li> If invoked with one argument, that argument is used as the toolName
128       *   value.
129       *   <li> If invoked with two arguments, then 
130       *   the second is used as the sensorshell.properties file
131       *   path.
132       *   <li> If invoked with three arguments, then the 
133       *   the third argument specifies a file of commands (the last of which should be 'quit').
134       * </ul>
135       * Unless three arguments are provided,
136       * the shell then provides a ">>" prompt and supports interactive entry of
137       * sensor data. The following commands are supported:
138       * <ul>
139       *   <li> "help" provides a summary of the available commands.
140       *   <li> "send" sends all of the accumulated data to the server.
141       *   <li> "quit" sends all of the accumulated data to the server and exits.
142       *   <li> "ping" checks to see if the host/user/password is valid. 
143       *   <li> "add" adds a single Sensor Data instance to the buffered list to send.
144       * </ul>
145       *
146       * @param args  The command line parameters. See above for details.
147       * @throws SensorShellException If problems occur sending data. 
148       */
149      public static void main(String args[]) throws SensorShellException {
150        // Print help line and exit if arg is -help.
151        if ((args.length == 1) && (args[0].equalsIgnoreCase("-help"))) {
152          System.out.println("java -jar sensorshell.jar [tool] [sensorshell.properties] [commandfile]");
153          return;
154        }
155        
156        // Set Parameter 1 (toolname) to supplied or default value.
157        String toolName = (args.length > 0) ? args[0] : "interactive";
158    
159        // Set Parameter 2 (sensorshell.properties file) to supplied or default value. Exit if error.
160        SensorShellProperties sensorProps = null;
161        try {
162          sensorProps = (args.length >= 2) ?
163              new SensorShellProperties(new File(args[1])) : new SensorShellProperties();
164        }
165        catch (SensorShellException e) {
166          System.out.println(e.getMessage());
167          System.out.println("Exiting...");
168          return;
169        }
170    
171        // Set Parameter 3 (command file). Null if not supplied. Exit if supplied and bogus.
172        File commandFile = null;
173        if (args.length == 3) {
174          commandFile = new File(args[2]);
175          if (!(commandFile.exists() && commandFile.isFile())) {
176            System.out.println("Could not find the command file. Exiting...");
177            return;
178          }
179        }
180    
181        // Set interactive parameter. From command line, always interactive unless using command file.
182        boolean interactive = ((commandFile == null));
183    
184        // Now create the (single) shell instance, supplying it with all the appropriate arguments.
185        SingleSensorShell shell = 
186          new SingleSensorShell(sensorProps, interactive, toolName, commandFile);
187    
188        // Start processing commands either interactively or from the command file.
189        while (true) {
190          // Get the next command
191          shell.printPrompt();
192          String inputString = shell.readLine();
193          shell.processInputString(inputString);
194          if (inputString.equalsIgnoreCase("quit")) {
195            return;
196          }
197        }
198      }
199    
200    }
201