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.datatype.XMLGregorianCalendar;
011    import javax.xml.namespace.QName;
012    
013    import org.hackystat.sensor.xmldata.XmlDataController;
014    import org.hackystat.sensor.xmldata.jaxb.Entries;
015    import org.hackystat.sensor.xmldata.jaxb.Entry;
016    import org.hackystat.sensor.xmldata.jaxb.ObjectFactory;
017    import org.hackystat.sensor.xmldata.jaxb.XmlData;
018    import org.hackystat.sensorshell.SensorShellException;
019    import org.hackystat.sensorshell.SensorShellProperties;
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 send generic sensor data, via the sensorshell, to the
027     * sensorbase. This option accepts a list of files that contain the generic
028     * sensor information.
029     * @author aito
030     * 
031     */
032    public class FileOption extends AbstractOption {
033      /** The name of this option, which is "-file". */
034      public static final String OPTION_NAME = "-file";
035    
036      /**
037       * Creates this option with the specified controller and parameters.
038       * @param controller the specified controller.
039       * @param parameters the specified parameters.
040       */
041      public FileOption(XmlDataController controller, List<String> parameters) {
042        super(controller, OPTION_NAME, parameters);
043      }
044    
045      /**
046       * Returns true if the the list of parameters contains 1 or more files that
047       * exist.
048       * @return true if this option's parameters are valid.
049       */
050      @Override
051      public boolean isValid() {
052        if (this.getParameters().size() == 0) {
053          String msg = "The number of parameters must include at least 1 file. "
054              + "Ex: -file foo.xml foo2.xml";
055          this.getController().fireMessage(msg);
056          return false;
057        }
058    
059        for (String parameter : this.getParameters()) {
060          File file = new File(parameter);
061          if (!file.exists()) {
062            String msg = "The file '" + file + "' does not exist.";
063            this.getController().fireMessage(msg);
064            return false;
065          }
066        }
067        return true;
068      }
069    
070      /**
071       * Executes this option by grabbing all information stored in the specified
072       * files, and sending them to the sensorbase.
073       */
074      @Override
075      public void execute() {
076        try {
077          // First, lets get the correct shell instance.
078          Shell shell = OptionUtil.createShell(new SensorShellProperties(), this.getController());
079    
080          // Then, send data from each file.
081          XMLGregorianCalendar runtime = Tstamp.makeTimestamp();
082          int entriesAdded = 0;
083          for (String filePath : this.getParameters()) {
084            this.getController().fireVerboseMessage("Sending data from: " + filePath);
085            Unmarshaller unmarshaller = OptionUtil.createUnmarshaller(ObjectFactory.class,
086                "xmldata.xsd");
087            XmlData xmlData = (XmlData) unmarshaller.unmarshal(new File(filePath));
088            Entries entries = xmlData.getEntries();
089    
090            // Only send data if the SDT is set or all entries have SDT attributes.
091            TstampSet tstampSet = new TstampSet();
092            Object sdtName = this.getController().getOptionObject(Options.SDT);
093            if (sdtName != null || this.hasSdtAttributes(entries.getEntry())) {
094              for (Entry entry : entries.getEntry()) {
095                // Then, lets set the "required" attributes.
096                Map<String, String> keyValMap = new HashMap<String, String>();
097                keyValMap.put("Tool", entry.getTool());
098                keyValMap.put("Resource", this.getResource(entry));
099                keyValMap.put("SensorDataType", (String) sdtName);
100                keyValMap.put("Timestamp", OptionUtil.getCurrentTimestamp(true, tstampSet)
101                    .toString());
102    
103                // If the SetRuntimeOption is set, use the same runtime.
104                if (Boolean.TRUE.equals(this.getController().getOptionObject(Options.SET_RUNTIME))) {
105                  keyValMap.put("Runtime", runtime.toString());
106                }
107    
108                // Next, add the optional attributes.
109                Map<QName, String> map = entry.getOtherAttributes();
110                for (Map.Entry<QName, String> attributeEntry : map.entrySet()) {
111                  String entryName = attributeEntry.getKey().toString();
112                  String entryValue = attributeEntry.getValue();
113    
114                  // If entries contain tstamps, override the current tstamp.
115                  if ("Timestamp".equals(entryName)) {
116                    long timestamp = OptionUtil.getTimestampInMillis(entryValue);
117                    Boolean isUnique = (Boolean) this.getController().getOptionObject(
118                        Options.UNIQUE_TSTAMP);
119                    entryValue = OptionUtil.massageTimestamp(isUnique, tstampSet, timestamp)
120                        .toString();
121                  }
122                  keyValMap.put(entryName, entryValue);
123                }
124                // Finally, add the mapping and send the data.
125                this.getController().fireVerboseMessage(OptionUtil.getMapVerboseString(keyValMap));
126                shell.add(keyValMap);
127                entriesAdded++;
128              }
129            }
130            else {
131              String msg = "The -sdt flag must be specified for all entries or each "
132                  + "xml entry must have the 'SensorDataType' attribute.";
133              throw new Exception(msg);
134            }
135          }
136    
137          // Fires the send message and quits the sensorshell.
138          OptionUtil.fireSendMessage(this.getController(), shell, entriesAdded);
139          shell.quit();
140        }
141        catch (JAXBException e) {
142          String msg = "There was a problem unmarshalling the data.  File(s) "
143              + "may not conform to the xmldata schema.";
144          this.getController().fireMessage(msg, e.toString());
145        }
146        catch (SAXException e) {
147          String msg = "The specified file(s) could not be parsed.";
148          this.getController().fireMessage(msg, e.toString());
149        }
150        catch (SensorShellException e) {
151          String msg = "The sensorshell.properties file in your userdir/.hackystat "
152              + "directory is invalid or does not exist.";
153          this.getController().fireMessage(msg);
154        }
155        catch (Exception e) {
156          String msg = "The specified file(s) failed to load.";
157          this.getController().fireMessage(msg, e.toString());
158        }
159      }
160    
161      /**
162       * Returns the Resource value of the specified entry. This method is helpful
163       * when an option is specified that alters the resource associated with the
164       * Resource key.
165       * @param entry the entry whose Resource value is determined.
166       * @return the resource string value.
167       */
168      private String getResource(Entry entry) {
169        if (this.getController().getOptionObject(Options.RESOURCE) == null) {
170          return entry.getResource();
171        }
172        return this.getController().getOptionObject(Options.RESOURCE).toString();
173      }
174      
175      /**
176       * Returns true if the list of JAXB Entry object's each contain the sensor
177       * data type attribute.
178       * @param entries the list of entries to search.
179       * @return true if each entry has the sdt attribute, false if not.
180       */
181      private boolean hasSdtAttributes(List<Entry> entries) {
182        for (Entry entry : entries) {
183          if (!this.hasSdtInAttributes(entry.getOtherAttributes())) {
184            return false;
185          }
186        }
187        return true;
188      }
189    
190      /**
191       * Returns true if the specified map contains the sensor data type attribute.
192       * If the mapping does not have the sdt attribute, false is returned.
193       * @param attributeMap the map to search.
194       * @return true if the map has the sdt attribute, false if not.
195       */
196      private boolean hasSdtInAttributes(Map<QName, String> attributeMap) {
197        for (QName name : attributeMap.keySet()) {
198          if ("SensorDataType".equals(name.toString())) {
199            return true;
200          }
201        }
202        return false;
203      }
204    }