001 package org.hackystat.sensorbase.client; 002 003 import java.io.StringReader; 004 import java.io.StringWriter; 005 import java.util.Date; 006 import java.util.GregorianCalendar; 007 import java.util.HashMap; 008 import java.util.Map; 009 010 import javax.xml.bind.JAXBContext; 011 import javax.xml.bind.Marshaller; 012 import javax.xml.bind.Unmarshaller; 013 import javax.xml.datatype.XMLGregorianCalendar; 014 import javax.xml.parsers.DocumentBuilder; 015 import javax.xml.parsers.DocumentBuilderFactory; 016 import javax.xml.transform.Transformer; 017 import javax.xml.transform.TransformerFactory; 018 import javax.xml.transform.dom.DOMSource; 019 import javax.xml.transform.stream.StreamResult; 020 021 import org.hackystat.sensorbase.resource.projects.jaxb.Invitations; 022 import org.hackystat.sensorbase.resource.projects.jaxb.MultiDayProjectSummary; 023 import org.hackystat.sensorbase.resource.projects.jaxb.Project; 024 import org.hackystat.sensorbase.resource.projects.jaxb.ProjectIndex; 025 import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef; 026 import org.hackystat.sensorbase.resource.projects.jaxb.ProjectSummary; 027 import org.hackystat.sensorbase.resource.sensorbase.SensorBaseResource; 028 import org.hackystat.sensorbase.resource.sensordata.jaxb.Property; 029 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData; 030 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex; 031 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef; 032 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDatas; 033 import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataType; 034 import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataTypeIndex; 035 import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataTypeRef; 036 import org.hackystat.sensorbase.resource.users.jaxb.Properties; 037 import org.hackystat.sensorbase.resource.users.jaxb.User; 038 import org.hackystat.sensorbase.resource.users.jaxb.UserIndex; 039 import org.hackystat.sensorbase.resource.users.jaxb.UserRef; 040 import org.hackystat.utilities.logger.RestletLoggerUtil; 041 import org.hackystat.utilities.tstamp.Tstamp; 042 import org.hackystat.utilities.uricache.UriCache; 043 import org.restlet.Client; 044 import org.restlet.data.ChallengeResponse; 045 import org.restlet.data.ChallengeScheme; 046 import org.restlet.data.Form; 047 import org.restlet.data.MediaType; 048 import org.restlet.data.Method; 049 import org.restlet.data.Preference; 050 import org.restlet.data.Protocol; 051 import org.restlet.data.Reference; 052 import org.restlet.data.Request; 053 import org.restlet.data.Response; 054 import org.restlet.data.Status; 055 import org.restlet.resource.Representation; 056 import org.w3c.dom.Document; 057 058 /** 059 * Provides a high-level interface for Clients wishing to communicate with a SensorBase. 060 * 061 * @author Philip Johnson 062 * 063 */ 064 public class SensorBaseClient { 065 066 /** The possible responses to a Project invitation. */ 067 public enum InvitationReply { ACCEPT, DECLINE } 068 069 /** Holds the userEmail to be associated with this client. */ 070 private String userEmail; 071 /** Holds the password to be associated with this client. */ 072 private String password; 073 /** The SensorBase host, such as "http://localhost:9876/sensorbase". */ 074 private String sensorBaseHost; 075 /** The Restlet Client instance used to communicate with the server. */ 076 private Client client; 077 /** SDT JAXBContext. */ 078 private static final JAXBContext sdtJAXB; 079 /** Users JAXBContext. */ 080 private static final JAXBContext userJAXB; 081 /** SensorData JAXBContext. */ 082 private static final JAXBContext sensordataJAXB; 083 /** Project JAXBContext. */ 084 private static final JAXBContext projectJAXB; 085 /** The http authentication approach. */ 086 private ChallengeScheme scheme = ChallengeScheme.HTTP_BASIC; 087 /** The preferred representation type. */ 088 private Preference<MediaType> xmlMedia = new Preference<MediaType>(MediaType.TEXT_XML); 089 /** For PMD. */ 090 private String sensordataUri = "sensordata/"; 091 /** For PMD. */ 092 private String projectsUri = "projects/"; 093 /** For PMD. */ 094 private String andEndTime = "&endTime="; 095 /** For PMD. */ 096 //private static final String sensorbaseclient = "sensorbaseclient"; 097 098 /** To facilitate debugging of problems using this system. */ 099 private boolean isTraceEnabled = false; 100 101 /** An associated UriCache to improve responsiveness. */ 102 private UriCache uriCache = null; 103 104 /** Indicates whether or not cache is enabled. */ 105 private boolean isCacheEnabled = false; 106 107 /** Timestamp of last time we tried to contact a server and failed, since this is expensive. */ 108 private static Map<String, Long> lastHostNotAvailable = new HashMap<String, Long>(); 109 110 /** The System property key used to retrieve the default timeout value in milliseconds. */ 111 public static final String SENSORBASECLIENT_TIMEOUT_KEY = "sensorbaseclient.timeout"; 112 113 // JAXBContexts are thread safe, so we can share them across all instances and threads. 114 // https://jaxb.dev.java.net/guide/Performance_and_thread_safety.html 115 static { 116 try { 117 sdtJAXB = JAXBContext 118 .newInstance(org.hackystat.sensorbase.resource.sensordatatypes.jaxb.ObjectFactory.class); 119 userJAXB = JAXBContext 120 .newInstance(org.hackystat.sensorbase.resource.users.jaxb.ObjectFactory.class); 121 sensordataJAXB = JAXBContext 122 .newInstance(org.hackystat.sensorbase.resource.sensordata.jaxb.ObjectFactory.class); 123 projectJAXB = JAXBContext 124 .newInstance(org.hackystat.sensorbase.resource.projects.jaxb.ObjectFactory.class); 125 } 126 catch (Exception e) { 127 throw new RuntimeException("Couldn't create JAXB context instances.", e); 128 } 129 } 130 131 /** 132 * Initializes a new SensorBaseClient, given the host, userEmail, and password. 133 * 134 * @param host The host, such as 'http://localhost:9876/sensorbase'. 135 * @param email The user's email that we will use for authentication. 136 * @param password The password we will use for authentication. 137 */ 138 public SensorBaseClient(String host, String email, String password) { 139 validateArg(host); 140 validateArg(email); 141 validateArg(password); 142 // Restlet logger leaves too many .lck files around, so disable it until we figure it out. 143 RestletLoggerUtil.disableLogging(); 144 this.userEmail = email; 145 this.password = password; 146 this.sensorBaseHost = host; 147 if (!this.sensorBaseHost.endsWith("/")) { 148 this.sensorBaseHost = this.sensorBaseHost + "/"; 149 } 150 if (this.isTraceEnabled) { 151 System.out.println("SensorBaseClient Tracing: INITIALIZE " + "host='" + host + "', email='" 152 + email + "', password='" + password + "'"); 153 } 154 this.client = new Client(Protocol.HTTP); 155 setTimeout(getDefaultTimeout()); 156 } 157 158 /** 159 * Attempts to provide a timeout value for this SensorBaseClient. 160 * @param milliseconds The number of milliseconds to wait before timing out. 161 */ 162 public final synchronized void setTimeout(int milliseconds) { 163 setClientTimeout(this.client, milliseconds); 164 } 165 166 /** 167 * Returns the default timeout in milliseconds. 168 * The default timeout is set to 2000 ms, but clients can change this by creating a 169 * System property called sensorbaseclient.timeout and set it to a String indicating 170 * the number of milliseconds. 171 * @return The default timeout. 172 */ 173 private static int getDefaultTimeout() { 174 String systemTimeout = System.getProperty(SENSORBASECLIENT_TIMEOUT_KEY, "2000"); 175 int timeout = 2000; 176 try { 177 timeout = Integer.parseInt(systemTimeout); 178 } 179 catch (Exception e) { 180 timeout = 2000; 181 } 182 return timeout; 183 } 184 185 /** 186 * When passed true, future HTTP calls using this client instance will print out information on 187 * the request and response. 188 * 189 * @param enable If true, trace output will be generated. 190 */ 191 public synchronized void enableHttpTracing(boolean enable) { 192 this.isTraceEnabled = enable; 193 } 194 195 /** 196 * Authenticates this user and password with the server. 197 * 198 * @return This SensorBaseClient instance. 199 * @throws SensorBaseClientException If authentication is not successful. 200 */ 201 public synchronized SensorBaseClient authenticate() throws SensorBaseClientException { 202 String uri = "ping?user=" + this.userEmail + "&password=" + this.password; 203 Response response = makeRequest(Method.GET, uri, null); 204 if (!response.getStatus().isSuccess()) { 205 throw new SensorBaseClientException(response.getStatus()); 206 } 207 String responseString; 208 try { 209 responseString = response.getEntity().getText(); 210 } 211 catch (Exception e) { 212 throw new SensorBaseClientException("Bad response", e); 213 } 214 if (!"SensorBase authenticated".equals(responseString)) { 215 throw new SensorBaseClientException("Authentication failed"); 216 } 217 return this; 218 } 219 220 /** 221 * Provides an easy way to construct SensorData instances. The keyValMap is processed and the 222 * key-value pairs are processed in the following way. 223 * <ul> 224 * <li> ["Owner", email] (if not supplied, defaults to this client's email) 225 * <li> ["Timestamp", timestamp string] (if not supplied, defaults to the current time.) 226 * <li> ["Runtime", timestamp string] (if not supplied, defaults to Timestamp.) 227 * <li> ["Resource", resource string] (if not supplied, defaults to "") 228 * <li> ["SensorDataType", sdt string] (if not supplied, defaults to "") 229 * <li> ["Tool", tool string] (if not supplied, defaults to "") 230 * <li> Any other key-val pairs are extracted and put into the SensorData properties. 231 * </ul> 232 * Throws an exception if Timestamp or Runtime are supplied but can't be parsed into an 233 * XMLGregorianCalendar instance. 234 * 235 * @param keyValMap The map of key-value pairs corresponding to SensorData fields and properties. 236 * @return A SensorData instance. 237 * @throws SensorBaseClientException If errors occur parsing the contents of the keyValMap. 238 */ 239 public synchronized SensorData makeSensorData(Map<String, String> keyValMap) 240 throws SensorBaseClientException { 241 // Begin by creating the sensor data instance. 242 SensorData data = new SensorData(); 243 XMLGregorianCalendar defaultTstamp = Tstamp.makeTimestamp(); 244 XMLGregorianCalendar tstamp = null; 245 try { 246 tstamp = Tstamp.makeTimestamp(takeFromMap(keyValMap, "Timestamp", defaultTstamp.toString())); 247 } 248 catch (Exception e) { 249 throw new SensorBaseClientException("Error parsing tstamp", e); 250 } 251 XMLGregorianCalendar runtime = null; 252 try { 253 runtime = Tstamp.makeTimestamp(takeFromMap(keyValMap, "Runtime", defaultTstamp.toString())); 254 } 255 catch (Exception e) { 256 throw new SensorBaseClientException("Error parsing runtime", e); 257 } 258 data.setOwner(takeFromMap(keyValMap, "Owner", userEmail)); 259 data.setResource(takeFromMap(keyValMap, "Resource", "")); 260 data.setRuntime(runtime); 261 data.setSensorDataType(takeFromMap(keyValMap, "SensorDataType", "")); 262 data.setTimestamp(tstamp); 263 data.setTool(takeFromMap(keyValMap, "Tool", "unknown")); 264 // Add all remaining key-val pairs to the property list. 265 data.setProperties(new org.hackystat.sensorbase.resource.sensordata.jaxb.Properties()); 266 for (Map.Entry<String, String> entry : keyValMap.entrySet()) { 267 Property property = new Property(); 268 property.setKey(entry.getKey()); 269 property.setValue(entry.getValue()); 270 data.getProperties().getProperty().add(property); 271 } 272 return data; 273 } 274 275 /** 276 * Returns the value associated with key in keyValMap, or the default, and also removes the 277 * mapping associated with key from the keyValMap. 278 * 279 * @param keyValMap The map 280 * @param key The key 281 * @param defaultValue The value to return if the key has no mapping. 282 * @return The value to be used. 283 */ 284 private String takeFromMap(Map<String, String> keyValMap, String key, String defaultValue) { 285 String value = (keyValMap.get(key) == null) ? defaultValue : keyValMap.get(key); 286 keyValMap.remove(key); 287 return value; 288 } 289 290 /** 291 * Returns the index of SensorDataTypes from this server. 292 * 293 * @return The SensorDataTypeIndex instance. 294 * @throws SensorBaseClientException If the server does not return the Index or returns an index 295 * that cannot be marshalled into Java SensorDataTypeIndex instance. 296 */ 297 public synchronized SensorDataTypeIndex getSensorDataTypeIndex() 298 throws SensorBaseClientException { 299 Response response = makeRequest(Method.GET, "sensordatatypes", null); 300 SensorDataTypeIndex index; 301 if (!response.getStatus().isSuccess()) { 302 throw new SensorBaseClientException(response.getStatus()); 303 } 304 try { 305 String xmlData = response.getEntity().getText(); 306 index = makeSensorDataTypeIndex(xmlData); 307 } 308 catch (Exception e) { 309 throw new SensorBaseClientException(response.getStatus(), e); 310 } 311 return index; 312 } 313 314 /** 315 * Returns the named SensorDataType from this server. 316 * 317 * @param sdtName The SDT name. 318 * @return The SensorDataType instance. 319 * @throws SensorBaseClientException If the server does not return the SDT or returns something 320 * that cannot be marshalled into Java SensorDataType instance. 321 */ 322 public synchronized SensorDataType getSensorDataType(String sdtName) 323 throws SensorBaseClientException { 324 Response response = makeRequest(Method.GET, "sensordatatypes/" + sdtName, null); 325 SensorDataType sdt; 326 if (!response.getStatus().isSuccess()) { 327 throw new SensorBaseClientException(response.getStatus()); 328 } 329 try { 330 String xmlData = response.getEntity().getText(); 331 sdt = makeSensorDataType(xmlData); 332 } 333 catch (Exception e) { 334 throw new SensorBaseClientException(response.getStatus(), e); 335 } 336 return sdt; 337 } 338 339 /** 340 * Returns the named SensorDataType associated with the SensorDataTypeRef. 341 * 342 * @param ref The SensorDataTypeRef instance 343 * @return The SensorDataType instance. 344 * @throws SensorBaseClientException If the server does not return the SDT or returns something 345 * that cannot be marshalled into Java SensorDataType instance. 346 */ 347 public synchronized SensorDataType getSensorDataType(SensorDataTypeRef ref) 348 throws SensorBaseClientException { 349 Response response = getUri(ref.getHref()); 350 SensorDataType sdt; 351 if (!response.getStatus().isSuccess()) { 352 throw new SensorBaseClientException(response.getStatus()); 353 } 354 try { 355 String xmlData = response.getEntity().getText(); 356 sdt = makeSensorDataType(xmlData); 357 } 358 catch (Exception e) { 359 throw new SensorBaseClientException(response.getStatus(), e); 360 } 361 return sdt; 362 } 363 364 /** 365 * Creates the passed SDT on the server. This is an admin-only operation. 366 * 367 * @param sdt The SDT to create. 368 * @throws SensorBaseClientException If the user is not the admin or if there is some problem with 369 * the SDT instance. 370 */ 371 public synchronized void putSensorDataType(SensorDataType sdt) throws SensorBaseClientException { 372 try { 373 String xmlData = makeSensorDataType(sdt); 374 Representation representation = SensorBaseResource.getStringRepresentation(xmlData); 375 String uri = "sensordatatypes/" + sdt.getName(); 376 Response response = makeRequest(Method.PUT, uri, representation); 377 if (!response.getStatus().isSuccess()) { 378 throw new SensorBaseClientException(response.getStatus()); 379 } 380 } 381 // Allow SensorBaseClientExceptions to be thrown out of this method. 382 catch (SensorBaseClientException f) { 383 throw f; 384 } 385 // All other exceptions are caught and rethrown. 386 catch (Exception e) { 387 throw new SensorBaseClientException("Error marshalling SDT", e); 388 } 389 } 390 391 /** 392 * Deletes the SDT given its name. 393 * 394 * @param sdtName The name of the SDT to delete. 395 * @throws SensorBaseClientException If the server does not indicate success. 396 */ 397 public synchronized void deleteSensorDataType(String sdtName) throws SensorBaseClientException { 398 Response response = makeRequest(Method.DELETE, "sensordatatypes/" + sdtName, null); 399 if (!response.getStatus().isSuccess()) { 400 throw new SensorBaseClientException(response.getStatus()); 401 } 402 } 403 404 /** 405 * Returns the index of Users from this server. This is an admin-only operation. 406 * 407 * @return The UserIndex instance. 408 * @throws SensorBaseClientException If the server does not return the Index or returns an index 409 * that cannot be marshalled into Java UserIndex instance. 410 */ 411 public synchronized UserIndex getUserIndex() throws SensorBaseClientException { 412 Response response = makeRequest(Method.GET, "users", null); 413 UserIndex index; 414 if (!response.getStatus().isSuccess()) { 415 throw new SensorBaseClientException(response.getStatus()); 416 } 417 try { 418 String xmlData = response.getEntity().getText(); 419 index = makeUserIndex(xmlData); 420 } 421 catch (Exception e) { 422 throw new SensorBaseClientException(response.getStatus(), e); 423 } 424 return index; 425 } 426 427 /** 428 * Returns the User instance associated with this SensorBaseClient instance. 429 * Requires a HTTP call to the SensorBase. 430 * @return The User instance for the user associated with this SensorBaseClient. 431 * @throws SensorBaseClientException If problems occur. 432 */ 433 public synchronized User getUser() throws SensorBaseClientException { 434 return getUser(this.userEmail); 435 } 436 437 /** 438 * Returns the named User from this server. 439 * 440 * @param email The user email. 441 * @return The User. 442 * @throws SensorBaseClientException If the server does not return the SDT or returns something 443 * that cannot be marshalled into Java User instance. 444 */ 445 public synchronized User getUser(String email) throws SensorBaseClientException { 446 Response response = makeRequest(Method.GET, "users/" + userEmail, null); 447 User user; 448 if (!response.getStatus().isSuccess()) { 449 throw new SensorBaseClientException(response.getStatus()); 450 } 451 try { 452 String xmlData = response.getEntity().getText(); 453 user = makeUser(xmlData); 454 } 455 catch (Exception e) { 456 throw new SensorBaseClientException(response.getStatus(), e); 457 } 458 return user; 459 } 460 461 /** 462 * Returns the named User associated with the UserRef. 463 * 464 * @param ref The UserRef instance 465 * @return The User instance. 466 * @throws SensorBaseClientException If the server does not return the user or returns something 467 * that cannot be marshalled into Java User instance. 468 */ 469 public synchronized User getUser(UserRef ref) throws SensorBaseClientException { 470 Response response = getUri(ref.getHref()); 471 User user; 472 if (!response.getStatus().isSuccess()) { 473 throw new SensorBaseClientException(response.getStatus()); 474 } 475 try { 476 String xmlData = response.getEntity().getText(); 477 user = makeUser(xmlData); 478 } 479 catch (Exception e) { 480 throw new SensorBaseClientException(response.getStatus(), e); 481 } 482 return user; 483 } 484 485 /** 486 * Deletes the User given their email. 487 * 488 * @param email The email of the User to delete. 489 * @throws SensorBaseClientException If the server does not indicate success. 490 */ 491 public synchronized void deleteUser(String email) throws SensorBaseClientException { 492 Response response = makeRequest(Method.DELETE, "users/" + email, null); 493 if (!response.getStatus().isSuccess()) { 494 throw new SensorBaseClientException(response.getStatus()); 495 } 496 } 497 498 /** 499 * Updates the specified user's properties. 500 * 501 * @param email The email of the User whose properties are to be deleted. 502 * @param properties The properties to post. 503 * @throws SensorBaseClientException If the server does not indicate success. 504 */ 505 public synchronized void updateUserProperties(String email, Properties properties) 506 throws SensorBaseClientException { 507 String xmlData; 508 try { 509 xmlData = makeProperties(properties); 510 } 511 catch (Exception e) { 512 throw new SensorBaseClientException("Failed to marshall Properties instance.", e); 513 } 514 Representation representation = SensorBaseResource.getStringRepresentation(xmlData); 515 Response response = makeRequest(Method.POST, "users/" + email, representation); 516 if (!response.getStatus().isSuccess()) { 517 throw new SensorBaseClientException(response.getStatus()); 518 } 519 } 520 521 /** 522 * GETs the URI string and returns the Restlet Response if the server indicates success. 523 * 524 * @param uriString The URI String, such as "http://localhost:9876/sensorbase/sensordatatypes". 525 * @return The response instance if the GET request succeeded. 526 * @throws SensorBaseClientException If the server indicates that a problem occurred. 527 */ 528 public synchronized Response getUri(String uriString) throws SensorBaseClientException { 529 Reference reference = new Reference(uriString); 530 Request request = new Request(Method.GET, reference); 531 request.getClientInfo().getAcceptedMediaTypes().add(xmlMedia); 532 ChallengeResponse authentication = new ChallengeResponse(scheme, this.userEmail, this.password); 533 request.setChallengeResponse(authentication); 534 if (this.isTraceEnabled) { 535 System.out.println("SensorBaseClient Tracing: GET " + reference); 536 } 537 Response response = this.client.handle(request); 538 if (this.isTraceEnabled) { 539 Status status = response.getStatus(); 540 System.out.println(" => " + status.getCode() + " " + status.getDescription()); 541 } 542 if (!response.getStatus().isSuccess()) { 543 throw new SensorBaseClientException(response.getStatus()); 544 } 545 return response; 546 } 547 548 /** 549 * Returns the index of SensorData from this server. This is an admin-only operation. 550 * 551 * @return The SensorDataIndex instance. 552 * @throws SensorBaseClientException If the server does not return the Index or returns an index 553 * that cannot be marshalled into Java SensorDataIndex instance. 554 */ 555 public synchronized SensorDataIndex getSensorDataIndex() throws SensorBaseClientException { 556 Response response = makeRequest(Method.GET, "sensordata", null); 557 SensorDataIndex index; 558 if (!response.getStatus().isSuccess()) { 559 throw new SensorBaseClientException(response.getStatus()); 560 } 561 try { 562 String xmlData = response.getEntity().getText(); 563 index = makeSensorDataIndex(xmlData); 564 } 565 catch (Exception e) { 566 throw new SensorBaseClientException(response.getStatus(), e); 567 } 568 setSensorDataIndexLastMod(index); 569 return index; 570 } 571 572 /** 573 * Computes the lastMod value for this index. Iterates through the individual refs to find their 574 * lastMod values, and stores the most recent lastMod value as the result. If the index is empty, 575 * then a default lastMod of 1000-01-01 is returned. We hope that indexes are cached so that this 576 * is not done a lot. 577 * 578 * @param index The index. 579 * @throws SensorBaseClientException If problems occur parsing the lastMod fields. 580 */ 581 private void setSensorDataIndexLastMod(SensorDataIndex index) throws SensorBaseClientException { 582 try { 583 index.setLastMod(Tstamp.makeTimestamp("1000-01-01")); 584 for (SensorDataRef ref : index.getSensorDataRef()) { 585 XMLGregorianCalendar lastMod = ref.getLastMod(); 586 if ((!(lastMod == null)) && Tstamp.greaterThan(lastMod, index.getLastMod())) { 587 index.setLastMod(lastMod); 588 } 589 } 590 } 591 catch (Exception e) { 592 throw new SensorBaseClientException("Error setting LastMod for index: " + index, e); 593 } 594 } 595 596 /** 597 * Returns the index of SensorData for this user from this server. 598 * 599 * @param email The user email. 600 * @return The SensorDataIndex instance. 601 * @throws SensorBaseClientException If the server does not return the Index or returns an index 602 * that cannot be marshalled into Java SensorDataIndex instance. 603 */ 604 public synchronized SensorDataIndex getSensorDataIndex(String email) 605 throws SensorBaseClientException { 606 Response response = makeRequest(Method.GET, sensordataUri + email, null); 607 SensorDataIndex index; 608 if (!response.getStatus().isSuccess()) { 609 throw new SensorBaseClientException(response.getStatus()); 610 } 611 try { 612 String xmlData = response.getEntity().getText(); 613 index = makeSensorDataIndex(xmlData); 614 } 615 catch (Exception e) { 616 throw new SensorBaseClientException(response.getStatus(), e); 617 } 618 setSensorDataIndexLastMod(index); 619 return index; 620 } 621 622 /** 623 * Returns the index of SensorData for this user from this server with the specified SDT. 624 * 625 * @param email The user email. 626 * @param sdtName The name of the SDT whose SensorData is to be returned. 627 * @return The SensorDataIndex instance. 628 * @throws SensorBaseClientException If the server does not return the Index or returns an index 629 * that cannot be marshalled into Java SensorDataIndex instance. 630 */ 631 public synchronized SensorDataIndex getSensorDataIndex(String email, String sdtName) 632 throws SensorBaseClientException { 633 Response response = makeRequest(Method.GET, sensordataUri + email + "?sdt=" + sdtName, null); 634 SensorDataIndex index; 635 if (!response.getStatus().isSuccess()) { 636 throw new SensorBaseClientException(response.getStatus()); 637 } 638 try { 639 String xmlData = response.getEntity().getText(); 640 index = makeSensorDataIndex(xmlData); 641 } 642 catch (Exception e) { 643 throw new SensorBaseClientException(response.getStatus(), e); 644 } 645 return index; 646 } 647 648 /** 649 * Returns an index to SensorData for the specified user of all sensor data that have arrived at 650 * the server since the specified start and end times. Uses the LastMod field to determine what 651 * data will be retrieved. 652 * <p> 653 * Note that data could be sent recently with a Timestamp (as opposed to LastMod field) from far 654 * back in the past, and the index will include references to such data. This method thus differs 655 * from all other SensorDataIndex-returning methods, because the others compare passed timestamp 656 * values to the Timestamp associated with the moment at which a sensor data instance is created, 657 * not the moment it ends up being received by the server. 658 * <p> 659 * This method is intended for use by user interface facilities such as the SensorDataViewer that 660 * wish to monitor the arrival of data at the SensorBase. 661 * 662 * @param email The user email. 663 * @param lastModStartTime A timestamp used to determine the start time of data to get. 664 * @param lastModEndTime A timestamp used to determine the end time of data to get. 665 * @return The SensorDataIndex instance. 666 * @throws SensorBaseClientException If the server does not return the Index or returns an index 667 * that cannot be marshalled into Java SensorDataIndex instance. 668 */ 669 public synchronized SensorDataIndex getSensorDataIndexLastMod(String email, 670 XMLGregorianCalendar lastModStartTime, XMLGregorianCalendar lastModEndTime) 671 throws SensorBaseClientException { 672 Response response = makeRequest(Method.GET, sensordataUri + email + "?lastModStartTime=" 673 + lastModStartTime + "&lastModEndTime=" + lastModEndTime, null); 674 SensorDataIndex index; 675 if (!response.getStatus().isSuccess()) { 676 throw new SensorBaseClientException(response.getStatus()); 677 } 678 try { 679 String xmlData = response.getEntity().getText(); 680 index = makeSensorDataIndex(xmlData); 681 } 682 catch (Exception e) { 683 throw new SensorBaseClientException(response.getStatus(), e); 684 } 685 return index; 686 } 687 688 /** 689 * Returns the SensorData for this user from this server with the specified timestamp. 690 * Uses the cache if enabled. 691 * 692 * @param email The user email. 693 * @param timestamp The timestamp. 694 * @return The SensorData instance. 695 * @throws SensorBaseClientException If the server does not return the success code or returns a 696 * String that cannot be marshalled into Java SensorData instance. 697 */ 698 public synchronized SensorData getSensorData(String email, XMLGregorianCalendar timestamp) 699 throws SensorBaseClientException { 700 SensorData data; 701 String uri = sensordataUri + email + "/" + timestamp; 702 // Check the cache, and return the sensor data instance from it if available. 703 if (this.isCacheEnabled) { 704 data = (SensorData)this.uriCache.get(uri); 705 if (data != null) { 706 return data; 707 } 708 } 709 // If not in the cache, request it from the sensorbase service. 710 Response response = makeRequest(Method.GET, uri, null); 711 if (!response.getStatus().isSuccess()) { 712 throw new SensorBaseClientException(response.getStatus()); 713 } 714 try { 715 String xmlData = response.getEntity().getText(); 716 data = makeSensorData(xmlData); 717 // Add it to the cache if we're using one. 718 if (this.isCacheEnabled) { 719 this.uriCache.put(uri, data); 720 } 721 } 722 catch (Exception e) { 723 throw new SensorBaseClientException(response.getStatus(), e); 724 } 725 return data; 726 } 727 728 /** 729 * Returns the SensorData for this user from this server given the passed uriString suffix. 730 * 731 * @param uriString A string that when prefixed with the sensorbase host should return 732 * a SensorData instance in XML format. A string such as "http://localhost:9876/sensorbase" 733 * will be prefixed to the uriString. 734 * @return The SensorData instance. 735 * @throws SensorBaseClientException If the server does not return the success code or returns a 736 * String that cannot be marshalled into Java SensorData instance. 737 */ 738 public synchronized SensorData getSensorData(String uriString) throws SensorBaseClientException { 739 SensorData data; 740 // Check the cache, and return the sensor data instance from it if available. 741 if (this.isCacheEnabled) { 742 data = (SensorData)this.uriCache.get(uriString); 743 if (data != null) { 744 return data; 745 } 746 } 747 // Otherwise get it from the sensorbase. 748 Response response = makeRequest(Method.GET, uriString, null); 749 if (!response.getStatus().isSuccess()) { 750 throw new SensorBaseClientException(response.getStatus()); 751 } 752 try { 753 String xmlData = response.getEntity().getText(); 754 data = makeSensorData(xmlData); 755 // Add it to the cache if we're using one. 756 if (this.isCacheEnabled) { 757 this.uriCache.put(uriString, data); 758 } 759 } 760 catch (Exception e) { 761 throw new SensorBaseClientException(response.getStatus(), e); 762 } 763 return data; 764 } 765 766 /** 767 * Returns the named SensorData associated with the SensorDataRef. 768 * 769 * @param ref The SensorDataRef instance 770 * @return The SensorData instance. 771 * @throws SensorBaseClientException If the server does not return the data or returns something 772 * that cannot be marshalled into Java SensorData instance. 773 */ 774 public synchronized SensorData getSensorData(SensorDataRef ref) throws SensorBaseClientException { 775 SensorData data; 776 String uri = ref.getHref(); 777 // Check the cache, and return the sensor data instance from it if available. 778 if (this.isCacheEnabled) { 779 data = (SensorData)this.uriCache.get(uri); 780 if (data != null) { 781 return data; 782 } 783 } 784 Response response = getUri(uri); 785 if (!response.getStatus().isSuccess()) { 786 throw new SensorBaseClientException(response.getStatus()); 787 } 788 try { 789 String xmlData = response.getEntity().getText(); 790 data = makeSensorData(xmlData); 791 // Add it to the cache if we're using one. 792 if (this.isCacheEnabled) { 793 this.uriCache.put(uri, data); 794 } 795 } 796 catch (Exception e) { 797 throw new SensorBaseClientException(response.getStatus(), e); 798 } 799 return data; 800 } 801 802 803 /** 804 * Creates the passed SensorData on the server. 805 * 806 * @param data The sensor data to create. 807 * @throws SensorBaseClientException If problems occur posting this data. 808 */ 809 public synchronized void putSensorData(SensorData data) throws SensorBaseClientException { 810 try { 811 String xmlData = makeSensorData(data); 812 Representation representation = SensorBaseResource.getStringRepresentation(xmlData); 813 String uri = sensordataUri + data.getOwner() + "/" + data.getTimestamp(); 814 Response response = makeRequest(Method.PUT, uri, representation); 815 if (!response.getStatus().isSuccess()) { 816 throw new SensorBaseClientException(response.getStatus()); 817 } 818 } 819 // Allow SensorBaseClientExceptions to be thrown out of this method. 820 catch (SensorBaseClientException f) { 821 throw f; 822 } 823 // All other exceptions are caught and rethrown. 824 catch (Exception e) { 825 throw new SensorBaseClientException("Error marshalling sensor data", e); 826 } 827 } 828 829 /** 830 * Creates the passed batch of SensorData on the server. Assumes that all of them have the same 831 * owner field, and that batch is non-empty. 832 * 833 * @param data The sensor data batch to create, represented as a SensorDatas instance. 834 * @throws SensorBaseClientException If problems occur posting this data. 835 */ 836 public synchronized void putSensorDataBatch(SensorDatas data) throws SensorBaseClientException { 837 try { 838 String xmlData = makeSensorDatas(data); 839 String owner = data.getSensorData().get(0).getOwner(); 840 Representation representation = SensorBaseResource.getStringRepresentation(xmlData); 841 String uri = sensordataUri + owner + "/batch"; 842 Response response = makeRequest(Method.PUT, uri, representation); 843 if (!response.getStatus().isSuccess()) { 844 throw new SensorBaseClientException(response.getStatus()); 845 } 846 } 847 // Allow SensorBaseClientExceptions to be thrown out of this method. 848 catch (SensorBaseClientException f) { 849 throw f; 850 } 851 // All other exceptions are caught and rethrown. 852 catch (Exception e) { 853 throw new SensorBaseClientException("Error marshalling batch sensor data", e); 854 } 855 } 856 857 /** 858 * Ensures that the SensorData instance with the specified user and tstamp is not on the server. 859 * Returns success even if the SensorData instance did not exist on the server. 860 * 861 * @param email The email of the User. 862 * @param timestamp The timestamp of the sensor data. 863 * @throws SensorBaseClientException If the server does not indicate success. 864 */ 865 public synchronized void deleteSensorData(String email, XMLGregorianCalendar timestamp) 866 throws SensorBaseClientException { 867 Response response = makeRequest(Method.DELETE, sensordataUri + email + "/" + timestamp, null); 868 if (!response.getStatus().isSuccess()) { 869 throw new SensorBaseClientException(response.getStatus()); 870 } 871 } 872 873 /** 874 * Deletes all sensor data associated with the specified user. 875 * Note that the user must be a test user. 876 * Returns success even if the user had no sensor data. 877 * 878 * @param email The email of the User. 879 * @throws SensorBaseClientException If the server does not indicate success. 880 */ 881 public synchronized void deleteSensorData(String email) throws SensorBaseClientException { 882 Response response = makeRequest(Method.DELETE, sensordataUri + email, null); 883 if (!response.getStatus().isSuccess()) { 884 throw new SensorBaseClientException(response.getStatus()); 885 } 886 } 887 888 /** 889 * Returns the index of all Projects from this server. This is an admin-only operation. 890 * 891 * @return The ProjectIndex instance. 892 * @throws SensorBaseClientException If the server does not return the Index or returns an index 893 * that cannot be marshalled into Java ProjectIndex instance. 894 */ 895 public synchronized ProjectIndex getProjectIndex() throws SensorBaseClientException { 896 Response response = makeRequest(Method.GET, projectsUri, null); 897 ProjectIndex index; 898 if (!response.getStatus().isSuccess()) { 899 throw new SensorBaseClientException(response.getStatus()); 900 } 901 try { 902 String xmlData = response.getEntity().getText(); 903 index = makeProjectIndex(xmlData); 904 } 905 catch (Exception e) { 906 throw new SensorBaseClientException(response.getStatus(), e); 907 } 908 return index; 909 } 910 911 /** 912 * Returns the index of all Projects from this server associated with this user. 913 * This includes the projects that this user owns, that this user is a member of, 914 * and that this user has been invited to participate in as a member (but has not 915 * yet accepted or declined.) 916 * 917 * @param email The user email. 918 * @return The ProjectIndex instance. 919 * @throws SensorBaseClientException If the server does not return the Index or returns an index 920 * that cannot be marshalled into Java ProjectIndex instance. 921 */ 922 public synchronized ProjectIndex getProjectIndex(String email) 923 throws SensorBaseClientException { 924 Response response = makeRequest(Method.GET, projectsUri + email, null); 925 ProjectIndex index; 926 if (!response.getStatus().isSuccess()) { 927 throw new SensorBaseClientException(response.getStatus()); 928 } 929 try { 930 String xmlData = response.getEntity().getText(); 931 index = makeProjectIndex(xmlData); 932 } 933 catch (Exception e) { 934 throw new SensorBaseClientException(response.getStatus(), e); 935 } 936 return index; 937 } 938 939 940 /** 941 * Returns the Project from this server. 942 * 943 * @param email The user email. 944 * @param projectName The project name. 945 * @return The Project 946 * @throws SensorBaseClientException If the server does not return success or returns something 947 * that cannot be marshalled into Java Project instance. 948 */ 949 public synchronized Project getProject(String email, String projectName) 950 throws SensorBaseClientException { 951 Response response = makeRequest(Method.GET, projectsUri + email + "/" + projectName, null); 952 Project project; 953 if (!response.getStatus().isSuccess()) { 954 throw new SensorBaseClientException(response.getStatus()); 955 } 956 try { 957 String xmlData = response.getEntity().getText(); 958 project = makeProject(xmlData); 959 } 960 catch (Exception e) { 961 throw new SensorBaseClientException(response.getStatus(), e); 962 } 963 return project; 964 } 965 966 /** 967 * True if the current user is the owner, member, or spectator of the specified project. 968 * If caching is enabled, then we cache the retrieved project definition for 969 * 3 minutes when this client is an owner, member, or spectator so that subsequent retrievals 970 * don't require an http access. 971 * 972 * @param email The email address of the project owner. 973 * @param projectName The name of the project. 974 * @return True if this user is now a member of the 975 */ 976 public synchronized boolean inProject(String email, String projectName) { 977 String key = email + '/' + projectName; 978 double maxLifeHours = 0.05; 979 if (this.isCacheEnabled && this.uriCache.get(key) != null) { 980 return true; 981 } 982 // otherwise we attempt to retrieve the project definition. 983 try { 984 Project project = getProject(email, projectName); 985 boolean success = isInProject(project); 986 if (this.isCacheEnabled && success) { 987 this.uriCache.put(key, project, maxLifeHours); 988 } 989 return success; 990 } 991 catch (Exception e) { 992 return false; 993 } 994 } 995 996 /** 997 * Returns true if this client's user is the owner or member or spectator in the project. 998 * @param project The project. 999 * @return True if the client is an owner, member, or spectator. 1000 */ 1001 private boolean isInProject(Project project) { 1002 if (project.getOwner().equals(this.userEmail)) { 1003 return true; 1004 } 1005 for (String member : project.getMembers().getMember()) { 1006 if (member.equals(this.userEmail)) { 1007 return true; 1008 } 1009 } 1010 for (String spectator : project.getSpectators().getSpectator()) { 1011 if (spectator.equals(this.userEmail)) { 1012 return true; 1013 } 1014 } 1015 return false; 1016 } 1017 1018 /** 1019 * Invites the user indicated via their email address to the named project owned by this user. 1020 * Has no effect if the user is already an invited member. 1021 * Returns the updated project representation. 1022 * 1023 * @param email The user to be invited to this project. 1024 * @param projectName The project name. 1025 * @return The project representation as a result of the invitation. 1026 * @throws SensorBaseClientException If the server does not return success. 1027 */ 1028 public synchronized Project invite(String email, String projectName) 1029 throws SensorBaseClientException { 1030 1031 // First, get the project representation. 1032 Project project = this.getProject(this.userEmail, projectName); 1033 // Make sure that the Invitations instance is not null. 1034 if (project.getInvitations() == null) { 1035 project.setInvitations(new Invitations()); 1036 } 1037 // If user hasn't already been invited, then invite them. 1038 if (!project.getInvitations().getInvitation().contains(email)) { 1039 project.getInvitations().getInvitation().add(email); 1040 this.putProject(project); 1041 } 1042 // Return the updated project representation from the server. 1043 return this.getProject(this.userEmail, projectName); 1044 } 1045 1046 /** 1047 * Accepts the invitation to be a member of the project owned by owner. 1048 * 1049 * @param owner The owner of the project that this user has been invited into 1050 * @param projectName the name of the project. 1051 * @param reply The reply, either ACCEPT or DECLINE. 1052 * @throws SensorBaseClientException If the server returns an error from this acceptance, for 1053 * example if the user has not actually been invited. 1054 */ 1055 public synchronized void reply(String owner, String projectName, InvitationReply reply) 1056 throws SensorBaseClientException { 1057 Response response = makeRequest(Method.POST, 1058 projectsUri + owner + "/" + projectName + "/invitation/" + 1059 reply.toString().toLowerCase(), null); 1060 if (!response.getStatus().isSuccess()) { 1061 throw new SensorBaseClientException(response.getStatus()); 1062 } 1063 } 1064 1065 /** 1066 * Renames the project. 1067 * 1068 * @param owner The owner of the project to be renamed. 1069 * @param projectName The current name of the project. 1070 * @param newProjectName The new name of the project. 1071 * @throws SensorBaseClientException If the server returns an error from this acceptance, for 1072 * example if the new project name is not unique. 1073 */ 1074 public synchronized void renameProject(String owner, String projectName, String newProjectName) 1075 throws SensorBaseClientException { 1076 Response response = makeRequest(Method.POST, 1077 projectsUri + owner + "/" + projectName + "/rename/" + newProjectName, null); 1078 if (!response.getStatus().isSuccess()) { 1079 throw new SensorBaseClientException(response.getStatus()); 1080 } 1081 } 1082 1083 1084 1085 /** 1086 * Returns the named Project associated with the ProjectRef. 1087 * 1088 * @param ref The ProjectRef instance 1089 * @return The Project instance. 1090 * @throws SensorBaseClientException If the server does not return the user or returns something 1091 * that cannot be marshalled into Java Project instance. 1092 */ 1093 public synchronized Project getProject(ProjectRef ref) throws SensorBaseClientException { 1094 Response response = getUri(ref.getHref()); 1095 Project project; 1096 if (!response.getStatus().isSuccess()) { 1097 throw new SensorBaseClientException(response.getStatus()); 1098 } 1099 try { 1100 String xmlData = response.getEntity().getText(); 1101 project = makeProject(xmlData); 1102 } 1103 catch (Exception e) { 1104 throw new SensorBaseClientException(response.getStatus(), e); 1105 } 1106 return project; 1107 } 1108 1109 /** 1110 * Returns a SensorDataIndex representing all the SensorData for this Project. 1111 * 1112 * @param owner The project owner's email. 1113 * @param projectName The project name. 1114 * @return A SensorDataIndex. 1115 * @throws SensorBaseClientException If the server does not return success or returns something 1116 * that cannot be marshalled into Java SensorDataIndex instance. 1117 */ 1118 public synchronized SensorDataIndex getProjectSensorData(String owner, String projectName) 1119 throws SensorBaseClientException { 1120 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1121 + "/sensordata", null); 1122 SensorDataIndex index; 1123 if (!response.getStatus().isSuccess()) { 1124 throw new SensorBaseClientException(response.getStatus()); 1125 } 1126 try { 1127 String xmlData = response.getEntity().getText(); 1128 index = makeSensorDataIndex(xmlData); 1129 } 1130 catch (Exception e) { 1131 throw new SensorBaseClientException(response.getStatus(), e); 1132 } 1133 return index; 1134 } 1135 1136 1137 1138 /** 1139 * Returns a SensorDataIndex representing the SensorData for the Project during the time interval. 1140 * 1141 * @param owner The project owner's email. 1142 * @param projectName The project name. 1143 * @param startTime The start time. 1144 * @param endTime The end time. 1145 * @return A SensorDataIndex. 1146 * @throws SensorBaseClientException If the server does not return success or returns something 1147 * that cannot be marshalled into Java SensorDataIndex instance. 1148 */ 1149 public synchronized SensorDataIndex getProjectSensorData(String owner, String projectName, 1150 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime) 1151 throws SensorBaseClientException { 1152 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1153 + "/sensordata?startTime=" + startTime + andEndTime + endTime, null); 1154 SensorDataIndex index; 1155 if (!response.getStatus().isSuccess()) { 1156 throw new SensorBaseClientException(response.getStatus()); 1157 } 1158 try { 1159 String xmlData = response.getEntity().getText(); 1160 index = makeSensorDataIndex(xmlData); 1161 } 1162 catch (Exception e) { 1163 throw new SensorBaseClientException(response.getStatus(), e); 1164 } 1165 return index; 1166 } 1167 1168 /** 1169 * Returns a SensorDataIndex representing the SensorData with the given SDT for the Project 1170 * during the time interval. 1171 * 1172 * @param owner The project owner's email. 1173 * @param projectName The project name. 1174 * @param startTime The start time. 1175 * @param endTime The end time. 1176 * @param sdt The SensorDataType. 1177 * @return A SensorDataIndex. 1178 * @throws SensorBaseClientException If the server does not return success or returns something 1179 * that cannot be marshalled into Java SensorDataIndex instance. 1180 */ 1181 public synchronized SensorDataIndex getProjectSensorData(String owner, String projectName, 1182 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt) 1183 throws SensorBaseClientException { 1184 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1185 + "/sensordata?sdt=" + sdt + "&startTime=" + startTime + andEndTime + endTime, null); 1186 SensorDataIndex index; 1187 if (!response.getStatus().isSuccess()) { 1188 throw new SensorBaseClientException(response.getStatus()); 1189 } 1190 try { 1191 String xmlData = response.getEntity().getText(); 1192 index = makeSensorDataIndex(xmlData); 1193 } 1194 catch (Exception e) { 1195 throw new SensorBaseClientException(response.getStatus(), e); 1196 } 1197 return index; 1198 } 1199 1200 /** 1201 * Returns a SensorDataIndex representing the SensorData with the given SDT for the Project 1202 * during the time interval. 1203 * 1204 * @param owner The project owner's email. 1205 * @param projectName The project name. 1206 * @param startTime The start time. 1207 * @param endTime The end time. 1208 * @param sdt The SensorDataType. 1209 * @param tool The tool that generated this sensor data of the given type. 1210 * @return A SensorDataIndex. 1211 * @throws SensorBaseClientException If the server does not return success or returns something 1212 * that cannot be marshalled into Java SensorDataIndex instance. 1213 */ 1214 public synchronized SensorDataIndex getProjectSensorData(String owner, String projectName, 1215 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt, String tool) 1216 throws SensorBaseClientException { 1217 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1218 + "/sensordata?sdt=" + sdt + "&startTime=" + startTime + andEndTime + endTime + 1219 "&tool=" + tool, null); 1220 SensorDataIndex index; 1221 if (!response.getStatus().isSuccess()) { 1222 throw new SensorBaseClientException(response.getStatus()); 1223 } 1224 try { 1225 String xmlData = response.getEntity().getText(); 1226 index = makeSensorDataIndex(xmlData); 1227 } 1228 catch (Exception e) { 1229 throw new SensorBaseClientException(response.getStatus(), e); 1230 } 1231 return index; 1232 } 1233 1234 /** 1235 * Returns a SensorDataIndex representing the SensorData with the startIndex and 1236 * maxInstances for the Project during the time interval. 1237 * The startIndex must be non-negative, and is zero-based. The maxInstances must be non-negative. 1238 * If startIndex is greater than the number of instances in the time interval, then an 1239 * empty SensorDataIndex is returned. 1240 * 1241 * @param owner The project owner's email. 1242 * @param projectName The project name. 1243 * @param startTime The start time. 1244 * @param endTime The end time. 1245 * @param startIndex The zero-based index to the first Sensor Data instance to be returned in 1246 * the time interval, when the instances are all ordered by timestamp. 1247 * @param maxInstances The maximum number of instances to return. 1248 * @return A SensorDataIndex. 1249 * @throws SensorBaseClientException If the server does not return success or returns something 1250 * that cannot be marshalled into Java SensorDataIndex instance. 1251 */ 1252 public synchronized SensorDataIndex getProjectSensorData(String owner, String projectName, 1253 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, int startIndex, 1254 int maxInstances) throws SensorBaseClientException { 1255 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1256 + "/sensordata?startTime=" + startTime + andEndTime + endTime + "&startIndex=" 1257 + startIndex + "&maxInstances=" + maxInstances, null); 1258 SensorDataIndex index; 1259 if (!response.getStatus().isSuccess()) { 1260 throw new SensorBaseClientException(response.getStatus()); 1261 } 1262 try { 1263 String xmlData = response.getEntity().getText(); 1264 index = makeSensorDataIndex(xmlData); 1265 } 1266 catch (Exception e) { 1267 throw new SensorBaseClientException(response.getStatus(), e); 1268 } 1269 return index; 1270 } 1271 1272 /** 1273 * Returns a SensorDataIndex containing a snapshot of the sensor data for the given project and 1274 * sdt during the specified time interval. A "snapshot" is the set of sensor data with the most 1275 * recent runtime value during that time interval. 1276 * @param owner The owner of the project. 1277 * @param projectName The project name. 1278 * @param startTime The start time. 1279 * @param endTime The end time. 1280 * @param sdt The sdt of interest for the sensor data. 1281 * @return The SensorDataIndex containing the "snapshot". 1282 * @throws SensorBaseClientException If problems occur. 1283 */ 1284 public synchronized SensorDataIndex getProjectSensorDataSnapshot(String owner, String projectName, 1285 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt) 1286 throws SensorBaseClientException { 1287 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1288 + "/snapshot?startTime=" + startTime + andEndTime + endTime + "&sdt=" + sdt, null); 1289 SensorDataIndex index; 1290 if (!response.getStatus().isSuccess()) { 1291 throw new SensorBaseClientException(response.getStatus()); 1292 } 1293 try { 1294 String xmlData = response.getEntity().getText(); 1295 index = makeSensorDataIndex(xmlData); 1296 } 1297 catch (Exception e) { 1298 throw new SensorBaseClientException(response.getStatus(), e); 1299 } 1300 return index; 1301 } 1302 1303 /** 1304 * Returns a SensorDataIndex containing a snapshot of the sensor data for the given project, sdt 1305 * and tool during the specified time interval. A "snapshot" is the set of sensor data with the 1306 * most recent runtime value during that time interval. 1307 * @param owner The owner of the project. 1308 * @param projectName The project name. 1309 * @param startTime The start time. 1310 * @param endTime The end time. 1311 * @param sdt The sdt of interest for the sensor data. 1312 * @param tool The tool of interest for the sensor data. 1313 * @return The SensorDataIndex containing the "snapshot". 1314 * @throws SensorBaseClientException If problems occur. 1315 */ 1316 public synchronized SensorDataIndex getProjectSensorDataSnapshot(String owner, String projectName, 1317 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt, String tool) 1318 throws SensorBaseClientException { 1319 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1320 + "/snapshot?startTime=" + startTime + andEndTime + endTime + "&sdt=" + sdt + 1321 "&tool=" + tool, null); 1322 SensorDataIndex index; 1323 if (!response.getStatus().isSuccess()) { 1324 throw new SensorBaseClientException(response.getStatus()); 1325 } 1326 try { 1327 String xmlData = response.getEntity().getText(); 1328 index = makeSensorDataIndex(xmlData); 1329 } 1330 catch (Exception e) { 1331 throw new SensorBaseClientException(response.getStatus(), e); 1332 } 1333 return index; 1334 } 1335 1336 /** 1337 * Returns a ProjectSummary representing a summary of the number of sensor data instances of 1338 * each type for the given interval. 1339 * @param owner The project owner. 1340 * @param projectName The project name. 1341 * @param startTime The start time. 1342 * @param endTime The end time. 1343 * @return A ProjectSummary. 1344 * @throws SensorBaseClientException If problems occur. 1345 */ 1346 public synchronized ProjectSummary getProjectSummary(String owner, String projectName, 1347 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime) 1348 throws SensorBaseClientException { 1349 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1350 + "/summary?startTime=" + startTime + andEndTime + endTime, null); 1351 1352 ProjectSummary summary; 1353 if (!response.getStatus().isSuccess()) { 1354 throw new SensorBaseClientException(response.getStatus()); 1355 } 1356 try { 1357 String xmlData = response.getEntity().getText(); 1358 summary = makeProjectSummary(xmlData); 1359 } 1360 catch (Exception e) { 1361 throw new SensorBaseClientException(response.getStatus(), e); 1362 } 1363 return summary; 1364 } 1365 1366 /** 1367 * Returns a MultiDayProjectSummary for the specified interval of days. 1368 * @param owner The project owner. 1369 * @param projectName The project name. 1370 * @param startTime The start day. 1371 * @param numDays The number of days, each one getting a ProjectSummary instance. 1372 * @return The MulitDayProjectSummary instance. 1373 * @throws SensorBaseClientException If problems occur. 1374 */ 1375 public synchronized MultiDayProjectSummary getMultiDayProjectSummary(String owner, 1376 String projectName, XMLGregorianCalendar startTime, int numDays) 1377 throws SensorBaseClientException { 1378 Response response = makeRequest(Method.GET, projectsUri + owner + "/" + projectName 1379 + "/summary?startTime=" + startTime + "&numDays=" + numDays, null); 1380 1381 MultiDayProjectSummary summary; 1382 if (!response.getStatus().isSuccess()) { 1383 throw new SensorBaseClientException(response.getStatus()); 1384 } 1385 try { 1386 String xmlData = response.getEntity().getText(); 1387 summary = makeMultiDayProjectSummary(xmlData); 1388 } 1389 catch (Exception e) { 1390 throw new SensorBaseClientException(response.getStatus(), e); 1391 } 1392 return summary; 1393 } 1394 1395 /** 1396 * Returns a MultiDayProjectSummary for the specified project, year, and month (zero based). 1397 * @param owner The project owner. 1398 * @param projectName The project name. 1399 * @param year The year. 1400 * @param month The month (zero based). 1401 * @return A MultiDayProjectSummary for the specified month. 1402 * @throws SensorBaseClientException If problems occur. 1403 */ 1404 public synchronized MultiDayProjectSummary getMonthProjectSummary (String owner, 1405 String projectName, int year, int month) throws SensorBaseClientException { 1406 XMLGregorianCalendar startDay = Tstamp.makeTimestamp(); 1407 startDay.setDay(1); 1408 startDay.setMonth(month); 1409 startDay.setYear(year); 1410 int numDays = startDay.toGregorianCalendar().getActualMaximum(GregorianCalendar.DAY_OF_MONTH); 1411 return getMultiDayProjectSummary(owner, projectName, startDay, numDays); 1412 } 1413 1414 /** 1415 * Creates the passed Project on the server. 1416 * 1417 * @param project The project to create. 1418 * @throws SensorBaseClientException If problems occur posting this data. 1419 */ 1420 public synchronized void putProject(Project project) throws SensorBaseClientException { 1421 try { 1422 String xmlData = makeProject(project); 1423 Representation representation = SensorBaseResource.getStringRepresentation(xmlData); 1424 String uri = projectsUri + project.getOwner() + "/" + project.getName(); 1425 Response response = makeRequest(Method.PUT, uri, representation); 1426 if (!response.getStatus().isSuccess()) { 1427 throw new SensorBaseClientException(response.getStatus()); 1428 } 1429 } 1430 // Allow SensorBaseClientExceptions to be thrown out of this method. 1431 catch (SensorBaseClientException f) { 1432 throw f; 1433 } 1434 // All other exceptions are caught and rethrown. 1435 catch (Exception e) { 1436 throw new SensorBaseClientException("Error marshalling sensor data", e); 1437 } 1438 } 1439 1440 /** 1441 * Deletes the Project given its owner and projectName. 1442 * 1443 * @param email The email of the project owner. 1444 * @param projectName The project name. 1445 * @throws SensorBaseClientException If the server does not indicate success. 1446 */ 1447 public synchronized void deleteProject(String email, String projectName) 1448 throws SensorBaseClientException { 1449 Response response = makeRequest(Method.DELETE, projectsUri + email + "/" + projectName, null); 1450 if (!response.getStatus().isSuccess()) { 1451 throw new SensorBaseClientException(response.getStatus()); 1452 } 1453 } 1454 1455 /** 1456 * Registers the given user email with the given SensorBase. 1457 * Timeout is set to 5 seconds. 1458 * 1459 * @param host The host name, such as "http://localhost:9876/sensorbase". 1460 * @param email The user email. 1461 * @throws SensorBaseClientException If problems occur during registration. 1462 */ 1463 public static void registerUser(String host, String email) throws SensorBaseClientException { 1464 RestletLoggerUtil.disableLogging(); 1465 String registerUri = host.endsWith("/") ? host + "register" : host + "/register"; 1466 Request request = new Request(); 1467 request.setResourceRef(registerUri); 1468 request.setMethod(Method.POST); 1469 Form form = new Form(); 1470 form.add("email", email); 1471 request.setEntity(form.getWebRepresentation()); 1472 Client client = new Client(Protocol.HTTP); 1473 setClientTimeout(client, getDefaultTimeout()); 1474 Response response = client.handle(request); 1475 if (!response.getStatus().isSuccess()) { 1476 throw new SensorBaseClientException(response.getStatus()); 1477 } 1478 } 1479 1480 /** 1481 * Sets the lastHostNotAvailable timestamp for the passed host. 1482 * @param host The host that was determined to be not available. 1483 */ 1484 private static void setLastHostNotAvailable(String host) { 1485 lastHostNotAvailable.put(host, (new Date()).getTime()); 1486 } 1487 1488 /** 1489 * Gets the lastHostNotAvailable timestamp associated with host. 1490 * Returns 0 if there is no lastHostNotAvailable timestamp. 1491 * @param host The host whose lastNotAvailable timestamp is to be retrieved. 1492 * @return The timestamp. 1493 */ 1494 private static long getLastHostNotAvailable(String host) { 1495 Long time = lastHostNotAvailable.get(host); 1496 return (time == null) ? 0 : time; 1497 } 1498 1499 /** 1500 * Returns true if the passed host is a SensorBase host. 1501 * The timeout is set at the default timeout value. 1502 * Since checking isHost() when the host is not available is expensive, we cache the timestamp 1503 * whenever we find the host to be unavailable and if there is another call to isHost() within 1504 * two seconds, we will immediately return false. This makes startup of clients like 1505 * SensorShell go much faster, since they call isHost() several times during startup. 1506 * 1507 * @param host The URL of a sensorbase host, such as "http://localhost:9876/sensorbase". 1508 * @return True if this URL responds as a SensorBase host. 1509 */ 1510 public static boolean isHost(String host) { 1511 RestletLoggerUtil.disableLogging(); 1512 // We return false immediately if we failed to contact the host within the last two seconds. 1513 long currTime = (new Date()).getTime(); 1514 if ((currTime - getLastHostNotAvailable(host)) < 2 * 1000) { 1515 return false; 1516 } 1517 1518 // All sensorbase hosts use the HTTP protocol. 1519 if (!host.startsWith("http://")) { 1520 setLastHostNotAvailable(host); 1521 return false; 1522 } 1523 // Create the host/register URL. 1524 try { 1525 String registerUri = host.endsWith("/") ? host + "ping" : host + "/ping"; 1526 Request request = new Request(); 1527 request.setResourceRef(registerUri); 1528 request.setMethod(Method.GET); 1529 Client client = new Client(Protocol.HTTP); 1530 setClientTimeout(client, getDefaultTimeout()); 1531 Response response = client.handle(request); 1532 String pingText = response.getEntity().getText(); 1533 boolean isAvailable = (response.getStatus().isSuccess() && "SensorBase".equals(pingText)); 1534 if (!isAvailable) { 1535 setLastHostNotAvailable(host); 1536 } 1537 return isAvailable; 1538 } 1539 catch (Exception e) { 1540 setLastHostNotAvailable(host); 1541 return false; 1542 } 1543 } 1544 1545 /** 1546 * Returns true if the user and password is registered as a user with this host. 1547 * 1548 * @param host The URL of a sensorbase host, such as "http://localhost:9876/sensorbase". 1549 * @param email The user email. 1550 * @param password The user password. 1551 * @return True if this user is registered with this host. 1552 */ 1553 public static boolean isRegistered(String host, String email, String password) { 1554 RestletLoggerUtil.disableLogging(); 1555 // Make sure the host is OK, which captures bogus hosts like "foo". 1556 if (!isHost(host)) { 1557 return false; 1558 } 1559 // Now try to authenticate. 1560 try { 1561 SensorBaseClient client = new SensorBaseClient(host, email, password); 1562 client.authenticate(); 1563 return true; 1564 } 1565 catch (Exception e) { 1566 return false; 1567 } 1568 } 1569 1570 1571 1572 /** 1573 * Throws an unchecked illegal argument exception if the arg is null or empty. 1574 * 1575 * @param arg The String that must be non-null and non-empty. 1576 */ 1577 private void validateArg(String arg) { 1578 if ((arg == null) || ("".equals(arg))) { 1579 throw new IllegalArgumentException(arg + " cannot be null or the empty string."); 1580 } 1581 } 1582 1583 /** 1584 * Does the housekeeping for making HTTP requests to the SensorBase by a test or admin user. 1585 * 1586 * @param method The type of Method. 1587 * @param requestString A string, such as "users". No preceding slash. 1588 * @param entity The representation to be sent with the request, or null if not needed. 1589 * @return The Response instance returned from the server. 1590 */ 1591 private Response makeRequest(Method method, String requestString, Representation entity) { 1592 Reference reference = new Reference(this.sensorBaseHost + requestString); 1593 Request request = (entity == null) ? new Request(method, reference) : new Request(method, 1594 reference, entity); 1595 request.getClientInfo().getAcceptedMediaTypes().add(xmlMedia); 1596 ChallengeResponse authentication = new ChallengeResponse(scheme, this.userEmail, this.password); 1597 request.setChallengeResponse(authentication); 1598 if (this.isTraceEnabled) { 1599 System.out.println("SensorBaseClient Tracing: " + method + " " + reference); 1600 if (entity != null) { 1601 try { 1602 System.out.println(entity.getText()); 1603 } 1604 catch (Exception e) { 1605 System.out.println(" Problems with getText() on entity."); 1606 } 1607 } 1608 } 1609 Response response = this.client.handle(request); 1610 if (this.isTraceEnabled) { 1611 Status status = response.getStatus(); 1612 System.out.println(" => " + status.getCode() + " " + status.getDescription()); 1613 } 1614 return response; 1615 } 1616 1617 /** 1618 * Takes a String encoding of a SensorDataType in XML format and converts it to an instance. 1619 * 1620 * @param xmlString The XML string representing a SensorDataType 1621 * @return The corresponding SensorDataType instance. 1622 * @throws Exception If problems occur during unmarshalling. 1623 */ 1624 private SensorDataType makeSensorDataType(String xmlString) throws Exception { 1625 Unmarshaller unmarshaller = sdtJAXB.createUnmarshaller(); 1626 return (SensorDataType) unmarshaller.unmarshal(new StringReader(xmlString)); 1627 } 1628 1629 /** 1630 * Takes a String encoding of a SensorDataTypeIndex in XML format and converts it to an instance. 1631 * 1632 * @param xmlString The XML string representing a SensorDataTypeIndex. 1633 * @return The corresponding SensorDataTypeIndex instance. 1634 * @throws Exception If problems occur during unmarshalling. 1635 */ 1636 private SensorDataTypeIndex makeSensorDataTypeIndex(String xmlString) throws Exception { 1637 Unmarshaller unmarshaller = sdtJAXB.createUnmarshaller(); 1638 return (SensorDataTypeIndex) unmarshaller.unmarshal(new StringReader(xmlString)); 1639 } 1640 1641 /** 1642 * Returns the passed SensorDataType instance as a String encoding of its XML representation. 1643 * 1644 * @param sdt The SensorDataType instance. 1645 * @return The XML String representation. 1646 * @throws Exception If problems occur during translation. 1647 */ 1648 private String makeSensorDataType(SensorDataType sdt) throws Exception { 1649 Marshaller marshaller = sdtJAXB.createMarshaller(); 1650 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1651 dbf.setNamespaceAware(true); 1652 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 1653 Document doc = documentBuilder.newDocument(); 1654 marshaller.marshal(sdt, doc); 1655 DOMSource domSource = new DOMSource(doc); 1656 StringWriter writer = new StringWriter(); 1657 StreamResult result = new StreamResult(writer); 1658 TransformerFactory tf = TransformerFactory.newInstance(); 1659 Transformer transformer = tf.newTransformer(); 1660 transformer.transform(domSource, result); 1661 String xmlString = writer.toString(); 1662 // Now remove the processing instruction. This approach seems like a total hack. 1663 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 1664 return xmlString; 1665 } 1666 1667 /** 1668 * Takes a String encoding of a User in XML format and converts it to an instance. 1669 * 1670 * @param xmlString The XML string representing a User 1671 * @return The corresponding User instance. 1672 * @throws Exception If problems occur during unmarshalling. 1673 */ 1674 private User makeUser(String xmlString) throws Exception { 1675 Unmarshaller unmarshaller = userJAXB.createUnmarshaller(); 1676 return (User) unmarshaller.unmarshal(new StringReader(xmlString)); 1677 } 1678 1679 /** 1680 * Takes a String encoding of a UserIndex in XML format and converts it to an instance. 1681 * 1682 * @param xmlString The XML string representing a UserIndex. 1683 * @return The corresponding UserIndex instance. 1684 * @throws Exception If problems occur during unmarshalling. 1685 */ 1686 private UserIndex makeUserIndex(String xmlString) throws Exception { 1687 Unmarshaller unmarshaller = userJAXB.createUnmarshaller(); 1688 return (UserIndex) unmarshaller.unmarshal(new StringReader(xmlString)); 1689 } 1690 1691 /** 1692 * Returns the passed Properties instance as a String encoding of its XML representation. 1693 * 1694 * @param properties The Properties instance. 1695 * @return The XML String representation. 1696 * @throws Exception If problems occur during translation. 1697 */ 1698 private String makeProperties(Properties properties) throws Exception { 1699 Marshaller marshaller = userJAXB.createMarshaller(); 1700 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1701 dbf.setNamespaceAware(true); 1702 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 1703 Document doc = documentBuilder.newDocument(); 1704 marshaller.marshal(properties, doc); 1705 DOMSource domSource = new DOMSource(doc); 1706 StringWriter writer = new StringWriter(); 1707 StreamResult result = new StreamResult(writer); 1708 TransformerFactory tf = TransformerFactory.newInstance(); 1709 Transformer transformer = tf.newTransformer(); 1710 transformer.transform(domSource, result); 1711 String xmlString = writer.toString(); 1712 // Now remove the processing instruction. This approach seems like a total hack. 1713 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 1714 return xmlString; 1715 } 1716 1717 /** 1718 * Takes an XML Document representing a SensorDataIndex and converts it to an instance. 1719 * 1720 * @param xmlString The XML string representing a SensorDataIndex. 1721 * @return The corresponding SensorDataIndex instance. 1722 * @throws Exception If problems occur during unmarshalling. 1723 */ 1724 private SensorDataIndex makeSensorDataIndex(String xmlString) throws Exception { 1725 Unmarshaller unmarshaller = sensordataJAXB.createUnmarshaller(); 1726 return (SensorDataIndex) unmarshaller.unmarshal(new StringReader(xmlString)); 1727 } 1728 1729 /** 1730 * Takes an XML Document representing a ProjectSummary and converts it to an instance. 1731 * 1732 * @param xmlString The XML string representing a ProjectSummary. 1733 * @return The corresponding ProjectSummary instance. 1734 * @throws Exception If problems occur during unmarshalling. 1735 */ 1736 private ProjectSummary makeProjectSummary(String xmlString) throws Exception { 1737 Unmarshaller unmarshaller = projectJAXB.createUnmarshaller(); 1738 return (ProjectSummary) unmarshaller.unmarshal(new StringReader(xmlString)); 1739 } 1740 1741 /** 1742 * Takes an XML Document representing a MultiDayProjectSummary and converts it to an instance. 1743 * 1744 * @param xmlString The XML string representing a MultiDayProjectSummary. 1745 * @return The corresponding MultiDayProjectSummary instance. 1746 * @throws Exception If problems occur during unmarshalling. 1747 */ 1748 private MultiDayProjectSummary makeMultiDayProjectSummary(String xmlString) throws Exception { 1749 Unmarshaller unmarshaller = projectJAXB.createUnmarshaller(); 1750 return (MultiDayProjectSummary) unmarshaller.unmarshal(new StringReader(xmlString)); 1751 } 1752 1753 /** 1754 * Takes a String encoding of a SensorData in XML format and converts it to an instance. 1755 * 1756 * @param xmlString The XML string representing a SensorData. 1757 * @return The corresponding SensorData instance. 1758 * @throws Exception If problems occur during unmarshalling. 1759 */ 1760 private SensorData makeSensorData(String xmlString) throws Exception { 1761 Unmarshaller unmarshaller = sensordataJAXB.createUnmarshaller(); 1762 return (SensorData) unmarshaller.unmarshal(new StringReader(xmlString)); 1763 } 1764 1765 /** 1766 * Returns the passed SensorData instance as a String encoding of its XML representation. Final 1767 * because it's called in constructor. 1768 * 1769 * @param data The SensorData instance. 1770 * @return The XML String representation. 1771 * @throws Exception If problems occur during translation. 1772 */ 1773 private final String makeSensorData(SensorData data) throws Exception { 1774 Marshaller marshaller = sensordataJAXB.createMarshaller(); 1775 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1776 dbf.setNamespaceAware(true); 1777 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 1778 Document doc = documentBuilder.newDocument(); 1779 marshaller.marshal(data, doc); 1780 DOMSource domSource = new DOMSource(doc); 1781 StringWriter writer = new StringWriter(); 1782 StreamResult result = new StreamResult(writer); 1783 TransformerFactory tf = TransformerFactory.newInstance(); 1784 Transformer transformer = tf.newTransformer(); 1785 transformer.transform(domSource, result); 1786 String xmlString = writer.toString(); 1787 // Now remove the processing instruction. This approach seems like a total hack. 1788 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 1789 return xmlString; 1790 } 1791 1792 /** 1793 * Returns the passed SensorDatas instance as a String encoding of its XML representation. 1794 * 1795 * @param data The SensorDatas instance. 1796 * @return The XML String representation. 1797 * @throws Exception If problems occur during translation. 1798 */ 1799 private String makeSensorDatas(SensorDatas data) throws Exception { 1800 Marshaller marshaller = sensordataJAXB.createMarshaller(); 1801 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1802 dbf.setNamespaceAware(true); 1803 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 1804 Document doc = documentBuilder.newDocument(); 1805 marshaller.marshal(data, doc); 1806 DOMSource domSource = new DOMSource(doc); 1807 StringWriter writer = new StringWriter(); 1808 StreamResult result = new StreamResult(writer); 1809 TransformerFactory tf = TransformerFactory.newInstance(); 1810 Transformer transformer = tf.newTransformer(); 1811 transformer.transform(domSource, result); 1812 String xmlString = writer.toString(); 1813 // Now remove the processing instruction. This approach seems like a total hack. 1814 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 1815 return xmlString; 1816 } 1817 1818 /** 1819 * Takes a String encoding of a Project in XML format and converts it to an instance. 1820 * 1821 * @param xmlString The XML string representing a Project 1822 * @return The corresponding Project instance. 1823 * @throws Exception If problems occur during unmarshalling. 1824 */ 1825 private Project makeProject(String xmlString) throws Exception { 1826 Unmarshaller unmarshaller = projectJAXB.createUnmarshaller(); 1827 return (Project) unmarshaller.unmarshal(new StringReader(xmlString)); 1828 } 1829 1830 /** 1831 * Takes a String encoding of a ProjectIndex in XML format and converts it to an instance. 1832 * 1833 * @param xmlString The XML string representing a ProjectIndex. 1834 * @return The corresponding ProjectIndex instance. 1835 * @throws Exception If problems occur during unmarshalling. 1836 */ 1837 private ProjectIndex makeProjectIndex(String xmlString) throws Exception { 1838 Unmarshaller unmarshaller = projectJAXB.createUnmarshaller(); 1839 return (ProjectIndex) unmarshaller.unmarshal(new StringReader(xmlString)); 1840 } 1841 1842 /** 1843 * Returns the passed Project instance as a String encoding of its XML representation. 1844 * 1845 * @param project The Project instance. 1846 * @return The XML String representation. 1847 * @throws Exception If problems occur during translation. 1848 */ 1849 private String makeProject(Project project) throws Exception { 1850 Marshaller marshaller = projectJAXB.createMarshaller(); 1851 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1852 dbf.setNamespaceAware(true); 1853 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 1854 Document doc = documentBuilder.newDocument(); 1855 marshaller.marshal(project, doc); 1856 DOMSource domSource = new DOMSource(doc); 1857 StringWriter writer = new StringWriter(); 1858 StreamResult result = new StreamResult(writer); 1859 TransformerFactory tf = TransformerFactory.newInstance(); 1860 Transformer transformer = tf.newTransformer(); 1861 transformer.transform(domSource, result); 1862 String xmlString = writer.toString(); 1863 // Now remove the processing instruction. This approach seems like a total hack. 1864 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 1865 return xmlString; 1866 } 1867 1868 /** 1869 * Attempts to set timeout values for the passed client. 1870 * @param client The client . 1871 * @param milliseconds The timeout value. 1872 */ 1873 private static void setClientTimeout(Client client, int milliseconds) { 1874 client.setConnectTimeout(milliseconds); 1875 // client.getContext().getParameters().removeAll("connectTimeout"); 1876 // client.getContext().getParameters().add("connectTimeout", String.valueOf(milliseconds)); 1877 // // For the Apache Commons client. 1878 // client.getContext().getParameters().removeAll("readTimeout"); 1879 // client.getContext().getParameters().add("readTimeout", String.valueOf(milliseconds)); 1880 // client.getContext().getParameters().removeAll("connectionManagerTimeout"); 1881 // client.getContext().getParameters().add("connectionManagerTimeout", 1882 // String.valueOf(milliseconds)); 1883 } 1884 1885 /** 1886 * Enables caching in this client. 1887 * If caching has already been enabled, then does nothing. 1888 * @param cacheName The name of the cache. 1889 * @param subDir The subdirectory in which the cache backend store is saved. 1890 * @param maxLife The default expiration time for objects, in days. 1891 * @param capacity The maximum number of instances to be held in-memory. 1892 */ 1893 public synchronized void enableCaching(String cacheName, String subDir, Double maxLife, 1894 Long capacity) { 1895 if (!this.isCacheEnabled) { 1896 this.uriCache = new UriCache(cacheName, subDir, maxLife, capacity); 1897 this.isCacheEnabled = true; 1898 } 1899 } 1900 1901 /** 1902 * Delete all entries from this cache. 1903 * If the cache is not enabled, then does nothing. 1904 */ 1905 public synchronized void clearCache() { 1906 if (this.isCacheEnabled) { 1907 this.uriCache.clear(); 1908 } 1909 } 1910 1911 /** 1912 * Compresses the server database tables. 1913 * You must be the admin user in order for this command to succeed. 1914 * @throws SensorBaseClientException If problems occur posting this data. 1915 */ 1916 public synchronized void compressTables() throws SensorBaseClientException { 1917 try { 1918 Response response = makeRequest(Method.PUT, "db/table/compress", null); 1919 if (!response.getStatus().isSuccess()) { 1920 throw new SensorBaseClientException(response.getStatus()); 1921 } 1922 } 1923 // Allow SensorBaseClientExceptions to be thrown out of this method. 1924 catch (SensorBaseClientException f) { 1925 throw f; 1926 } 1927 // All other exceptions are caught and rethrown. 1928 catch (Exception e) { 1929 throw new SensorBaseClientException("Error in db command.", e); 1930 } 1931 } 1932 1933 /** 1934 * Indexes the server database tables. 1935 * You must be the admin user in order for this command to succeed. 1936 * @throws SensorBaseClientException If problems occur posting this data. 1937 */ 1938 public synchronized void indexTables() throws SensorBaseClientException { 1939 try { 1940 Response response = makeRequest(Method.PUT, "db/table/index", null); 1941 if (!response.getStatus().isSuccess()) { 1942 throw new SensorBaseClientException(response.getStatus()); 1943 } 1944 } 1945 // Allow SensorBaseClientExceptions to be thrown out of this method. 1946 catch (SensorBaseClientException f) { 1947 throw f; 1948 } 1949 // All other exceptions are caught and rethrown. 1950 catch (Exception e) { 1951 throw new SensorBaseClientException("Error in db command", e); 1952 } 1953 } 1954 1955 /** 1956 * Gets the rowcount for the specified table. 1957 * You must be the admin user in order for this command to succeed. 1958 * @param table The name of the table whose rowcount is to be retrieved. 1959 * @return The number of rows in that table. 1960 * @throws SensorBaseClientException If problems occur posting this data. 1961 * This can happen if the user is not the admin user, or if the table name is invalid. 1962 */ 1963 public synchronized int rowCount(String table) throws SensorBaseClientException { 1964 try { 1965 Response response = makeRequest(Method.GET, "db/table/" + table + "/rowcount", null); 1966 if (!response.getStatus().isSuccess()) { 1967 throw new SensorBaseClientException(response.getStatus()); 1968 } 1969 String rowCountString = response.getEntity().getText(); 1970 return Integer.valueOf(rowCountString).intValue(); 1971 } 1972 // Allow SensorBaseClientExceptions to be thrown out of this method. 1973 catch (SensorBaseClientException f) { 1974 throw f; 1975 } 1976 // All other exceptions are caught and rethrown. 1977 catch (Exception e) { 1978 throw new SensorBaseClientException("Error in rowcount command", e); 1979 } 1980 } 1981 1982 }