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 }