package com.devicehive.resource.impl;
/*
* #%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.Messages;
import com.devicehive.json.strategies.JsonPolicyDef;
import com.devicehive.model.DeviceNotification;
import com.devicehive.model.ErrorResponse;
import com.devicehive.model.wrappers.DeviceNotificationWrapper;
import com.devicehive.resource.DeviceNotificationResource;
import com.devicehive.resource.converters.TimestampQueryParamParser;
import com.devicehive.resource.util.CommandResponseFilterAndSort;
import com.devicehive.resource.util.ResponseFactory;
import com.devicehive.service.DeviceNotificationService;
import com.devicehive.service.DeviceService;
import com.devicehive.service.time.TimestampService;
import com.devicehive.vo.DeviceVO;
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.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.CompletionCallback;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Response;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import static javax.ws.rs.core.Response.Status.*;
/**
* {@inheritDoc}
*/
@Service
public class DeviceNotificationResourceImpl implements DeviceNotificationResource {
private static final Logger logger = LoggerFactory.getLogger(DeviceNotificationResourceImpl.class);
@Autowired
private DeviceNotificationService notificationService;
@Autowired
private DeviceService deviceService;
@Autowired
private TimestampService timestampService;
/**
* {@inheritDoc}
*/
@Override
public void query(String guid, String startTs, String endTs, String notification, String sortField,
String sortOrderSt, Integer take, Integer skip, @Suspended final AsyncResponse asyncResponse) {
logger.debug("Device notification query requested for device {}", guid);
final Date timestampSt = TimestampQueryParamParser.parse(startTs);
final Date timestampEnd = TimestampQueryParamParser.parse(endTs);
DeviceVO byGuidWithPermissionsCheck = deviceService.getDeviceWithNetworkAndDeviceClass(guid);
if (byGuidWithPermissionsCheck == null) {
ErrorResponse errorCode = new ErrorResponse(NOT_FOUND.getStatusCode(), String.format(Messages.DEVICE_NOT_FOUND, guid));
Response response = ResponseFactory.response(NOT_FOUND, errorCode);
asyncResponse.resume(response);
} else {
Set<String> notificationNames = StringUtils.isNoneEmpty(notification)
? Collections.singleton(notification)
: Collections.emptySet();
notificationService.find(Collections.singleton(guid), notificationNames, timestampSt, timestampEnd)
.thenApply(notifications -> {
final Comparator<DeviceNotification> comparator = CommandResponseFilterAndSort.buildDeviceNotificationComparator(sortField);
final Boolean reverse = sortOrderSt == null ? null : "desc".equalsIgnoreCase(sortOrderSt);
final List<DeviceNotification> sortedDeviceNotifications = CommandResponseFilterAndSort.orderAndLimit(notifications, comparator, reverse, skip, take);
return ResponseFactory.response(OK, sortedDeviceNotifications, JsonPolicyDef.Policy.NOTIFICATION_TO_CLIENT);
})
.thenAccept(asyncResponse::resume);
}
}
/**
* {@inheritDoc}
*/
@Override
public void get(String guid, Long notificationId, @Suspended final AsyncResponse asyncResponse) {
logger.debug("Device notification requested. Guid {}, notification id {}", guid, notificationId);
DeviceVO device = deviceService.getDeviceWithNetworkAndDeviceClass(guid);
if (device == null) {
ErrorResponse errorCode = new ErrorResponse(NOT_FOUND.getStatusCode(), String.format(Messages.DEVICE_NOT_FOUND, guid));
Response response = ResponseFactory.response(NOT_FOUND, errorCode);
asyncResponse.resume(response);
} else {
notificationService.findOne(notificationId, guid)
.thenApply(notification -> notification
.map(n -> {
logger.debug("Device notification proceed successfully");
return ResponseFactory.response(Response.Status.OK, n, JsonPolicyDef.Policy.NOTIFICATION_TO_CLIENT);
}).orElseGet(() -> {
logger.warn("Device notification get failed. NOT FOUND: No notification with id = {} found for device with guid = {}", notificationId, guid);
ErrorResponse errorCode = new ErrorResponse(NOT_FOUND.getStatusCode(), String.format(Messages.NOTIFICATION_NOT_FOUND, notificationId));
return ResponseFactory.response(NOT_FOUND, errorCode);
}))
.exceptionally(e -> {
//TODO: change error message here
logger.warn("Device notification get failed. NOT FOUND: No notification with id = {} found for device with guid = {}", notificationId, guid);
ErrorResponse errorCode = new ErrorResponse(NOT_FOUND.getStatusCode(), String.format(Messages.NOTIFICATION_NOT_FOUND, notificationId));
return ResponseFactory.response(NOT_FOUND, errorCode);
})
.thenAccept(asyncResponse::resume);
}
}
/**
* {@inheritDoc}
*/
@Override
public void poll(final String deviceGuid, final String namesString, final String timestamp, final long timeout, final AsyncResponse asyncResponse) throws Exception {
poll(timeout, deviceGuid, namesString, timestamp, asyncResponse);
}
@Override
public void pollMany(final long timeout, String deviceGuidsString, final String namesString, final String timestamp, final AsyncResponse asyncResponse) throws Exception {
poll(timeout, deviceGuidsString, namesString, timestamp, asyncResponse);
}
private void poll(final long timeout,
final String deviceGuidsString,
final String namesString,
final String timestamp,
final AsyncResponse asyncResponse) throws InterruptedException {
final HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
final Date ts = TimestampQueryParamParser.parse(timestamp == null ? timestampService.getDateAsString() : timestamp);
final Response response = ResponseFactory.response(
Response.Status.OK,
Collections.emptyList(),
JsonPolicyDef.Policy.NOTIFICATION_TO_CLIENT);
asyncResponse.setTimeoutHandler(asyncRes -> asyncRes.resume(response));
Set<String> availableDevices;
if (deviceGuidsString == null) {
availableDevices = deviceService.findByGuidWithPermissionsCheck(Collections.emptyList(), principal)
.stream()
.map(DeviceVO::getGuid)
.collect(Collectors.toSet());
} else {
availableDevices = Optional.ofNullable(StringUtils.split(deviceGuidsString, ','))
.map(Arrays::asList)
.map(list -> deviceService.findByGuidWithPermissionsCheck(list, principal))
.map(list -> list.stream().map(DeviceVO::getGuid).collect(Collectors.toSet()))
.orElse(Collections.emptySet());
}
Set<String> notifications = Optional.ofNullable(StringUtils.split(namesString, ','))
.map(Arrays::asList)
.map(list -> list.stream().collect(Collectors.toSet()))
.orElse(Collections.emptySet());
BiConsumer<DeviceNotification, String> callback = (notification, subscriptionId) -> {
if (!asyncResponse.isDone()) {
asyncResponse.resume(ResponseFactory.response(
Response.Status.OK,
Collections.singleton(notification),
JsonPolicyDef.Policy.NOTIFICATION_TO_CLIENT));
}
};
if (!availableDevices.isEmpty()) {
Pair<String, CompletableFuture<List<DeviceNotification>>> pair = notificationService
.subscribe(availableDevices, notifications, ts, callback);
pair.getRight().thenAccept(collection -> {
if (!collection.isEmpty() && !asyncResponse.isDone()) {
asyncResponse.resume(ResponseFactory.response(
Response.Status.OK,
collection,
JsonPolicyDef.Policy.NOTIFICATION_TO_CLIENT));
}
if (timeout == 0) {
asyncResponse.setTimeout(1, TimeUnit.MILLISECONDS); // setting timeout to 0 would cause
// the thread to suspend indefinitely, see AsyncResponse docs
} else {
asyncResponse.setTimeout(timeout, TimeUnit.SECONDS);
}
});
asyncResponse.register(new CompletionCallback() {
@Override
public void onComplete(Throwable throwable) {
notificationService.unsubscribe(pair.getLeft(), null);
}
});
} else {
if (!asyncResponse.isDone()) {
asyncResponse.resume(response);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void insert(String guid, DeviceNotificationWrapper notificationSubmit, @Suspended final AsyncResponse asyncResponse) {
logger.debug("DeviceNotification insert requested: {}", notificationSubmit);
if (notificationSubmit.getNotification() == null) {
logger.warn("DeviceNotification insert proceed with error. BAD REQUEST: notification is required.");
ErrorResponse errorResponseEntity = new ErrorResponse(BAD_REQUEST.getStatusCode(),
Messages.INVALID_REQUEST_PARAMETERS);
Response response = ResponseFactory.response(BAD_REQUEST, errorResponseEntity);
asyncResponse.resume(response);
} else {
DeviceVO device = deviceService.getDeviceWithNetworkAndDeviceClass(guid);
if (device == null) {
logger.warn("DeviceNotification insert proceed with error. NOT FOUND: device {} not found.", guid);
Response response = ResponseFactory.response(NOT_FOUND, new ErrorResponse(NOT_FOUND.getStatusCode(),
String.format(Messages.DEVICE_NOT_FOUND, guid)));
asyncResponse.resume(response);
} else {
if (device.getNetwork() == null) {
logger.warn("DeviceNotification insert proceed with error. FORBIDDEN: Device {} is not connected to network.", guid);
Response response = ResponseFactory.response(FORBIDDEN, new ErrorResponse(FORBIDDEN.getStatusCode(),
String.format(Messages.DEVICE_IS_NOT_CONNECTED_TO_NETWORK, guid)));
asyncResponse.resume(response);
} else {
DeviceNotification toInsert = notificationService.convertWrapperToNotification(notificationSubmit, device);
notificationService.insert(toInsert, device)
.thenAccept(notification -> {
logger.debug("Device notification insert proceed successfully. deviceId = {} notification = {}",
guid, notification.getNotification());
asyncResponse.resume(ResponseFactory.response(
Response.Status.CREATED,
notification,
JsonPolicyDef.Policy.NOTIFICATION_TO_CLIENT));
})
.exceptionally(e -> {
// FIX ERROR
logger.warn("Device notification insert failed for device with guid = {}.", guid);
ErrorResponse errorCode = new ErrorResponse(NOT_FOUND.getStatusCode(), String.format(Messages.NOTIFICATION_NOT_FOUND, -1L));
Response jaxResponse = ResponseFactory.response(NOT_FOUND, errorCode);
asyncResponse.resume(jaxResponse);
return null;
});
}
}
}
}
}