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 }