001    package org.hackystat.sensorbase.resource.projects;
002    
003    import java.util.ArrayList;
004    import java.util.List;
005    
006    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
007    import org.hackystat.sensorbase.resource.projects.jaxb.UriPatterns;
008    import org.hackystat.sensorbase.resource.sensorbase.SensorBaseResource;
009    import org.restlet.Context;
010    import org.restlet.data.MediaType;
011    import org.restlet.data.Request;
012    import org.restlet.data.Response;
013    import org.restlet.data.Status;
014    import org.restlet.resource.Representation;
015    import org.restlet.resource.Variant;
016    
017    /**
018     * The resource for processing GET/PUT/DELETE host/projects/{email}/{projectname}.
019     * Returns a representation of the Project resource associated with this user. 
020     * 
021     * @author Philip Johnson
022     */
023    public class UserProjectResource extends SensorBaseResource {
024      
025      /**
026       * Provides the following representational variants: TEXT_XML.
027       * @param context The context.
028       * @param request The request object.
029       * @param response The response object.
030       */
031      public UserProjectResource(Context context, Request request, Response response) {
032        super(context, request, response);
033      }
034      
035      /**
036       * Returns an XML representation of the Project associated with this User.
037       * <ul>
038       * <li> The uriUser must be defined as a User.
039       * <li> The Project must be defined for this User.
040       * <li> The authenticated user must be the admin, or uriUser, or a member of the project, or 
041       * invited to be in the Project, or a spectator.
042       * </ul>
043       * 
044       * @param variant The representational variant requested, or null if conditions are violated.
045       * @return The representation. 
046       */
047      @Override
048      public Representation represent(Variant variant) {
049        // Validate
050        if (!validateUriUserIsUser() ||
051            !validateUriProjectName() || 
052            !validateProjectViewer()) {
053          return null;
054        }
055        
056        // It's all good, so return the Project representation.
057        if (variant.getMediaType().equals(MediaType.TEXT_XML)) {
058          try {
059            String xmlData = super.projectManager.getProjectString(this.user, this.projectName);
060            return super.getStringRepresentation(xmlData);
061          }
062          catch (Exception e) {
063            setStatusInternalError(e);
064          }
065        }
066        return null;
067      }
068      
069      /** 
070       * Indicate the PUT method is supported. 
071       * @return True.
072       */
073      @Override
074      public boolean allowPut() {
075          return true;
076      }
077    
078      /**
079       * Implement the PUT method that creates a new Project or updates an existing Project.
080       * <ul>
081       * <li> The XML must be marshallable into a Project instance using the Project XmlSchema.
082       * <li> The User must exist.
083       * <li> The project representation must include a name, owner, start time, and end time.
084       * <li> The Project name in the URI string must match the Project name in the XML.
085       * <li> The authenticated user must be the uriUser or the admin.
086       * <li> The project cannot be the Default project.   
087       * <li> All members in the new project representation must have been members previously.
088       * <li> All Members, Invitees, and Spectators must be defined Users.
089       * <li> The project owner cannot be a Member or Invitee or Spectator.
090       * <li> No Invitee can be a Member or Spectator.
091       * <li> No Spectator can be a Member or Invitee.
092       * <li> If no UriPatterns are supplied, then the '*' UriPattern is provided by default.
093       * </ul>
094       * @param entity The XML representation of the new Project.
095       */
096      @Override
097      public void storeRepresentation(Representation entity) {
098        if (!validateUriUserIsUser() ||
099            !validateAuthUserIsAdminOrUriUser()) {
100          return;
101        }  
102    
103        String entityString = null;
104        Project newProject;
105        // Try to make the XML payload into a Project, return failure if this fails. 
106        try { 
107          entityString = entity.getText();
108          newProject = super.projectManager.makeProject(entityString);
109        }
110        catch (Exception e) {
111          setStatusMiscError(String.format("Illegal project definition: %s", entityString));
112          return;
113        }
114        // Error if the Project name, owner, start date, or end date is not supplied.
115        if ((newProject.getName() == null) || (newProject.getName().trim().equals(""))) {
116          setStatusMiscError("Project name missing.");
117          return;
118        }
119        if ((newProject.getOwner() == null) || (newProject.getOwner().trim().equals(""))) {
120          setStatusMiscError("Project owner must be supplied.");
121          return;
122        }
123        if (newProject.getStartTime() == null) {
124          setStatusMiscError("Project start time must be supplied.");
125          return;
126        }
127        if (newProject.getEndTime() == null) {
128          setStatusMiscError("Project end time must be supplied.");
129          return;
130        }
131        // Error if the URI ProjectName is not the same as the XML Project name.
132        if (!(this.projectName.equals(newProject.getName()))) {
133          setStatusMiscError("Different URI/XML project names");
134          return;
135        }
136        // Error if the project is the Default project.
137        if (this.projectName.equals(ProjectManager.DEFAULT_PROJECT_NAME)) {
138          setStatusMiscError("Cannot modify the Default project");
139          return;
140        }
141        
142        // Get or create a list of new members (possibly empty).
143        List<String> newMembers = ((newProject.getMembers() == null) ? new ArrayList<String>()
144            : newProject.getMembers().getMember());
145    
146    
147        // Error if oldProject exists and new one contains a member not in old one.
148        for (String newMember : newMembers) {
149          if (!super.projectManager.isMember(this.user, this.projectName, newMember)) {
150            String msg = "New project contains non-member: " + newMember;
151            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
152            return;
153          }
154        }
155        
156        // Get a (possibly empty) list of the invitees.
157        List<String> newInvitees = ((newProject.getInvitations() == null) ? 
158            new ArrayList<String>() : newProject.getInvitations().getInvitation());
159        
160        // Get a (possibly empty) list of spectators.
161        List<String> newSpectators = ((newProject.getSpectators() == null) ? 
162            new ArrayList<String>() : newProject.getSpectators().getSpectator());
163    
164        // Make sure all newMembers, newInvitees, and newSpecators are defined Users.
165        for (String member : newMembers) {
166          if (!super.userManager.isUser(member)) {
167            String msg = "Member is not a registered user: " + member;
168            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
169            return;
170          }
171        }
172        for (String invitee : newInvitees) {
173          if (!super.userManager.isUser(invitee)) {
174            String msg = "Invited member is not a registered user: " + invitee;
175            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
176            return;
177          }
178        }
179        for (String spectator : newSpectators) {
180          if (!super.userManager.isUser(spectator)) {
181            String msg = "Spectator is not a registered user: " + spectator;
182            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
183            return;
184          }
185        }
186        
187        // No invitee can be a (new) member.
188        for (String invitee : newInvitees) {
189          if (newMembers.contains(invitee)) {
190            String msg = "Invited member cannot already be a member: " + invitee;
191            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
192            return;
193          }
194        }
195        // Project owner cannot be a member or invitee.
196        if (newMembers.contains(this.uriUser)) {
197          String msg = "Project owner cannot also be a member: " + this.uriUser;
198          getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
199          return;
200        }
201        if (newInvitees.contains(this.uriUser)) {
202          String msg = "Project owner cannot also be an invited member: " + this.uriUser;
203          getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
204          return;
205        }
206        // Spectator cannot be a project owner.
207        if (newSpectators.contains(this.uriUser)) {
208          String msg = "Project owner cannot also be a specator: " + this.uriUser;
209          getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
210          return;
211        }
212        // No spectator can be a (new) member.
213        for (String spectator : newSpectators) {
214          if (newMembers.contains(spectator)) {
215            String msg = "Spectator cannot also be a member: " + spectator;
216            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
217            return;
218          }
219        }
220        // No spectator can be an invitee.
221        for (String spectator : newSpectators) {
222          if (newInvitees.contains(spectator)) {
223            String msg = "Spectator cannot also be an invitee: " + spectator;
224            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, msg);
225            return;
226          }
227        }
228        
229        // Make sure there are UriPatterns. If not, provide a default of "*".
230        if (newProject.getUriPatterns() == null) {
231          newProject.setUriPatterns(new UriPatterns());
232        }
233        if (newProject.getUriPatterns().getUriPattern().isEmpty()) {
234          newProject.getUriPatterns().getUriPattern().add("*");
235        }
236    
237        // if we're still here, add it to the Manager and return success.
238        super.projectManager.putProject(newProject);      
239        getResponse().setStatus(Status.SUCCESS_CREATED);
240      }
241      
242      /** 
243       * Indicate the DELETE method is supported. 
244       * @return True.
245       */
246      @Override
247      public boolean allowDelete() {
248          return true;
249      }
250      
251      /**
252       * Implement the DELETE method that deletes an existing Project for a given User.
253       * <ul> 
254       * <li> The User must be currently defined.
255       * <li> The authenticated user must be the uriUser or the Admin. 
256       * <li> The User must be the admin or the Owner.
257       * <li> The project name must not be "Default".
258       * </ul>
259       * If the Project doesn't exist, that's fine, it's still "deleted".
260       */
261      @Override
262      public void removeRepresentations() {
263        try {
264          if (!validateUriUserIsUser() ||
265              !validateAuthUserIsAdminOrUriUser()) {
266            return;
267          }  
268          if ("Default".equals(this.projectName)) {
269            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Cannot delete Default project.");
270            return;
271          }    
272          // Otherwise, delete it and return success.
273          super.projectManager.deleteProject(this.user, this.projectName);      
274          getResponse().setStatus(Status.SUCCESS_OK);
275        }
276        catch (Exception e) {
277          setStatusInternalError(e);
278        }
279      }
280    }