001    package org.hackystat.sensorbase.server;
002    
003    import java.util.Map;
004    import java.util.Set;
005    
006    import org.hackystat.sensorbase.db.DbManager;
007    import org.hackystat.sensorbase.mailer.Mailer;
008    import org.hackystat.sensorbase.resource.db.CompressResource;
009    import org.hackystat.sensorbase.resource.db.IndexResource;
010    import org.hackystat.sensorbase.resource.db.RowCountResource;
011    import org.hackystat.sensorbase.resource.ping.PingResource;
012    import org.hackystat.sensorbase.resource.projects.ProjectManager;
013    import org.hackystat.sensorbase.resource.projects.ProjectsResource;
014    import org.hackystat.sensorbase.resource.projects.UserProjectInvitationResource;
015    import org.hackystat.sensorbase.resource.projects.UserProjectRenameResource;
016    import org.hackystat.sensorbase.resource.projects.UserProjectResource;
017    import org.hackystat.sensorbase.resource.projects.UserProjectSensorDataResource;
018    import org.hackystat.sensorbase.resource.projects.UserProjectSnapshotResource;
019    import org.hackystat.sensorbase.resource.projects.UserProjectSummaryResource;
020    import org.hackystat.sensorbase.resource.projects.UserProjectsResource;
021    import org.hackystat.sensorbase.resource.registration.HomePageResource;
022    import org.hackystat.sensorbase.resource.registration.RegistrationResource;
023    import org.hackystat.sensorbase.resource.sensordata.SensorDataManager;
024    import org.hackystat.sensorbase.resource.sensordata.SensorDataResource;
025    import org.hackystat.sensorbase.resource.sensordata.UserSensorDataResource;
026    import org.hackystat.sensorbase.resource.sensordatatypes.SdtManager;
027    import org.hackystat.sensorbase.resource.sensordatatypes.SensorDataTypeResource;
028    import org.hackystat.sensorbase.resource.sensordatatypes.SensorDataTypesResource;
029    import org.hackystat.sensorbase.resource.users.UserManager;
030    import org.hackystat.sensorbase.resource.users.UserResource;
031    import org.hackystat.sensorbase.resource.users.UsersResource;
032    import org.hackystat.utilities.logger.HackystatLogger;
033    import org.hackystat.utilities.logger.RestletLoggerUtil;
034    import org.restlet.Application;
035    import org.restlet.Component;
036    import org.restlet.Guard;
037    import org.restlet.Restlet;
038    import org.restlet.Router;
039    import org.restlet.data.Protocol;
040    
041    import static org.hackystat.sensorbase.server.ServerProperties.HOSTNAME_KEY;
042    import static org.hackystat.sensorbase.server.ServerProperties.PORT_KEY;
043    import static org.hackystat.sensorbase.server.ServerProperties.CONTEXT_ROOT_KEY;
044    import static org.hackystat.sensorbase.server.ServerProperties.LOGGING_LEVEL_KEY;
045    
046    import java.util.logging.Logger;
047    
048    
049    /**
050     * Sets up the HTTP Server process and dispatching to the associated resources. 
051     * @author Philip Johnson
052     */
053    public class Server extends Application { 
054    
055      /** Holds the Restlet Component associated with this Server. */
056      private Component component; 
057      
058      /** Holds the host name associated with this Server. */
059      private String hostName;
060      
061      /** Holds the HackystatLogger for the sensorbase. */
062      private Logger logger; 
063      
064      /** Holds the ServerProperties instance associated with this sensorbase. */
065      private ServerProperties serverProperties;
066      
067      /**
068       * Creates a new instance of a SensorBase HTTP server, listening on the supplied port.
069       * SensorBase properties are initialized from the User's sensorbase.properties file.  
070       * @return The Server instance created. 
071       * @throws Exception If problems occur starting up this server. 
072       */
073      public static Server newInstance() throws Exception {
074        return newInstance(new ServerProperties());
075      }
076      
077      /**
078       * Creates a new instance of a SensorBase HTTP server suitable for unit testing. 
079       * SensorBase properties are initialized from the User's sensorbase.properties file, 
080       * then set to their "testing" versions.   
081       * @return The Server instance created. 
082       * @throws Exception If problems occur starting up this server. 
083       */
084      public static Server newTestInstance() throws Exception {
085        ServerProperties properties = new ServerProperties();
086        properties.setTestProperties();
087        return newInstance(properties);
088      }
089      
090      /**
091       * Creates a new instance of a SensorBase HTTP server, listening on the supplied port.
092       * @param  serverProperties The ServerProperties used to initialize this server.
093       * @return The Server instance created. 
094       * @throws Exception If problems occur starting up this server. 
095       */
096      public static Server newInstance(ServerProperties serverProperties) throws Exception {
097        Server server = new Server();
098        server.logger = HackystatLogger.getLogger("org.hackystat.sensorbase", "sensorbase");
099        server.serverProperties = serverProperties;
100        server.hostName = "http://" +
101                          server.serverProperties.get(HOSTNAME_KEY) + 
102                          ":" + 
103                          server.serverProperties.get(PORT_KEY) + 
104                          "/" +
105                          server.serverProperties.get(CONTEXT_ROOT_KEY) +
106                          "/";
107        int port = Integer.valueOf(server.serverProperties.get(PORT_KEY));
108        server.component = new Component();
109        server.component.getServers().add(Protocol.HTTP, port);
110        server.component.getDefaultHost()
111          .attach("/" + server.serverProperties.get(CONTEXT_ROOT_KEY), server);
112    
113        // Set up logging.
114        RestletLoggerUtil.disableLogging();
115        HackystatLogger.setLoggingLevel(server.logger, server.serverProperties.get(LOGGING_LEVEL_KEY));
116        server.logger.warning("Starting sensorbase.");
117        server.logger.warning("Host: " + server.hostName);
118        server.logger.info(server.serverProperties.echoProperties());
119    
120        try {
121          Mailer.getInstance();
122        }
123        catch (Throwable e) {
124          String msg = "ERROR: JavaMail not installed correctly! Mail services will fail!";
125          server.logger.warning(msg);
126        }
127    
128        // Now create all of the Resource Managers and store them in the Context.
129        // Ordering constraints: 
130        // - DbManager must precede all resource managers so it can initialize the tables
131        //   before the resource managers add data to them.
132        // - UserManager must be initialized before ProjectManager, since ProjectManager needs
133        //   to know about the Users. 
134        Map<String, Object> attributes = 
135          server.getContext().getAttributes();
136        DbManager dbManager = new DbManager(server);  // we need this later in this method.
137        attributes.put("DbManager", dbManager);
138        attributes.put("SdtManager", new SdtManager(server));
139        attributes.put("UserManager", new UserManager(server));
140        attributes.put("ProjectManager", new ProjectManager(server));
141        attributes.put("SensorDataManager", new SensorDataManager(server));
142        attributes.put("SensorBaseServer", server);
143        attributes.put("ServerProperties", server.serverProperties);
144        
145        // Now let's open for business. 
146        server.logger.info("Maximum Java heap size (MB): " + 
147            (Runtime.getRuntime().maxMemory() / 1000000.0));
148        server.logger.info("Table counts: " + getTableCounts(dbManager));
149        server.component.start();
150        server.logger.warning("SensorBase (Version " + getVersion() + ") now running.");
151        return server;
152      }
153      
154      /**
155       * Returns a string with the counts of rows in the various tables. 
156       * @param dbManager The dbManager. 
157       * @return A string with info on row counts. 
158       */
159      private static String getTableCounts (DbManager dbManager) {
160        Set<String> tableNames = dbManager.getTableNames();
161        StringBuffer buff = new StringBuffer();
162        for (String tableName : tableNames) {
163          int tableRows = dbManager.getRowCount(tableName);
164          buff.append(tableName).append(':').append(tableRows).append(' ');
165        }
166        return buff.toString();
167      }
168      
169     
170      /**
171       * Starts up the SensorBase web service using the properties specified in sensor.properties.  
172       * Control-c to exit. 
173       * @param args Ignored. 
174       * @throws Exception if problems occur.
175       */
176      public static void main(final String[] args) throws Exception {
177        Server.newInstance();
178      }
179    
180      /**
181       * Dispatch to the Projects, SensorData, SensorDataTypes, or Users Resource depending on the URL.
182       * We will authenticate all requests except for registration (users?email={email}).
183       * @return The router Restlet.
184       */
185      @Override
186      public Restlet createRoot() {
187        // First, create a Router that will have a Guard placed in front of it so that this Router's
188        // requests will require authentication.
189        Router authRouter = new Router(getContext());
190        
191        // SENSORDATATYPES 
192        authRouter.attach("/sensordatatypes", 
193            SensorDataTypesResource.class);
194        authRouter.attach("/sensordatatypes/{sensordatatypename}", 
195            SensorDataTypeResource.class);
196        
197        // USERS
198        authRouter.attach("/users", 
199            UsersResource.class);
200        authRouter.attach("/users/{user}", 
201            UserResource.class);
202        
203        // SENSORDATA 
204        authRouter.attach("/sensordata", 
205            SensorDataResource.class);
206        authRouter.attach("/sensordata/{user}", 
207            UserSensorDataResource.class);
208        authRouter.attach("/sensordata/{user}?sdt={sensordatatype}", 
209            UserSensorDataResource.class);
210        authRouter.attach(
211            "/sensordata/{user}?lastModStartTime={lastModStartTime}&lastModEndTime={lastModEndTime}", 
212            UserSensorDataResource.class);
213        authRouter.attach("/sensordata/{user}/{timestamp}", 
214            UserSensorDataResource.class);
215        
216        // PROJECTS
217        authRouter.attach("/projects", 
218            ProjectsResource.class);
219        authRouter.attach("/projects/{user}", 
220            UserProjectsResource.class);
221        String projectUri = "/projects/{user}/{projectname}";
222        authRouter.attach(projectUri,
223            UserProjectResource.class);
224        
225        // PROJECTS SNAPSHOT
226        authRouter.attach(projectUri + "/snapshot" + 
227            "?startTime={startTime}&endTime={endTime}&sdt={sdt}&tool={tool}", 
228            UserProjectSnapshotResource.class);
229        authRouter.attach(projectUri + "/snapshot" + 
230            "?startTime={startTime}&endTime={endTime}&sdt={sdt}",
231            UserProjectSnapshotResource.class);
232        
233        // PROJECTS SUMMARY 
234        authRouter.attach(projectUri + "/summary" +  
235            "?startTime={startTime}&endTime={endTime}", 
236            UserProjectSummaryResource.class);
237        authRouter.attach(projectUri + "/summary" +  
238            "?startTime={startTime}&numDays={numDays}", 
239            UserProjectSummaryResource.class);
240        
241        // PROJECTS SENSORDATA
242        String projectSensorDataUri = projectUri + "/sensordata";
243        authRouter.attach(projectSensorDataUri, 
244            UserProjectSensorDataResource.class);
245        authRouter.attach(projectSensorDataUri +
246         "?startTime={startTime}&endTime={endTime}&startIndex={startIndex}&maxInstances={maxInstances}",
247           UserProjectSensorDataResource.class);
248        authRouter.attach(projectSensorDataUri +  
249            "?startTime={startTime}&endTime={endTime}", 
250            UserProjectSensorDataResource.class);
251        authRouter.attach(projectSensorDataUri +  
252            "?sdt={sdt}&startTime={startTime}&endTime={endTime}&tool={tool}",  
253            UserProjectSensorDataResource.class);
254        authRouter.attach(projectSensorDataUri +  
255            "?sdt={sdt}&startTime={startTime}&endTime={endTime}",  
256            UserProjectSensorDataResource.class);
257        
258        // PROJECTS INVITATION 
259        authRouter.attach(projectUri + "/invitation/{rsvp}", 
260            UserProjectInvitationResource.class);
261        
262        // PROJECTS RENAME
263        authRouter.attach(projectUri + "/rename/{newprojectname}", 
264            UserProjectRenameResource.class);
265        
266        // DB Commands
267        authRouter.attach("/db/table/compress", CompressResource.class);
268        authRouter.attach("/db/table/index", IndexResource.class);
269        authRouter.attach("/db/table/{table}/rowcount", RowCountResource.class);
270        
271        // Here's the Guard that we will place in front of authRouter.
272        authRouter.attach("", HomePageResource.class);
273        Guard guard = new Authenticator(getContext());
274        guard.setNext(authRouter);
275        
276        // Now create our "top-level" router which will allow the registration URI to proceed without
277        // authentication, but all other URI patterns will go to the guarded Router. 
278        Router router = new Router(getContext());
279        router.attach("/register", RegistrationResource.class);
280        router.attach("/ping", PingResource.class);
281        router.attach("/ping?user={user}&password={password}", PingResource.class);
282        router.attachDefault(guard);
283        
284        return router;
285      }
286    
287    
288      /**
289       * Returns the version associated with this Package, if available from the jar file manifest.
290       * If not being run from a jar file, then returns "Development". 
291       * @return The version.
292       */
293      public static String getVersion() {
294        String version = 
295          Package.getPackage("org.hackystat.sensorbase.server").getImplementationVersion();
296        return (version == null) ? "Development" : version; 
297      }
298      
299      /**
300       * Returns the host name associated with this server. 
301       * Example: "http://localhost:9876/sensorbase/"
302       * @return The host name. 
303       */
304      public String getHostName() {
305        return this.hostName;
306      }
307      
308      /**
309       * Returns the ServerProperties instance associated with this server. 
310       * @return The server properties.
311       */
312      public ServerProperties getServerProperties() {
313        return this.serverProperties;
314      }
315      
316      /**
317       * Returns the logger for the SensorBase. 
318       * @return The logger.
319       */
320      @Override
321      public Logger getLogger() {
322        return this.logger;
323      }
324    }
325