001    package org.hackystat.sensor.xmldata.option;
002    
003    import java.net.MalformedURLException;
004    import java.net.URL;
005    import java.text.ParsePosition;
006    import java.text.SimpleDateFormat;
007    import java.util.Date;
008    import java.util.Locale;
009    import java.util.Map;
010    
011    import javax.xml.bind.JAXBContext;
012    import javax.xml.bind.JAXBException;
013    import javax.xml.bind.Unmarshaller;
014    import javax.xml.datatype.XMLGregorianCalendar;
015    import javax.xml.validation.Schema;
016    import javax.xml.validation.SchemaFactory;
017    
018    import org.hackystat.sensor.xmldata.XmlDataController;
019    import org.hackystat.sensorshell.SensorShellProperties;
020    import org.hackystat.sensorshell.SensorShell;
021    import org.hackystat.sensorshell.Shell;
022    import org.hackystat.utilities.tstamp.Tstamp;
023    import org.hackystat.utilities.tstamp.TstampSet;
024    import org.xml.sax.SAXException;
025    
026    /**
027     * The option utility class that contains methods used by multiple options.
028     * @author aito
029     * 
030     */
031    public class OptionUtil {
032      /** Private constructor that prevents instantiation. */
033      private OptionUtil() {
034      }
035    
036      /**
037       * Returns the string containing the information stored in the key-value
038       * mapping of sensor data. This string is helpful when running this option in
039       * verbose mode.
040       * @param keyValMap the map used to generate the returned string.
041       * @return the informative string.
042       */
043      public static String getMapVerboseString(Map<String, String> keyValMap) {
044        if (!keyValMap.isEmpty()) {
045          String verboseString = "[";
046          for (Map.Entry<String, String> entry : keyValMap.entrySet()) {
047            verboseString = verboseString.concat(entry.getKey() + "=" + entry.getValue()) + ", ";
048          }
049    
050          // Remove the last ', ' from the string.
051          verboseString = verboseString.substring(0, verboseString.length() - 2);
052          return verboseString.concat("]");
053        }
054        return "";
055      }
056    
057      /**
058       * Returns the long value of the specified timestamp string representation.
059       * This method expects the timestamp string to be a long or in the
060       * SimpleDateFormat: MM/dd/yyyy-hh:mm:ss. If the timestamp does not fit either
061       * specification, a runtime exception is thrown.
062       * @param timestamp the specified string representation of a timestamp.
063       * @return the long value of the specified string timestamp.
064       * @throws Exception thrown if the specified timestamp string is not in a
065       * valid SimpleDateFormat.
066       */
067      public static long getTimestampInMillis(String timestamp) throws Exception {
068        if (OptionUtil.isTimestampLong(timestamp)) {
069          return Long.valueOf(timestamp);
070        }
071        else if (OptionUtil.isTimestampSimpleDate(timestamp)) {
072          SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy-hh:mm:ss", Locale.US);
073          return format.parse(timestamp, new ParsePosition(0)).getTime();
074        }
075        String msg = "The timestamp must either be specified as a "
076            + "long or in the format: MM/dd/yyyy-hh:mm:ss";
077        throw new Exception(msg);
078      }
079    
080      /**
081       * Returns true if the specified timestamp string representation is in long
082       * format.
083       * @param timestamp the string to test.
084       * @return true if the timestamp is a long, false if not.
085       */
086      private static boolean isTimestampLong(String timestamp) {
087        try {
088          Long.valueOf(timestamp);
089          return true;
090        }
091        catch (NumberFormatException nfe) {
092          return false;
093        }
094      }
095    
096      /**
097       * Returns true if the specified timestamp is in the SimpleDateFormat. 
098       * This should be: MM/dd/yyyy-hh:mm:ss.
099       * @param timestamp the timestamp to test.
100       * @return true if the timestamp is in the specified SimpleDateFormat, false
101       * if not.
102       */
103      private static boolean isTimestampSimpleDate(String timestamp) {
104        try {
105          SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy-hh:mm:ss", Locale.US);
106          format.parse(timestamp, new ParsePosition(0)).getTime();
107          return true;
108        }
109        catch (NullPointerException npe) {
110          return false;
111        }
112      }
113    
114      /**
115       * Returns the current timestamp based on the specified parameters.
116       * @param isUnique if this is true, a unique timestamp, based on the specified
117       * tstampSet, is returned.
118       * @param tstampSet the set of timestamps that is managed to ensure that a
119       * unique timestamp is generated.
120       * @return the XmlGregorianCalendar instance representing the current
121       * timestamp.
122       */
123      public static XMLGregorianCalendar getCurrentTimestamp(boolean isUnique, TstampSet tstampSet) {
124        if (isUnique) {
125          return Tstamp.makeTimestamp(tstampSet.getUniqueTstamp(new Date().getTime()));
126        }
127        return Tstamp.makeTimestamp();
128      }
129    
130      /**
131       * "Massages" the specified timestamp by using the specified parameters.
132       * @param isUnique if this is true, the specified timestamp is changed to be
133       * unique based on the specified tstampSet.
134       * @param tstampSet the set of timestamps that is managed to ensure that a
135       * unique timestamp is generated.
136       * @param timestamp the timestamp to massage.
137       * @return the XmlGregorianCalendar instance representing the current
138       * timestamp.
139       */
140      public static XMLGregorianCalendar massageTimestamp(Boolean isUnique, TstampSet tstampSet,
141          long timestamp) {
142        if (isUnique) {
143          return Tstamp.makeTimestamp(tstampSet.getUniqueTstamp(timestamp));
144        }
145        return Tstamp.makeTimestamp(timestamp);
146      }
147    
148      /**
149       * The helper method that returns an unmarshaller that is created using the
150       * specified JAXB context class and schema file. The schema file name is the
151       * name of a file that is relative to the '[top-level dir]/xml/schema/'
152       * directory.
153       * @param contextClass the specified context class used to build the
154       * unmarshaller.
155       * @param schemaFileName the schema file that adds schema validation to the
156       * unmarshaller.
157       * @return the unmarshaller instance.
158       * @throws JAXBException thrown if there is a problem creating the
159       * unmarshaller with the specified context class.
160       * @throws SAXException thrown if there is a problem adding schema validation
161       * with the specified schema file.
162       * @throws MalformedURLException thrown if there is a problem finding the xsd
163       * schema directory.
164       */
165      public static Unmarshaller createUnmarshaller(Class<?> contextClass, String schemaFileName)
166        throws JAXBException, SAXException, MalformedURLException {
167        JAXBContext context = JAXBContext.newInstance(contextClass);
168        Unmarshaller unmarshaller = context.createUnmarshaller();
169    
170        // Retrieves the url of the schema files.
171        String classJar = OptionUtil.class.getResource("").toString();
172        int index = classJar.indexOf('!');
173        String jarString = classJar.substring(0, index + 1);
174        URL jarUrl = new URL(jarString + "/xml/schema/" + schemaFileName);
175    
176        // Adds schema validation to the unmarshalled file.
177        SchemaFactory schemaFactory = SchemaFactory
178            .newInstance("http://www.w3.org/2001/XMLSchema");
179        Schema schema = schemaFactory.newSchema(jarUrl);
180        unmarshaller.setSchema(schema);
181        return unmarshaller;
182      }
183    
184      /**
185       * The helper method that fires a message to the specified controller based on
186       * the connectivity of the sensorshell to a Hackystat server. If the server
187       * exists, a normal message is sent. If the server does not exist, a message
188       * informing the user of offline storage is sent. This method should be used
189       * by all options that send data.
190       * @param controller the controller that the message is fired to.
191       * @param shell the shell used to test the connectivity of the Hackystat
192       * server.
193       * @param entriesAdded the number of entries added to the specified shell.
194       */
195      public static void fireSendMessage(XmlDataController controller, Shell shell,
196          int entriesAdded) {
197        if (shell.ping()) {
198          controller.fireMessage(entriesAdded + " entries sent to " + controller.getHost());
199        }
200        else {
201          controller.fireMessage("Server not available. Storing " + entriesAdded
202              + " data entries offline.");
203        }
204      }
205    
206      /**
207       * Creates a Shell instance based on the information found in the specified
208       * controller.
209       * @param properties the properties used to create the shell instance.
210       * @param controller the controller that contains the information used to
211       * determine which shell to use.
212       * @return the Shell instance.
213       * @throws Exception thrown if there is a problem instantiating a
214       * MultiSensorShell instance.
215       */
216      public static Shell createShell(SensorShellProperties properties,
217          XmlDataController controller) throws Exception {
218        
219        /*  Ignore Multishell command line parameter; use whatever is in sensorshell.properties. 
220        Boolean isMultiShellOption = (Boolean) controller.getOptionObject(Options.MULTI_SHELL);
221        boolean isMultiShell = (isMultiShellOption != null) && isMultiShellOption.booleanValue();
222        controller.fireMessage("MultiSensorShell " + ((isMultiShell) ? "is" : "is not")
223            + " enabled.");
224        String multi = String.valueOf(isMultiShell);
225        Properties preferMultiShell = new Properties();
226        preferMultiShell.setProperty(SensorShellProperties.SENSORSHELL_MULTISHELL_ENABLED_KEY,
227            multi);
228        SensorShellProperties newProps = new SensorShellProperties(properties, preferMultiShell);
229        */
230        return new SensorShell(new SensorShellProperties(), false, "XmlData");
231      }
232    }