package com.sixsq.slipstream.application; /* * +=================================================================+ * SlipStream Server (WAR) * ===== * Copyright (C) 2013 SixSq Sarl (sixsq.com) * ===== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * -=================================================================- */ import com.sixsq.slipstream.action.ActionRouter; import com.sixsq.slipstream.attribute.AttributeRouter; import com.sixsq.slipstream.authn.BasicAuthenticator; import com.sixsq.slipstream.authn.CookieAuthenticator; import com.sixsq.slipstream.authn.LoginResource; import com.sixsq.slipstream.authn.LogoutResource; import com.sixsq.slipstream.authn.RegistrationResource; import com.sixsq.slipstream.authn.ResetPasswordResource; import com.sixsq.slipstream.authz.SuperEnroler; import com.sixsq.slipstream.cloudusage.CloudUsageRouter; import com.sixsq.slipstream.configuration.Configuration; import com.sixsq.slipstream.connector.Connector; import com.sixsq.slipstream.connector.DiscoverableConnectorServiceLoader; import com.sixsq.slipstream.dashboard.DashboardRouter; import com.sixsq.slipstream.es.CljElasticsearchHelper; import com.sixsq.slipstream.event.Event; import com.sixsq.slipstream.event.EventRouter; import com.sixsq.slipstream.exceptions.ConfigurationException; import com.sixsq.slipstream.exceptions.NotFoundException; import com.sixsq.slipstream.exceptions.Util; import com.sixsq.slipstream.exceptions.ValidationException; import com.sixsq.slipstream.filter.TrimmedMediaTypesFilter; import com.sixsq.slipstream.initialstartup.CloudIds; import com.sixsq.slipstream.initialstartup.Modules; import com.sixsq.slipstream.initialstartup.Users; import com.sixsq.slipstream.metrics.GraphiteRouter; import com.sixsq.slipstream.module.ModuleListRouter; import com.sixsq.slipstream.metrics.Metrics; import com.sixsq.slipstream.module.ModuleRouter; import com.sixsq.slipstream.persistence.Module; import com.sixsq.slipstream.persistence.User; import com.sixsq.slipstream.resource.AppStoreResource; import com.sixsq.slipstream.resource.ModulesChooserResource; import com.sixsq.slipstream.resource.ReportRouter; import com.sixsq.slipstream.resource.RootRedirectResource; import com.sixsq.slipstream.resource.ServiceCatalogRouter; import com.sixsq.slipstream.resource.configuration.ServiceConfigurationResource; import com.sixsq.slipstream.resource.NuvlaboxAdminRouter; import com.sixsq.slipstream.resource.NuvlaboxAdminResource; import com.sixsq.slipstream.run.RunRouter; import com.sixsq.slipstream.run.VmsRouter; import com.sixsq.slipstream.serviceinfo.ServiceInfoRouter; import com.sixsq.slipstream.ui.UIResourceRouter; import com.sixsq.slipstream.usage.UsageRouter; import com.sixsq.slipstream.user.UserRouter; import com.sixsq.slipstream.util.ConfigurationUtil; import com.sixsq.slipstream.util.Logger; import org.restlet.Application; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.resource.Directory; import org.restlet.resource.ServerResource; import org.restlet.routing.Filter; import org.restlet.routing.Router; import org.restlet.routing.Template; import org.restlet.routing.TemplateRoute; import org.restlet.security.Authenticator; import org.restlet.service.MetadataService; import slipstream.async.Collector; import slipstream.async.GarbageCollector; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.ServiceLoader; public class RootApplication extends Application { @SuppressWarnings("serial") private class Authenticators extends ArrayList<Authenticator> { public Authenticator getFirst() { return this.get(0); } public Authenticator getLast() { return this.get(this.size() -1); } } private class AuthenticatorsTemplateRoute { private TemplateRoute templateRoute; private Authenticators authenticators; public AuthenticatorsTemplateRoute(TemplateRoute templateRoute, Authenticators authenticators){ this.templateRoute = templateRoute; this.authenticators = authenticators; } public Authenticators getAuthenticators() { return authenticators; } public TemplateRoute getTemplateRoute() { return templateRoute; } } public RootApplication() throws ValidationException { super(); try { createShutdownHook(); CljElasticsearchHelper.init(); createStartupMetadata(); loadOptionalConfiguration(); initializeStatusServiceToHandleErrors(); verifyMinimumDatabaseInfo(); initializeMetadataService(); // Load the configuration early Configuration.getInstance(); Collector.start(); GarbageCollector.start(); Metrics.addJvmMetrics(); if (Configuration.isMetricsGraphiteEnabled()) { Metrics.addGraphiteReporter(); } if (Configuration.isMetricsLoggerEnabled()) { Metrics.addSlf4jReporter(); } logServerStarted(); } catch (ConfigurationException e) { Util.throwConfigurationException(e); } } private void createShutdownHook() { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { String message = "Server shutdown"; Event.postEvent("system", Event.Severity.high, message, "system", Event.EventType.system); } })); } private void loadOptionalConfiguration() { // Note: Connectors have already been loaded by the Configuration class // Load modules Modules.load(); // Load cloud-ids CloudIds.load(); // Load users Users.load(); } private void initializeMetadataService() { MetadataService ms = getMetadataService(); ms.addCommonExtensions(); ms.setDefaultCharacterSet(CharacterSet.UTF_8); ms.addExtension("tgz", MediaType.APPLICATION_COMPRESS, true); ms.addExtension("multipart", MediaType.MULTIPART_ALL); } private void logServerStarted() { String message = "Server started"; Logger.debug(message); Logger.info(message); Logger.warning(message); Logger.severe(message); Event.postEvent("system", Event.Severity.high, message, "system", Event.EventType.system); } protected void loadConnectors() { ServiceLoader<Connector> connectorLoader = ServiceLoader.load(Connector.class); for (Connector c : connectorLoader) { getLogger().info("Connector name: " + c.getCloudServiceName()); } } private void createStartupMetadata() { try { Users.create(); } catch (ValidationException e) { getLogger().warning("Error creating default users... already existing?"); } catch (NotFoundException e) { getLogger().warning("Error creating default users... already existing?"); } catch (NoSuchAlgorithmException e) { getLogger().warning("Error creating default users... already existing?"); } catch (UnsupportedEncodingException e) { getLogger().warning("Error creating default users... already existing?"); } } private void initializeStatusServiceToHandleErrors() { CommonStatusService statusService = new CommonStatusService(); setStatusService(statusService); } private static void verifyMinimumDatabaseInfo() throws ConfigurationException, ValidationException { User user = Users.loadSuper(); if (user == null) { throw new ConfigurationException("super user is missing"); } // get the instance, which will load the configuration and perform // self validity check Configuration.getInstance(); } @Override public Restlet createInboundRoot() { enableTunnelService(); RootRouter router = new RootRouter(getContext()); try { attachModulesChooser(router); attachMetering(router); attachAction(router); attachModule(router); attachUser(router); attachDashboard(router); attachVms(router); attachRun(router); attachTeapot(router); attachRootRedirect(router); attachLogin(router); attachLogout(router); attachConfiguration(router); attachServiceCatalog(router); // needs to be after configuration attachReports(router); attachEvent(router); attachUsage(router); attachServiceInfo(router); attachAttribute(router); attachCloudUsage(router); attachNuvlaboxAdmin(router); attachAppStore(router); attachUIResource(router); } catch (ConfigurationException e) { Util.throwConfigurationException(e); } catch (ValidationException e) { Util.throwConfigurationException(e); } Directory directoryStaticContent = attachStaticContent(); router.attachDefault(directoryStaticContent); Directory directoryDownloads = attachDownloadsDirectory(); router.attach("/downloads", directoryDownloads); // Some browsers need to have their media types preferences trimmed. // Create a filter and put this in front of the application router. return new TrimmedMediaTypesFilter(getContext(), router); } @Override public void start() throws Exception { super.start(); DiscoverableConnectorServiceLoader.initializeAll(); } @Override public void stop() throws Exception { DiscoverableConnectorServiceLoader.shutdownAll(); super.stop(); } /** * During dev, set static content to local dir (e.g. * "file:///Users/meb/Documents/workspace/SlipStream/SlipStreamServer/src/main/webapp/static-content/" * ) */ private Directory attachStaticContent() { String staticContentLocation = System.getProperty("static.content.location", "war:///static-content"); Directory directory = new Directory(getContext(), staticContentLocation); directory.setModifiable(false); directory.setListingAllowed(true); return directory; } private Directory attachDownloadsDirectory() { String staticContentLocation = System.getProperty( "downloads.directory.location", "file:///opt/slipstream/downloads"); Directory directory = new Directory(getContext(), staticContentLocation); directory.setModifiable(false); return directory; } private void attachReports(RootRouter router) throws ConfigurationException, ValidationException { router.attach("/reports", new ReportRouter(getContext(), router.getApplication())); } private void attachTeapot(RootRouter router) throws ConfigurationException, ValidationException { router.attach("/teapot", new Filter() { protected int beforeHandle(Request request, Response response) { response.setStatus(new Status(418, "I'm a teapot!", "I'm a teapot!", "http://tools.ietf.org/html/rfc2324")); return Filter.STOP; } }); } private void attachConfiguration(RootRouter router) { String rootUri = ServiceConfigurationResource.CONFIGURATION_PATH.replaceAll("^/", ""); guardAndAttach(router, ServiceConfigurationResource.class, rootUri); } private void attachLogout(RootRouter router) { TemplateRoute route; route = router.attach("/logout", LogoutResource.class); route.getTemplate().setMatchingMode(Template.MODE_STARTS_WITH); } private void attachLogin(RootRouter router) { TemplateRoute route = router.attach(LoginResource.getResourceRoot(), LoginResource.class); route.getTemplate().setMatchingMode(Template.MODE_STARTS_WITH); router.attach(RegistrationResource.getResourceRoot(), RegistrationResource.class); router.attach(ResetPasswordResource.getResourceRoot(), ResetPasswordResource.class); } private void attachRun(RootRouter router) { guardAndAttach(router, new RunRouter(getContext()), "run"); } private void attachDashboard(RootRouter router) { guardAndAttach(router, new DashboardRouter(getContext()), "dashboard"); } private void attachServiceCatalog(RootRouter router) { guardAndAttach(router, new ServiceCatalogRouter(getContext()), "service_catalog"); } private void attachVms(RootRouter router) { guardAndAttach(router, new VmsRouter(getContext()), "vms"); } private Authenticators getAuthenticators(Application application) { Authenticators authenticators = new Authenticators(); Authenticator basicAuthenticator = new BasicAuthenticator(getContext()); basicAuthenticator.setEnroler(new SuperEnroler(application)); Authenticator cookieAuthenticator = new CookieAuthenticator(getContext()); cookieAuthenticator.setOptional(true); cookieAuthenticator.setNext(basicAuthenticator); cookieAuthenticator.setEnroler(new SuperEnroler(application)); authenticators.add(cookieAuthenticator); authenticators.add(basicAuthenticator); return authenticators; } private TemplateRoute attach(Router rootRouter, String rootUri, Authenticator authenticator) { TemplateRoute route = rootRouter.attach(convertToRouterRoot(rootUri), authenticator); route.getTemplate().setMatchingMode(Template.MODE_STARTS_WITH); return route; } private AuthenticatorsTemplateRoute guardAndAttach(Router rootRouter, Class<? extends ServerResource> router, String rootUri) { Authenticators authenticators = getAuthenticators(rootRouter.getApplication()); authenticators.getLast().setNext(router); TemplateRoute route = attach(rootRouter, rootUri, authenticators.getFirst()); return new AuthenticatorsTemplateRoute(route, authenticators); } private AuthenticatorsTemplateRoute guardAndAttach(Router rootRouter, Router router, String rootUri) { Authenticators authenticators = getAuthenticators(rootRouter.getApplication()); authenticators.getLast().setNext(router); TemplateRoute route = attach(rootRouter, rootUri, authenticators.getFirst()); return new AuthenticatorsTemplateRoute(route, authenticators); } private void attachRootRedirect(RootRouter rootRouter) { rootRouter.attach("", RootRedirectResource.class); rootRouter.attach("/", RootRedirectResource.class); } private void attachUser(RootRouter router) { guardAndAttach(router, new UserRouter(getContext()), User.RESOURCE_URL_PREFIX); } private void attachModule(RootRouter router) { guardAndAttach(router, new ModuleRouter(getContext()), Module.RESOURCE_URI_PREFIX); } private String convertToRouterRoot(String prefix) { return "/" + (prefix.endsWith("/") ? prefix.substring(0, prefix.length() - 1) : prefix); } private void attachAction(RootRouter router) { TemplateRoute route; route = router.attach("/action/", new ActionRouter()); route.getTemplate().setMatchingMode(Template.MODE_STARTS_WITH); } private void enableTunnelService() { getTunnelService().setExtensionsTunnel(true); getTunnelService().setEnabled(true); } public void addConfigurationToRequest(Request request) throws ConfigurationException, ValidationException { ConfigurationUtil.addConfigurationToRequest(request); } private void attachMetering(RootRouter router) throws ConfigurationException, ValidationException { guardAndAttach(router, new GraphiteRouter(getContext()), GraphiteRouter.ROOT_URI); } private void attachEvent(RootRouter router) throws ValidationException { guardAndAttach(router, new EventRouter(getContext()), "event"); } private void attachUsage(RootRouter router) throws ValidationException { guardAndAttach(router, new UsageRouter(getContext()), "usage"); } private void attachServiceInfo(RootRouter router) throws ValidationException { guardAndAttach(router, new ServiceInfoRouter(getContext()), "service-offer"); } private void attachAttribute(RootRouter router) throws ValidationException { guardAndAttach(router, new AttributeRouter(getContext()), "service-attribute"); } private void attachCloudUsage(RootRouter router) throws ValidationException { guardAndAttach(router, new CloudUsageRouter(getContext()), "cloud-usage"); } private void attachAppStore(RootRouter router) throws ValidationException { guardAndAttach(router, new ModuleListRouter(getContext(), AppStoreResource.class), "appstore"); } private void attachUIResource(RootRouter router) throws ValidationException { guardAndAttach(router, new UIResourceRouter(getContext()), "ui"); } private void attachModulesChooser(RootRouter router) throws ValidationException { guardAndAttach(router, new ModuleListRouter(getContext(), ModulesChooserResource.class), "chooser"); } private void attachNuvlaboxAdmin(RootRouter router) throws ValidationException { guardAndAttach(router, new NuvlaboxAdminRouter(getContext()), "nuvlabox-admin"); } public class RootRouter extends Router { public RootRouter(Context context) { super(context); } @Override public void doHandle(Restlet next, Request request, Response response) { try { addConfigurationToRequest(request); } catch (ConfigurationException e) { Util.throwConfigurationException(e); } catch (ValidationException e) { Util.throwClientValidationError(e.getMessage()); } super.doHandle(next, request, response); } } }