001    package org.hackystat.sensorbase.resource.sensorbase;
002    
003    import org.hackystat.sensorbase.resource.projects.ProjectManager;
004    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
005    import org.hackystat.sensorbase.resource.sensordata.SensorDataManager;
006    import org.hackystat.sensorbase.resource.sensordatatypes.SdtManager;
007    import org.hackystat.sensorbase.resource.users.UserManager;
008    import org.hackystat.sensorbase.resource.users.jaxb.User;
009    import org.hackystat.sensorbase.server.Server;
010    import org.restlet.Context;
011    import org.restlet.data.CharacterSet;
012    import org.restlet.data.Language;
013    import org.restlet.data.MediaType;
014    import org.restlet.data.Request;
015    import org.restlet.data.Response;
016    import org.restlet.data.Status;
017    import org.restlet.resource.Representation;
018    import org.restlet.resource.Resource;
019    import org.restlet.resource.StringRepresentation;
020    import org.restlet.resource.Variant;
021    
022    /**
023     * An abstract superclass for all SensorBase resources that supplies common 
024     * initialization and validation processing. 
025     * <p>
026     * Initialization processing includes:
027     * <ul>
028     * <li> Extracting the authenticated user identifier (when authentication available)
029     * <li> Extracting the user email from the URI (when available)
030     * <li> Declares that the TEXT/XML representational variant is supported.
031     * <li> Providing instance variables bound to the ProjectManager, SdtManager, UserManager, and 
032     * SensorDataManager.
033     * </ul>
034     * <p>
035     * Validation processing involves a set of "validated" methods. These check the values
036     * of various parameters in the request, potentially initializing instance variables
037     * as a result.  If the validation process fails, these methods set the Restlet 
038     * Status value appropriately and return false. 
039     * 
040     * @author Philip Johnson
041     *
042     */
043    public abstract class SensorBaseResource extends Resource {
044      
045      /** The authenticated user, retrieved from the ChallengeResponse, or null. */
046      protected String authUser = null;
047      
048      /** To be retrieved from the URL as the 'user' template parameter, or null. */
049      protected String uriUser = null; 
050      
051      /** The user instance corresponding to the user indicated in the URI string, or null. */
052      protected User user = null;
053      
054      /** The projectName found within the URL string, or null. */
055      protected String projectName = null;
056      
057      /** The project corresponding to the projectName, or null. */
058      protected Project project = null;
059      
060      /** The ProjectManager. */
061      protected ProjectManager projectManager = null;
062      
063      /** The UserManager. */
064      protected UserManager userManager = null;
065      
066      /** The SdtManager. */
067      protected SdtManager sdtManager = null;
068      
069      /** The SensorDataManager. */
070      protected SensorDataManager sensorDataManager = null;
071      
072      /** The server. */
073      protected Server server; 
074      
075      /** Everyone generally wants to create one of these, so declare it here. */
076      protected String responseMsg;
077      
078      /**
079       * Provides the following representational variants: TEXT_XML.
080       * @param context The context.
081       * @param request The request object.
082       * @param response The response object.
083       */
084      public SensorBaseResource(Context context, Request request, Response response) {
085        super(context, request, response);
086        if (request.getChallengeResponse() != null) {
087          this.authUser = request.getChallengeResponse().getIdentifier();
088        }
089        this.server = (Server)getContext().getAttributes().get("SensorBaseServer");
090        this.uriUser = (String) request.getAttributes().get("user");
091        this.projectName = (String) request.getAttributes().get("projectname");
092        
093        this.projectManager = (ProjectManager)getContext().getAttributes().get("ProjectManager");
094        this.userManager = (UserManager)getContext().getAttributes().get("UserManager");
095        this.sdtManager = (SdtManager)getContext().getAttributes().get("SdtManager");
096        this.sensorDataManager = 
097          (SensorDataManager)getContext().getAttributes().get("SensorDataManager");
098        getVariants().clear(); // copied from BookmarksResource.java, not sure why needed.
099        getVariants().add(new Variant(MediaType.TEXT_XML));
100      }
101      
102    
103      /**
104       * The Restlet getRepresentation method which must be overridden by all concrete Resources.
105       * @param variant The variant requested.
106       * @return The Representation. 
107       */
108      @Override
109      public abstract Representation represent(Variant variant);
110      
111      /**
112       * Creates and returns a new Restlet StringRepresentation built from xmlData.
113       * The xmlData will be prefixed with a processing instruction indicating UTF-8 and version 1.0.
114       * @param xmlData The xml data as a string. 
115       * @return A StringRepresentation of that xmldata. 
116       */
117      public static StringRepresentation getStringRepresentation(String xmlData) {
118        StringBuilder builder = new StringBuilder(500);
119        builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
120        builder.append(xmlData);
121        return new StringRepresentation(builder, MediaType.TEXT_XML, Language.ALL, CharacterSet.UTF_8);
122      }
123      
124      /**
125       * Returns true if the authorized user is the administrator.
126       * Otherwise sets the Response status and returns false. 
127       * @return True if the authorized user is the admin. 
128       */
129      protected boolean validateAuthUserIsAdmin() {
130        try {
131          if (userManager.isAdmin(this.authUser)) {
132            return true;
133          }
134          else {
135            this.responseMsg = ResponseMessage.adminOnly(this);
136            getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED, removeNewLines(this.responseMsg));
137            return false;
138          }
139        }
140        catch (RuntimeException e) {
141          this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
142          getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
143        }
144        return false;
145      }
146      
147      /**
148       * Returns true if the user in the URI string is defined in the UserManager.
149       * Otherwise sets the Response status and returns false.
150       * If it returns true, then this.user has the corresponding User instance. 
151       * @return True if the URI user is a real user.
152       */
153      protected boolean validateUriUserIsUser() {
154        try {
155          this.user = this.userManager.getUser(this.uriUser);
156          if (this.user == null) {
157            this.responseMsg = ResponseMessage.undefinedUser(this, this.uriUser);
158            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, removeNewLines(this.responseMsg));
159            return false;
160          }
161          else {
162            return true;
163          }
164        }
165        catch (RuntimeException e) {
166          this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
167          getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
168        }
169        return false;
170      }
171      
172      
173      /**
174       * Returns true if the project name in the URI string is defined in the ProjectManager.
175       * Otherwise sets the Response status and returns false.
176       * If it returns true, then this.project has the corresponding Project instance. 
177       * @return True if the URI project name is a real project.
178       */
179      protected boolean validateUriProjectName() {
180        try {
181          this.project = projectManager.getProject(this.user, this.projectName);
182          if (this.project == null) {
183            this.responseMsg = ResponseMessage.undefinedProject(this, this.user, this.projectName);
184            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, removeNewLines(this.responseMsg));
185            return false;
186          }
187          else {
188            return true;
189          }
190        }
191        catch (RuntimeException e) {
192          this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
193          getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
194        }
195        return false;
196      }
197      
198      /**
199       * Returns true if the authorized user is the owner of the project in the URL string.
200       * Otherwise sets the Response status and returns false.
201       * @return True if the authorized user is the owner of the Project.
202       */
203      protected boolean validateProjectOwner() {
204        try {
205          if (project.getOwner().equals(this.authUser)) {
206            return true;
207          }
208          else {
209            this.responseMsg = ResponseMessage.notProjectOwner(this, this.authUser, this.projectName);
210            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, removeNewLines(this.responseMsg));
211            return false;
212          }
213        }
214        catch (RuntimeException e) {
215          this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
216          getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
217        }
218        return false;
219      }
220      
221      /**
222       * Returns true if the authorized user is either the admin or user in the URI string.
223       * Otherwise sets the Response status and returns false.
224       * @return True if the authorized user is the admin or the URI user. 
225       */
226      protected boolean validateAuthUserIsAdminOrUriUser() {
227        try {
228          if (userManager.isAdmin(this.authUser) || this.uriUser.equals(this.authUser)) {
229            return true;
230          }
231          else {
232            this.responseMsg = ResponseMessage.adminOrAuthUserOnly(this, this.authUser, this.uriUser);
233            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, removeNewLines(this.responseMsg));
234            return false;
235          }
236        }
237        catch (RuntimeException e) {
238          this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
239          getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
240        }
241        return false;
242      }
243      
244      
245      /**
246       * Returns true if the authorized user can view the project definition. 
247       * This is true if the authorized user is the admin, the project owner, or member,
248       * spectator, or invitee.
249       * Otherwise sets the Response status and returns false.
250       * @return True if the authorized user is a project participant.
251       */
252      protected boolean validateProjectViewer() {
253        try {
254          if (userManager.isAdmin(this.authUser) || 
255              uriUser.equals(this.authUser) ||
256              projectManager.isMember(this.user, this.projectName, this.authUser) ||
257              projectManager.isInvited(this.user, this.projectName, this.authUser) ||
258              projectManager.isSpectator(this.user, this.projectName, this.authUser)) {
259            return true;
260          }
261          else {
262            setStatusMiscError(String.format("User %s not authorized to view project %s", this.authUser,
263                this.projectName));
264            return false;
265          }
266        }
267        catch (RuntimeException e) {
268          this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
269          getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
270        }
271        return false;
272      }
273      
274      /**
275       * Helper function that removes any newline characters from the supplied string and 
276       * replaces them with a blank line. 
277       * @param msg The msg whose newlines are to be removed. 
278       * @return The string without newlines. 
279       */
280      private String removeNewLines(String msg) {
281        return msg.replace(System.getProperty("line.separator"), " ");
282      }
283      
284      /**
285       * Called when an exception is caught while processing a request.
286       * Just sets the response code.  
287       * @param timestamp The timestamp that could not be parsed.
288       */
289      protected void setStatusBadTimestamp (String timestamp) { 
290        this.responseMsg = ResponseMessage.badTimestamp(this, timestamp);
291        getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg)); 
292      }
293      
294      
295      /**
296       * Called when an exception is caught while processing a request.
297       * Just sets the response code.  
298       * @param e The exception that was caught.
299       */
300      protected void setStatusInternalError (Exception e) { 
301        this.responseMsg = ResponseMessage.internalError(this, this.getLogger(), e);
302        getResponse().setStatus(Status.SERVER_ERROR_INTERNAL, removeNewLines(this.responseMsg));
303      }
304      
305      /**
306       * Called when a miscellaneous "one off" error is caught during processing.
307       * @param msg A description of the error.
308       */
309      protected void setStatusMiscError (String msg) { 
310        this.responseMsg = ResponseMessage.miscError(this, msg);
311        getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST, removeNewLines(this.responseMsg));
312      }
313    }