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.DeviceNotification; import com.devicehive.model.websockets.InsertNotification; import com.devicehive.model.wrappers.DeviceNotificationWrapper; import com.devicehive.resource.util.JsonTypes; import com.devicehive.service.DeviceNotificationService; import com.devicehive.service.DeviceService; import com.devicehive.util.ServerResponsesFactory; import com.devicehive.vo.DeviceVO; 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.StringUtils; 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 java.util.stream.StreamSupport; import static com.devicehive.configuration.Constants.*; import static com.devicehive.json.strategies.JsonPolicyDef.Policy.NOTIFICATION_TO_DEVICE; import static com.devicehive.messages.handler.WebSocketClientHandler.sendMessage; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; @Component public class NotificationHandlers { private static final Logger logger = LoggerFactory.getLogger(NotificationHandlers.class); public static final String SUBSCSRIPTION_SET_NAME = "notificationSubscriptions"; @Autowired private DeviceService deviceService; @Autowired private DeviceNotificationService notificationService; @Autowired private Gson gson; @PreAuthorize("isAuthenticated() and hasPermission(null, 'GET_DEVICE_NOTIFICATION')") public WebSocketResponse processNotificationSubscribe(JsonObject request, WebSocketSession session) throws InterruptedException { HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Date timestamp = gson.fromJson(request.get(Constants.TIMESTAMP), Date.class); Set<String> devices = gson.fromJson(request.get(Constants.DEVICE_GUIDS), JsonTypes.STRING_SET_TYPE); Set<String> names = gson.fromJson(request.get(Constants.NAMES), JsonTypes.STRING_SET_TYPE); String deviceId = Optional.ofNullable(request.get(Constants.DEVICE_GUID)) .map(JsonElement::getAsString) .orElse(null); logger.debug("notification/subscribe requested for devices: {}, {}. Timestamp: {}. Names {} Session: {}", devices, deviceId, timestamp, names, session.getId()); 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<DeviceNotification, String> callback = (notification, subscriptionId) -> { JsonObject json = ServerResponsesFactory.createNotificationInsertMessage(notification, subscriptionId); sendMessage(json, session); }; Pair<String, CompletableFuture<List<DeviceNotification>>> pair = notificationService .subscribe(devices, names, timestamp, callback); pair.getRight().thenAccept(collection -> collection.forEach(notification -> { JsonObject json = ServerResponsesFactory.createNotificationInsertMessage(notification, pair.getLeft()); sendMessage(json, session); })); logger.debug("notification/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; } /** * Implementation of the <a href="http://www.devicehive.com/restful#WsReference/Client/notificationunsubscribe"> * WebSocket API: Client: notification/unsubscribe</a> Unsubscribes from device notifications. * * @param session Current session * @return Json object with the following structure <code> { "action": {string}, "status": {string}, "requestId": * {object} } </code> */ @PreAuthorize("isAuthenticated() and hasPermission(null, 'GET_DEVICE_NOTIFICATION')") public WebSocketResponse processNotificationUnsubscribe(JsonObject request, WebSocketSession session) { HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Optional<String> subId = Optional.ofNullable(request.get(SUBSCRIPTION_ID)) .map(s -> { try { return s.getAsString(); } catch (UnsupportedOperationException e) { logger.error("Subscription Id is null"); return StringUtils.EMPTY; } }); Set<String> deviceGuids = gson.fromJson(request.get(DEVICE_GUIDS), JsonTypes.STRING_SET_TYPE); logger.debug("notification/unsubscribe action. Session {} ", session.getId()); if (!subId.isPresent() && deviceGuids == null) { List<DeviceVO> actualDevices = deviceService.list(null, null, null, null, null, null, null, true, null, null, principal).join(); deviceGuids = actualDevices.stream().map(DeviceVO::getGuid).collect(Collectors.toSet()); notificationService.unsubscribe(null, deviceGuids); } else if (subId.isPresent()) { notificationService.unsubscribe(subId.get(), deviceGuids); } else { notificationService.unsubscribe(null, deviceGuids); } logger.debug("notification/unsubscribe completed for session {}", session.getId()); ((CopyOnWriteArraySet) session .getAttributes() .get(SUBSCSRIPTION_SET_NAME)) .remove(subId); return new WebSocketResponse(); } @PreAuthorize("isAuthenticated() and hasPermission(null, 'CREATE_DEVICE_NOTIFICATION')") public WebSocketResponse processNotificationInsert(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); DeviceNotificationWrapper notificationSubmit = gson.fromJson(request.get(Constants.NOTIFICATION), DeviceNotificationWrapper.class); logger.debug("notification/insert requested. Session {}. Guid {}", session, deviceGuid); if (notificationSubmit == null || notificationSubmit.getNotification() == null) { logger.debug( "notification/insert proceed with error. Bad notification: notification is required."); throw new HiveException(Messages.NOTIFICATION_REQUIRED, SC_BAD_REQUEST); } 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() || StreamSupport.stream(devices.spliterator(), true).allMatch(o -> o == null)) { logger.debug("notification/insert canceled for session: {}. Guid is not provided", session); throw new HiveException(Messages.DEVICE_GUID_REQUIRED, SC_FORBIDDEN); } WebSocketResponse response = new WebSocketResponse(); for (DeviceVO device : devices) { if (device.getNetwork() == null) { logger.debug("notification/insert. No network specified for device with guid = {}", deviceGuid); throw new HiveException(String.format(Messages.DEVICE_IS_NOT_CONNECTED_TO_NETWORK, deviceGuid), SC_FORBIDDEN); } DeviceNotification message = notificationService.convertWrapperToNotification(notificationSubmit, device); notificationService.insert(message, device) .thenApply(notification -> { logger.debug("notification/insert proceed successfully. Session {}. Guid {}", session, deviceGuid); response.addValue(NOTIFICATION, new InsertNotification(message.getId(), message.getTimestamp()), NOTIFICATION_TO_DEVICE); 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; } 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 = 955343867580964077L; }; } throw new HiveException(Messages.INVALID_REQUEST_PARAMETERS, SC_BAD_REQUEST); } }