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    }