package com.devicehive.service;
/*
* #%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.dao.DeviceDao;
import com.devicehive.model.DeviceNotification;
import com.devicehive.model.SpecialNotifications;
import com.devicehive.model.eventbus.events.NotificationEvent;
import com.devicehive.model.rpc.*;
import com.devicehive.model.wrappers.DeviceNotificationWrapper;
import com.devicehive.service.helpers.ResponseConsumer;
import com.devicehive.service.time.TimestampService;
import com.devicehive.shim.api.Request;
import com.devicehive.shim.api.Response;
import com.devicehive.shim.api.client.RpcClient;
import com.devicehive.util.ServerResponsesFactory;
import com.devicehive.vo.DeviceVO;
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.stereotype.Service;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Service
public class DeviceNotificationService {
private static final Logger logger = LoggerFactory.getLogger(DeviceNotificationService.class);
private DeviceEquipmentService deviceEquipmentService;
private TimestampService timestampService;
private DeviceDao deviceDao;
private RpcClient rpcClient;
@Autowired
public DeviceNotificationService(DeviceEquipmentService deviceEquipmentService,
TimestampService timestampService,
DeviceDao deviceDao,
RpcClient rpcClient) {
this.deviceEquipmentService = deviceEquipmentService;
this.timestampService = timestampService;
this.deviceDao = deviceDao;
this.rpcClient = rpcClient;
}
public CompletableFuture<Optional<DeviceNotification>> findOne(Long id, String guid) {
NotificationSearchRequest searchRequest = new NotificationSearchRequest();
searchRequest.setId(id);
searchRequest.setGuid(guid);
CompletableFuture<Response> future = new CompletableFuture<>();
rpcClient.call(Request.newBuilder()
.withBody(searchRequest)
.withPartitionKey(searchRequest.getGuid())
.build(), new ResponseConsumer(future));
return future.thenApply(r -> ((NotificationSearchResponse) r.getBody()).getNotifications().stream().findFirst());
}
@SuppressWarnings("unchecked")
public CompletableFuture<List<DeviceNotification>> find(Set<String> guids, Set<String> names,
Date timestampSt, Date timestampEnd) {
List<CompletableFuture<Response>> futures = guids.stream()
.map(guid -> {
NotificationSearchRequest searchRequest = new NotificationSearchRequest();
searchRequest.setGuid(guid);
searchRequest.setNames(names);
searchRequest.setTimestampStart(timestampSt);
searchRequest.setTimestampEnd(timestampEnd);
return searchRequest;
})
.map(searchRequest -> {
CompletableFuture<Response> future = new CompletableFuture<>();
rpcClient.call(Request.newBuilder()
.withBody(searchRequest)
.withPartitionKey(searchRequest.getGuid())
.build(), new ResponseConsumer(future));
return future;
})
.collect(Collectors.toList());
// List<CompletableFuture<Response>> => CompletableFuture<List<DeviceNotification>>
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join) // List<CompletableFuture<Response>> => CompletableFuture<List<Response>>
.map(r -> r.getBody().cast(NotificationSearchResponse.class).getNotifications()) // CompletableFuture<List<Response>> => CompletableFuture<List<List<DeviceNotification>>>
.flatMap(Collection::stream) // CompletableFuture<List<List<DeviceNotification>>> => CompletableFuture<List<DeviceNotification>>
.collect(Collectors.toList()));
}
public CompletableFuture<DeviceNotification> insert(final DeviceNotification notification,
final DeviceVO device) {
List<CompletableFuture<Response>> futures = processDeviceNotification(notification, device).stream()
.map(n -> {
CompletableFuture<Response> future = new CompletableFuture<>();
rpcClient.call(Request.newBuilder()
.withBody(new NotificationInsertRequest(n))
.withPartitionKey(device.getGuid())
.build(), new ResponseConsumer(future));
return future;
})
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
.thenApply(x -> futures.stream()
.map(CompletableFuture::join)
.map(r -> r.getBody().cast(NotificationInsertResponse.class).getDeviceNotification())
.filter(n -> !SpecialNotifications.DEVICE_UPDATE.equals(n.getNotification())) // we are not going to return DEVICE_UPDATE notification
.collect(Collectors.toList()).get(0)); // after filter we should get only one notification
}
public Pair<String, CompletableFuture<List<DeviceNotification>>> subscribe(
final Set<String> devices,
final Set<String> names,
final Date timestamp,
final BiConsumer<DeviceNotification, String> callback) {
final String subscriptionId = UUID.randomUUID().toString();
Set<NotificationSubscribeRequest> subscribeRequests = devices.stream()
.map(device -> new NotificationSubscribeRequest(subscriptionId, device, names, timestamp))
.collect(Collectors.toSet());
Collection<CompletableFuture<Collection<DeviceNotification>>> futures = new ArrayList<>();
for (NotificationSubscribeRequest sr : subscribeRequests) {
CompletableFuture<Collection<DeviceNotification>> future = new CompletableFuture<>();
Consumer<Response> responseConsumer = response -> {
String resAction = response.getBody().getAction();
if (resAction.equals(Action.NOTIFICATION_SUBSCRIBE_RESPONSE.name())) {
NotificationSubscribeResponse r = response.getBody().cast(NotificationSubscribeResponse.class);
future.complete(r.getNotifications());
} else if (resAction.equals(Action.NOTIFICATION_EVENT.name())) {
NotificationEvent event = response.getBody().cast(NotificationEvent.class);
callback.accept(event.getNotification(), subscriptionId);
} else {
logger.warn("Unknown action received from backend {}", resAction);
}
};
futures.add(future);
Request request = Request.newBuilder()
.withBody(sr)
.withPartitionKey(sr.getDevice())
.withSingleReply(false)
.build();
rpcClient.call(request, responseConsumer);
}
CompletableFuture<List<DeviceNotification>> future = CompletableFuture
.allOf(futures.toArray(new CompletableFuture[futures.size()]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.flatMap(Collection::stream)
.collect(Collectors.toList()));
return Pair.of(subscriptionId, future);
}
public void unsubscribe(String subId, Set<String> deviceGuids) {
NotificationUnsubscribeRequest unsubscribeRequest = new NotificationUnsubscribeRequest(subId, deviceGuids);
Request request = Request.newBuilder()
.withBody(unsubscribeRequest)
.build();
rpcClient.push(request);
}
public DeviceNotification convertWrapperToNotification(DeviceNotificationWrapper notificationSubmit, DeviceVO device) {
DeviceNotification notification = new DeviceNotification();
notification.setId(Math.abs(new Random().nextInt()));
notification.setDeviceGuid(device.getGuid());
if (notificationSubmit.getTimestamp() == null) {
notification.setTimestamp(timestampService.getDate());
} else {
notification.setTimestamp(notificationSubmit.getTimestamp());
}
notification.setNotification(notificationSubmit.getNotification());
notification.setParameters(notificationSubmit.getParameters());
return notification;
}
private List<DeviceNotification> processDeviceNotification(DeviceNotification notificationMessage, DeviceVO device) {
List<DeviceNotification> notificationsToCreate = new ArrayList<>();
if (notificationMessage.getNotification() != null && notificationMessage.getNotification().equals(SpecialNotifications.EQUIPMENT)) {
deviceEquipmentService.refreshDeviceEquipment(notificationMessage, device);
}
notificationsToCreate.add(notificationMessage);
return notificationsToCreate;
}
}