001 package org.hackystat.sensorbase.resource.sensordata; 002 003 import java.io.IOException; 004 005 import javax.xml.datatype.XMLGregorianCalendar; 006 007 import org.hackystat.utilities.tstamp.Tstamp; 008 import org.hackystat.sensorbase.resource.sensorbase.SensorBaseResource; 009 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData; 010 import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDatas; 011 import org.restlet.Context; 012 import org.restlet.data.MediaType; 013 import org.restlet.data.Request; 014 import org.restlet.data.Response; 015 import org.restlet.data.Status; 016 import org.restlet.resource.Representation; 017 import org.restlet.resource.Variant; 018 019 /** 020 * The resource for all URIs that extend sensordata. This includes: 021 * <ul> 022 * <li> host/sensordata/{user} 023 * <li> host/sensordata/{user}?sdt={sensordatatype} 024 * <li> host/sensordata/{user}?lastModStartTime={lastModTimestamp}&lastModEndTime={lastModTimestamp} 025 * <li> host/sensordata/{user}/{timestamp} 026 * </ul> 027 * 028 * @author Philip Johnson 029 */ 030 public class UserSensorDataResource extends SensorBaseResource { 031 032 /** To be retrieved from the URL, or else null if not found. */ 033 private String sdtName; 034 /** To be retrieved from the URL, or else null if not found. */ 035 private String timestamp; 036 /** To be retrieved from the URL, or else null if not found. */ 037 private String lastModStartTime; 038 /** To be retrieved from the URL, or else null if not found. */ 039 private String lastModEndTime; 040 041 /** 042 * Provides the following representational variants: TEXT_XML. 043 * 044 * @param context The context. 045 * @param request The request object. 046 * @param response The response object. 047 */ 048 public UserSensorDataResource(Context context, Request request, Response response) { 049 super(context, request, response); 050 this.sdtName = (String) request.getAttributes().get("sensordatatype"); 051 this.timestamp = (String) request.getAttributes().get("timestamp"); 052 this.lastModStartTime = (String) request.getAttributes().get("lastModStartTime"); 053 this.lastModEndTime = (String) request.getAttributes().get("lastModEndTime"); 054 } 055 056 /** 057 * Returns a SensorDataIndex when a GET is called with: 058 * <ul> 059 * <li> sensordata/{email} 060 * <li> sensordata/{email}?sdt={sensordatatype} 061 * <li> sensordata/{user}?lastModStartTime={timestamp}&lastModEndTime={timestamp} 062 * </ul> 063 * Returns a SensorData when a GET is called with: 064 * <ul> 065 * <li> sensordata/{email}/{timestamp} 066 * </ul> 067 * <p> 068 * The user must be defined, and the authenticated user must be the uriUser or the Admin or in a 069 * project with the uriUser on that day. 070 * 071 * @param variant The representational variant requested. 072 * @return The representation. 073 */ 074 @Override 075 public Representation represent(Variant variant) { 076 if (!validateUriUserIsUser()) { 077 return null; 078 } 079 // Return error if authUser is not UriUser, not admin, and not in a Project with UriUser. 080 if (!userManager.isAdmin(this.authUser) && 081 !this.uriUser.equals(this.authUser) && 082 !super.projectManager.inProject(this.authUser, this.uriUser, this.timestamp)) { 083 setStatusMiscError(String.format("Request requires authorized user %s to be in at least one " 084 + "project with the URI user %s on the specified day", this.authUser, this.uriUser)); 085 return null; 086 } 087 // Now check to make sure they want XML. 088 if (variant.getMediaType().equals(MediaType.TEXT_XML)) { 089 // Return index of all data for URI: sensordata/{email} 090 if ((this.sdtName == null) && (this.timestamp == null) && (this.lastModStartTime == null)) { 091 String xmlData = super.sensorDataManager.getSensorDataIndex(this.user); 092 return super.getStringRepresentation(xmlData); 093 } 094 // Return index of data for a given SDT for URI: sensordata/{email}?sdt={sensordatatype} 095 if ((this.sdtName != null) && (this.timestamp == null) && (this.lastModStartTime == null)) { 096 String xmlData = super.sensorDataManager.getSensorDataIndex(this.user, this.sdtName); 097 return super.getStringRepresentation(xmlData); 098 } 099 // Return index of data since the tstamp for URI: 100 // sensordata/{user}?lastModStartTime={timestamp}&lastModEndTime={timestamp} 101 if ((this.sdtName == null) && (this.timestamp == null) && (this.lastModStartTime != null)) { 102 // First, check to see that we can convert the lastModStartTime and EndTime into a tstamp. 103 XMLGregorianCalendar lastModStart; 104 XMLGregorianCalendar lastModEnd; 105 try { 106 lastModStart = Tstamp.makeTimestamp(this.lastModStartTime); 107 } 108 catch (Exception e) { 109 setStatusBadTimestamp(lastModStartTime); 110 return null; 111 } 112 try { 113 lastModEnd = Tstamp.makeTimestamp(this.lastModEndTime); 114 } 115 catch (Exception e) { 116 setStatusBadTimestamp(lastModEndTime); 117 return null; 118 } 119 // Now, get the data and return. 120 String xmlData = super.sensorDataManager.getSensorDataIndexLastMod(this.user, lastModStart, 121 lastModEnd); 122 return super.getStringRepresentation(xmlData); 123 } 124 // Return sensordata representation for URI: sensordata/{email}/{timestamp} 125 if ((this.sdtName == null) && (this.timestamp != null) && (this.lastModStartTime == null)) { 126 // First, try to parse the timestamp string, and return error if it doesn't parse. 127 XMLGregorianCalendar tstamp; 128 try { 129 tstamp = Tstamp.makeTimestamp(this.timestamp); 130 } 131 catch (Exception e) { 132 setStatusBadTimestamp(timestamp); 133 return null; 134 } 135 String xmlData = super.sensorDataManager.getSensorData(this.user, tstamp); 136 // Now, see if we actually have the SensorData. 137 if (xmlData == null) { // NOPMD (deeply nested if-then-else) 138 setStatusMiscError("Unknown sensor data"); 139 return null; 140 } 141 // We have the SensorData, so retrieve its xml string representation and return it. 142 try { 143 return getStringRepresentation(xmlData); 144 } 145 catch (Exception e) { 146 setStatusInternalError(e); 147 return null; 148 } 149 } 150 } 151 // Otherwise we don't understand the URI that they invoked us with. 152 setStatusMiscError("Request could not be understood."); 153 return null; 154 } 155 156 /** 157 * Indicate the PUT method is supported. 158 * 159 * @return True. 160 */ 161 @Override 162 public boolean allowPut() { 163 return true; 164 } 165 166 /** 167 * Implement the PUT method that creates a new sensor data instance. 168 * <ul> 169 * <li> The XML must be marshallable into a sensor data instance. 170 * <li> The timestamp in the URL must match the timestamp in the XML. 171 * <li> The User and SDT must exist. 172 * </ul> 173 * Note that we are not validating that this sensor data instance contains all of the Required 174 * Properties specified by the SDT. This should be done later, on demand, as part of analyses. 175 * <p> 176 * We are also not at this point checking to see whether the User and SDT exist. 177 * 178 * @param entity The XML representation of the new sensor data instance.. 179 */ 180 @Override 181 public void storeRepresentation(Representation entity) { 182 try { 183 if (!validateUriUserIsUser() || 184 !validateAuthUserIsAdminOrUriUser()) { 185 return; 186 } 187 188 // Get the payload. 189 String entityString = null; 190 try { 191 entityString = entity.getText(); 192 } 193 catch (IOException e) { 194 setStatusMiscError("Bad or missing content"); 195 return; 196 } 197 198 // if the "timestamp" is "batch", then our payload is <SensorDatas>, otherwise <SensorData> 199 if (this.timestamp.equals("batch")) { 200 putSensorDatas(entityString); 201 } 202 else { 203 putSensorData(entityString); 204 } 205 } 206 catch (RuntimeException e) { 207 setStatusInternalError(e); 208 return; 209 } 210 } 211 212 /** 213 * Put a SensorData XML payload. 214 * 215 * @param entityString An entity string that should represent a SensorData instance. 216 */ 217 private void putSensorData(String entityString) { 218 SensorData data; 219 // Try to make the XML payload into sensor data, return failure if this fails. 220 try { 221 data = this.sensorDataManager.makeSensorData(entityString); 222 } 223 catch (Exception e) { 224 setStatusMiscError("Invalid SensorData representation: " + entityString); 225 return; 226 } 227 228 try { 229 // Return failure if the payload XML timestamp doesn't match the URI timestamp. 230 if ((this.timestamp == null) || (!this.timestamp.equals(data.getTimestamp().toString()))) { 231 setStatusMiscError("Timestamp in URI does not match timestamp in sensor data instance."); 232 return; 233 } 234 // Return failure if the SensorData Owner doesn't match the UriUser 235 if (!this.uriUser.equals(super.sensorDataManager.convertOwnerToEmail(data.getOwner()))) { 236 setStatusMiscError("SensorData payload owner field does not match user field in URI"); 237 return; 238 } 239 // otherwise we add it to the Manager and return success. 240 super.sensorDataManager.putSensorData(data); 241 getResponse().setStatus(Status.SUCCESS_CREATED); 242 } 243 catch (RuntimeException e) { 244 setStatusInternalError(e); 245 return; 246 } 247 } 248 249 /** 250 * Put a SensorDatas payload. 251 * 252 * @param entityString An entity string that should represent a SensorDatas instance. 253 */ 254 private void putSensorDatas(String entityString) { 255 SensorDatas datas; 256 // Try to make the XML payload into a collection of SensorData, return failure if this fails. 257 try { 258 datas = this.sensorDataManager.makeSensorDatas(entityString); 259 } 260 catch (Exception e) { 261 setStatusMiscError("Invalid SensorDatas representation: " + entityString); 262 return; 263 } 264 265 try { 266 // Makes sure all SensorData matches the UriUser, otherwise fail and don't add anything. 267 for (SensorData data : datas.getSensorData()) { 268 if (!this.uriUser.equals(super.sensorDataManager.convertOwnerToEmail(data.getOwner()))) { 269 setStatusMiscError("At least 1 SensorData owner field does not match user field in URI"); 270 return; 271 } 272 } 273 // Otherwise we should be OK. Add all of them and return success. 274 for (SensorData data : datas.getSensorData()) { 275 super.sensorDataManager.putSensorData(data); 276 } 277 getResponse().setStatus(Status.SUCCESS_CREATED); 278 } 279 catch (RuntimeException e) { 280 setStatusInternalError(e); 281 return; 282 } 283 } 284 285 /** 286 * Indicate the DELETE method is supported. 287 * 288 * @return True. 289 */ 290 @Override 291 public boolean allowDelete() { 292 return true; 293 } 294 295 /** 296 * Implement the DELETE method that ensures the specified sensor data instance no longer exists. 297 * <ul> 298 * <li> The user must exist. 299 * <li> The timestamp must be well-formed. 300 * <li> The authenticated user must be the uriUser or the admin. 301 * </ul> 302 * If not timestamp is supplied, then all sensor data will be deleted if the user is the test 303 * user. 304 */ 305 @Override 306 public void removeRepresentations() { 307 if (!validateUriUserIsUser() || 308 !validateAuthUserIsAdminOrUriUser()) { 309 return; 310 } 311 312 // If timestamp is null, then delete all data if a test user is deleting its own data. 313 if (this.timestamp == null) { 314 if ((this.authUser != null) && this.authUser.equals(this.uriUser) 315 && userManager.isTestUser(this.user)) { 316 super.sensorDataManager.deleteData(this.user); 317 getResponse().setStatus(Status.SUCCESS_OK); 318 return; 319 } 320 else { 321 setStatusMiscError("Can't delete all sensor data from a non-test user."); 322 return; 323 } 324 } 325 326 XMLGregorianCalendar tstamp; 327 try { 328 tstamp = Tstamp.makeTimestamp(this.timestamp); 329 super.sensorDataManager.deleteData(this.user, tstamp); 330 } 331 catch (Exception e) { 332 setStatusMiscError("Bad timestamp: " + this.timestamp); 333 return; 334 } 335 getResponse().setStatus(Status.SUCCESS_OK); 336 } 337 }