001 package org.hackystat.sensor.xmldata.option; 002 003 import java.io.File; 004 import java.util.HashMap; 005 import java.util.List; 006 import java.util.Map; 007 008 import javax.xml.bind.JAXBException; 009 import javax.xml.bind.Unmarshaller; 010 import javax.xml.namespace.QName; 011 012 import org.hackystat.sensor.xmldata.XmlDataController; 013 import org.hackystat.sensor.xmldata.jaxb.v7.Entry; 014 import org.hackystat.sensor.xmldata.jaxb.v7.ObjectFactory; 015 import org.hackystat.sensor.xmldata.jaxb.v7.Sensor; 016 import org.hackystat.sensor.xmldata.util.SensorDataPropertyMap; 017 import org.hackystat.sensorshell.SensorShellException; 018 import org.hackystat.sensorshell.SensorShellProperties; 019 import org.hackystat.sensorshell.SensorShell; 020 import org.hackystat.sensorshell.Shell; 021 import org.hackystat.utilities.tstamp.Tstamp; 022 import org.hackystat.utilities.tstamp.TstampSet; 023 import org.xml.sax.SAXException; 024 025 /** 026 * The option used to port Hackystat version 7 (v7) data to the Hackystat 8 027 * sensorbase with the following steps: 028 * 029 * <pre> 030 * 1. The user provides the Hackystat 7 user directory, the version 7 031 * account, the version 8 username, and version 8 password. 032 * 2. Then this option traverses the Hackystat 7 data directory and converts the information to 033 * valid Hackystat 8 data. 034 * 3. Finally, the converted data is sent to the Hackystat 8 sensorbase. 035 * </pre> 036 * 037 * @author aito 038 * 039 */ 040 public class MigrationOption extends AbstractOption { 041 /** The name of this option, which is "-migration". */ 042 public static final String OPTION_NAME = "-migration"; 043 /** The version 7 data directory specified to convert to version 8 data. */ 044 private File v7DataDir = null; 045 /** 046 * The sensor properties file containing the version 8 host and account 047 * information. 048 */ 049 private SensorShellProperties properties = null; 050 051 /** 052 * Creates this option with the specified controller and parameters. 053 * @param controller the specified controller. 054 * @param parameters the specified parameters. 055 */ 056 public MigrationOption(XmlDataController controller, List<String> parameters) { 057 super(controller, OPTION_NAME, parameters); 058 } 059 060 /** 061 * Returns true if the specified option parameters follows the convention: 062 * 063 * <pre> 064 * [v7 directory] [v7 account] [v8 host] [v8 username] [v8 password] 065 * 066 * Ex: -migration C:\foo ABCDEF http://localhost:9876/sensorbase austen@hawaii.edu fooPassword 067 * Note that the v7 directory does not include the v7 account name. 068 * </pre> 069 * 070 * @return true if the option parameters are correct, false if not. 071 */ 072 @Override 073 public boolean isValid() { 074 // Check if the correct amount of parameters exist. 075 if (this.getParameters().isEmpty() || this.getParameters().size() < 5 076 || this.getParameters().size() > 5) { 077 String msg = "The -migration option only accepts five parameters, " 078 + "[v7 directory] [v7 account] [v8 host] [v8 username] [v8 password]"; 079 this.getController().fireMessage(msg); 080 return false; 081 } 082 083 // Verify that the version 7 directory exists. 084 File v7Dir = new File(this.getParameters().get(0)); 085 String v7Account = this.getParameters().get(1); 086 if (!new File(v7Dir.getAbsolutePath() + "/" + v7Account).exists()) { 087 String msg = "The version 7 user directory, " + this.getParameters().get(0) + "/" 088 + this.getParameters().get(1) + ", does not exist."; 089 this.getController().fireMessage(msg); 090 return false; 091 } 092 093 // Verify that the version 8 host, account and password is valid. 094 SensorShellProperties props; 095 try { 096 props = new SensorShellProperties(this.getParameters().get(2), this 097 .getParameters().get(3), this.getParameters().get(4)); 098 } 099 catch (SensorShellException e) { 100 this.getController().fireMessage("Error instantiating the SensorShell: " + e); 101 return false; 102 } 103 SensorShell shell = new SensorShell(props, false, "XmlData"); 104 if (!shell.ping()) { 105 String msg = "The host, connection or account information is incorrect: Hackystat Host=" 106 + this.getParameters().get(2) + ", v8Account=" + this.getParameters().get(3) 107 + ", v8Password=" + this.getParameters().get(4); 108 this.getController().fireMessage(msg); 109 return false; 110 } 111 return true; 112 } 113 114 /** Sets the variables used by the execute method. */ 115 @Override 116 public void process() { 117 if (this.isValid()) { 118 // Sets the version 7 information. 119 File v7Dir = new File(this.getParameters().get(0)); 120 String v7Account = this.getParameters().get(1); 121 this.v7DataDir = new File(v7Dir.getAbsolutePath() + "/" + v7Account + "/data"); 122 123 // Sets the version 8 information. 124 try { 125 this. properties = new SensorShellProperties(this.getParameters().get(2), this.getParameters() 126 .get(3), this.getParameters().get(4)); 127 } 128 catch (SensorShellException e) { 129 this.getController().fireMessage("Error creating SensorShellProperties: " + e); 130 } 131 } 132 } 133 134 /** 135 * Executes this option by converting all version 7 data found in the 136 * specified directory to version 8 compatiable data. The converted data is 137 * sent to the Hackystat 8 sensorbase. 138 */ 139 @Override 140 public void execute() { 141 try { 142 // First, lets create a Shell and an Unmarshaller. 143 Shell shell = OptionUtil.createShell(this.properties, this.getController()); 144 Unmarshaller unmarshaller = OptionUtil.createUnmarshaller(ObjectFactory.class, 145 "v7data.xsd"); 146 147 // Then iterate over each file in the version 7 data directory. 148 int entriesAdded = 0; 149 TstampSet tstampSet = new TstampSet(); 150 for (File sdtDir : this.v7DataDir.listFiles()) { 151 for (File sensorDataFile : sdtDir.listFiles()) { 152 this.getController().fireMessage( 153 Tstamp.makeTimestamp().toString() + " Processing " + sensorDataFile); 154 155 Sensor sensor = (Sensor) unmarshaller.unmarshal(sensorDataFile); 156 for (Entry entry : sensor.getEntry()) { 157 Map<String, String> keyValMap = new HashMap<String, String>(); 158 keyValMap.put("SensorDataType", sdtDir.getName()); 159 keyValMap.put("Timestamp", OptionUtil.getCurrentTimestamp(true, tstampSet) 160 .toString()); 161 162 // Add an entry for each key-value attribute in the data file. 163 for (Map.Entry<QName, String> attribute : entry.getOtherAttributes().entrySet()) { 164 this.addEntry(keyValMap, attribute, tstampSet); 165 } 166 167 shell.add(keyValMap); 168 this.getController().fireVerboseMessage(OptionUtil.getMapVerboseString(keyValMap)); 169 entriesAdded++; 170 } 171 } 172 } 173 174 // Fires the send message and quits the sensorshell. 175 OptionUtil.fireSendMessage(this.getController(), shell, entriesAdded); 176 shell.quit(); 177 } 178 catch (JAXBException e) { 179 String msg = "There was a problem unmarshalling the data. The v7 data file(s) " 180 + "may not conform to the xmldata schema."; 181 this.getController().fireMessage(msg, e.toString()); 182 } 183 catch (SAXException e) { 184 String msg = "The v7 data file(s) could not be parsed."; 185 this.getController().fireMessage(msg, e.toString()); 186 } 187 catch (Exception e) { 188 String msg = "The v7 data file(s) failed to load. Please make sure the " 189 + "specified directory has version 7 data."; 190 this.getController().fireMessage(msg, e.toString()); 191 } 192 } 193 194 /** 195 * Adds the entry to the specified key-value mapping. This method performs 196 * additional processing on the entry, such as converting the version 7 197 * timestamp to a compatible version 8 timestamp. 198 * @param keyValMap the map reference that is populated by this method. 199 * @param entry the entry, whose information is used to populate the specified 200 * map. 201 * @param tstampSet the set of timestamps used to generate a unique timestamp 202 * for each entry. 203 */ 204 private void addEntry(Map<String, String> keyValMap, Map.Entry<QName, String> entry, 205 TstampSet tstampSet) { 206 try { 207 String entryName = entry.getKey().toString(); 208 String entryValue = entry.getValue(); 209 210 // Handles the timestamp attribute and it's uniqueness. 211 if ("tstamp".equalsIgnoreCase(entryName)) { 212 entryName = "Timestamp"; 213 long timestamp = OptionUtil.getTimestampInMillis(entryValue); 214 Boolean isUnique = (Boolean) this.getController().getOptionObject( 215 Options.UNIQUE_TSTAMP); 216 entryValue = OptionUtil.massageTimestamp(isUnique, tstampSet, timestamp).toString(); 217 keyValMap.put(entryName, entryValue); 218 } 219 220 // Converts the file attribute to resource. 221 else if ("file".equalsIgnoreCase(entryName) || "path".equalsIgnoreCase(entryName)) { 222 keyValMap.put("Resource", entryValue); 223 } 224 225 // Converts a pMap encoded string to key-value pairs. 226 else if ("pMap".equalsIgnoreCase(entryName)) { 227 SensorDataPropertyMap pMap = new SensorDataPropertyMap(entryValue); 228 for (Object key : pMap.keySet()) { 229 String keyName = (String) key; 230 keyValMap.put(keyName, pMap.get(keyName)); 231 } 232 } 233 } 234 catch (Exception e) { 235 this.getController().fireMessage(e.getMessage()); 236 } 237 } 238 }