/** * 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.thing; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.security.RolesAllowed; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; 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.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.Configuration; import org.eclipse.smarthome.config.core.status.ConfigStatusInfo; import org.eclipse.smarthome.config.core.status.ConfigStatusService; import org.eclipse.smarthome.config.core.validation.ConfigValidationException; import org.eclipse.smarthome.core.auth.Role; import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.ItemFactory; import org.eclipse.smarthome.core.items.ItemNotFoundException; import org.eclipse.smarthome.core.items.ItemRegistry; import org.eclipse.smarthome.core.items.ManagedItemProvider; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.ManagedThingProvider; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingRegistry; import org.eclipse.smarthome.core.thing.ThingStatusInfo; import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.binding.firmware.FirmwareUID; import org.eclipse.smarthome.core.thing.dto.ChannelDTO; import org.eclipse.smarthome.core.thing.dto.ChannelDTOMapper; import org.eclipse.smarthome.core.thing.dto.ThingDTO; import org.eclipse.smarthome.core.thing.dto.ThingDTOMapper; import org.eclipse.smarthome.core.thing.firmware.FirmwareStatusInfo; import org.eclipse.smarthome.core.thing.firmware.FirmwareUpdateService; import org.eclipse.smarthome.core.thing.firmware.dto.FirmwareStatusDTO; import org.eclipse.smarthome.core.thing.i18n.ThingStatusInfoI18nLocalizationService; import org.eclipse.smarthome.core.thing.link.ItemChannelLink; import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry; import org.eclipse.smarthome.core.thing.link.ManagedItemChannelLinkProvider; import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ThingType; import org.eclipse.smarthome.core.thing.type.ThingTypeRegistry; import org.eclipse.smarthome.core.thing.util.ThingHelper; 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 things and is registered with the * Jersey servlet. * * @author Dennis Nobel - Initial contribution * @author Kai Kreuzer - refactored for using the OSGi JAX-RS connector and * refactored create and update methods * @author Thomas Höfer - added validation of configuration and localization of thing status * @author Yordan Zhelev - Added Swagger annotations * @author Jörg Plewe - refactoring, error handling * @author Chris Jackson - added channel configuration updates, * return empty set for config/status if no status available, * add editable flag to thing responses * @author Franck Dechavanne - Added DTOs to ApiResponses */ @Path(ThingResource.PATH_THINGS) @Api(value = ThingResource.PATH_THINGS) public class ThingResource implements SatisfiableRESTResource { private final Logger logger = LoggerFactory.getLogger(ThingResource.class); /** The URI path to this resource */ public static final String PATH_THINGS = "things"; private ItemChannelLinkRegistry itemChannelLinkRegistry; private ItemFactory itemFactory; private ItemRegistry itemRegistry; private ManagedItemChannelLinkProvider managedItemChannelLinkProvider; private ManagedItemProvider managedItemProvider; private ManagedThingProvider managedThingProvider; private ThingRegistry thingRegistry; private ConfigStatusService configStatusService; private ConfigDescriptionRegistry configDescRegistry; private ThingTypeRegistry thingTypeRegistry; private ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService; private FirmwareUpdateService firmwareUpdateService; @Context private UriInfo uriInfo; /** * create a new Thing * * @param thingBean * @return Response holding the newly created Thing or error information */ @POST @RolesAllowed({ Role.ADMIN }) @Consumes(MediaType.APPLICATION_JSON) @ApiOperation(value = "Creates a new thing and adds it to the registry.") @ApiResponses(value = { @ApiResponse(code = 201, message = "Created", response = String.class), @ApiResponse(code = 400, message = "A uid must be provided, if no binding can create a thing of this type."), @ApiResponse(code = 409, message = "A thing with the same uid already exists.") }) public Response create(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, @ApiParam(value = "thing data", required = true) ThingDTO thingBean) { final Locale locale = LocaleUtil.getLocale(language); ThingUID thingUID = thingBean.UID == null ? null : new ThingUID(thingBean.UID); ThingTypeUID thingTypeUID = new ThingTypeUID(thingBean.thingTypeUID); if (thingUID != null) { // check if a thing with this UID already exists Thing thing = thingRegistry.get(thingUID); if (thing != null) { // report a conflict return getThingResponse(Status.CONFLICT, thing, locale, "Thing " + thingUID.toString() + " already exists!"); } } ThingUID bridgeUID = null; if (thingBean.bridgeUID != null) { bridgeUID = new ThingUID(thingBean.bridgeUID); } // turn the ThingDTO's configuration into a Configuration Configuration configuration = new Configuration( normalizeConfiguration(thingBean.configuration, thingTypeUID, thingUID)); Thing thing = thingRegistry.createThingOfType(thingTypeUID, thingUID, bridgeUID, thingBean.label, configuration); if (thing != null) { if (thingBean.properties != null) { for (Entry<String, String> entry : thingBean.properties.entrySet()) { thing.setProperty(entry.getKey(), entry.getValue()); } } if (thingBean.channels != null) { List<Channel> channels = new ArrayList<>(); for (ChannelDTO channelDTO : thingBean.channels) { channels.add(ChannelDTOMapper.map(channelDTO)); } ThingHelper.addChannelsToThing(thing, channels); } if (thingBean.location != null) { thing.setLocation(thingBean.location); } } else if (thingUID != null) { // if there wasn't any ThingFactory capable of creating the thing, // we create the Thing exactly the way we received it, i.e. we // cannot take its thing type into account for automatically // populating channels and properties. thing = ThingDTOMapper.map(thingBean); } else { return getThingResponse(Status.BAD_REQUEST, thing, locale, "A UID must be provided, since no binding can create the thing!"); } thingRegistry.add(thing); return getThingResponse(Status.CREATED, thing, locale, null); } @GET @RolesAllowed({ Role.USER, Role.ADMIN }) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get all available things.", response = EnrichedThingDTO.class, responseContainer = "Set") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedThingDTO.class, responseContainer = "Set") }) public Response getAll(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language) { final Locale locale = LocaleUtil.getLocale(language); Collection<Thing> things = thingRegistry.getAll(); Set<EnrichedThingDTO> thingBeans = convertToListBean(things, locale); return Response.ok(thingBeans).build(); } @GET @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}") @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets thing by UID.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ThingDTO.class), @ApiResponse(code = 404, message = "Thing not found.") }) public Response getByUID(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID) { final Locale locale = LocaleUtil.getLocale(language); Thing thing = thingRegistry.get((new ThingUID(thingUID))); // return Thing data if it does exist if (thing != null) { return getThingResponse(Status.OK, thing, locale, null); } else { return getThingNotFoundResponse(thingUID); } } /** * link a Channel of a Thing to an Item * * @param thingUID * @param channelId * @param itemName * @return Response with status/error information */ @POST @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/channels/{channelId}/link") @Consumes(MediaType.TEXT_PLAIN) @ApiOperation(value = "Links item to a channel. Creates item if such does not exist yet.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 403, message = "Channel is not linkable for the thing, as it is not of kind 'state'!"), @ApiResponse(code = 404, message = "Thing not found or channel not found.") }) public Response link(@PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, @PathParam("channelId") @ApiParam(value = "channelId") String channelId, @ApiParam(value = "item name") String itemName) { Thing thing = thingRegistry.get(new ThingUID(thingUID)); if (thing == null) { logger.warn("Received HTTP POST request at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } Channel channel = findChannel(channelId, thing); if (channel == null) { logger.info("Received HTTP POST request at '{}' for the unknown channel '{}' of the thing '{}'", uriInfo.getPath(), channel, thingUID); String message = "Channel " + channelId + " for Thing " + thingUID + " does not exist!"; return JSONResponse.createResponse(Status.NOT_FOUND, null, message); } if (channel.getKind() != ChannelKind.STATE) { logger.info("Tried to link channel '{}' of thing '{}', which is not of kind 'state'", channel, thingUID); String message = "Channel " + channelId + " for Thing " + thingUID + " is not linkable, as it is not of kind 'state'!"; return JSONResponse.createResponse(Status.FORBIDDEN, null, message); } try { itemRegistry.getItem(itemName); } catch (ItemNotFoundException ex) { GenericItem item = itemFactory.createItem(channel.getAcceptedItemType(), itemName); managedItemProvider.add(item); } ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId); unlinkChannelIfAlreadyLinked(channelUID); managedItemChannelLinkProvider.add(new ItemChannelLink(itemName, channelUID)); return Response.ok().build(); } /** * Delete a Thing, if possible. Thing deletion might be impossible if the * Thing is not managed, will return CONFLICT. Thing deletion might happen * delayed, will return ACCEPTED. * * @param thingUID * @param force * @return Response with status/error information */ @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}") @ApiOperation(value = "Removes a thing from the registry. Set \'force\' to __true__ if you want the thing te be removed immediately.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK, was deleted."), @ApiResponse(code = 202, message = "ACCEPTED for asynchronous deletion."), @ApiResponse(code = 404, message = "Thing not found."), @ApiResponse(code = 409, message = "Thing could not be deleted because it's not editable.") }) public Response remove(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, @DefaultValue("false") @QueryParam("force") @ApiParam(value = "force") boolean force) { final Locale locale = LocaleUtil.getLocale(language); ThingUID thingUIDObject = new ThingUID(thingUID); // check whether thing exists and throw 404 if not Thing thing = thingRegistry.get(thingUIDObject); if (thing == null) { logger.info("Received HTTP DELETE request for update at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } // ask whether the Thing exists as a managed thing, so it can get // updated, 409 otherwise Thing managed = managedThingProvider.get(thingUIDObject); if (null == managed) { logger.info("Received HTTP DELETE request for update at '{}' for an unmanaged thing '{}'.", uriInfo.getPath(), thingUID); return getThingResponse(Status.CONFLICT, thing, locale, "Cannot delete Thing " + thingUID + " as it is not editable."); } // only move on if Thing is known to be managed, so it can get updated if (force) { if (null == thingRegistry.forceRemove(thingUIDObject)) { return getThingResponse(Status.INTERNAL_SERVER_ERROR, thing, locale, "Cannot delete Thing " + thingUID + " for unknown reasons."); } } else { if (null != thingRegistry.remove(thingUIDObject)) { return getThingResponse(Status.ACCEPTED, thing, locale, null); } } return Response.ok().build(); } /** * Unlink a Channel of a Thing from an Item. * * @param thingUID * @param channelId * @param itemName * @return Response with status/error information */ @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/channels/{channelId}/link") @ApiOperation(value = "Unlinks item from a channel.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Thing not found.") }) public Response unlink(@PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, @PathParam("channelId") @ApiParam(value = "channelId") String channelId, @ApiParam(value = "channelId") String itemName) { Thing thing = thingRegistry.get(new ThingUID(thingUID)); if (thing == null) { logger.warn("Received HTTP POST request at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } ChannelUID channelUID = new ChannelUID(new ThingUID(thingUID), channelId); if (itemChannelLinkRegistry.isLinked(itemName, channelUID)) { managedItemChannelLinkProvider.remove(new ItemChannelLink(itemName, channelUID).getID()); } return Response.ok().build(); } /** * Update Thing. * * @param thingUID * @param thingBean * @return Response with the updated Thing or error information * @throws IOException */ @PUT @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}") @Consumes(MediaType.APPLICATION_JSON) @ApiOperation(value = "Updates a thing.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ThingDTO.class), @ApiResponse(code = 404, message = "Thing not found."), @ApiResponse(code = 409, message = "Thing could not be updated as it is not editable.") }) public Response update(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, @ApiParam(value = "thing", required = true) ThingDTO thingBean) throws IOException { final Locale locale = LocaleUtil.getLocale(language); ThingUID thingUIDObject = new ThingUID(thingUID); // ask whether the Thing exists at all, 404 otherwise Thing thing = thingRegistry.get(thingUIDObject); if (null == thing) { logger.info("Received HTTP PUT request for update at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } // ask whether the Thing exists as a managed thing, so it can get // updated, 409 otherwise Thing managed = managedThingProvider.get(thingUIDObject); if (null == managed) { logger.info("Received HTTP PUT request for update at '{}' for an unmanaged thing '{}'.", uriInfo.getPath(), thingUID); return getThingResponse(Status.CONFLICT, thing, locale, "Cannot update Thing " + thingUID + " as it is not editable."); } // check configuration thingBean.configuration = normalizeConfiguration(thingBean.configuration, thing.getThingTypeUID(), thing.getUID()); thing = ThingHelper.merge(thing, thingBean); // update, returns null in case Thing cannot be found Thing oldthing = managedThingProvider.update(thing); if (null == oldthing) { return getThingNotFoundResponse(thingUID); } // everything went well return getThingResponse(Status.OK, thing, locale, null); } /** * Updates Thing configuration. * * @param thingUID * @param configurationParameters * @return Response with the updated Thing or error information * @throws IOException */ @PUT @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/config") @Consumes(MediaType.APPLICATION_JSON) @ApiOperation(value = "Updates thing's configuration.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Thing.class), @ApiResponse(code = 400, message = "Configuration of the thing is not valid."), @ApiResponse(code = 404, message = "Thing not found"), @ApiResponse(code = 409, message = "Thing could not be updated as it is not editable.") }) public Response updateConfiguration(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) String language, @PathParam("thingUID") @ApiParam(value = "thing") String thingUID, @ApiParam(value = "configuration parameters") Map<String, Object> configurationParameters) throws IOException { final Locale locale = LocaleUtil.getLocale(language); ThingUID thingUIDObject = new ThingUID(thingUID); // ask whether the Thing exists at all, 404 otherwise Thing thing = thingRegistry.get(thingUIDObject); if (null == thing) { logger.info("Received HTTP PUT request for update configuration at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } // ask whether the Thing exists as a managed thing, so it can get // updated, 409 otherwise Thing managed = managedThingProvider.get(thingUIDObject); if (null == managed) { logger.info("Received HTTP PUT request for update configuration at '{}' for an unmanaged thing '{}'.", uriInfo.getPath(), thingUID); return getThingResponse(Status.CONFLICT, thing, locale, "Cannot update Thing " + thingUID + " as it is not editable."); } // only move on if Thing is known to be managed, so it can get updated try { // note that we create a Configuration instance here in order to // have normalized types thingRegistry.updateConfiguration(thingUIDObject, new Configuration( normalizeConfiguration(configurationParameters, thing.getThingTypeUID(), thing.getUID())) .getProperties()); } catch (ConfigValidationException ex) { logger.debug("Config description validation exception occurred for thingUID {} - Messages: {}", thingUID, ex.getValidationMessages()); return Response.status(Status.BAD_REQUEST).entity(ex.getValidationMessages(locale)).build(); } catch (Exception ex) { logger.error("Exception during HTTP PUT request for update config at '{}' for thing '{}': {}", uriInfo.getPath(), thingUID, ex.getMessage()); return JSONResponse.createResponse(Status.INTERNAL_SERVER_ERROR, null, ex.getMessage()); } return getThingResponse(Status.OK, thing, locale, null); } @GET @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{thingUID}/status") @ApiOperation(value = "Gets thing's status.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 404, message = "Thing not found.") }) public Response getStatus(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) String language, @PathParam("thingUID") @ApiParam(value = "thing") String thingUID) throws IOException { ThingUID thingUIDObject = new ThingUID(thingUID); // Check if the Thing exists, 404 if not Thing thing = thingRegistry.get(thingUIDObject); if (null == thing) { logger.info("Received HTTP GET request for thing config status at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } ThingStatusInfo thingStatusInfo = thingStatusInfoI18nLocalizationService.getLocalizedThingStatusInfo(thing, LocaleUtil.getLocale(language)); return Response.ok().entity(thingStatusInfo).build(); } @GET @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{thingUID}/config/status") @ApiOperation(value = "Gets thing's config status.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), @ApiResponse(code = 404, message = "Thing not found.") }) public Response getConfigStatus(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) String language, @PathParam("thingUID") @ApiParam(value = "thing") String thingUID) throws IOException { ThingUID thingUIDObject = new ThingUID(thingUID); // Check if the Thing exists, 404 if not Thing thing = thingRegistry.get(thingUIDObject); if (null == thing) { logger.info("Received HTTP GET request for thing config status at '{}' for the unknown thing '{}'.", uriInfo.getPath(), thingUID); return getThingNotFoundResponse(thingUID); } ConfigStatusInfo info = configStatusService.getConfigStatus(thingUID, LocaleUtil.getLocale(language)); if (info != null) { return Response.ok().entity(info.getConfigStatusMessages()).build(); } return Response.ok().entity(Collections.EMPTY_SET).build(); } @PUT @Path("/{thingUID}/firmware/{firmwareVersion}") @Consumes(MediaType.APPLICATION_JSON) @ApiOperation(value = "Update thing firmware.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 400, message = "Firmware update preconditions not satisfied."), @ApiResponse(code = 404, message = "Thing not found.") }) public Response updateFirmware( @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, @PathParam("thingUID") @ApiParam(value = "thing") String thingUID, @PathParam("firmwareVersion") @ApiParam(value = "version") String firmwareVersion) throws IOException { Thing thing = thingRegistry.get(new ThingUID(thingUID)); if (thing == null) { return getThingNotFoundResponse(thingUID); } FirmwareUID firmwareUID = new FirmwareUID(thing.getThingTypeUID(), firmwareVersion); try { firmwareUpdateService.updateFirmware(thing.getUID(), firmwareUID, LocaleUtil.getLocale(language)); } catch (IllegalArgumentException | NullPointerException | IllegalStateException ex) { return JSONResponse.createResponse(Status.BAD_REQUEST, null, "Firmware update preconditions not satisfied."); } return Response.status(Status.OK).build(); } @GET @Path("/{thingUID}/firmware/status") @ApiOperation(value = "Gets thing's firmware status.") @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Firmware status info not found.") }) public Response getFirmwareStatus(@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) String language, @PathParam("thingUID") @ApiParam(value = "thing") String thingUID) throws IOException { ThingUID thingUIDObject = new ThingUID(thingUID); FirmwareStatusInfo info = firmwareUpdateService.getFirmwareStatusInfo(thingUIDObject); if (info == null) { return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Firmware status info not found."); } return Response.ok().entity(buildFirmwareStatusDTO(info)).build(); } private FirmwareStatusDTO getThingFirmwareStatus(ThingUID thingUID) { FirmwareStatusInfo info = firmwareUpdateService.getFirmwareStatusInfo(thingUID); if (info != null) { return buildFirmwareStatusDTO(info); } return null; } private FirmwareStatusDTO buildFirmwareStatusDTO(FirmwareStatusInfo info) { String updatableFirmwareVersion = info.getUpdatableFirmwareUID() == null ? null : info.getUpdatableFirmwareUID().getFirmwareVersion(); return new FirmwareStatusDTO(info.getFirmwareStatus().name(), updatableFirmwareVersion); } /** * helper: Response to be sent to client if a Thing cannot be found * * @param thingUID * @return Response configured for NOT_FOUND */ private static Response getThingNotFoundResponse(String thingUID) { String message = "Thing " + thingUID + " does not exist!"; return JSONResponse.createResponse(Status.NOT_FOUND, null, message); } /** * helper: create a Response holding a Thing and/or error information. * * @param status * @param thing * @param errormessage an optional error message (may be null), ignored if the status family is successful * @return Response */ private Response getThingResponse(Status status, Thing thing, Locale locale, String errormessage) { ThingStatusInfo thingStatusInfo = thingStatusInfoI18nLocalizationService.getLocalizedThingStatusInfo(thing, locale); boolean managed = managedThingProvider.get(thing.getUID()) != null; EnrichedThingDTO enrichedThingDTO = thing != null ? EnrichedThingDTOMapper.map(thing, thingStatusInfo, this.getThingFirmwareStatus(thing.getUID()), getLinkedItemsMap(thing), managed) : null; return JSONResponse.createResponse(status, enrichedThingDTO, errormessage); } protected void setItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = itemChannelLinkRegistry; } protected void setItemFactory(ItemFactory itemFactory) { this.itemFactory = itemFactory; } protected void setItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = itemRegistry; } protected void setManagedItemChannelLinkProvider(ManagedItemChannelLinkProvider managedItemChannelLinkProvider) { this.managedItemChannelLinkProvider = managedItemChannelLinkProvider; } protected void setManagedItemProvider(ManagedItemProvider managedItemProvider) { this.managedItemProvider = managedItemProvider; } protected void setManagedThingProvider(ManagedThingProvider managedThingProvider) { this.managedThingProvider = managedThingProvider; } protected void setThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = thingRegistry; } protected void unsetItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { this.itemChannelLinkRegistry = null; } protected void unsetItemFactory(ItemFactory itemFactory) { this.itemFactory = null; } protected void unsetItemRegistry(ItemRegistry itemRegistry) { this.itemRegistry = null; } protected void unsetManagedItemChannelLinkProvider(ManagedItemChannelLinkProvider managedItemChannelLinkProvider) { this.managedItemChannelLinkProvider = null; } protected void unsetManagedItemProvider(ManagedItemProvider managedItemProvider) { this.managedItemProvider = null; } protected void unsetManagedThingProvider(ManagedThingProvider managedThingProvider) { this.managedThingProvider = null; } protected void unsetThingRegistry(ThingRegistry thingRegistry) { this.thingRegistry = null; } protected void setConfigStatusService(ConfigStatusService configStatusService) { this.configStatusService = configStatusService; } protected void unsetConfigStatusService(ConfigStatusService configStatusService) { this.configStatusService = null; } protected void setThingStatusInfoI18nLocalizationService( ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService) { this.thingStatusInfoI18nLocalizationService = thingStatusInfoI18nLocalizationService; } protected void unsetThingStatusInfoI18nLocalizationService( ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService) { this.thingStatusInfoI18nLocalizationService = null; } private Set<EnrichedThingDTO> convertToListBean(Collection<Thing> things, Locale locale) { Set<EnrichedThingDTO> thingBeans = new LinkedHashSet<>(); for (Thing thing : things) { boolean managed = managedThingProvider.get(thing.getUID()) != null; ThingStatusInfo thingStatusInfo = thingStatusInfoI18nLocalizationService.getLocalizedThingStatusInfo(thing, locale); EnrichedThingDTO thingBean = EnrichedThingDTOMapper.map(thing, thingStatusInfo, this.getThingFirmwareStatus(thing.getUID()), getLinkedItemsMap(thing), managed); thingBeans.add(thingBean); } return thingBeans; } private Map<String, Set<String>> getLinkedItemsMap(Thing thing) { Map<String, Set<String>> linkedItemsMap = new HashMap<>(); for (Channel channel : thing.getChannels()) { Set<String> linkedItems = itemChannelLinkRegistry.getLinkedItemNames(channel.getUID()); linkedItemsMap.put(channel.getUID().getId(), linkedItems); } return linkedItemsMap; } private Channel findChannel(String channelId, Thing thing) { for (Channel channel : thing.getChannels()) { if (channel.getUID().getId().equals(channelId)) { return channel; } } return null; } private void unlinkChannelIfAlreadyLinked(ChannelUID channelUID) { Collection<ItemChannelLink> links = managedItemChannelLinkProvider.getAll(); for (ItemChannelLink link : links) { if (link.getUID().equals(channelUID)) { logger.debug( "Channel '{}' is already linked to item '{}' and will be unlinked before it will be linked to the new item.", channelUID, link.getItemName()); managedItemChannelLinkProvider.remove(link.getID()); } } } public static void updateConfiguration(Thing thing, Configuration configuration) { for (String parameterName : configuration.keySet()) { thing.getConfiguration().put(parameterName, configuration.get(parameterName)); } } protected void setConfigDescriptionRegistry(ConfigDescriptionRegistry configDescriptionRegistry) { this.configDescRegistry = configDescriptionRegistry; } protected void unsetConfigDescriptionRegistry(ConfigDescriptionRegistry configDescriptionRegistry) { this.configDescRegistry = null; } protected void setThingTypeRegistry(ThingTypeRegistry thingTypeRegistry) { this.thingTypeRegistry = thingTypeRegistry; } protected void unsetThingTypeRegistry(ThingTypeRegistry thingTypeRegistry) { this.thingTypeRegistry = null; } protected void setFirmwareUpdateService(FirmwareUpdateService firmwareUpdateService) { this.firmwareUpdateService = firmwareUpdateService; } protected void unsetFirmwareUpdateService(FirmwareUpdateService firmwareUpdateService) { this.firmwareUpdateService = null; } private Map<String, Object> normalizeConfiguration(Map<String, Object> properties, ThingTypeUID thingTypeUID, ThingUID thingUID) { if (properties == null || properties.isEmpty()) { return properties; } ThingType thingType = thingTypeRegistry.getThingType(thingTypeUID); if (thingType == null) { return properties; } List<ConfigDescription> configDescriptions = new ArrayList<>(2); ConfigDescription typeConfigDesc = configDescRegistry.getConfigDescription(thingType.getConfigDescriptionURI()); if (typeConfigDesc != null) { configDescriptions.add(typeConfigDesc); } ConfigDescription thingConfigDesc = configDescRegistry.getConfigDescription(getConfigDescriptionURI(thingUID)); if (thingConfigDesc != null) { configDescriptions.add(thingConfigDesc); } if (configDescriptions.isEmpty()) { return properties; } return ConfigUtil.normalizeTypes(properties, configDescriptions); } private URI getConfigDescriptionURI(ThingUID thingUID) { String uriString = "thing:" + thingUID; try { return new URI(uriString); } catch (URISyntaxException e) { throw new BadRequestException("Invalid URI syntax: " + uriString); } } @Override public boolean isSatisfied() { return itemChannelLinkRegistry != null && itemFactory != null && itemRegistry != null && managedItemChannelLinkProvider != null && managedItemProvider != null && managedThingProvider != null && thingRegistry != null && configStatusService != null && configDescRegistry != null && thingTypeRegistry != null && firmwareUpdateService != null && thingStatusInfoI18nLocalizationService != null; } }