/** * 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.extensions; import java.net.URI; import java.net.URISyntaxException; import java.text.Collator; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArraySet; import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.apache.commons.lang.StringUtils; import org.eclipse.smarthome.core.auth.Role; import org.eclipse.smarthome.core.common.ThreadPoolManager; import org.eclipse.smarthome.core.events.Event; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.extension.Extension; import org.eclipse.smarthome.core.extension.ExtensionEventFactory; import org.eclipse.smarthome.core.extension.ExtensionService; import org.eclipse.smarthome.core.extension.ExtensionType; import org.eclipse.smarthome.io.rest.JSONResponse; import org.eclipse.smarthome.io.rest.LocaleUtil; import org.eclipse.smarthome.io.rest.SatisfiableRESTResource; 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; /** * This class acts as a REST resource for extensions and provides methods to install and uninstall them. * * @author Kai Kreuzer - Initial contribution and API * @author Franck Dechavanne - Added DTOs to ApiResponses */ @Path(ExtensionResource.PATH_EXTENSIONS) @RolesAllowed({ Role.ADMIN }) @Api(value = ExtensionResource.PATH_EXTENSIONS) public class ExtensionResource implements SatisfiableRESTResource { private static final String THREAD_POOL_NAME = "extensionService"; public static final String PATH_EXTENSIONS = "extensions"; private final Logger logger = LoggerFactory.getLogger(ExtensionResource.class); private Set<ExtensionService> extensionServices = new CopyOnWriteArraySet<>(); private EventPublisher eventPublisher; protected void addExtensionService(ExtensionService featureService) { this.extensionServices.add(featureService); } protected void removeExtensionService(ExtensionService featureService) { this.extensionServices.remove(featureService); } protected void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } protected void unsetEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = null; } @Context UriInfo uriInfo; @GET @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get all extensions.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class) }) public List<Extension> getExtensions( @HeaderParam("Accept-Language") @ApiParam(value = "language") String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = LocaleUtil.getLocale(language); return getAllExtensions(locale); } @GET @Path("/types") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get all extension types.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class) }) public Set<ExtensionType> getTypes(@HeaderParam("Accept-Language") @ApiParam(value = "language") String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = LocaleUtil.getLocale(language); return getAllExtensionTypes(locale); } @GET @Path("/{extensionId: [a-zA-Z_0-9-]*}") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get extension with given ID.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 404, message = "Not found") }) public Response getById(@HeaderParam("Accept-Language") @ApiParam(value = "language") String language, @PathParam("extensionId") @ApiParam(value = "extension ID", required = true) String extensionId) { logger.debug("Received HTTP GET request at '{}'.", uriInfo.getPath()); Locale locale = LocaleUtil.getLocale(language); ExtensionService extensionService = getExtensionService(extensionId); Object responseObject = extensionService.getExtension(extensionId, locale); if (responseObject != null) { return Response.ok(responseObject).build(); } else { return Response.status(404).build(); } } @POST @Path("/{extensionId: [a-zA-Z_0-9-:]*}/install") @ApiOperation(value = "Installs the extension with the given ID.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) public Response installExtension( final @PathParam("extensionId") @ApiParam(value = "extension ID", required = true) String extensionId) { ThreadPoolManager.getPool(THREAD_POOL_NAME).submit(new Runnable() { @Override public void run() { try { ExtensionService extensionService = getExtensionService(extensionId); extensionService.install(extensionId); } catch (Exception e) { logger.error("Exception while installing extension: {}", e.getMessage()); postFailureEvent(extensionId, e.getMessage()); } } }); return Response.ok().build(); } @POST @Path("/url/{url}/install") @ApiOperation(value = "Installs the extension from the given URL.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 400, message = "The given URL is malformed or not valid.") }) public Response installExtensionByURL( final @PathParam("url") @ApiParam(value = "extension install URL", required = true) String url) { try { URI extensionURI = new URI(url); String extensionId = getExtensionId(extensionURI); installExtension(extensionId); } catch (URISyntaxException | IllegalArgumentException e) { logger.error("Exception while parsing the extension URL '{}': {}", url, e.getMessage()); return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "The given URL is malformed or not valid."); } return Response.ok().build(); } @POST @Path("/{extensionId: [a-zA-Z_0-9-:]*}/uninstall") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) public Response uninstallExtension( final @PathParam("extensionId") @ApiParam(value = "extension ID", required = true) String extensionId) { ThreadPoolManager.getPool(THREAD_POOL_NAME).submit(new Runnable() { @Override public void run() { try { ExtensionService extensionService = getExtensionService(extensionId); extensionService.uninstall(extensionId); } catch (Exception e) { logger.error("Exception while uninstalling extension: {}", e.getMessage()); postFailureEvent(extensionId, e.getMessage()); } } }); return Response.ok().build(); } private void postFailureEvent(String extensionId, String msg) { if (eventPublisher != null) { Event event = ExtensionEventFactory.createExtensionFailureEvent(extensionId, msg); eventPublisher.post(event); } } @Override public boolean isSatisfied() { return !extensionServices.isEmpty(); } private List<Extension> getAllExtensions(Locale locale) { List<Extension> ret = new ArrayList<>(); for (ExtensionService extensionService : extensionServices) { ret.addAll(extensionService.getExtensions(locale)); } return ret; } private Set<ExtensionType> getAllExtensionTypes(Locale locale) { final Collator coll = Collator.getInstance(locale); coll.setStrength(Collator.PRIMARY); Set<ExtensionType> ret = new TreeSet<>(new Comparator<ExtensionType>() { @Override public int compare(ExtensionType o1, ExtensionType o2) { return coll.compare(o1.getLabel(), o2.getLabel()); } }); for (ExtensionService extensionService : extensionServices) { ret.addAll(extensionService.getTypes(locale)); } return ret; } private ExtensionService getExtensionService(final String extensionId) { for (ExtensionService extensionService : extensionServices) { for (Extension extension : extensionService.getExtensions(Locale.getDefault())) { if (extensionId.equals(extension.getId())) { return extensionService; } } } throw new IllegalArgumentException("No extension service registered for " + extensionId); } private String getExtensionId(URI extensionURI) { for (ExtensionService extensionService : extensionServices) { String extensionId = extensionService.getExtensionId(extensionURI); if (StringUtils.isNotBlank(extensionId)) { return extensionId; } } throw new IllegalArgumentException("No extension service registered for URI " + extensionURI); } }