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    }