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