/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.rest; import java.text.MessageFormat; import java.util.HashSet; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.eclipse.skalli.core.rest.admin.ProjectBackupResource; import org.eclipse.skalli.core.rest.admin.StatisticsBackupResource; import org.eclipse.skalli.core.rest.admin.StatisticsResource; import org.eclipse.skalli.core.rest.admin.StatusResource; import org.eclipse.skalli.core.rest.monitor.Monitorable; import org.eclipse.skalli.core.rest.resources.InheritableExtensionResource; import org.eclipse.skalli.core.rest.resources.InheritableExtensionsResource; import org.eclipse.skalli.core.rest.resources.IssuesResource; import org.eclipse.skalli.core.rest.resources.ProjectResource; import org.eclipse.skalli.core.rest.resources.ProjectsResource; import org.eclipse.skalli.core.rest.resources.SubprojectsResource; import org.eclipse.skalli.core.rest.resources.TimelineResource; import org.eclipse.skalli.core.rest.resources.UserPermitsResource; import org.eclipse.skalli.core.rest.resources.UserResource; import org.eclipse.skalli.services.configuration.ConfigSection; import org.eclipse.skalli.services.extension.rest.ErrorRepresentation; import org.eclipse.skalli.services.extension.rest.RestExtension; import org.eclipse.skalli.services.rest.RequestContext; import org.restlet.Application; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.resource.ServerResource; import org.restlet.routing.Router; import org.restlet.service.StatusService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RestApplication extends Application { private static final Logger LOG = LoggerFactory.getLogger(RestApplication.class); private final static Set<ConfigSection<?>> configSections = new HashSet<ConfigSection<?>>(); private final static Set<Monitorable> serviceMonitors = new HashSet<Monitorable>(); private final static Set<RestExtension> extensions = new HashSet<RestExtension>(); @Override public synchronized Restlet createInboundRoot() { Router router = new Router(getContext()); attachConfigurationResources(router); router.attach("/admin/status", StatusResource.class); //$NON-NLS-1$ router.attach("/admin/statistics", StatisticsResource.class); //$NON-NLS-1$ router.attach("/admin/statistics/backup", StatisticsBackupResource.class); //$NON-NLS-1$ router.attach("/admin/statistics/{section}", StatisticsResource.class); //$NON-NLS-1$ router.attach("/admin/statistics/{section}/{filter}", StatisticsResource.class); //$NON-NLS-1$ router.attach("/admin/backup", ProjectBackupResource.class); //$NON-NLS-1$ attachServiceMonitors("/admin/services/", router); //$NON-NLS-1$ router.attach("/projects", ProjectsResource.class); //$NON-NLS-1$ router.attach("/projects/{id}", ProjectResource.class); //$NON-NLS-1$ router.attach("/projects/{id}/issues", IssuesResource.class); //$NON-NLS-1$ router.attach("/projects/{id}/subprojects", SubprojectsResource.class); //$NON-NLS-1$ router.attach("/projects/{id}/timeline", TimelineResource.class); //$NON-NLS-1$ router.attach("/projects/{id}/extensions", InheritableExtensionsResource.class); //$NON-NLS-1$ router.attach("/projects/{id}/extensions/{shortName}", InheritableExtensionResource.class); //$NON-NLS-1$ router.attach("/user/{userId}", UserResource.class); //$NON-NLS-1$ router.attach("/users/{userId}", UserResource.class); //$NON-NLS-1$ router.attach("/users/{userId}/permits", UserPermitsResource.class); //$NON-NLS-1$ router.attach("/users/{userId}/permits/{projectId}", UserPermitsResource.class); //$NON-NLS-1$ attachCustomResources(router); return router; } protected void bindConfigSection(ConfigSection<?> configSection) { configSections.add(configSection); } protected void unbindConfigSection(ConfigSection<?> configSection) { configSections.remove(configSection); } protected void bindMonitorable(Monitorable monitorable) { // TODO find out if resources can be attached dynamically to the router: // monitors come and go together with their service, but currently // everything is attached to the router in createInboundRoot() // serviceMonitors.add(monitorable); } protected void unbindMonitorable(Monitorable monitorable) { // TODO find out if resources can be detached dynamically from the router: // monitors come and go together with their service, but currently // everything is attached to the router in createInboundRoot() serviceMonitors.remove(monitorable); } protected void bindRestExtension(RestExtension restExtension) { // TODO find out if resources can be attached dynamically to the router: // monitors come and go together with their service, but currently // everything is attached to the router in createInboundRoot() // extensions.add(restExtension); } protected void unbindRestExtension(RestExtension restExtension) { // TODO find out if resources can be detached dynamically from the router: // monitors come and go together with their service, but currently // everything is attached to the router in createInboundRoot() extensions.remove(restExtension); } public RestApplication() { setStatusService(new StatusService() { @Override public Representation getRepresentation(Status status, Request request, Response response) { Throwable t = status.getThrowable(); if (t != null) { RequestContext context = new RequestContext(request); String errorId = MessageFormat.format("rest:{0}:00", context.getPath()); //$NON-NLS-1$ String message = "An unexpected exception happend. Please report this error " + "response to the server administrators."; LOG.error(MessageFormat.format(MessageFormat.format("{0} ({1})", message, errorId), t)); //$NON-NLS-1$ return new ErrorRepresentation(context, status, errorId, message); } return super.getRepresentation(status, request, response); } }); } private void attachConfigurationResources(Router router) { for (ConfigSection<?> configSection : configSections) { String[] resourcePaths = configSection.getResourcePaths(); if (resourcePaths == null || resourcePaths.length == 0) { LOG.warn(MessageFormat.format( "Configuration extension ''{0}'' does not register any resource paths", configSection.getStorageKey())); return; } for (String resourcePath: resourcePaths) { resourcePath = StringUtils.trim(resourcePath); if (StringUtils.isBlank(resourcePath) || "/".equals(resourcePath)) { //$NON-NLS-1$ LOG.warn(MessageFormat.format( "Configuration extension ''{0}'': resource path '/' is not allowed", configSection.getStorageKey())); continue; } if (!resourcePath.startsWith("/")) { //$NON-NLS-1$ resourcePath = "/" + resourcePath; //$NON-NLS-1$ } Class<? extends ServerResource> resource = configSection.getServerResource(resourcePath); if (resource != null) { router.attach("/config" + resourcePath, resource); //$NON-NLS-1$ LOG.info(MessageFormat.format( "Attached REST resource ''{0}'' to path ''{1}'' for configuration extension ''{2}''", resource.getName(), resourcePath, configSection.getStorageKey())); } else { LOG.warn(MessageFormat.format( "Configuration extension ''{0}'': No REST resource provided for path ''{1}''", configSection.getStorageKey(), resourcePath)); } } } } private void attachServiceMonitors(String basePath, Router router) { for (Monitorable serviceMonitor : serviceMonitors) { String servicePath = basePath + serviceMonitor.getServiceComponentName() + "/"; //$NON-NLS-1$ for (String resourceName : serviceMonitor.getResourceNames()) { String path = servicePath + "monitors/" + resourceName; //$NON-NLS-1$ Class<? extends ServerResource> monitorResource = serviceMonitor.getServerResource(resourceName); if (monitorResource != null) { router.attach(path, monitorResource); LOG.info(MessageFormat.format("Attached service monitor to path {0}", path)); } else { LOG.warn(MessageFormat.format("No monitor resource provided for path {0}", path)); } } } } private void attachCustomResources(Router router) { for (RestExtension ext : extensions) { String[] resourcePaths = ext.getResourcePaths(); if (resourcePaths == null || resourcePaths.length == 0) { LOG.warn(MessageFormat.format( "REST extension ''{0}'' does not register any resource paths", ext.getClass().getName())); continue; } for (String resourcePath: resourcePaths) { resourcePath = StringUtils.trim(resourcePath); if (StringUtils.isBlank(resourcePath) || "/".equals(resourcePath)) { //$NON-NLS-1$ LOG.warn(MessageFormat.format( "REST extension ''{0}'': resource path '/' is not allowed", ext.getClass().getName())); continue; } if (!resourcePath.startsWith("/")) { //$NON-NLS-1$ resourcePath = "/" + resourcePath; //$NON-NLS-1$ } Class<? extends ServerResource> resource = ext.getServerResource(resourcePath); if (resource != null) { router.attach(resourcePath, resource); LOG.info(MessageFormat.format( "Attached REST resource ''{0}'' to path ''{1}''", resource.getName(), resourcePath)); } else { LOG.warn(MessageFormat.format( "REST extension ''{0}'': No REST resource provided for path ''{1}''", ext.getClass().getName(), resourcePath)); } } } } }