package com.devicehive.dao.riak; /* * #%L * DeviceHive Dao Riak Implementation * %% * 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.basho.riak.client.api.commands.indexes.BinIndexQuery; import com.basho.riak.client.api.commands.kv.DeleteValue; import com.basho.riak.client.api.commands.kv.FetchValue; import com.basho.riak.client.api.commands.kv.StoreValue; import com.basho.riak.client.api.commands.mapreduce.BucketMapReduce; import com.basho.riak.client.api.commands.mapreduce.MapReduce; import com.basho.riak.client.core.query.Location; import com.basho.riak.client.core.query.Namespace; import com.devicehive.auth.HivePrincipal; import com.devicehive.dao.DeviceClassDao; import com.devicehive.dao.DeviceDao; import com.devicehive.dao.NetworkDao; import com.devicehive.dao.riak.model.NetworkDevice; import com.devicehive.dao.riak.model.RiakDevice; import com.devicehive.dao.riak.model.RiakNetwork; import com.devicehive.exceptions.HivePersistenceLayerException; import com.devicehive.vo.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import javax.annotation.PostConstruct; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @Repository public class DeviceDaoRiakImpl extends RiakGenericDao implements DeviceDao { private static final Logger LOGGER = LoggerFactory.getLogger(DeviceDaoRiakImpl.class); private static final Namespace DEVICE_NS = new Namespace("device"); private static final Location COUNTERS_LOCATION = new Location(new Namespace("counters", "dh_counters"), "deviceCounter"); @Autowired private NetworkDao networkDao; @Autowired private DeviceClassDao deviceClassDao; @Autowired private UserNetworkDaoRiakImpl userNetworkDao; @Autowired private NetworkDeviceDaoRiakImpl networkDeviceDao; public DeviceDaoRiakImpl() { } @PostConstruct public void init() { ((NetworkDaoRiakImpl) networkDao).setDeviceDao(this); } @Override public DeviceVO findByUUID(String uuid) { BinIndexQuery biq = new BinIndexQuery.Builder(DEVICE_NS, "guid", uuid).build(); try { BinIndexQuery.Response response = client.execute(biq); List<BinIndexQuery.Response.Entry> entries = response.getEntries(); if (entries.isEmpty()) { return null; } Location location = entries.get(0).getRiakObjectLocation(); FetchValue fetchOp = new FetchValue.Builder(location) .withOption(quorum.getReadQuorumOption(), quorum.getReadQuorum()) .build(); RiakDevice device = getOrNull(client.execute(fetchOp), RiakDevice.class); //TODO [rafa] refreshRefs DeviceVO deviceVO = RiakDevice.convertToVo(device); // deviceVO.setDeviceClass(device.getDeviceClass()); // deviceVO.setNetwork(device.getNetwork()); //TODO [rafa] do we need next refresh commands? Seems that all references are reconstructed. refreshRefs(deviceVO); return deviceVO; } catch (ExecutionException | InterruptedException e) { LOGGER.error("Exception accessing Riak Storage.", e); throw new HivePersistenceLayerException("Cannot find device by UUID.", e); } } @Override public void persist(DeviceVO vo) { RiakDevice device = RiakDevice.convertToEntity(vo); try { if (device.getId() == null) { device.setId(getId()); } if (device.getDeviceClass() != null && device.getDeviceClass().getEquipment() != null) { device.getDeviceClass().getEquipment().clear(); } Location location = new Location(DEVICE_NS, String.valueOf(device.getId())); StoreValue storeOp = new StoreValue.Builder(device) .withLocation(location) .withOption(quorum.getWriteQuorumOption(), quorum.getWriteQuorum()) .build(); client.execute(storeOp); vo.setId(device.getId()); } catch (ExecutionException | InterruptedException e) { LOGGER.error("Exception accessing Riak Storage.", e); throw new HivePersistenceLayerException("Cannot persist device.", e); } RiakNetwork network = device.getNetwork(); if (network != null && network.getId() != null) { LOGGER.debug("Creating relation between network[{}] and device[{}]", network.getId(), device.getGuid()); networkDeviceDao.saveOrUpdate(new NetworkDevice(network.getId(), device.getGuid())); LOGGER.debug("Creating relation finished between network[{}] and device[{}]", network.getId(), device.getGuid()); } } @Override public DeviceVO merge(DeviceVO device) { persist(device); return device; } @Override public int deleteByUUID(String guid) { try { BinIndexQuery biq = new BinIndexQuery.Builder(DEVICE_NS, "guid", guid).build(); BinIndexQuery.Response response = client.execute(biq); List<BinIndexQuery.Response.Entry> entries = response.getEntries(); if (entries.isEmpty()) { return 0; } Location location = entries.get(0).getRiakObjectLocation(); DeleteValue deleteOp = new DeleteValue.Builder(location).build(); client.execute(deleteOp); return 1; } catch (ExecutionException | InterruptedException e) { LOGGER.error("Exception accessing Riak Storage.", e); throw new HivePersistenceLayerException("Cannot delete device by UUID.", e); } } @Override public List<DeviceVO> getDeviceList(List<String> guids, HivePrincipal principal) { if (guids.isEmpty()) { return list(null, null, null, null, null, null, null, true, null, null, principal); } List<DeviceVO> deviceList = guids.stream().map(this::findByUUID).collect(Collectors.toList()); if (principal != null) { UserVO user = principal.getUser(); if (user != null && !user.isAdmin()) { Set<Long> networks = userNetworkDao.findNetworksForUser(user.getId()); deviceList = deviceList .stream() .filter(d -> networks.contains(d.getNetwork().getId())) .collect(Collectors.toList()); } if (principal.getDeviceGuids() != null) { deviceList = deviceList.stream() .filter(d -> principal.getDeviceGuids().contains(d.getGuid())) .collect(Collectors.toList()); } if (principal.getNetworkIds() != null) { deviceList = deviceList.stream() .filter(d -> principal.getNetworkIds().contains(d.getNetwork().getId())) .collect(Collectors.toList()); } } return deviceList; } @Override public long getAllowedDeviceCount(HivePrincipal principal, List<String> guids) { return getDeviceList(guids, principal).size(); } @Override public List<DeviceVO> list(String name, String namePattern, Long networkId, String networkName, Long deviceClassId, String deviceClassName, String sortField, Boolean isSortOrderAsc, Integer take, Integer skip, HivePrincipal principal) { //TODO [rafa] when filtering by device class name we have to instead query DeviceClass bucket for ids, and then use ids. // here is what happens, since device class is not embeddable in case of Riak we need to either keep id only and perform the logic above. // or we need to update device class embedded data in every device corresponding to the class, which is nighmare. BucketMapReduce.Builder builder = new BucketMapReduce.Builder() .withNamespace(DEVICE_NS); addMapValues(builder); if (name != null) { addReduceFilter(builder, "name", FilterOperator.EQUAL, name); } else if (namePattern != null) { namePattern = namePattern.replace("%", ""); addReduceFilter(builder, "name", FilterOperator.REGEX, namePattern); } addReduceFilter(builder, "network.id", FilterOperator.EQUAL, networkId); addReduceFilter(builder, "network.name", FilterOperator.EQUAL, networkName); addReduceFilter(builder, "deviceClass.id", FilterOperator.EQUAL, deviceClassId); addReduceFilter(builder, "deviceClass.name", FilterOperator.EQUAL, deviceClassName); if (principal != null) { UserVO user = principal.getUser(); if (user != null && !user.isAdmin()) { Set<Long> networks = userNetworkDao.findNetworksForUser(user.getId()); if (principal.getNetworkIds() != null) { networks.retainAll(principal.getNetworkIds()); } addReduceFilter(builder, "network.id", FilterOperator.IN, networks); } if (principal.getDeviceGuids() != null) { Set<String> deviceGuids = principal.getDeviceGuids(); addReduceFilter(builder, "guid", FilterOperator.IN, deviceGuids); } } addReduceSort(builder, sortField, isSortOrderAsc); addReducePaging(builder, true, take, skip); try { MapReduce.Response response = client.execute(builder.build()); return response.getResultsFromAllPhases(RiakDevice.class).stream() .map(RiakDevice::convertToVo).collect(Collectors.toList()); } catch (InterruptedException | ExecutionException e) { LOGGER.error("Exception accessing Riak Storage.", e); throw new HivePersistenceLayerException("Cannot get list of devices.", e); } } private DeviceVO refreshRefs(DeviceVO device) { if (device != null) { if (device.getNetwork() != null) { // todo: remove when migrate Device->DeviceVO NetworkVO networkVO = networkDao.find(device.getNetwork().getId()); device.setNetwork(networkVO); } if (device.getDeviceClass() != null) { DeviceClassWithEquipmentVO deviceClassWithEquipmentVO = deviceClassDao.find(device.getDeviceClass().getId()); device.setDeviceClass(deviceClassWithEquipmentVO); } } return device; } private Long getId() { return getId(COUNTERS_LOCATION); } }