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    }