package com.devicehive.websockets.handlers; /* * #%L * DeviceHive Frontend Logic * %% * Copyright (C) 2016 DataArt * %% * 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. * #L% */ import com.devicehive.auth.HivePrincipal; import com.devicehive.configuration.Constants; import com.devicehive.configuration.Messages; import com.devicehive.exceptions.HiveException; import com.devicehive.model.DeviceCommand; import com.devicehive.model.websockets.InsertCommand; import com.devicehive.model.wrappers.DeviceCommandWrapper; import com.devicehive.resource.util.JsonTypes; import com.devicehive.service.DeviceCommandService; import com.devicehive.service.DeviceService; import com.devicehive.util.ServerResponsesFactory; import com.devicehive.vo.DeviceVO; import com.devicehive.vo.UserVO; import com.devicehive.websockets.converters.WebSocketResponse; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketSession; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.BiConsumer; import java.util.stream.Collectors; import static com.devicehive.configuration.Constants.*; import static com.devicehive.json.strategies.JsonPolicyDef.Policy.COMMAND_TO_CLIENT; import static com.devicehive.messages.handler.WebSocketClientHandler.sendMessage; import static javax.servlet.http.HttpServletResponse.*; @Component public class CommandHandlers { private static final Logger logger = LoggerFactory.getLogger(CommandHandlers.class); public static final String SUBSCSRIPTION_SET_NAME = "commandSubscriptions"; @Autowired private Gson gson; @Autowired private DeviceService deviceService; @Autowired private DeviceCommandService commandService; @PreAuthorize("isAuthenticated() and hasPermission(null, 'GET_DEVICE_COMMAND')") public WebSocketResponse processCommandSubscribe(JsonObject request, WebSocketSession session) throws InterruptedException { HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); final Date timestamp = gson.fromJson(request.get(TIMESTAMP), Date.class); final String deviceId = Optional.ofNullable(request.get(Constants.DEVICE_GUID)) .map(JsonElement::getAsString) .orElse(null); final Set<String> names = gson.fromJson(request.getAsJsonArray(NAMES), JsonTypes.STRING_SET_TYPE); Set<String> devices = gson.fromJson(request.getAsJsonArray(DEVICE_GUIDS), JsonTypes.STRING_SET_TYPE); logger.debug("command/subscribe requested for devices: {}, {}. Timestamp: {}. Names {} Session: {}", devices, deviceId, timestamp, names, session); devices = prepareActualList(devices, deviceId); List<DeviceVO> actualDevices; if (devices != null) { actualDevices = deviceService.findByGuidWithPermissionsCheck(devices, principal); if (actualDevices.size() != devices.size()) { throw new HiveException(String.format(Messages.DEVICES_NOT_FOUND, devices), SC_FORBIDDEN); } } else { actualDevices = deviceService.list(null, null, null, null, null, null, null, true, null, null, principal).join(); devices = actualDevices.stream().map(DeviceVO::getGuid).collect(Collectors.toSet()); } BiConsumer<DeviceCommand, String> callback = (command, subscriptionId) -> { JsonObject json = ServerResponsesFactory.createCommandInsertMessage(command, subscriptionId); sendMessage(json, session); }; Pair<String, CompletableFuture<List<DeviceCommand>>> pair = commandService .sendSubscribeRequest(devices, names, timestamp, callback); pair.getRight().thenAccept(collection -> collection.forEach(cmd -> sendMessage(ServerResponsesFactory.createCommandInsertMessage(cmd, pair.getLeft()), session))); logger.debug("command/subscribe done for devices: {}, {}. Timestamp: {}. Names {} Session: {}", devices, deviceId, timestamp, names, session.getId()); ((CopyOnWriteArraySet) session .getAttributes() .get(SUBSCSRIPTION_SET_NAME)) .add(pair.getLeft()); WebSocketResponse response = new WebSocketResponse(); response.addValue(SUBSCRIPTION_ID, pair.getLeft(), null); return response; } @PreAuthorize("isAuthenticated() and hasPermission(null, 'GET_DEVICE_COMMAND')") public WebSocketResponse processCommandUnsubscribe(JsonObject request, WebSocketSession session) { HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); final Optional<String> subscriptionId = Optional.ofNullable(request.get(SUBSCRIPTION_ID)) .map(JsonElement::getAsString); Set<String> guids = gson.fromJson(request.getAsJsonArray(DEVICE_GUIDS), JsonTypes.STRING_SET_TYPE); logger.debug("command/unsubscribe action. Session {} ", session.getId()); if (!subscriptionId.isPresent() && guids == null) { List<DeviceVO> actualDevices = deviceService.list(null, null, null, null, null, null, null, true, null, null, principal).join(); guids = actualDevices.stream().map(DeviceVO::getGuid).collect(Collectors.toSet()); commandService.sendUnsubscribeRequest(null, guids); } else if (subscriptionId.isPresent()) { commandService.sendUnsubscribeRequest(subscriptionId.get(), guids); } else { commandService.sendUnsubscribeRequest(null, guids); } ((CopyOnWriteArraySet) session .getAttributes() .get(SUBSCSRIPTION_SET_NAME)) .remove(subscriptionId); return new WebSocketResponse(); } @PreAuthorize("isAuthenticated() and hasPermission(null, 'CREATE_DEVICE_COMMAND')") public WebSocketResponse processCommandInsert(JsonObject request, WebSocketSession session) { HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); final String deviceGuid = Optional.ofNullable(request.get(Constants.DEVICE_GUID)) .map(JsonElement::getAsString) .orElse(null); final DeviceCommandWrapper deviceCommand = gson .fromJson(request.getAsJsonObject(COMMAND), DeviceCommandWrapper.class); logger.debug("command/insert action for {}, Session ", deviceGuid, session.getId()); Set<DeviceVO> devices = new HashSet<>(); if (deviceGuid == null) { for (String guid : principal.getDeviceGuids()) { devices.add(deviceService.findByGuidWithPermissionsCheck(guid, principal)); } } else { devices.add(deviceService.findByGuidWithPermissionsCheck(deviceGuid, principal)); } if (devices.isEmpty()) { throw new HiveException(String.format(Messages.DEVICE_NOT_FOUND, deviceGuid), SC_NOT_FOUND); } if (deviceCommand == null) { throw new HiveException(Messages.EMPTY_COMMAND, SC_BAD_REQUEST); } final UserVO user = principal.getUser(); WebSocketResponse response = new WebSocketResponse(); for (DeviceVO device : devices) { commandService.insert(deviceCommand, device, user) .thenApply(cmd -> { commandUpdateSubscribeAction(cmd.getId(), device.getGuid(), session); response.addValue(COMMAND, new InsertCommand(cmd.getId(), cmd.getTimestamp(), cmd.getUserId()), COMMAND_TO_CLIENT); return response; }) .exceptionally(ex -> { logger.warn("Unable to insert notification.", ex); throw new HiveException(Messages.INTERNAL_SERVER_ERROR, SC_INTERNAL_SERVER_ERROR); }).join(); } return response; } @PreAuthorize("isAuthenticated() and hasPermission(null, 'UPDATE_DEVICE_COMMAND')") public WebSocketResponse processCommandUpdate(JsonObject request, WebSocketSession session) { HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String guid = request.get(DEVICE_GUID).getAsString(); final Long id = Long.valueOf(request.get(COMMAND_ID).getAsString()); // TODO: nullable long? final DeviceCommandWrapper commandUpdate = gson .fromJson(request.getAsJsonObject(COMMAND), DeviceCommandWrapper.class); logger.debug("command/update requested for session: {}. Device guid: {}. Command id: {}", session, guid, id); /*todo - is check really redundant now? if (guid == null) { logger.debug("command/update canceled for session: {}. Guid is not provided", session); throw new HiveException(Messages.DEVICE_GUID_REQUIRED, SC_BAD_REQUEST); }*/ if (id == null) { logger.debug("command/update canceled for session: {}. Command id is not provided", session); throw new HiveException(Messages.COMMAND_ID_REQUIRED, SC_BAD_REQUEST); } Set<DeviceVO> devices = new HashSet<>(); if (guid == null) { for (String deviceGuid : principal.getDeviceGuids()) { devices.add(deviceService.findByGuidWithPermissionsCheck(deviceGuid, principal)); } } else { devices.add(deviceService.findByGuidWithPermissionsCheck(guid, principal)); } if (devices.isEmpty()) { throw new HiveException(String.format(Messages.DEVICE_NOT_FOUND, id), SC_NOT_FOUND); } Optional<DeviceCommand> savedCommand = Optional.empty(); for (DeviceVO device : devices) { savedCommand = commandService.findOne(id, device.getGuid()).join(); if (savedCommand.isPresent()) { commandService.update(savedCommand.get(), commandUpdate); } } if (!savedCommand.isPresent()) { throw new HiveException(String.format(Messages.COMMAND_NOT_FOUND, id), SC_NOT_FOUND); } logger.debug("command/update proceed successfully for session: {}. Device guid: {}. Command id: {}", session, guid, id); return new WebSocketResponse(); } private Set<String> prepareActualList(Set<String> deviceIdSet, final String deviceId) { if (deviceId == null && deviceIdSet == null) { return null; } if (deviceIdSet != null && deviceId == null) { deviceIdSet.remove(null); return deviceIdSet; } if (deviceIdSet == null) { return new HashSet<String>() { { add(deviceId); } private static final long serialVersionUID = -8657632518613033661L; }; } throw new HiveException(Messages.INVALID_REQUEST_PARAMETERS, SC_BAD_REQUEST); } private void commandUpdateSubscribeAction(Long commandId, String guid, WebSocketSession session) { if (commandId == null) { throw new HiveException(String.format(Messages.COLUMN_CANNOT_BE_NULL, "commandId"), SC_BAD_REQUEST); } BiConsumer<DeviceCommand, String> callback = (command, subscriptionId) -> { JsonObject json = ServerResponsesFactory.createCommandUpdateMessage(command); sendMessage(json, session); }; commandService.sendSubscribeToUpdateRequest(commandId, guid, callback); // TODO: make sure this is the correct place to create update message } }