package com.devicehive.service;
/*
* #%L
* DeviceHive Java Server Common business 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.dao.DeviceDao;
import com.devicehive.exceptions.HiveException;
import com.devicehive.model.DeviceNotification;
import com.devicehive.model.SpecialNotifications;
import com.devicehive.model.rpc.ListDeviceRequest;
import com.devicehive.model.rpc.ListDeviceResponse;
import com.devicehive.model.updates.DeviceUpdate;
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.util.ServerResponsesFactory;
import com.devicehive.vo.*;
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.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import static javax.ws.rs.core.Response.Status.*;
@Component
public class DeviceService {
private static final Logger logger = LoggerFactory.getLogger(DeviceService.class);
@Autowired
private DeviceNotificationService deviceNotificationService;
@Autowired
private NetworkService networkService;
@Autowired
private UserService userService;
@Autowired
private DeviceClassService deviceClassService;
@Autowired
private TimestampService timestampService;
@Autowired
private HiveValidator hiveValidator;
@Autowired
private DeviceDao deviceDao;
@Autowired
private RpcClient rpcClient;
//todo equipmentSet is not used
@Transactional(propagation = Propagation.REQUIRED)
public void deviceSaveAndNotify(DeviceUpdate device, Set<DeviceClassEquipmentVO> equipmentSet, HivePrincipal principal) {
logger.debug("Device: {}. Current principal: {}.", device.getGuid(), principal == null ? null : principal.getName());
validateDevice(device);
DeviceNotification dn;
if (principal != null && principal.isAuthenticated()) {
if (principal.getUser() != null) {
dn = deviceSaveByUser(device, principal.getUser());
} else if (principal.getNetworkIds() != null && principal.getDeviceGuids() != null) {
dn = deviceSaveByPrincipalPermissions(device, principal);
} else {
throw new HiveException(Messages.UNAUTHORIZED_REASON_PHRASE, UNAUTHORIZED.getStatusCode());
}
} else {
throw new HiveException(Messages.UNAUTHORIZED_REASON_PHRASE, UNAUTHORIZED.getStatusCode());
}
dn.setTimestamp(timestampService.getDate());
deviceNotificationService.insert(dn, device.convertTo());
}
@Transactional(propagation = Propagation.REQUIRED)
private DeviceNotification deviceSaveByUser(DeviceUpdate deviceUpdate, UserVO user) {
logger.debug("Device save executed for device: id {}, user: {}", deviceUpdate.getGuid(), user.getId());
//todo: rework when migration to VO will be done
NetworkVO vo = deviceUpdate.getNetwork() != null ? deviceUpdate.getNetwork().get() : null;
NetworkVO nwVo = networkService.createOrUpdateNetworkByUser(Optional.ofNullable(vo), user);
NetworkVO network = nwVo != null ? findNetworkForAuth(nwVo) : null;
network = findNetworkForAuth(network);
DeviceClassWithEquipmentVO deviceClass = prepareDeviceClassForNewlyCreatedDevice(deviceUpdate);
// TODO [requies a lot of details]!
DeviceVO existingDevice = deviceDao.findByUUID(deviceUpdate.getGuid().orElse(null));
if (existingDevice == null) {
DeviceVO device = deviceUpdate.convertTo();
if (deviceClass != null) {
//TODO [rafa] changed
DeviceClassVO dc = new DeviceClassVO();
dc.setId(deviceClass.getId());
dc.setName(deviceClass.getName());
dc.setIsPermanent(deviceClass.getIsPermanent());
device.setDeviceClass(dc);
}
if (network != null) {
device.setNetwork(network);
}
if (device.getBlocked() == null) {
device.setBlocked(false);
}
deviceDao.persist(device);
return ServerResponsesFactory.createNotificationForDevice(device, SpecialNotifications.DEVICE_ADD);
} else {
if (!userService.hasAccessToDevice(user, existingDevice.getGuid())) {
logger.error("User {} has no access to device {}", user.getId(), existingDevice.getGuid());
throw new HiveException(Messages.NO_ACCESS_TO_DEVICE, FORBIDDEN.getStatusCode());
}
if (deviceUpdate.getDeviceClass() != null) {
DeviceClassVO dc = new DeviceClassVO();
dc.setId(deviceClass.getId());
dc.setName(deviceClass.getName());
dc.setIsPermanent(deviceClass.getIsPermanent());
existingDevice.setDeviceClass(dc);
}
if (deviceUpdate.getData() != null) {
existingDevice.setData(deviceUpdate.getData().orElse(null));
}
if (deviceUpdate.getNetwork() != null) {
existingDevice.setNetwork(network);
}
if (deviceUpdate.getName() != null) {
existingDevice.setName(deviceUpdate.getName().orElse(null));
}
if (deviceUpdate.getBlocked() != null) {
existingDevice.setBlocked(deviceUpdate.getBlocked().orElse(null));
}
deviceDao.merge(existingDevice);
return ServerResponsesFactory.createNotificationForDevice(existingDevice, SpecialNotifications.DEVICE_UPDATE);
}
}
private DeviceClassWithEquipmentVO prepareDeviceClassForNewlyCreatedDevice(DeviceUpdate deviceUpdate) {
DeviceClassWithEquipmentVO deviceClass = null;
if (deviceUpdate.getDeviceClass() != null && deviceUpdate.getDeviceClass().isPresent()) {
//TODO [rafa] if device class equipment not present - we need to clone it from device.
Set<DeviceClassEquipmentVO> customEquipmentSet = null;
if (deviceUpdate.getDeviceClass().get().getEquipment() != null) {
customEquipmentSet = deviceUpdate.getDeviceClass().get().getEquipment().orElse(new HashSet<>());
}
deviceClass = deviceClassService.createOrUpdateDeviceClass(deviceUpdate.getDeviceClass(), customEquipmentSet);
}
return deviceClass;
}
private DeviceNotification deviceSaveByPrincipalPermissions(DeviceUpdate deviceUpdate, HivePrincipal principal) {
logger.debug("Device save executed for device: id {}, user: {}", deviceUpdate.getGuid(), principal.getName());
//TODO [requires a lot of details]
DeviceVO existingDevice = deviceDao.findByUUID(deviceUpdate.getGuid().orElse(null));
if (existingDevice != null && !principal.hasAccessToNetwork(existingDevice.getNetwork().getId())) {
logger.error("Principal {} has no access to device network {}", principal.getName(), existingDevice.getNetwork().getId());
throw new HiveException(Messages.NO_ACCESS_TO_NETWORK, FORBIDDEN.getStatusCode());
}
NetworkVO nw = deviceUpdate.getNetwork() != null ? deviceUpdate.getNetwork().get() : null;
NetworkVO network = networkService.createOrVerifyNetwork(Optional.ofNullable(nw));
network = findNetworkForAuth(network);
DeviceClassWithEquipmentVO deviceClass = prepareDeviceClassForNewlyCreatedDevice(deviceUpdate);
if (existingDevice == null) {
DeviceClassVO dc = new DeviceClassVO();
dc.setId(deviceClass.getId());
dc.setName(deviceClass.getName());
DeviceVO device = deviceUpdate.convertTo();
device.setDeviceClass(dc);
device.setNetwork(network);
deviceDao.persist(device);
return ServerResponsesFactory.createNotificationForDevice(device, SpecialNotifications.DEVICE_ADD);
} else {
if (!principal.hasAccessToDevice(deviceUpdate.getGuid().orElse(null))) {
logger.error("Principal {} has no access to device {}", principal, existingDevice.getGuid());
throw new HiveException(Messages.NO_ACCESS_TO_DEVICE, FORBIDDEN.getStatusCode());
}
if (deviceUpdate.getDeviceClass() != null && !existingDevice.getDeviceClass().getIsPermanent()) {
DeviceClassVO dc = new DeviceClassVO();
dc.setId(deviceClass.getId());
dc.setName(deviceClass.getName());
existingDevice.setDeviceClass(dc);
}
if (deviceUpdate.getData() != null) {
existingDevice.setData(deviceUpdate.getData().orElse(null));
}
if (deviceUpdate.getNetwork() != null) {
existingDevice.setNetwork(network);
}
if (deviceUpdate.getName() != null) {
existingDevice.setName(deviceUpdate.getName().orElse(null));
}
if (deviceUpdate.getBlocked() != null) {
existingDevice.setBlocked(Boolean.TRUE.equals(deviceUpdate.getBlocked().orElse(null)));
}
deviceDao.merge(existingDevice);
return ServerResponsesFactory.createNotificationForDevice(existingDevice, SpecialNotifications.DEVICE_UPDATE);
}
}
@Transactional
public DeviceNotification deviceSave(DeviceUpdate deviceUpdate,
Set<DeviceClassEquipmentVO> equipmentSet) {
logger.debug("Device save executed for device update: id {}", deviceUpdate.getGuid());
NetworkVO network = (deviceUpdate.getNetwork() != null)? deviceUpdate.getNetwork().get() : null;
if (network != null) {
network = networkService.createOrVerifyNetwork(Optional.ofNullable(network));
}
DeviceClassWithEquipmentVO deviceClass = prepareDeviceClassForNewlyCreatedDevice(deviceUpdate);
//TODO [requires a lot of details]
DeviceVO existingDevice = deviceDao.findByUUID(deviceUpdate.getGuid().orElse(null));
if (existingDevice == null) {
DeviceVO device = deviceUpdate.convertTo();
if (deviceClass != null) {
DeviceClassVO dc = new DeviceClassVO();
dc.setId(deviceClass.getId());
dc.setName(deviceClass.getName());
device.setDeviceClass(dc);
}
if (network != null) {
device.setNetwork(network);
}
deviceDao.persist(device);
return ServerResponsesFactory.createNotificationForDevice(device, SpecialNotifications.DEVICE_ADD);
} else {
if (deviceUpdate.getDeviceClass() != null) {
DeviceClassVO dc = new DeviceClassVO();
dc.setId(deviceClass.getId());
dc.setName(deviceClass.getName());
existingDevice.setDeviceClass(dc);
}
if (deviceUpdate.getData() != null) {
existingDevice.setData(deviceUpdate.getData().orElse(null));
}
if (deviceUpdate.getNetwork() != null) {
existingDevice.setNetwork(network);
}
if (deviceUpdate.getBlocked() != null) {
existingDevice.setBlocked(Boolean.TRUE.equals(deviceUpdate.getBlocked().orElse(null)));
}
deviceDao.merge(existingDevice);
return ServerResponsesFactory.createNotificationForDevice(existingDevice, SpecialNotifications.DEVICE_UPDATE);
}
}
@Transactional(propagation = Propagation.SUPPORTS)
public DeviceVO findByGuidWithPermissionsCheck(String guid, HivePrincipal principal) {
List<DeviceVO> result = findByGuidWithPermissionsCheck(Collections.singletonList(guid), principal);
return result.isEmpty() ? null : result.get(0);
}
@Transactional(propagation = Propagation.SUPPORTS)
public List<DeviceVO> findByGuidWithPermissionsCheck(Collection<String> guids, HivePrincipal principal) {
return getDeviceList(new ArrayList<>(guids), principal);
}
/**
* Implementation for model: if field exists and null - error if field does not exists - use field from database
*
* @param device device to check
*/
@Transactional(readOnly = true)
public void validateDevice(DeviceUpdate device) throws HiveException {
if (device == null) {
logger.error("Device validation: device is empty");
throw new HiveException(Messages.EMPTY_DEVICE);
}
if (device.getName() != null && device.getName().orElse(null) == null) {
logger.error("Device validation: device name is empty");
throw new HiveException(Messages.EMPTY_DEVICE_NAME);
}
if (device.getDeviceClass() != null && device.getDeviceClass().orElse(null) == null) {
logger.error("Device validation: device class is empty");
throw new HiveException(Messages.EMPTY_DEVICE_CLASS);
}
hiveValidator.validate(device);
}
@Transactional(readOnly = true)
public DeviceVO getDeviceWithNetworkAndDeviceClass(String deviceId) {
DeviceVO device = deviceDao.findByUUID(deviceId);
if (device == null) {
logger.error("Device with guid {} not found", deviceId);
throw new HiveException(String.format(Messages.DEVICE_NOT_FOUND, deviceId), NOT_FOUND.getStatusCode());
}
return device;
}
//TODO: only migrated to genericDAO, need to migrate Device PK to guid and use directly GenericDAO#remove
@Transactional
public boolean deleteDevice(@NotNull String guid) {
return deviceDao.deleteByUUID(guid) != 0;
}
//@Transactional(readOnly = true)
public CompletableFuture<List<DeviceVO>> list(String name,
String namePattern,
Long networkId,
String networkName,
Long deviceClassId,
String deviceClassName,
String sortField,
@NotNull Boolean sortOrderAsc,
Integer take,
Integer skip,
HivePrincipal principal) {
ListDeviceRequest request = new ListDeviceRequest();
request.setName(name);
request.setNamePattern(namePattern);
request.setNetworkId(networkId);
request.setNetworkName(networkName);
request.setDeviceClassId(deviceClassId);
request.setDeviceClassName(deviceClassName);
request.setSortField(sortField);
request.setSortOrderAsc(sortOrderAsc);
request.setTake(take);
request.setSkip(skip);
request.setPrincipal(principal);
CompletableFuture<Response> future = new CompletableFuture<>();
rpcClient.call(Request.newBuilder().withBody(request).build(), new ResponseConsumer(future));
return future.thenApply(r -> ((ListDeviceResponse) r.getBody()).getDevices());
}
@Transactional(propagation = Propagation.SUPPORTS)
//TODO: need to remove it
public long getAllowedDevicesCount(HivePrincipal principal, List<String> guids) {
return deviceDao.getAllowedDeviceCount(principal, guids);
}
private List<DeviceVO> getDeviceList(List<String> guids, HivePrincipal principal) {
return deviceDao.getDeviceList(guids, principal);
}
private NetworkVO findNetworkForAuth(NetworkVO network) {
if (network == null) {
HivePrincipal principal = (HivePrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserVO user = findUserFromAuth(principal);
if (user != null) {
Set<NetworkVO> userNetworks = userService.findUserWithNetworks(user.getId()).getNetworks();
if (userNetworks.isEmpty()) {
throw new HiveException(Messages.NO_NETWORKS_ASSIGNED_TO_USER, PRECONDITION_FAILED.getStatusCode());
}
return userNetworks.iterator().next();
}
}
return network;
}
private UserVO findUserFromAuth(HivePrincipal principal) {
return principal.getUser();
}
}