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.model.DeviceCommand; import com.devicehive.model.eventbus.events.CommandEvent; import com.devicehive.model.eventbus.events.CommandUpdateEvent; import com.devicehive.model.rpc.*; import com.devicehive.model.wrappers.DeviceCommandWrapper; 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.HiveValidator; import com.devicehive.vo.DeviceVO; import com.devicehive.vo.UserVO; 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 DeviceCommandService { private static final Logger logger = LoggerFactory.getLogger(DeviceCommandService.class); private TimestampService timestampService; private HiveValidator hiveValidator; private RpcClient rpcClient; @Autowired public DeviceCommandService(TimestampService timestampService, HiveValidator hiveValidator, RpcClient rpcClient) { this.timestampService = timestampService; this.hiveValidator = hiveValidator; this.rpcClient = rpcClient; } public CompletableFuture<Optional<DeviceCommand>> findOne(Long id, String guid) { CommandSearchRequest searchRequest = new CommandSearchRequest(); searchRequest.setId(id); searchRequest.setGuid(guid); CompletableFuture<Response> future = new CompletableFuture<>(); rpcClient.call(Request.newBuilder() .withBody(searchRequest) .build(), new ResponseConsumer(future)); return future.thenApply(r -> r.getBody().cast(CommandSearchResponse.class).getCommands().stream().findFirst()); } public CompletableFuture<List<DeviceCommand>> find(Collection<String> guids, Collection<String> names, Date timestampSt, Date timestampEnd, String status) { List<CompletableFuture<Response>> futures = guids.stream() .map(guid -> { CommandSearchRequest searchRequest = new CommandSearchRequest(); searchRequest.setGuid(guid); if (names != null) { searchRequest.setNames(new HashSet<>(names)); } searchRequest.setTimestampStart(timestampSt); searchRequest.setTimestampEnd(timestampEnd); searchRequest.setStatus(status); 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<DeviceCommand>> return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])) .thenApply(v -> futures.stream() .map(CompletableFuture::join) // List<CompletableFuture<Response>> => CompletableFuture<List<Response>> .map(r -> ((CommandSearchResponse) r.getBody()).getCommands()) // CompletableFuture<List<Response>> => CompletableFuture<List<List<DeviceCommand>>> .flatMap(Collection::stream) // CompletableFuture<List<List<DeviceCommand>>> => CompletableFuture<List<DeviceCommand>> .collect(Collectors.toList())); } public CompletableFuture<DeviceCommand> insert(DeviceCommandWrapper commandWrapper, DeviceVO device, UserVO user) { DeviceCommand command = convertWrapperToCommand(commandWrapper, device, user); CompletableFuture<Response> future = new CompletableFuture<>(); rpcClient.call(Request.newBuilder() .withBody(new CommandInsertRequest(command)) .withPartitionKey(device.getGuid()) .build(), new ResponseConsumer(future)); return future.thenApply(r -> ((CommandInsertResponse) r.getBody()).getDeviceCommand()); } public Pair<String, CompletableFuture<List<DeviceCommand>>> sendSubscribeRequest( final Set<String> devices, final Set<String> names, final Date timestamp, final BiConsumer<DeviceCommand, String> callback) throws InterruptedException { final String subscriptionId = UUID.randomUUID().toString(); Collection<CompletableFuture<Collection<DeviceCommand>>> futures = devices.stream() .map(device -> new CommandSubscribeRequest(subscriptionId, device, names, timestamp)) .map(subscribeRequest -> { CompletableFuture<Collection<DeviceCommand>> future = new CompletableFuture<>(); Consumer<Response> responseConsumer = response -> { String resAction = response.getBody().getAction(); if (resAction.equals(Action.COMMAND_SUBSCRIBE_RESPONSE.name())) { future.complete(response.getBody().cast(CommandSubscribeResponse.class).getCommands()); } else if (resAction.equals(Action.COMMAND_EVENT.name())) { callback.accept(response.getBody().cast(CommandEvent.class).getCommand(), subscriptionId); } else { logger.warn("Unknown action received from backend {}", resAction); } }; Request request = Request.newBuilder() .withBody(subscribeRequest) .withPartitionKey(subscribeRequest.getDevice()) .withSingleReply(false) .build(); rpcClient.call(request, responseConsumer); return future; }).collect(Collectors.toList()); CompletableFuture<List<DeviceCommand>> 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 sendUnsubscribeRequest(String subId, Set<String> deviceGuids) { CommandUnsubscribeRequest unsubscribeRequest = new CommandUnsubscribeRequest(subId, deviceGuids); Request request = Request.newBuilder() .withBody(unsubscribeRequest) .build(); rpcClient.push(request); } public CompletableFuture<Pair<String, DeviceCommand>> sendSubscribeToUpdateRequest(final long commandId, final String guid, BiConsumer<DeviceCommand, String> callback) { CompletableFuture<Pair<String, DeviceCommand>> future = new CompletableFuture<>(); final String subscriptionId = UUID.randomUUID().toString(); Consumer<Response> responseConsumer = response -> { String resAction = response.getBody().getAction(); if (resAction.equals(Action.COMMAND_UPDATE_SUBSCRIBE_RESPONSE.name())) { future.complete(Pair.of(response.getBody().cast(CommandUpdateSubscribeResponse.class).getSubscriptionId(), response.getBody().cast(CommandUpdateSubscribeResponse.class).getDeviceCommand())); } else if (resAction.equals(Action.COMMAND_UPDATE_EVENT.name())) { callback.accept(response.getBody().cast(CommandUpdateEvent.class).getDeviceCommand(), subscriptionId); } else { logger.warn("Unknown action received from backend {}", resAction); } }; rpcClient.call(Request.newBuilder() .withBody(new CommandUpdateSubscribeRequest(commandId, guid, subscriptionId)) .build(), responseConsumer); return future; } public CompletableFuture<Void> update(DeviceCommand cmd, DeviceCommandWrapper commandWrapper) { if (cmd == null) { throw new NoSuchElementException("Command not found"); } cmd.setIsUpdated(true); if (commandWrapper.getCommand() != null) { cmd.setCommand(commandWrapper.getCommand().orElse(null)); } if (commandWrapper.getTimestamp() != null && commandWrapper.getTimestamp().isPresent()) { cmd.setTimestamp(commandWrapper.getTimestamp().get()); } if (commandWrapper.getParameters() != null) { cmd.setParameters(commandWrapper.getParameters().orElse(null)); } if (commandWrapper.getLifetime() != null) { cmd.setLifetime(commandWrapper.getLifetime().orElse(null)); } if (commandWrapper.getStatus() != null) { cmd.setStatus(commandWrapper.getStatus().orElse(null)); } if (commandWrapper.getResult() != null) { cmd.setResult(commandWrapper.getResult().orElse(null)); } hiveValidator.validate(cmd); CompletableFuture<Response> future = new CompletableFuture<>(); rpcClient.call(Request.newBuilder() .withBody(new CommandUpdateRequest(cmd)) .build(), new ResponseConsumer(future)); return future.thenApply(response -> null); } private DeviceCommand convertWrapperToCommand(DeviceCommandWrapper commandWrapper, DeviceVO device, UserVO user) { DeviceCommand command = new DeviceCommand(); command.setId(Math.abs(new Random().nextInt())); command.setDeviceGuid(device.getGuid()); command.setIsUpdated(false); if (commandWrapper.getTimestamp() != null && commandWrapper.getTimestamp().isPresent()) { command.setTimestamp(commandWrapper.getTimestamp().get()); } else { command.setTimestamp(timestampService.getDate()); } if (user != null) { command.setUserId(user.getId()); } if (commandWrapper.getCommand() != null) { command.setCommand(commandWrapper.getCommand().orElseGet(null)); } if (commandWrapper.getParameters() != null) { command.setParameters(commandWrapper.getParameters().orElse(null)); } if (commandWrapper.getLifetime() != null) { command.setLifetime(commandWrapper.getLifetime().orElse(null)); } if (commandWrapper.getStatus() != null) { command.setStatus(commandWrapper.getStatus().orElse(null)); } if (commandWrapper.getResult() != null) { command.setResult(commandWrapper.getResult().orElse(null)); } hiveValidator.validate(command); return command; } }