001 package org.hackystat.sensorbase.resource.projects; 002 003 import static org.hackystat.sensorbase.server.ServerProperties.XML_DIR_KEY; 004 005 import java.io.File; 006 import java.io.StringReader; 007 import java.io.StringWriter; 008 import java.util.ArrayList; 009 import java.util.HashMap; 010 import java.util.HashSet; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.Set; 014 import java.util.Map.Entry; 015 016 import javax.xml.bind.JAXBContext; 017 import javax.xml.bind.Marshaller; 018 import javax.xml.bind.Unmarshaller; 019 import javax.xml.datatype.XMLGregorianCalendar; 020 import javax.xml.parsers.DocumentBuilder; 021 import javax.xml.parsers.DocumentBuilderFactory; 022 import javax.xml.transform.Transformer; 023 import javax.xml.transform.TransformerFactory; 024 import javax.xml.transform.dom.DOMSource; 025 import javax.xml.transform.stream.StreamResult; 026 027 import org.hackystat.sensorbase.db.DbManager; 028 import org.hackystat.utilities.stacktrace.StackTrace; 029 import org.hackystat.utilities.tstamp.Tstamp; 030 import org.hackystat.sensorbase.resource.projects.jaxb.Invitations; 031 import org.hackystat.sensorbase.resource.projects.jaxb.Members; 032 import org.hackystat.sensorbase.resource.projects.jaxb.MultiDayProjectSummary; 033 import org.hackystat.sensorbase.resource.projects.jaxb.Project; 034 import org.hackystat.sensorbase.resource.projects.jaxb.ProjectIndex; 035 import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef; 036 import org.hackystat.sensorbase.resource.projects.jaxb.ProjectSummary; 037 import org.hackystat.sensorbase.resource.projects.jaxb.Projects; 038 import org.hackystat.sensorbase.resource.projects.jaxb.Properties; 039 import org.hackystat.sensorbase.resource.projects.jaxb.Spectators; 040 import org.hackystat.sensorbase.resource.projects.jaxb.UriPatterns; 041 import org.hackystat.sensorbase.resource.sensordata.SensorDataManager; 042 import org.hackystat.sensorbase.resource.users.UserManager; 043 import org.hackystat.sensorbase.resource.users.jaxb.User; 044 import org.hackystat.sensorbase.server.Server; 045 import org.w3c.dom.Document; 046 047 /** 048 * Provides a manager for the Project resource. 049 * @author Philip Johnson 050 */ 051 public class ProjectManager { 052 053 /** The String naming the Default project. */ 054 public static final String DEFAULT_PROJECT_NAME = "Default"; 055 056 /** Holds the class-wide JAXBContext, which is thread-safe. */ 057 private JAXBContext jaxbContext; 058 059 /** The Server associated with this SdtManager. */ 060 Server server; 061 062 /** The DbManager associated with this server. */ 063 DbManager dbManager; 064 065 /** The UserManager. */ 066 UserManager userManager; 067 068 /** The ProjectIndex open tag. */ 069 public static final String projectIndexOpenTag = "<ProjectIndex>"; 070 071 /** The ProjectIndex close tag. */ 072 public static final String projectIndexCloseTag = "</ProjectIndex>"; 073 074 /** The initial size for Collection instances that hold the Projects. */ 075 private static final int projectSetSize = 127; 076 077 /** The in-memory repository of Projects, keyed by Owner and Project name. */ 078 private Map<User, Map<String, Project>> owner2name2project = 079 new HashMap<User, Map<String, Project>>(projectSetSize); 080 081 /** The in-memory repository of Project XML strings, keyed by Project. */ 082 private ProjectStringMap project2xml = new ProjectStringMap(); 083 084 /** The in-memory repository of ProjectRef XML strings, keyed by Project. */ 085 private ProjectStringMap project2ref = new ProjectStringMap(); 086 087 /** The http string identifier. */ 088 private static final String http = "http"; 089 090 /** 091 * The constructor for ProjectManagers. 092 * There is one ProjectManager per Server. 093 * @param server The Server instance associated with this ProjectManager. 094 */ 095 public ProjectManager(Server server) { 096 this.server = server; 097 // Note: cannot get a SensorDataManager at this point; has not yet been instantiated. 098 this.userManager = 099 (UserManager)this.server.getContext().getAttributes().get("UserManager"); 100 this.dbManager = (DbManager)this.server.getContext().getAttributes().get("DbManager"); 101 try { 102 this.jaxbContext = 103 JAXBContext.newInstance("org.hackystat.sensorbase.resource.projects.jaxb"); 104 loadDefaultProjects(); //NOPMD it's throwing a false warning. 105 initializeCache(); //NOPMD 106 initializeDefaultProjects(); //NOPMD 107 } 108 catch (Exception e) { 109 String msg = "Exception during ProjectManager initialization processing"; 110 server.getLogger().warning(msg + "\n" + StackTrace.toString(e)); 111 throw new RuntimeException(msg, e); 112 } 113 } 114 115 /** 116 * Loads the default Projects from the defaults file and adds them to the database. 117 * @throws Exception If problems occur. 118 */ 119 private final void loadDefaultProjects() throws Exception { 120 // Get the default User definitions from the XML defaults file. 121 File defaultsFile = findDefaultsFile(); 122 // Add these users to the database if we've found a default file. 123 if (defaultsFile.exists()) { 124 server.getLogger().info("Loading Project defaults from " + defaultsFile.getPath()); 125 Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 126 Projects projects = (Projects) unmarshaller.unmarshal(defaultsFile); 127 for (Project project : projects.getProject()) { 128 provideDefaults(project); 129 this.dbManager.storeProject(project, this.makeProject(project), 130 this.makeProjectRefString(project)); 131 } 132 } 133 } 134 135 /** Read in all Projects from the database and initialize the in-memory cache. */ 136 private final void initializeCache() { 137 try { 138 ProjectIndex index = makeProjectIndex(this.dbManager.getProjectIndex()); 139 140 for (ProjectRef ref : index.getProjectRef()) { 141 String owner = ref.getOwner(); 142 User user = this.userManager.getUser(owner); 143 // Check to make sure user exists. DB is not normalized! 144 if (user == null) { 145 String msg = "Project with undefined user '" + owner + "' found while initializing " 146 + " project cache from database. Project will be ignored."; 147 server.getLogger().fine(msg); 148 } 149 else { 150 String projectName = ref.getName(); 151 if (this.hasProject(user, projectName)) { 152 String msg = "Duplicate project for " + user + " with name " + projectName + 153 " found in database. Ignoring."; 154 server.getLogger().warning(msg); 155 } 156 else { 157 String projectString = this.dbManager.getProject(user, projectName); 158 Project project = makeProject(projectString); 159 provideDefaults(project); 160 this.updateCache(project); 161 } 162 } 163 } 164 } 165 catch (Exception e) { 166 server.getLogger().warning("Failed to initialize users " + StackTrace.toString(e)); 167 } 168 } 169 170 /** 171 * Updates the in-memory cache with information about this Project. 172 * Fixes the Default project dates to always be valid. 173 * @param project The project to be added to the cache. 174 * @throws Exception If problems occur updating the cache. 175 */ 176 private final void updateCache(Project project) throws Exception { 177 provideDefaults(project); 178 // Fix bogus default project start/end dates. 179 if (project.getName().equals("Default")) { 180 project.setStartTime(Tstamp.getDefaultProjectStartTime()); 181 project.setEndTime(Tstamp.getDefaultProjectEndTime()); 182 } 183 updateCache(project, this.makeProject(project), this.makeProjectRefString(project)); 184 } 185 186 /** 187 * Updates the cache given all the Project representations. 188 * Throws unchecked exceptions if the Owner is not defined as a User. 189 * @param project The Project. 190 * @param projectXml The Project as an XML string. 191 * @param projectRef The Project as an XML reference. 192 */ 193 private final void updateCache(Project project, String projectXml, String projectRef) { 194 // First, put the [owner, project] mapping 195 String email = project.getOwner(); 196 User user = userManager.getUser(email); 197 if (user == null) { 198 throw new IllegalArgumentException("Project with undefined User " + email + " " + project); 199 } 200 if (!owner2name2project.containsKey(user)) { 201 owner2name2project.put(user, new HashMap<String, Project>()); 202 } 203 owner2name2project.get(user).put(project.getName(), project); 204 this.project2xml.put(project, projectXml); 205 this.project2ref.put(project, projectRef); 206 } 207 208 209 /** Make sure that all Users have a "Default" project defined for them. */ 210 private void initializeDefaultProjects() { 211 for (User user : userManager.getUsers()) { 212 if (!hasProject(user, "Default")) { 213 addDefaultProject(user); 214 } 215 } 216 } 217 218 /** 219 * Checks the ServerProperties for the XML_DIR property. 220 * If this property is null, returns the File for ./xml/defaults/sensordatatypes.defaults.xml. 221 * @return The File instance (which might not point to an existing file.) 222 */ 223 private File findDefaultsFile() { 224 String defaultsPath = "/defaults/projects.defaults.xml"; 225 String xmlDir = this.server.getServerProperties().get(XML_DIR_KEY); 226 return (xmlDir == null) ? 227 new File (System.getProperty("user.dir") + "/xml" + defaultsPath) : 228 new File (xmlDir + defaultsPath); 229 } 230 231 /** 232 * Converts an "Owner" string to an email address. 233 * The owner string might be a URI (starting with http) or an email address. 234 * @param owner The owner string. 235 * @return The email address corresponding to the owner string. 236 */ 237 public synchronized String convertOwnerToEmail(String owner) { 238 if (owner.startsWith(http)) { 239 int lastSlash = owner.lastIndexOf('/'); 240 if (lastSlash < 0) { 241 throw new IllegalArgumentException("Could not convert owner to URI"); 242 } 243 return owner.substring(lastSlash + 1); 244 } 245 // Otherwise owner is already the email. 246 return owner; 247 } 248 249 /** 250 * Returns the owner string as a URI. 251 * The owner string could either be an email address or the URI. 252 * @param owner The owner string. 253 * @return The URI corresponding to the owner string. 254 */ 255 public synchronized String convertOwnerToUri(String owner) { 256 return (owner.startsWith(http)) ? owner : 257 this.server.getServerProperties().getFullHost() + "users/" + owner; 258 } 259 260 /** 261 * Returns the XML string containing the ProjectIndex with all defined Projects. 262 * Uses the in-memory cache of ProjectRef strings. 263 * @return The XML string providing an index to all current Projects. 264 */ 265 public synchronized String getProjectIndex() { 266 StringBuilder builder = new StringBuilder(512); 267 builder.append(projectIndexOpenTag); 268 for (String ref : this.project2ref.values()) { 269 builder.append(ref); 270 } 271 builder.append(projectIndexCloseTag); 272 return builder.toString(); 273 } 274 275 /** 276 * Returns the XML string containing the ProjectIndex with all Projects associated with 277 * this user. 278 * Uses the in-memory cache of ProjectRef strings. 279 * @param user The user whose associated Projects are to be retrieved. All projects for 280 * which this user is an owner, member, spectator, or invitee are returned. 281 * @return The XML string providing an index to all Projects associated with this user. 282 */ 283 public synchronized String getProjectIndex(User user) { 284 String email = user.getEmail(); 285 StringBuilder builder = new StringBuilder(512); 286 builder.append(projectIndexOpenTag); 287 for (Map<String, Project> name2project : this.owner2name2project.values()) { 288 for (Project project : name2project.values()) { 289 Members members = project.getMembers(); 290 Invitations invitations = project.getInvitations(); 291 Spectators spectators = project.getSpectators(); 292 if (project.getOwner().equals(email) || 293 (members != null) && (members.getMember().contains(email)) || 294 (spectators != null) && (spectators.getSpectator().contains(email)) || 295 (invitations != null) && (invitations.getInvitation().contains(email))) { 296 builder.append(this.project2ref.get(project)); 297 } 298 } 299 } 300 builder.append(projectIndexCloseTag); 301 return builder.toString(); 302 } 303 304 /** 305 * Ensures that project has default values for Invitations, Members, Properties, 306 * Spectators, and UriPatterns. 307 * @param project The project to check. 308 * @return The project representation with initialized fields as needed. 309 */ 310 private Project provideDefaults(Project project) { 311 if (project.getInvitations() == null) { 312 project.setInvitations(new Invitations()); 313 } 314 if (project.getSpectators() == null) { 315 project.setSpectators(new Spectators()); 316 } 317 if (project.getMembers() == null) { 318 project.setMembers(new Members()); 319 } 320 if (project.getProperties() == null) { 321 project.setProperties(new Properties()); 322 } 323 if (project.getUriPatterns() == null) { 324 // If missing, default to matching everything. 325 UriPatterns uriPatterns = new UriPatterns(); 326 uriPatterns.getUriPattern().add("*"); 327 project.setUriPatterns(uriPatterns); 328 } 329 if (project.getLastMod() == null) { 330 project.setLastMod(Tstamp.makeTimestamp()); 331 } 332 return project; 333 } 334 335 /** 336 * Updates the Manager with this Project. Any old definition is overwritten. 337 * Provide default values for UriPatterns, Properties, Members, and Invitations if not provided. 338 * @param project The Project. 339 */ 340 public synchronized void putProject(Project project) { 341 try { 342 provideDefaults(project); 343 project.setLastMod(Tstamp.makeTimestamp()); 344 String xmlProject = this.makeProject(project); 345 String xmlRef = this.makeProjectRefString(project); 346 this.updateCache(project, xmlProject, xmlRef); 347 this.dbManager.storeProject(project, xmlProject, xmlRef); 348 } 349 catch (Exception e) { 350 server.getLogger().warning("Failed to put Project" + StackTrace.toString(e)); 351 } 352 } 353 354 /** 355 * Renames the project. 356 * @param owner The owner of the project to be renamed. 357 * @param projectName The project to be renamed. 358 * @param newProjectName The new project name. 359 * @throws Exception If projectName could not be found, or if newProjectName names an 360 * existing project. 361 */ 362 public synchronized void renameProject(User owner, String projectName, String newProjectName) 363 throws Exception { 364 if (hasProject(owner, newProjectName)) { 365 throw new Exception("Project " + newProjectName + " is already defined."); 366 } 367 Project project = getProject(owner, projectName); 368 if (project == null) { 369 throw new Exception("Project " + projectName + " not found."); 370 } 371 project.setName(newProjectName); 372 deleteProject(owner, projectName); 373 putProject(project); 374 } 375 376 /** 377 * Returns true if the passed Project name is defined for this User (who must be the owner). 378 * @param owner The project owner (can be null). 379 * @param projectName A project name (can be null). 380 * @return True if a Project with that name is owned by that User. False if the User or 381 * Project is not defined. 382 */ 383 public synchronized boolean hasProject(User owner, String projectName) { 384 return 385 (owner != null) && 386 (projectName != null) && 387 this.owner2name2project.containsKey(owner) && 388 this.owner2name2project.get(owner).containsKey(projectName); 389 } 390 391 /** 392 * Returns true if member is a member of the project owned by owner. 393 * @param owner The owner of projectName. 394 * @param projectName The name of the project owned by owner. 395 * @param member The user whose membership is being checked. 396 * @return True if member is a member of project, false otherwise. 397 */ 398 public synchronized boolean isMember(User owner, String projectName, String member) { 399 // Return false if owner, project, member are invalid. 400 if ((owner == null) || (member == null) || (projectName == null) || 401 !this.owner2name2project.containsKey(owner) || 402 !this.owner2name2project.get(owner).containsKey(projectName)) { 403 return false; 404 } 405 // Now we can get the project. 406 Project project = this.owner2name2project.get(owner).get(projectName); 407 // Return false if the <Members> field is null. 408 if (!project.isSetMembers()) { 409 return false; 410 } 411 // Look for the member in the list. 412 List<String> members = project.getMembers().getMember(); 413 for (String currMember : members) { 414 if (currMember.equals(member)) { 415 return true; 416 } 417 } 418 // Got here, which means that we never found the member. 419 return false; 420 } 421 422 /** 423 * Returns true if member is invited to be a member of the project owned by owner. 424 * @param owner The owner of projectName. 425 * @param projectName The name of the project owned by owner. 426 * @param invitee The user whose invitation status is being checked. 427 * @return True if member is invited to be a member of project, false otherwise. 428 */ 429 public synchronized boolean isInvited(User owner, String projectName, String invitee) { 430 // Return false if owner, project, member are invalid. 431 if ((owner == null) || (invitee == null) || (projectName == null) || 432 !this.owner2name2project.containsKey(owner) || 433 !this.owner2name2project.get(owner).containsKey(projectName)) { 434 return false; 435 } 436 // Now we can get the project. 437 Project project = this.owner2name2project.get(owner).get(projectName); 438 // Return false if the <Members> field is null. 439 if (!project.isSetInvitations()) { 440 return false; 441 } 442 // Look for the member in the list. 443 List<String> invitees = project.getInvitations().getInvitation(); 444 for (String currInvitee : invitees) { 445 if (currInvitee.equals(invitee)) { 446 return true; 447 } 448 } 449 // Got here, which means that we never found the invitee. 450 return false; 451 } 452 453 /** 454 * Returns true if member is a spectator of the project owned by owner. 455 * @param owner The owner of projectName. 456 * @param projectName The name of the project owned by owner. 457 * @param spectator The user whose spectator status is being checked. 458 * @return True if spectator is a spectator. 459 */ 460 public synchronized boolean isSpectator(User owner, String projectName, String spectator) { 461 // Return false if owner, project, member are invalid. 462 if ((owner == null) || (spectator == null) || (projectName == null) || 463 !this.owner2name2project.containsKey(owner) || 464 !this.owner2name2project.get(owner).containsKey(projectName)) { 465 return false; 466 } 467 // Now we can get the project. 468 Project project = this.owner2name2project.get(owner).get(projectName); 469 // Return false if the <Spectators> field is null. 470 if (!project.isSetSpectators()) { 471 return false; 472 } 473 // Look for the member in the list. 474 List<String> spectators = project.getSpectators().getSpectator(); 475 for (String currSpectator : spectators) { 476 if (currSpectator.equals(spectator)) { 477 return true; 478 } 479 } 480 // Got here, which means that we never found the invitee. 481 return false; 482 } 483 484 485 /** 486 * Returns true if user1 and user2 are members of the same Project and 487 * that project encompasses the given day. 488 * @param userEmail1 The first user. 489 * @param userEmail2 The second user. 490 * @param tstampString The date in question, which could be null. 491 * @return True if the two users are in the same project that encompasses the given day. 492 */ 493 public synchronized boolean inProject(String userEmail1, String userEmail2, String tstampString) { 494 // If any params are null, return false. 495 if ((tstampString == null) || (userEmail1 == null) || (userEmail2 == null)) { 496 return false; 497 } 498 // If either email cannot be converted to a user, return false. 499 User user1 = this.userManager.getUser(userEmail1); 500 if (user1 == null) { 501 return false; 502 } 503 User user2 = this.userManager.getUser(userEmail2); 504 if (user2 == null) { 505 return false; 506 } 507 // Return false if timestamp is null or cannot be converted to a real timestamp. 508 XMLGregorianCalendar timestamp = null; 509 try { 510 timestamp = Tstamp.makeTimestamp(tstampString); 511 } 512 catch (Exception e) { 513 return false; 514 } 515 // Now look through all projects and see if there is a project with both users that encompasses 516 // the given timestamp. 517 for (Entry<User, Map<String, Project>> entry : this.owner2name2project.entrySet()) { 518 for (Project project : entry.getValue().values()) { 519 if (belongs(project, user1) && belongs(project, user2) && 520 Tstamp.inBetween(project.getStartTime(), timestamp, project.getEndTime())) { 521 return true; 522 } 523 } 524 } 525 return false; 526 } 527 528 /** 529 * Returns true if user is the owner or a member or a spectator of Project. 530 * @param project The project. 531 * @param user The user who's belonging is being assessed. 532 * @return True if user is the owner or a member of project. 533 */ 534 private boolean belongs(Project project, User user) { 535 return 536 (project.getOwner().equals(user.getEmail()) || 537 project.getMembers().getMember().contains(user.getEmail()) || 538 project.getSpectators().getSpectator().contains(user.getEmail()) 539 ); 540 } 541 542 543 /** 544 * Ensures that the passed Project is no longer present in this Manager. 545 * @param owner The user who owns this Project. 546 * @param projectName The name of the project. 547 */ 548 public synchronized void deleteProject(User owner, String projectName) { 549 if (this.owner2name2project.containsKey(owner)) { 550 Project project = this.owner2name2project.get(owner).get(projectName); 551 if (project != null) { 552 this.project2ref.remove(project); 553 this.project2xml.remove(project); 554 this.owner2name2project.get(owner).remove(projectName); 555 } 556 } 557 this.dbManager.deleteProject(owner, projectName); 558 } 559 560 /** 561 * Deletes all projects including the default project owned by this user. 562 * Note that this method should only be called immediately before deleting 563 * the user, since the system will be left in an inconsistent state if 564 * the Default project is deleted but the user is still around. 565 * (Here's a place where we really should be using a transaction.) 566 * @param owner The user of interest. 567 */ 568 public synchronized void deleteProjects(User owner) { 569 if (this.owner2name2project.containsKey(owner)) { 570 for (Project project : this.getProjects(owner)) { 571 this.project2ref.remove(project); 572 this.project2xml.remove(project); 573 this.owner2name2project.get(owner).remove(project.getName()); 574 this.dbManager.deleteProject(owner, project.getName()); 575 } 576 } 577 } 578 579 /** 580 * Returns the Project Xml String associated with this User and project name. 581 * @param owner The user that owns this project. 582 * @param projectName The name of the project. 583 * @return The Project XML string, or null if not found. 584 */ 585 public synchronized String getProjectString(User owner, String projectName) { 586 if (hasProject(owner, projectName)) { 587 Project project = this.owner2name2project.get(owner).get(projectName); 588 return this.project2xml.get(project); 589 } 590 return null; 591 } 592 593 /** 594 * Returns a set containing the current Project instances. 595 * For thread safety, a fresh Set of Projects is built each time this is called. 596 * @return A Set containing the current Projects. 597 */ 598 public synchronized Set<Project> getProjects() { 599 Set<Project> projectSet = new HashSet<Project>(projectSetSize); 600 for (User user : this.owner2name2project.keySet()) { 601 for (String projectName : this.owner2name2project.get(user).keySet()) { 602 projectSet.add(this.owner2name2project.get(user).get(projectName)); 603 } 604 } 605 return projectSet; 606 } 607 608 /** 609 * Returns all of the projects owned by this user. 610 * @param user The user whose projects are to be returned. 611 * @return A set of the Projects owned by this user. 612 */ 613 public synchronized Set<Project> getProjects(User user) { 614 Set<Project> projectSet = new HashSet<Project>(projectSetSize); 615 for (String projectName : this.owner2name2project.get(user).keySet()) { 616 projectSet.add(this.owner2name2project.get(user).get(projectName)); 617 } 618 return projectSet; 619 } 620 621 622 /** 623 * Returns an XML SensorDataIndex String for all data associated with the Project 624 * owned by this user. 625 * Assumes that the owner and projectName define an existing Project. 626 * @param owner The User that owns this Project. 627 * @param project the Project instance. 628 * @return The XML SensorDataIndex string providing an index to all data for this project. 629 * @throws Exception If things go wrong. 630 */ 631 public synchronized String getProjectSensorDataIndex(User owner, Project project) 632 throws Exception { 633 SensorDataManager sensorDataManager = this.getSensorDataManager(); 634 XMLGregorianCalendar startTime = project.getStartTime(); 635 XMLGregorianCalendar endTime = project.getEndTime(); 636 List<String> patterns = project.getUriPatterns().getUriPattern(); 637 List<User> users = getProjectUsers(project); 638 return sensorDataManager.getSensorDataIndex(users, startTime, endTime, patterns, null); 639 } 640 641 642 /** 643 * Returns the XML SensorDataIndex string for the data associated with this Project within the 644 * specified start and end times. 645 * Assumes that owner, project, startTime, and endTime are non-null, and that startTime and 646 * endTime are within the Project start and end time interval. 647 * @param owner The User who owns this Project. 648 * @param project the Project. 649 * @param startTime The startTime. 650 * @param endTime The endTime. 651 * @param sdt The SensorDataType of interest, or null if all sensordatatypes are to be retrieved. 652 * @return The XML String providing a SensorDataIndex to the sensor data in this project 653 * starting at startTime and ending at endTime. 654 * @throws Exception if problems occur. 655 */ 656 public synchronized String getProjectSensorDataIndex(User owner, 657 Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt) 658 throws Exception { 659 SensorDataManager sensorDataManager = this.getSensorDataManager(); 660 List<String> patterns = project.getUriPatterns().getUriPattern(); 661 List<User> users = getProjectUsers(project); 662 return sensorDataManager.getSensorDataIndex(users, startTime, endTime, patterns, sdt); 663 } 664 665 /** 666 * Returns the XML SensorDataIndex string for the data associated with this Project within the 667 * specified start and end times. 668 * Assumes that owner, project, startTime, and endTime are non-null, and that startTime and 669 * endTime are within the Project start and end time interval. 670 * @param owner The User who owns this Project. 671 * @param project the Project. 672 * @param startTime The startTime. 673 * @param endTime The endTime. 674 * @param sdt The SensorDataType of interest, or null if all sensordatatypes are to be retrieved. 675 * @param tool The tool of interest. 676 * @return The XML String providing a SensorDataIndex to the sensor data in this project 677 * starting at startTime and ending at endTime. 678 * @throws Exception if problems occur. 679 */ 680 public synchronized String getProjectSensorDataIndex(User owner, 681 Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt, 682 String tool) 683 throws Exception { 684 SensorDataManager sensorDataManager = this.getSensorDataManager(); 685 List<String> patterns = project.getUriPatterns().getUriPattern(); 686 List<User> users = getProjectUsers(project); 687 return sensorDataManager.getSensorDataIndex(users, startTime, endTime, patterns, sdt, tool); 688 } 689 690 /** 691 * Returns the XML SensorDataIndex string for the data associated with this Project within the 692 * specified start and end times and startIndex and maxInstances. 693 * Assumes that owner, project, startTime, and endTime are non-null, and that startTime and 694 * endTime are within the Project start and end time interval, and that startIndex and 695 * maxInstances are non-negative. 696 * @param owner The User who owns this Project. 697 * @param project the Project. 698 * @param startTime The startTime. 699 * @param endTime The endTime. 700 * @param startIndex The starting index within the timestamp-ordered list of all sensor data 701 * instances associated with this project at the time of this call. 702 * @param maxInstances The maximum number of instances to return in the index. 703 * @return The XML String providing a SensorDataIndex to the sensor data in this project 704 * starting at startTime and ending at endTime with the specified startIndex and maxInstances. 705 * @throws Exception if problems occur. 706 */ 707 public synchronized String getProjectSensorDataIndex(User owner, 708 Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, int startIndex, 709 int maxInstances) 710 throws Exception { 711 SensorDataManager sensorDataManager = this.getSensorDataManager(); 712 List<String> patterns = project.getUriPatterns().getUriPattern(); 713 List<User> users = getProjectUsers(project); 714 return sensorDataManager.getSensorDataIndex(users, startTime, endTime, patterns, startIndex, 715 maxInstances); 716 } 717 718 /** 719 * Returns a string containing a SensorDataIndex representing the "snapshot" of the sensor data 720 * for the given project in the given interval for the given sdt. Tool is optional and can be 721 * null. 722 * @param project The project. 723 * @param startTime The start time. 724 * @param endTime The end time. 725 * @param sdt The sensor data type of interest. 726 * @param tool The tool of interest, or null if any tool is acceptable. 727 * @return The SensorDataIndex containing the snapshot of sensor data. 728 * @throws Exception If problems occur. 729 */ 730 public synchronized String getProjectSensorDataSnapshot( 731 Project project, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, String sdt, 732 String tool) throws Exception { 733 List<String> patterns = project.getUriPatterns().getUriPattern(); 734 List<User> users = getProjectUsers(project); 735 return dbManager.getProjectSensorDataSnapshot(users, startTime, endTime, patterns, sdt, tool); 736 } 737 738 /** 739 * Creates and returns the list of User instances associated with project. 740 * The users are the owner plus all members. 741 * If the owner email or member emails cannot be resolved to User instances, they are silently 742 * ignored. 743 * @param project The project whose users are to be found and returned. 744 * @return The list of Users associated with this project. 745 */ 746 private List<User> getProjectUsers(Project project) { 747 List<User> users = new ArrayList<User>(); 748 User owner = userManager.getUser(project.getOwner()); 749 if (owner != null) { 750 users.add(owner); 751 } 752 for (String member : project.getMembers().getMember()) { 753 User user = userManager.getUser(member); 754 if (user != null) { 755 users.add(user); 756 } 757 } 758 return users; 759 } 760 761 762 /** 763 * Returns the XML ProjectSummary string for the data associated with this Project within the 764 * specified start and end times. 765 * Note that the Project start and end times may further constrain the returned set of data. 766 * This method chooses the greater of startString and the Project startTime, and the lesser of 767 * endString and the Project endTime. 768 * Assumes that User and Project are valid. 769 * @param project the Project. 770 * @param startTime The startTimeXml. 771 * @param endTime The endTime. 772 * @return The XML String providing a ProjectSummary of this project 773 * starting at startTime and ending at endTime. 774 * @throws Exception if startString or endString are not XMLGregorianCalendars. 775 */ 776 public synchronized String getProjectSummaryString(Project project, 777 XMLGregorianCalendar startTime, XMLGregorianCalendar endTime) throws Exception { 778 List<String> patterns = project.getUriPatterns().getUriPattern(); 779 List<User> users = getProjectUsers(project); 780 String href = this.server.getHostName() + "projects/" + project.getOwner() + "/" + 781 project.getName() + "/summary?startTime=" + startTime + "&endTime=" + endTime; 782 ProjectSummary summary = dbManager.getProjectSummary(users, startTime, endTime, patterns, href); 783 return makeProjectSummaryString(summary); 784 } 785 786 /** 787 * Returns a MultiDayProjectSummary instance for the given project, startTime, and number of days. 788 * @param project The Project. 789 * @param startTime The startTime. 790 * @param numDays The number of days. 791 * @return The MultiDayProjectSummary instance for the given set of days. 792 * @throws Exception If problems occur. 793 */ 794 public synchronized String getMultiDayProjectSummaryString(Project project, 795 XMLGregorianCalendar startTime, Integer numDays) throws Exception { 796 List<String> patterns = project.getUriPatterns().getUriPattern(); 797 List<User> users = getProjectUsers(project); 798 MultiDayProjectSummary multiSummary = new MultiDayProjectSummary(); 799 for (int i = 0; i < numDays; i++) { 800 XMLGregorianCalendar start = Tstamp.incrementDays(startTime, i); 801 XMLGregorianCalendar end = Tstamp.incrementDays(startTime, i + 1); 802 String href = this.server.getHostName() + "projects/" + project.getOwner() + "/" + 803 project.getName() + "/summary?startTime=" + start + "&endTime=" + end; 804 ProjectSummary summary = dbManager.getProjectSummary(users, start, end, patterns, href); 805 multiSummary.getProjectSummary().add(summary); 806 } 807 return makeMultiDayProjectSummaryString(multiSummary); 808 } 809 810 811 /** 812 * Creates and stores the "Default" project for the specified user. 813 * @param owner The user who will own this Project. 814 */ 815 public final synchronized void addDefaultProject(User owner) { 816 Project project = new Project(); 817 provideDefaults(project); 818 project.setDescription("The default Project"); 819 project.setStartTime(Tstamp.getDefaultProjectStartTime()); 820 project.setEndTime(Tstamp.getDefaultProjectEndTime()); 821 project.setName(DEFAULT_PROJECT_NAME); 822 project.setOwner(owner.getEmail()); 823 putProject(project); 824 } 825 826 /** 827 * Returns true if the passed user has any defined Projects. 828 * @param owner The user who is the owner of the Projects. 829 * @return True if that User is defined and has at least one Project. 830 */ 831 public synchronized boolean hasProjects(User owner) { 832 return this.owner2name2project.containsKey(owner); 833 } 834 835 /** 836 * Returns the Project associated with user and projectName, or null if not found. 837 * @param owner The user. 838 * @param projectName A project name 839 * @return The project, or null if not found. 840 */ 841 public synchronized Project getProject(User owner, String projectName) { 842 if ((owner == null) || (projectName == null)) { 843 return null; 844 } 845 Project project = ((hasProject(owner, projectName)) ? 846 owner2name2project.get(owner).get(projectName) : null); 847 if (project != null) { 848 project = provideDefaults(project); 849 } 850 return project; 851 852 } 853 854 /** 855 * Takes a String encoding of a Project in XML format and converts it to an instance. 856 * 857 * @param xmlString The XML string representing a Project 858 * @return The corresponding Project instance. 859 * @throws Exception If problems occur during unmarshalling. 860 */ 861 public final synchronized Project makeProject(String xmlString) throws Exception { 862 //System.out.println("Got here mp.1"); 863 Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller(); 864 //System.out.println("Got here mp.2"); 865 return (Project)unmarshaller.unmarshal(new StringReader(xmlString)); 866 } 867 868 /** 869 * Takes a String encoding of a ProjectIndex in XML format and converts it to an instance. 870 * 871 * @param xmlString The XML string representing a ProjectIndex. 872 * @return The corresponding ProjectIndex instance. 873 * @throws Exception If problems occur during unmarshalling. 874 */ 875 public final synchronized ProjectIndex makeProjectIndex(String xmlString) 876 throws Exception { 877 Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller(); 878 return (ProjectIndex)unmarshaller.unmarshal(new StringReader(xmlString)); 879 } 880 881 /** 882 * Returns the passed Project instance as a String encoding of its XML representation. 883 * Final because it's called in constructor. 884 * @param project The Project instance. 885 * @return The XML String representation. 886 * @throws Exception If problems occur during translation. 887 */ 888 public final synchronized String makeProject (Project project) throws Exception { 889 Marshaller marshaller = jaxbContext.createMarshaller(); 890 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 891 dbf.setNamespaceAware(true); 892 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 893 Document doc = documentBuilder.newDocument(); 894 marshaller.marshal(project, doc); 895 DOMSource domSource = new DOMSource(doc); 896 StringWriter writer = new StringWriter(); 897 StreamResult result = new StreamResult(writer); 898 TransformerFactory tf = TransformerFactory.newInstance(); 899 Transformer transformer = tf.newTransformer(); 900 transformer.transform(domSource, result); 901 String xmlString = writer.toString(); 902 // Now remove the processing instruction. This approach seems like a total hack. 903 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 904 return xmlString; 905 } 906 907 /** 908 * Returns the passed Project instance as a String encoding of its XML representation 909 * as a ProjectRef object. 910 * Final because it's called in constructor. 911 * @param project The Project instance. 912 * @return The XML String representation of it as a ProjectRef 913 * @throws Exception If problems occur during translation. 914 */ 915 public final synchronized String makeProjectRefString (Project project) 916 throws Exception { 917 ProjectRef ref = makeProjectRef(project); 918 Marshaller marshaller = jaxbContext.createMarshaller(); 919 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 920 dbf.setNamespaceAware(true); 921 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 922 Document doc = documentBuilder.newDocument(); 923 marshaller.marshal(ref, doc); 924 DOMSource domSource = new DOMSource(doc); 925 StringWriter writer = new StringWriter(); 926 StreamResult result = new StreamResult(writer); 927 TransformerFactory tf = TransformerFactory.newInstance(); 928 Transformer transformer = tf.newTransformer(); 929 transformer.transform(domSource, result); 930 String xmlString = writer.toString(); 931 // Now remove the processing instruction. This approach seems like a total hack. 932 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 933 return xmlString; 934 } 935 936 /** 937 * Returns the passed ProjectSummary instance as a String encoding of its XML representation. 938 * @param summary The ProjectSummary instance. 939 * @return The XML String representation of it. 940 * @throws Exception If problems occur during translation. 941 */ 942 public final synchronized String makeProjectSummaryString (ProjectSummary summary) 943 throws Exception { 944 Marshaller marshaller = jaxbContext.createMarshaller(); 945 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 946 dbf.setNamespaceAware(true); 947 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 948 Document doc = documentBuilder.newDocument(); 949 marshaller.marshal(summary, doc); 950 DOMSource domSource = new DOMSource(doc); 951 StringWriter writer = new StringWriter(); 952 StreamResult result = new StreamResult(writer); 953 TransformerFactory tf = TransformerFactory.newInstance(); 954 Transformer transformer = tf.newTransformer(); 955 transformer.transform(domSource, result); 956 String xmlString = writer.toString(); 957 // Now remove the processing instruction. This approach seems like a total hack. 958 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 959 return xmlString; 960 } 961 962 /** 963 * Returns the passed MultiDayProjectSummary as a String encoding of its XML representation. 964 * @param summary The MultiDayProjectSummary instance. 965 * @return The XML String representation of it. 966 * @throws Exception If problems occur during translation. 967 */ 968 public final synchronized String makeMultiDayProjectSummaryString (MultiDayProjectSummary summary) 969 throws Exception { 970 Marshaller marshaller = jaxbContext.createMarshaller(); 971 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 972 dbf.setNamespaceAware(true); 973 DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); 974 Document doc = documentBuilder.newDocument(); 975 marshaller.marshal(summary, doc); 976 DOMSource domSource = new DOMSource(doc); 977 StringWriter writer = new StringWriter(); 978 StreamResult result = new StreamResult(writer); 979 TransformerFactory tf = TransformerFactory.newInstance(); 980 Transformer transformer = tf.newTransformer(); 981 transformer.transform(domSource, result); 982 String xmlString = writer.toString(); 983 // Now remove the processing instruction. This approach seems like a total hack. 984 xmlString = xmlString.substring(xmlString.indexOf('>') + 1); 985 return xmlString; 986 } 987 /** 988 * Returns a ProjectRef instance constructed from a Project instance. 989 * @param project The Project instance. 990 * @return A ProjectRef instance. 991 */ 992 public synchronized ProjectRef makeProjectRef(Project project) { 993 ProjectRef ref = new ProjectRef(); 994 String ownerEmail = convertOwnerToEmail(project.getOwner()); 995 ref.setName(project.getName()); 996 ref.setOwner(ownerEmail); 997 ref.setLastMod(project.getLastMod()); 998 ref.setHref(this.server.getHostName() + "projects/" + ownerEmail + "/" + project.getName()); 999 return ref; 1000 } 1001 1002 /** 1003 * Returns the SensorDataManager. 1004 * @return The SensorDataManager. 1005 */ 1006 private SensorDataManager getSensorDataManager() { 1007 return (SensorDataManager)this.server.getContext().getAttributes().get("SensorDataManager"); 1008 } 1009 1010 }