001 package org.hackystat.sensorbase.resource.sensordata; 002 003 import static org.hackystat.sensorbase.server.ServerProperties.XML_DIR_KEY; 004 005 import java.io.File; 006 import java.io.StringReader; 007 import java.io.StringWriter; 008 import java.util.List; 009 import javax.xml.bind.JAXBContext; 010 import javax.xml.bind.Marshaller; 011 import javax.xml.bind.Unmarshaller; 012 import javax.xml.datatype.XMLGregorianCalendar; 013 import javax.xml.parsers.DocumentBuilder; 014 import javax.xml.parsers.DocumentBuilderFactory; 015 import javax.xml.transform.Transformer; 016 import javax.xml.transform.TransformerFactory; 017 import javax.xml.transform.dom.DOMSource; 018 import javax.xml.transform.stream.StreamResult; 019 020 import org.hackystat.sensorbase.db.DbManager; 021 import org.hackystat.utilities.stacktrace.StackTrace; 022 import org.hackystat.utilities.tstamp.Tstamp; 023 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData; 024 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex; 025 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef; 026 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDatas; 027 import org.hackystat.sensorbase.resource.users.UserManager; 028 import org.hackystat.sensorbase.resource.users.jaxb.User; 029 import org.hackystat.sensorbase.server.Server; 030 import org.w3c.dom.Document; 031 032 /** 033 * Provides a manager for the Sensor Data resource. 034 * As with all of the Resource managers the methods in this class can be grouped into 035 * three general categories: 036 * <ul> 037 * <li> URI/Name conversion methods (convert*): these methods translate between the URI and 038 * Name-based representations of SensorDataType and User resources. 039 * <li> Database access methods (get*, has*, put*): these methods communicate with the underlying 040 * storage system. 041 * <li> XML/Java translation methods (make*): these methods translate between the XML String 042 * representation of a resource and its Java class instance representation. 043 * </ul> 044 * <p> 045 * See https://jaxb.dev.java.net/guide/Performance_and_thread_safety.html for info 046 * on JAXB performance and thread safety. 047 * @author Philip Johnson 048 */ 049 public class SensorDataManager { 050 051 /** Holds the class-wide JAXBContext, which is thread-safe. */ 052 private JAXBContext jaxbContext; 053 054 /** The Server associated with this SensorDataManager. */ 055 Server server; 056 057 /** The DbManager associated with this server. */ 058 DbManager dbManager; 059 060 /** The http string identifier. */ 061 private static final String http = "http"; 062 063 /** 064 * The constructor for SensorDataManagers. 065 * There is one SensorDataManager per Server. 066 * @param server The Server instance associated with this SensorDataManager. 067 */ 068 public SensorDataManager(Server server) { 069 this.server = server; 070 this.dbManager = (DbManager)this.server.getContext().getAttributes().get("DbManager"); 071 UserManager userManager = (UserManager)server.getContext().getAttributes().get("UserManager"); 072 try { 073 this.jaxbContext = 074 JAXBContext.newInstance( 075 org.hackystat.sensorbase.resource.sensordata.jaxb.ObjectFactory.class); 076 loadDefaultSensorData(userManager); // NOPMD (Incorrect overridable method warning) 077 } 078 catch (Exception e) { 079 String msg = "Exception during SensorDataManager initialization processing"; 080 server.getLogger().warning(msg + "\n" + StackTrace.toString(e)); 081 throw new RuntimeException(msg, e); 082 } 083 } 084 085 086 /** 087 * Loads the sensor data from the sensordata defaults file. 088 * @param userManager The User Manager. 089 * @throws Exception If problems occur. 090 */ 091 private void loadDefaultSensorData(UserManager userManager) throws Exception { 092 // Get the default Sensor Data definitions from the XML defaults file. 093 File defaultsFile = findDefaultsFile(); 094 // Initialize the SDTs if we've found a default file. 095 if (defaultsFile.exists()) { 096 server.getLogger().info("Loading SensorData defaults: " 097 + defaultsFile.getPath()); 098 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 099 SensorDatas sensorDatas = (SensorDatas) unmarshaller.unmarshal(defaultsFile); 100 // Initialize the database. 101 for (SensorData data : sensorDatas.getSensorData()) { 102 data.setLastMod(Tstamp.makeTimestamp()); 103 String email = convertOwnerToEmail(data.getOwner()); 104 if (!userManager.isUser(email)) { 105 throw new IllegalArgumentException("Owner is not a defined User: " + email); 106 } 107 this.dbManager.storeSensorData(data, this.makeSensorData(data), 108 this.makeSensorDataRefString(data)); 109 } 110 } 111 } 112 113 /** 114 * Checks the ServerProperties for the XML_DIR property. 115 * If this property is null, returns the File for ./xml/defaults/sensordatatypes.defaults.xml. 116 * @return The File instance (which might not point to an existing file.) 117 */ 118 private File findDefaultsFile() { 119 String defaultsPath = "/defaults/sensordata.defaults.xml"; 120 String xmlDir = server.getServerProperties().get(XML_DIR_KEY); 121 return (xmlDir == null) ? 122 new File (System.getProperty("user.dir") + "/xml" + defaultsPath) : 123 new File (xmlDir + defaultsPath); 124 } 125 126 127 /** 128 * Converts an "Owner" string to an email address. 129 * The owner string might be a URI (starting with http) or an email address. 130 * @param owner The owner string. 131 * @return The email address corresponding to the owner string. 132 */ 133 public String convertOwnerToEmail(String owner) { 134 if (owner.startsWith(http)) { 135 int lastSlash = owner.lastIndexOf('/'); 136 if (lastSlash < 0) { 137 throw new IllegalArgumentException("Could not convert owner to URI"); 138 } 139 return owner.substring(lastSlash + 1); 140 } 141 // Otherwise owner is already the email. 142 return owner; 143 } 144 145 /** 146 * Returns the owner string as a URI. 147 * The owner string could either be an email address or the URI. 148 * @param owner The owner string. 149 * @return The URI corresponding to the owner string. 150 */ 151 public String convertOwnerToUri(String owner) { 152 return (owner.startsWith(http)) ? owner : 153 server.getServerProperties().getFullHost() + "users/" + owner; 154 } 155 156 /** 157 * Converts an "sdt" string to the sdt name. 158 * The sdt string might be a URI (starting with http) or the sdt name. 159 * @param sdt The sdt string. 160 * @return The sdt name corresponding to the sdt string. 161 */ 162 public String convertSdtToName(String sdt) { 163 if (sdt.startsWith(http)) { 164 int lastSlash = sdt.lastIndexOf('/'); 165 if (lastSlash < 0) { 166 throw new IllegalArgumentException("Could not convert sdt to name"); 167 } 168 return sdt.substring(lastSlash + 1); 169 } 170 // Otherwise sdt is already the name. 171 return sdt; 172 } 173 174 /** 175 * Returns the sdt string as a URI. 176 * The sdt string could either be an sdt name or a URI. 177 * @param sdt The sdt string. 178 * @return The URI corresponding to the sdt string. 179 */ 180 public String convertSdtToUri(String sdt) { 181 return (sdt.startsWith(http)) ? sdt : 182 server.getServerProperties().getFullHost() + "sensordatatypes/" + sdt; 183 } 184 185 186 /** 187 * Returns the XML SensorDataIndex for all sensor data. 188 * @return The XML String providing an index of all relevent sensor data resources. 189 */ 190 public String getSensorDataIndex() { 191 return dbManager.getSensorDataIndex(); 192 } 193 194 /** 195 * Returns the XML SensorDataIndex for all sensor data for this user. 196 * @param user The User whose sensor data is to be returned. 197 * @return The XML String providing an index of all relevent sensor data resources. 198 */ 199 public String getSensorDataIndex(User user) { 200 return dbManager.getSensorDataIndex(user); 201 } 202 203 /** 204 * Returns the XML SensorDataIndex for all sensor data for this user and sensor data type. 205 * @param user The User whose sensor data is to be returned. 206 * @param sdtName The sensor data type name. 207 * @return The XML String providing an index of all relevent sensor data resources. 208 */ 209 public String getSensorDataIndex(User user, String sdtName) { 210 return this.dbManager.getSensorDataIndex(user, sdtName); 211 } 212 213 /** 214 * Returns the XML SensorDataIndex for all sensor data matching these users, start/end time, and 215 * whose resource string matches at least one in the list of UriPatterns. 216 * Client must guarantee that startTime and endTime are within Project dates, and that 217 * startIndex and maxInstances are non-negative. 218 * @param users The users. 219 * @param startTime The start time. 220 * @param endTime The end time. 221 * @param uriPatterns A list of UriPatterns. 222 * @param startIndex The starting index. 223 * @param maxInstances The maximum number of instances to return. 224 * @return The XML SensorDataIndex string corresponding to the matching sensor data. 225 */ 226 public String getSensorDataIndex(List<User> users, XMLGregorianCalendar startTime, 227 XMLGregorianCalendar endTime, List<String> uriPatterns, int startIndex, int maxInstances) { 228 return this.dbManager.getSensorDataIndex(users, startTime, endTime, uriPatterns, startIndex, 229 maxInstances); 230 } 231 232 /** 233 * Returns the XML SensorDataIndex for all sensor data matching these users, start/end time, and 234 * whose resource string matches at least one in the list of UriPatterns. 235 * @param users The users. 236 * @param startTime The start time. 237 * @param endTime The end time. 238 * @param uriPatterns A list of UriPatterns. 239 * @param sdt The sensordatatype of interest, or null if sensordata from all SDTs should be 240 * retrieved. 241 * @param tool The tool of interest. 242 * @return The XML SensorDataIndex string corresponding to the matching sensor data. 243 */ 244 public String getSensorDataIndex(List<User> users, XMLGregorianCalendar startTime, 245 XMLGregorianCalendar endTime, List<String> uriPatterns, String sdt, String tool) { 246 return this.dbManager.getSensorDataIndex(users, startTime, endTime, uriPatterns, sdt, tool); 247 } 248 249 /** 250 * Returns the XML SensorDataIndex for all sensor data matching these users, start/end time, and 251 * whose resource string matches at least one in the list of UriPatterns. 252 * @param users The users. 253 * @param startTime The start time. 254 * @param endTime The end time. 255 * @param uriPatterns A list of UriPatterns. 256 * @param sdt The sensordatatype of interest, or null if sensordata from all SDTs should be 257 * retrieved. 258 * @return The XML SensorDataIndex string corresponding to the matching sensor data. 259 */ 260 public String getSensorDataIndex(List<User> users, XMLGregorianCalendar startTime, 261 XMLGregorianCalendar endTime, List<String> uriPatterns, String sdt) { 262 return this.dbManager.getSensorDataIndex(users, startTime, endTime, uriPatterns, sdt); 263 } 264 265 /** 266 * Returns the XML SensorDataIndex for all sensor data for the given user that arrived 267 * at the server between the given timestamps. This method uses the LastMod timestamp 268 * rather than the "regular" timestamp, and is used for real-time monitoring of data 269 * arriving at the server. 270 * @param user The user whose data is being monitored. 271 * @param lastModStartTime The lastMod start time of interest. 272 * @param lastModEndTime The lastMod end time of interest. 273 * @return The XML SensorDataIndex for the recently arrived data based upon the timestamps. 274 */ 275 public String getSensorDataIndexLastMod(User user, XMLGregorianCalendar lastModStartTime, 276 XMLGregorianCalendar lastModEndTime) { 277 return this.dbManager.getSensorDataIndexLastMod(user, lastModStartTime, lastModEndTime); 278 } 279 280 281 /** 282 * Updates the Manager with this sensor data. Any old definition is overwritten for 283 * this user and timestamp. 284 * If runtime is not specified, it is defaulted to the timestamp. 285 * If tool, resource, or SDT are not specified, they default to "". 286 * @param data The sensor data. 287 */ 288 public void putSensorData(SensorData data) { 289 if (data.getRuntime() == null) { 290 data.setRuntime(data.getTimestamp()); 291 } 292 if (data.getTool() == null) { 293 data.setTool(""); 294 } 295 if (data.getResource() == null) { 296 data.setResource(""); 297 } 298 if (data.getSensorDataType() == null) { 299 data.setSensorDataType(""); 300 } 301 try { 302 data.setLastMod(Tstamp.makeTimestamp()); 303 this.dbManager.storeSensorData(data, this.makeSensorData(data), 304 this.makeSensorDataRefString(data)); 305 server.getLogger().info("Put: " + data.getTimestamp() + " " + data.getOwner() + 306 " " + data.getTool() + " " + data.getSensorDataType() + " " + data.getResource()); 307 } 308 catch (Exception e) { 309 server.getLogger().warning("Failed to put sensor data " + StackTrace.toString(e)); 310 } 311 } 312 313 /** 314 * Returns true if the passed [user, timestamp] has sensor data defined for it. 315 * Generally, you will want to use getSensorData(user, timestamp) and check for the 316 * return value == null to avoid a redundant DB call. 317 * @param user The user. 318 * @param timestamp The timestamp 319 * @return True if there is any sensor data for this [user, timestamp]. 320 */ 321 public boolean hasSensorData(User user, XMLGregorianCalendar timestamp) { 322 return this.dbManager.hasSensorData(user, timestamp); 323 } 324 325 /** 326 * Returns the SensorData XML String corresponding to [user, timestamp], or null if not found. 327 * @param user The user 328 * @param timestamp The timestamp. 329 * @return The SensorData XML String, or null if not found. 330 */ 331 public String getSensorData(User user, XMLGregorianCalendar timestamp) { 332 return this.dbManager.getSensorData(user, timestamp); 333 } 334 335 /** 336 * Ensures that sensor data with the given user and timestamp is no longer 337 * present in this manager. 338 * @param user The user. 339 * @param timestamp The timestamp associated with this sensor data. 340 */ 341 public void deleteData(User user, XMLGregorianCalendar timestamp) { 342 this.dbManager.deleteSensorData(user, timestamp); 343 } 344 345 /** 346 * Ensures that sensor data with the given user is no longer present in this manager. 347 * @param user The user. 348 */ 349 public void deleteData(User user) { 350 this.dbManager.deleteSensorData(user); 351 } 352 353 354 /** 355 * Takes an XML Document representing a SensorDataIndex and converts it to an instance. 356 * 357 * @param xmlString The XML string representing a SensorDataIndex. 358 * @return The corresponding SensorDataIndex instance. 359 * @throws Exception If problems occur during unmarshalling. 360 */ 361 public SensorDataIndex makeSensorDataIndex(String xmlString) throws Exception { 362 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 363 return (SensorDataIndex) unmarshaller.unmarshal(new StringReader(xmlString)); 364 } 365 366 /** 367 * Takes a String encoding of a SensorData in XML format and converts it to an instance. 368 * 369 * @param xmlString The XML string representing a SensorData. 370 * @return The corresponding SensorData instance. 371 * @throws Exception If problems occur during unmarshalling. 372 */ 373 public SensorData makeSensorData(String xmlString) throws Exception { 374 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 375 return (SensorData)unmarshaller.unmarshal(new StringReader(xmlString)); 376 } 377 378 /** 379 * Takes a String encoding of a SensorDatas in XML format and converts it to an instance. 380 * 381 * @param xmlString The XML string representing a SensorData. 382 * @return The corresponding SensorData instance. 383 * @throws Exception If problems occur during unmarshalling. 384 */ 385 public SensorDatas makeSensorDatas(String xmlString) throws Exception { 386 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 387 return (SensorDatas)unmarshaller.unmarshal(new StringReader(xmlString)); 388 } 389 390 /** 391 * Returns the passed SensorData instance as a String encoding of its XML representation. 392 * Final because it's called in constructor. 393 * @param data The SensorData instance. 394 * @return The XML String representation. 395 * @throws Exception If problems occur during translation. 396 */ 397 public final String makeSensorData (SensorData data) throws Exception { 398 Marshaller marshaller = jaxbContext.createMarshaller(); 399 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 400 dbf.setNamespaceAware(true); 401 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 402 Document doc = documentBuilder.newDocument(); 403 marshaller.marshal(data, doc); 404 DOMSource domSource = new DOMSource(doc); 405 StringWriter writer = new StringWriter(); 406 StreamResult result = new StreamResult(writer); 407 TransformerFactory tf = TransformerFactory.newInstance(); 408 Transformer transformer = tf.newTransformer(); 409 transformer.transform(domSource, result); 410 String xmlString = writer.toString(); 411 // Now remove the processing instruction. This approach seems like a total hack. 412 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 413 return xmlString; 414 } 415 416 /** 417 * Returns the passed SensorData instance as a String encoding of its XML representation 418 * as a SensorDataRef object. 419 * Final because it's called in constructor. 420 * @param data The SensorData instance. 421 * @return The XML String representation of it as a SensorDataRef 422 * @throws Exception If problems occur during translation. 423 */ 424 public final String makeSensorDataRefString (SensorData data) throws Exception { 425 SensorDataRef ref = makeSensorDataRef(data); 426 Marshaller marshaller = jaxbContext.createMarshaller(); 427 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 428 dbf.setNamespaceAware(true); 429 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 430 Document doc = documentBuilder.newDocument(); 431 marshaller.marshal(ref, doc); 432 DOMSource domSource = new DOMSource(doc); 433 StringWriter writer = new StringWriter(); 434 StreamResult result = new StreamResult(writer); 435 TransformerFactory tf = TransformerFactory.newInstance(); 436 Transformer transformer = tf.newTransformer(); 437 transformer.transform(domSource, result); 438 String xmlString = writer.toString(); 439 // Now remove the processing instruction. This approach seems like a total hack. 440 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 441 return xmlString; 442 } 443 444 /** 445 * Returns a SensorDataRef instance constructed from a SensorData instance. 446 * @param data The sensor data instance. 447 * @return A SensorDataRef instance. 448 */ 449 public SensorDataRef makeSensorDataRef(SensorData data) { 450 SensorDataRef ref = new SensorDataRef(); 451 String email = convertOwnerToEmail(data.getOwner()); 452 String sdt = convertSdtToName(data.getSensorDataType()); 453 XMLGregorianCalendar timestamp = data.getTimestamp(); 454 ref.setOwner(email); 455 ref.setSensorDataType(sdt); 456 ref.setTimestamp(timestamp); 457 ref.setTool(data.getTool()); 458 ref.setHref(this.server.getHostName() + "sensordata/" + email + "/" + timestamp.toString()); 459 ref.setLastMod(data.getLastMod()); 460 return ref; 461 } 462 463 }