/** * Copyright (c) 2014-2017 by the respective copyright holders. * 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 */ package org.eclipse.smarthome.io.rest.core.service; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.security.RolesAllowed; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionRegistry; import org.eclipse.smarthome.config.core.ConfigUtil; import org.eclipse.smarthome.config.core.ConfigurableService; import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.auth.Role; import org.eclipse.smarthome.io.rest.SatisfiableRESTResource; import org.eclipse.smarthome.io.rest.core.config.ConfigurationService; import org.eclipse.smarthome.io.rest.core.internal.RESTCoreActivator; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; /** * {@link ConfigurableServiceResource} provides access to configurable services. It lists the available services and * allows to get, update and delete the configuration for a service ID. See also {@link ConfigurableService}. * * @author Dennis Nobel - Initial contribution * @author Franck Dechavanne - Added DTOs to ApiResponses * */ @Path(ConfigurableServiceResource.PATH_SERVICES) @RolesAllowed({ Role.ADMIN }) @Api(value = ConfigurableServiceResource.PATH_SERVICES) public class ConfigurableServiceResource implements SatisfiableRESTResource { /** The URI path to this resource */ public static final String PATH_SERVICES = "services"; private static final String CONFIGURABLE_SERVICE_FILTER = "(" + ConfigurableService.SERVICE_PROPERTY_DESCRIPTION_URI + "=*)"; private final Logger logger = LoggerFactory.getLogger(ConfigurableServiceResource.class); private ConfigurationService configurationService; private ConfigDescriptionRegistry configDescRegistry; @GET @Produces({ MediaType.APPLICATION_JSON }) @ApiOperation(value = "Get all configurable services.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ConfigurableServiceDTO.class, responseContainer = "List") }) public List<ConfigurableServiceDTO> getAll() { List<ConfigurableServiceDTO> services = getConfigurableServices(); return services; } @GET @Path("/{serviceId}") @Produces({ MediaType.APPLICATION_JSON }) @ApiOperation(value = "Get configurable service for given service ID.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ConfigurableServiceDTO.class), @ApiResponse(code = 404, message = "Not found") }) public Response getById(@PathParam("serviceId") @ApiParam(value = "service ID", required = true) String serviceId) { ConfigurableServiceDTO configurableService = getServiceById(serviceId); if (configurableService != null) { return Response.ok(configurableService).build(); } else { return Response.status(404).build(); } } private ConfigurableServiceDTO getServiceById(String serviceId) { List<ConfigurableServiceDTO> configurableServices = getConfigurableServices(); for (ConfigurableServiceDTO configurableService : configurableServices) { if (configurableService.id.equals(serviceId)) { return configurableService; } } return null; } @GET @Path("/{serviceId}/config") @Produces({ MediaType.APPLICATION_JSON }) @ApiOperation(value = "Get service configuration for given service ID.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 500, message = "Configuration can not be read due to internal error") }) public Response getConfiguration( @PathParam("serviceId") @ApiParam(value = "service ID", required = true) String serviceId) { try { Configuration configuration = configurationService.get(serviceId); return configuration != null ? Response.ok(configuration.getProperties()).build() : Response.ok(Collections.emptyMap()).build(); } catch (IOException ex) { logger.error("Cannot get configuration for service {}: " + ex.getMessage(), serviceId, ex); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } @PUT @Path("/{serviceId}/config") @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) @ApiOperation(value = "Updates a service configuration for given service ID and returns the old configuration.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 204, message = "No old configuration"), @ApiResponse(code = 500, message = "Configuration can not be updated due to internal error") }) public Response updateConfiguration( @PathParam("serviceId") @ApiParam(value = "service ID", required = true) String serviceId, Map<String, Object> configuration) { try { Configuration oldConfiguration = configurationService.get(serviceId); configurationService.update(serviceId, new Configuration(normalizeConfiguration(configuration, serviceId))); return oldConfiguration != null ? Response.ok(oldConfiguration.getProperties()).build() : Response.noContent().build(); } catch (IOException ex) { logger.error("Cannot update configuration for service {}: " + ex.getMessage(), serviceId, ex); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } private Map<String, Object> normalizeConfiguration(Map<String, Object> properties, String serviceId) { if (properties == null || properties.isEmpty()) { return properties; } ConfigurableServiceDTO service = getServiceById(serviceId); if (service == null) { return properties; } URI uri; try { uri = new URI(service.configDescriptionURI); } catch (URISyntaxException e) { logger.warn("Not a valid URI: {}", service.configDescriptionURI); return properties; } ConfigDescription configDesc = configDescRegistry.getConfigDescription(uri); if (configDesc == null) { return properties; } return ConfigUtil.normalizeTypes(properties, Collections.singletonList(configDesc)); } @DELETE @Path("/{serviceId}/config") @Produces({ MediaType.APPLICATION_JSON }) @ApiOperation(value = "Deletes a service configuration for given service ID and returns the old configuration.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 204, message = "No old configuration"), @ApiResponse(code = 500, message = "Configuration can not be deleted due to internal error") }) public Response deleteConfiguration( @PathParam("serviceId") @ApiParam(value = "service ID", required = true) String serviceId) { try { Configuration oldConfiguration = configurationService.get(serviceId); configurationService.delete(serviceId); return oldConfiguration != null ? Response.ok(oldConfiguration).build() : Response.noContent().build(); } catch (IOException ex) { logger.error("Cannot delete configuration for service {}: " + ex.getMessage(), serviceId, ex); return Response.status(Status.INTERNAL_SERVER_ERROR).build(); } } private List<ConfigurableServiceDTO> getConfigurableServices() { List<ConfigurableServiceDTO> services = new ArrayList<>(); try { ServiceReference<?>[] serviceReferences = RESTCoreActivator.getBundleContext() .getServiceReferences((String) null, CONFIGURABLE_SERVICE_FILTER); if (serviceReferences != null) { for (ServiceReference<?> serviceReference : serviceReferences) { String id = getServiceId(serviceReference); String label = (String) serviceReference.getProperty(ConfigurableService.SERVICE_PROPERTY_LABEL); String category = (String) serviceReference .getProperty(ConfigurableService.SERVICE_PROPERTY_CATEGORY); String configDescriptionURI = (String) serviceReference .getProperty(ConfigurableService.SERVICE_PROPERTY_DESCRIPTION_URI); services.add(new ConfigurableServiceDTO(id, label, category, configDescriptionURI)); } } } catch (InvalidSyntaxException ex) { logger.error("Cannot get service references, because syntax is invalid: " + ex.getMessage(), ex); } return services; } private String getServiceId(ServiceReference<?> serviceReference) { Object pid = serviceReference.getProperty(Constants.SERVICE_PID); if (pid != null) { return (String) pid; } else { return (String) serviceReference.getProperty(ComponentConstants.COMPONENT_NAME); } } protected void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } protected void unsetConfigurationService(ConfigurationService configurationService) { this.configurationService = null; } protected void setConfigDescriptionRegistry(ConfigDescriptionRegistry configDescriptionRegistry) { this.configDescRegistry = configDescriptionRegistry; } protected void unsetConfigDescriptionRegistry(ConfigDescriptionRegistry configDescriptionRegistry) { this.configDescRegistry = null; } @Override public boolean isSatisfied() { return configurationService != null && configDescRegistry != null; } }