Commit d727a8a3b4770a47557b087ac87dd824db202240

Authored by Andrew Shvayka
Committed by GitHub
1 parent f6da85e9

Caching of main requests related to relations and GWs API (#567)

* added enable/disable condition for transport protocols

* fix coap tests

* configured relations cache

* minor fix

* Improvements

* delete devices caching
... ... @@ -77,6 +77,8 @@ http:
77 77
78 78 # MQTT server parameters
79 79 mqtt:
  80 + # Enable/disable mqtt transport protocol.
  81 + enabled: "${MQTT_ENABLED:true}"
80 82 bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
81 83 bind_port: "${MQTT_BIND_PORT:1883}"
82 84 adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}"
... ... @@ -102,6 +104,8 @@ mqtt:
102 104
103 105 # CoAP server parameters
104 106 coap:
  107 + # Enable/disable coap transport protocol.
  108 + enabled: "${COAP_ENABLED:true}"
105 109 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
106 110 bind_port: "${COAP_BIND_PORT:5683}"
107 111 adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
... ... @@ -208,6 +212,18 @@ cache:
208 212 policy: "${CACHE_DEVICE_CREDENTIAL_MAX_SIZE_POLICY:PER_NODE}"
209 213 size: "${CACHE_DEVICE_CREDENTIAL_MAX_SIZE_SIZE:1000000}"
210 214
  215 +caching:
  216 + specs:
  217 + relations:
  218 + timeToLiveInMinutes: 1440
  219 + maxSize: 100000
  220 + deviceCredentials:
  221 + timeToLiveInMinutes: 1440
  222 + maxSize: 100000
  223 + devices:
  224 + timeToLiveInMinutes: 1440
  225 + maxSize: 100000
  226 +
211 227 # Check new version updates parameters
212 228 updates:
213 229 # Enable/disable updates checking.
... ...
... ... @@ -17,4 +17,6 @@ package org.thingsboard.server.common.data;
17 17
18 18 public class CacheConstants {
19 19 public static final String DEVICE_CREDENTIALS_CACHE = "deviceCredentials";
  20 + public static final String RELATIONS_CACHE = "relations";
  21 + public static final String DEVICE_CACHE = "devices";
20 22 }
... ...
... ... @@ -149,6 +149,10 @@
149 149 <artifactId>hazelcast</artifactId>
150 150 </dependency>
151 151 <dependency>
  152 + <groupId>com.github.ben-manes.caffeine</groupId>
  153 + <artifactId>caffeine</artifactId>
  154 + </dependency>
  155 + <dependency>
152 156 <groupId>com.hazelcast</groupId>
153 157 <artifactId>hazelcast-spring</artifactId>
154 158 </dependency>
... ... @@ -174,6 +178,10 @@
174 178 <artifactId>hsqldb</artifactId>
175 179 <scope>test</scope>
176 180 </dependency>
  181 + <dependency>
  182 + <groupId>org.springframework</groupId>
  183 + <artifactId>spring-context-support</artifactId>
  184 + </dependency>
177 185 </dependencies>
178 186 <build>
179 187 <plugins>
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.cache;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class CacheSpecs {
  22 + private Integer timeToLiveInMinutes;
  23 + private Integer maxSize;
  24 +}
... ...
... ... @@ -23,6 +23,8 @@ import java.lang.reflect.Method;
23 23
24 24 public class PreviousDeviceCredentialsIdKeyGenerator implements KeyGenerator {
25 25
  26 + private static final String NOT_VALID_DEVICE = "notValidDeviceCredentialsId";
  27 +
26 28 @Override
27 29 public Object generate(Object o, Method method, Object... objects) {
28 30 DeviceCredentialsService deviceCredentialsService = (DeviceCredentialsService) o;
... ... @@ -33,6 +35,6 @@ public class PreviousDeviceCredentialsIdKeyGenerator implements KeyGenerator {
33 35 return oldDeviceCredentials.getCredentialsId();
34 36 }
35 37 }
36   - return null;
  38 + return NOT_VALID_DEVICE;
37 39 }
38 40 }
... ...
... ... @@ -15,76 +15,57 @@
15 15 */
16 16 package org.thingsboard.server.dao.cache;
17 17
18   -import com.hazelcast.config.*;
19   -import com.hazelcast.core.Hazelcast;
20   -import com.hazelcast.core.HazelcastInstance;
21   -import com.hazelcast.instance.GroupProperty;
22   -import com.hazelcast.spring.cache.HazelcastCacheManager;
23   -import com.hazelcast.zookeeper.ZookeeperDiscoveryProperties;
24   -import com.hazelcast.zookeeper.ZookeeperDiscoveryStrategyFactory;
25   -import org.springframework.beans.factory.annotation.Value;
26   -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  18 +import com.github.benmanes.caffeine.cache.Caffeine;
  19 +import com.github.benmanes.caffeine.cache.Ticker;
  20 +import lombok.Data;
  21 +import org.springframework.boot.context.properties.ConfigurationProperties;
27 22 import org.springframework.cache.CacheManager;
28 23 import org.springframework.cache.annotation.EnableCaching;
  24 +import org.springframework.cache.caffeine.CaffeineCache;
29 25 import org.springframework.cache.interceptor.KeyGenerator;
  26 +import org.springframework.cache.support.SimpleCacheManager;
30 27 import org.springframework.context.annotation.Bean;
31 28 import org.springframework.context.annotation.Configuration;
32   -import org.thingsboard.server.common.data.CacheConstants;
  29 +
  30 +import java.util.List;
  31 +import java.util.Map;
  32 +import java.util.concurrent.TimeUnit;
  33 +import java.util.stream.Collectors;
33 34
34 35 @Configuration
  36 +@ConfigurationProperties(prefix = "caching")
35 37 @EnableCaching
36   -@ConditionalOnProperty(prefix = "cache", value = "enabled", havingValue = "true")
  38 +@Data
37 39 public class ServiceCacheConfiguration {
38 40
39   - private static final String HAZELCAST_CLUSTER_NAME = "hazelcast";
40   -
41   - @Value("${cache.device_credentials.max_size.size}")
42   - private Integer cacheDeviceCredentialsMaxSizeSize;
43   - @Value("${cache.device_credentials.max_size.policy}")
44   - private String cacheDeviceCredentialsMaxSizePolicy;
45   - @Value("${cache.device_credentials.time_to_live}")
46   - private Integer cacheDeviceCredentialsTTL;
47   -
48   - @Value("${zk.enabled}")
49   - private boolean zkEnabled;
50   - @Value("${zk.url}")
51   - private String zkUrl;
52   - @Value("${zk.zk_dir}")
53   - private String zkDir;
  41 + private Map<String, CacheSpecs> specs;
54 42
55 43 @Bean
56   - public HazelcastInstance hazelcastInstance() {
57   - Config config = new Config();
58   -
59   - if (zkEnabled) {
60   - addZkConfig(config);
  44 + public CacheManager cacheManager() {
  45 + SimpleCacheManager manager = new SimpleCacheManager();
  46 + if (specs != null) {
  47 + List<CaffeineCache> caches =
  48 + specs.entrySet().stream()
  49 + .map(entry -> buildCache(entry.getKey(),
  50 + entry.getValue()))
  51 + .collect(Collectors.toList());
  52 + manager.setCaches(caches);
61 53 }
62   -
63   - config.addMapConfig(createDeviceCredentialsCacheConfig());
64   -
65   - return Hazelcast.newHazelcastInstance(config);
  54 + return manager;
66 55 }
67 56
68   - private void addZkConfig(Config config) {
69   - config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
70   - config.setProperty(GroupProperty.DISCOVERY_SPI_ENABLED.getName(), Boolean.TRUE.toString());
71   - DiscoveryStrategyConfig discoveryStrategyConfig = new DiscoveryStrategyConfig(new ZookeeperDiscoveryStrategyFactory());
72   - discoveryStrategyConfig.addProperty(ZookeeperDiscoveryProperties.ZOOKEEPER_URL.key(), zkUrl);
73   - discoveryStrategyConfig.addProperty(ZookeeperDiscoveryProperties.ZOOKEEPER_PATH.key(), zkDir);
74   - discoveryStrategyConfig.addProperty(ZookeeperDiscoveryProperties.GROUP.key(), HAZELCAST_CLUSTER_NAME);
75   - config.getNetworkConfig().getJoin().getDiscoveryConfig().addDiscoveryStrategyConfig(discoveryStrategyConfig);
  57 + private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) {
  58 + final Caffeine<Object, Object> caffeineBuilder
  59 + = Caffeine.newBuilder()
  60 + .expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES)
  61 + .maximumSize(cacheSpec.getMaxSize())
  62 + .ticker(ticker());
  63 + return new CaffeineCache(name, caffeineBuilder.build());
76 64 }
77 65
78   - private MapConfig createDeviceCredentialsCacheConfig() {
79   - MapConfig deviceCredentialsCacheConfig = new MapConfig(CacheConstants.DEVICE_CREDENTIALS_CACHE);
80   - deviceCredentialsCacheConfig.setTimeToLiveSeconds(cacheDeviceCredentialsTTL);
81   - deviceCredentialsCacheConfig.setEvictionPolicy(EvictionPolicy.LRU);
82   - deviceCredentialsCacheConfig.setMaxSizeConfig(
83   - new MaxSizeConfig(
84   - cacheDeviceCredentialsMaxSizeSize,
85   - MaxSizeConfig.MaxSizePolicy.valueOf(cacheDeviceCredentialsMaxSizePolicy))
86   - );
87   - return deviceCredentialsCacheConfig;
  66 + @Bean
  67 + public Ticker ticker() {
  68 + return Ticker.systemTicker();
88 69 }
89 70
90 71 @Bean
... ... @@ -92,8 +73,4 @@ public class ServiceCacheConfiguration {
92 73 return new PreviousDeviceCredentialsIdKeyGenerator();
93 74 }
94 75
95   - @Bean
96   - public CacheManager cacheManager() {
97   - return new HazelcastCacheManager(hazelcastInstance());
98   - }
99 76 }
... ...
... ... @@ -34,7 +34,7 @@ public interface DeviceService {
34 34
35 35 ListenableFuture<Device> findDeviceByIdAsync(DeviceId deviceId);
36 36
37   - Optional<Device> findDeviceByTenantIdAndName(TenantId tenantId, String name);
  37 + Device findDeviceByTenantIdAndName(TenantId tenantId, String name);
38 38
39 39 Device saveDevice(Device device);
40 40
... ...
... ... @@ -22,6 +22,10 @@ import com.google.common.util.concurrent.ListenableFuture;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.apache.commons.lang3.RandomStringUtils;
24 24 import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.cache.Cache;
  26 +import org.springframework.cache.CacheManager;
  27 +import org.springframework.cache.annotation.CacheEvict;
  28 +import org.springframework.cache.annotation.Cacheable;
25 29 import org.springframework.stereotype.Service;
26 30 import org.springframework.util.StringUtils;
27 31 import org.thingsboard.server.common.data.*;
... ... @@ -33,12 +37,12 @@ import org.thingsboard.server.common.data.id.TenantId;
33 37 import org.thingsboard.server.common.data.page.TextPageData;
34 38 import org.thingsboard.server.common.data.page.TextPageLink;
35 39 import org.thingsboard.server.common.data.relation.EntityRelation;
  40 +import org.thingsboard.server.common.data.relation.EntitySearchDirection;
36 41 import org.thingsboard.server.common.data.security.DeviceCredentials;
37 42 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
38 43 import org.thingsboard.server.dao.customer.CustomerDao;
39 44 import org.thingsboard.server.dao.entity.AbstractEntityService;
40 45 import org.thingsboard.server.dao.exception.DataValidationException;
41   -import org.thingsboard.server.common.data.relation.EntitySearchDirection;
42 46 import org.thingsboard.server.dao.service.DataValidator;
43 47 import org.thingsboard.server.dao.service.PaginatedRemover;
44 48 import org.thingsboard.server.dao.tenant.TenantDao;
... ... @@ -47,6 +51,7 @@ import javax.annotation.Nullable;
47 51 import java.util.*;
48 52 import java.util.stream.Collectors;
49 53
  54 +import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE;
50 55 import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
51 56 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
52 57 import static org.thingsboard.server.dao.service.Validator.*;
... ... @@ -71,6 +76,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
71 76 @Autowired
72 77 private DeviceCredentialsService deviceCredentialsService;
73 78
  79 + @Autowired
  80 + private CacheManager cacheManager;
  81 +
74 82 @Override
75 83 public Device findDeviceById(DeviceId deviceId) {
76 84 log.trace("Executing findDeviceById [{}]", deviceId);
... ... @@ -85,18 +93,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
85 93 return deviceDao.findByIdAsync(deviceId.getId());
86 94 }
87 95
  96 + @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}")
88 97 @Override
89   - public Optional<Device> findDeviceByTenantIdAndName(TenantId tenantId, String name) {
  98 + public Device findDeviceByTenantIdAndName(TenantId tenantId, String name) {
90 99 log.trace("Executing findDeviceByTenantIdAndName [{}][{}]", tenantId, name);
91 100 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
92 101 Optional<Device> deviceOpt = deviceDao.findDeviceByTenantIdAndName(tenantId.getId(), name);
93   - if (deviceOpt.isPresent()) {
94   - return Optional.of(deviceOpt.get());
95   - } else {
96   - return Optional.empty();
97   - }
  102 + return deviceOpt.orElse(null);
98 103 }
99 104
  105 + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
100 106 @Override
101 107 public Device saveDevice(Device device) {
102 108 log.trace("Executing saveDevice [{}]", device);
... ... @@ -129,12 +135,18 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
129 135 @Override
130 136 public void deleteDevice(DeviceId deviceId) {
131 137 log.trace("Executing deleteDevice [{}]", deviceId);
  138 + Cache cache = cacheManager.getCache(DEVICE_CACHE);
132 139 validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
133 140 DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId);
134 141 if (deviceCredentials != null) {
135 142 deviceCredentialsService.deleteDeviceCredentials(deviceCredentials);
136 143 }
137 144 deleteEntityRelations(deviceId);
  145 + Device device = deviceDao.findById(deviceId.getId());
  146 + List<Object> list = new ArrayList<>();
  147 + list.add(device.getTenantId());
  148 + list.add(device.getName());
  149 + cache.evict(list);
138 150 deviceDao.removeById(deviceId.getId());
139 151 }
140 152
... ... @@ -190,7 +202,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
190 202 validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
191 203 validateString(type, "Incorrect type " + type);
192 204 validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink);
193   - List<Device> devices = deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
  205 + List<Device> devices = deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
194 206 return new TextPageData<>(devices, pageLink);
195 207 }
196 208
... ... @@ -244,10 +256,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
244 256 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
245 257 ListenableFuture<List<EntitySubtype>> tenantDeviceTypes = deviceDao.findTenantDeviceTypesAsync(tenantId.getId());
246 258 return Futures.transform(tenantDeviceTypes,
247   - (Function<List<EntitySubtype>, List<EntitySubtype>>) deviceTypes -> {
248   - deviceTypes.sort(Comparator.comparing(EntitySubtype::getType));
249   - return deviceTypes;
250   - });
  259 + (Function<List<EntitySubtype>, List<EntitySubtype>>) deviceTypes -> {
  260 + deviceTypes.sort(Comparator.comparing(EntitySubtype::getType));
  261 + return deviceTypes;
  262 + });
251 263 }
252 264
253 265 private DataValidator<Device> deviceValidator =
... ...
... ... @@ -21,6 +21,11 @@ import com.google.common.util.concurrent.Futures;
21 21 import com.google.common.util.concurrent.ListenableFuture;
22 22 import lombok.extern.slf4j.Slf4j;
23 23 import org.springframework.beans.factory.annotation.Autowired;
  24 +import org.springframework.cache.Cache;
  25 +import org.springframework.cache.CacheManager;
  26 +import org.springframework.cache.annotation.CacheEvict;
  27 +import org.springframework.cache.annotation.Cacheable;
  28 +import org.springframework.cache.annotation.Caching;
24 29 import org.springframework.stereotype.Service;
25 30 import org.springframework.util.StringUtils;
26 31 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -34,6 +39,8 @@ import java.util.concurrent.ConcurrentHashMap;
34 39 import java.util.concurrent.ExecutionException;
35 40 import java.util.function.BiConsumer;
36 41
  42 +import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
  43 +
37 44 /**
38 45 * Created by ashvayka on 28.04.17.
39 46 */
... ... @@ -47,6 +54,9 @@ public class BaseRelationService implements RelationService {
47 54 @Autowired
48 55 private EntityService entityService;
49 56
  57 + @Autowired
  58 + private CacheManager cacheManager;
  59 +
50 60 @Override
51 61 public ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
52 62 log.trace("Executing checkRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
... ... @@ -54,6 +64,7 @@ public class BaseRelationService implements RelationService {
54 64 return relationDao.checkRelation(from, to, relationType, typeGroup);
55 65 }
56 66
  67 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType}")
57 68 @Override
58 69 public ListenableFuture<EntityRelation> getRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
59 70 log.trace("Executing EntityRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
... ... @@ -61,6 +72,12 @@ public class BaseRelationService implements RelationService {
61 72 return relationDao.getRelation(from, to, relationType, typeGroup);
62 73 }
63 74
  75 + @Caching(evict = {
  76 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.from"),
  77 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"),
  78 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"),
  79 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type}")
  80 + })
64 81 @Override
65 82 public boolean saveRelation(EntityRelation relation) {
66 83 log.trace("Executing saveRelation [{}]", relation);
... ... @@ -68,6 +85,12 @@ public class BaseRelationService implements RelationService {
68 85 return relationDao.saveRelation(relation);
69 86 }
70 87
  88 + @Caching(evict = {
  89 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.from"),
  90 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"),
  91 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"),
  92 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type}")
  93 + })
71 94 @Override
72 95 public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
73 96 log.trace("Executing saveRelationAsync [{}]", relation);
... ... @@ -75,6 +98,13 @@ public class BaseRelationService implements RelationService {
75 98 return relationDao.saveRelationAsync(relation);
76 99 }
77 100
  101 + @Caching(evict = {
  102 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.from"),
  103 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"),
  104 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"),
  105 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type}"),
  106 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type}")
  107 + })
78 108 @Override
79 109 public boolean deleteRelation(EntityRelation relation) {
80 110 log.trace("Executing deleteRelation [{}]", relation);
... ... @@ -82,6 +112,13 @@ public class BaseRelationService implements RelationService {
82 112 return relationDao.deleteRelation(relation);
83 113 }
84 114
  115 + @Caching(evict = {
  116 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.from"),
  117 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"),
  118 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"),
  119 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type}"),
  120 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type}")
  121 + })
85 122 @Override
86 123 public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
87 124 log.trace("Executing deleteRelationAsync [{}]", relation);
... ... @@ -89,6 +126,13 @@ public class BaseRelationService implements RelationService {
89 126 return relationDao.deleteRelationAsync(relation);
90 127 }
91 128
  129 + @Caching(evict = {
  130 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#from"),
  131 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType}"),
  132 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#to"),
  133 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType}"),
  134 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType}")
  135 + })
92 136 @Override
93 137 public boolean deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
94 138 log.trace("Executing deleteRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
... ... @@ -96,6 +140,13 @@ public class BaseRelationService implements RelationService {
96 140 return relationDao.deleteRelation(from, to, relationType, typeGroup);
97 141 }
98 142
  143 + @Caching(evict = {
  144 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#from"),
  145 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType}"),
  146 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#to"),
  147 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType}"),
  148 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType}")
  149 + })
99 150 @Override
100 151 public ListenableFuture<Boolean> deleteRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
101 152 log.trace("Executing deleteRelationAsync [{}][{}][{}][{}]", from, to, relationType, typeGroup);
... ... @@ -105,23 +156,17 @@ public class BaseRelationService implements RelationService {
105 156
106 157 @Override
107 158 public boolean deleteEntityRelations(EntityId entity) {
  159 + Cache cache = cacheManager.getCache(RELATIONS_CACHE);
108 160 log.trace("Executing deleteEntityRelations [{}]", entity);
109 161 validate(entity);
110   - List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
  162 + List<ListenableFuture<List<EntityRelation>>> inboundRelationsListTo = new ArrayList<>();
111 163 for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
112   - inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
  164 + inboundRelationsListTo.add(relationDao.findAllByTo(entity, typeGroup));
113 165 }
114   - ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
115   - ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, new Function<List<List<EntityRelation>>, List<Boolean>>() {
116   - @Override
117   - public List<Boolean> apply(List<List<EntityRelation>> relations) {
118   - List<Boolean> results = new ArrayList<>();
119   - for (List<EntityRelation> relationList : relations) {
120   - relationList.stream().forEach(relation -> results.add(relationDao.deleteRelation(relation)));
121   - }
122   - return results;
123   - }
124   - });
  166 + ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
  167 + ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo, (List<List<EntityRelation>> relations) ->
  168 + getBooleans(relations, cache, true));
  169 +
125 170 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
126 171 boolean inboundDeleteResult = false;
127 172 try {
... ... @@ -129,12 +174,39 @@ public class BaseRelationService implements RelationService {
129 174 } catch (InterruptedException | ExecutionException e) {
130 175 log.error("Error deleting entity inbound relations", e);
131 176 }
  177 +
  178 + List<ListenableFuture<List<EntityRelation>>> inboundRelationsListFrom = new ArrayList<>();
  179 + for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
  180 + inboundRelationsListFrom.add(relationDao.findAllByFrom(entity, typeGroup));
  181 + }
  182 + ListenableFuture<List<List<EntityRelation>>> inboundRelationsFrom = Futures.allAsList(inboundRelationsListFrom);
  183 + Futures.transform(inboundRelationsFrom, (Function<List<List<EntityRelation>>, List<Boolean>>) relations ->
  184 + getBooleans(relations, cache, false));
  185 +
132 186 boolean outboundDeleteResult = relationDao.deleteOutboundRelations(entity);
133 187 return inboundDeleteResult && outboundDeleteResult;
134 188 }
135 189
  190 + private List<Boolean> getBooleans(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
  191 + List<Boolean> results = new ArrayList<>();
  192 + for (List<EntityRelation> relationList : relations) {
  193 + relationList.stream().forEach(relation -> {
  194 + checkFromDeleteSync(cache, results, relation, isRemove);
  195 + });
  196 + }
  197 + return results;
  198 + }
  199 +
  200 + private void checkFromDeleteSync(Cache cache, List<Boolean> results, EntityRelation relation, boolean isRemove) {
  201 + if (isRemove) {
  202 + results.add(relationDao.deleteRelation(relation));
  203 + }
  204 + cacheEviction(relation, relation.getTo(), cache);
  205 + }
  206 +
136 207 @Override
137 208 public ListenableFuture<Boolean> deleteEntityRelationsAsync(EntityId entity) {
  209 + Cache cache = cacheManager.getCache(RELATIONS_CACHE);
138 210 log.trace("Executing deleteEntityRelationsAsync [{}]", entity);
139 211 validate(entity);
140 212 List<ListenableFuture<List<EntityRelation>>> inboundRelationsList = new ArrayList<>();
... ... @@ -142,24 +214,61 @@ public class BaseRelationService implements RelationService {
142 214 inboundRelationsList.add(relationDao.findAllByTo(entity, typeGroup));
143 215 }
144 216 ListenableFuture<List<List<EntityRelation>>> inboundRelations = Futures.allAsList(inboundRelationsList);
145   - ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations, new AsyncFunction<List<List<EntityRelation>>, List<Boolean>>() {
146   - @Override
147   - public ListenableFuture<List<Boolean>> apply(List<List<EntityRelation>> relations) throws Exception {
148   - List<ListenableFuture<Boolean>> results = new ArrayList<>();
149   - for (List<EntityRelation> relationList : relations) {
150   - relationList.stream().forEach(relation -> results.add(relationDao.deleteRelationAsync(relation)));
151   - }
152   - return Futures.allAsList(results);
153   - }
  217 + ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelations,
  218 + (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
  219 + List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, true);
  220 + return Futures.allAsList(results);
154 221 });
155 222
156 223 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
157 224
158   - ListenableFuture<Boolean> outboundFuture = relationDao.deleteOutboundRelationsAsync(entity);
  225 + List<ListenableFuture<List<EntityRelation>>> inboundRelationsList1 = new ArrayList<>();
  226 + for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) {
  227 + inboundRelationsList1.add(relationDao.findAllByTo(entity, typeGroup));
  228 + }
  229 + ListenableFuture<List<List<EntityRelation>>> inboundRelations1 = Futures.allAsList(inboundRelationsList1);
  230 + Futures.transform(inboundRelations1, (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
  231 + List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, false);
  232 + return Futures.allAsList(results);
  233 + });
159 234
  235 + ListenableFuture<Boolean> outboundFuture = relationDao.deleteOutboundRelationsAsync(entity);
160 236 return Futures.transform(Futures.allAsList(Arrays.asList(inboundFuture, outboundFuture)), getListToBooleanFunction());
161 237 }
162 238
  239 + private List<ListenableFuture<Boolean>> getListenableFutures(List<List<EntityRelation>> relations, Cache cache, boolean isRemove) {
  240 + List<ListenableFuture<Boolean>> results = new ArrayList<>();
  241 + for (List<EntityRelation> relationList : relations) {
  242 + relationList.stream().forEach(relation -> {
  243 + checkFromDeleteAsync(cache, results, relation, isRemove);
  244 + });
  245 + }
  246 + return results;
  247 + }
  248 +
  249 + private void checkFromDeleteAsync(Cache cache, List<ListenableFuture<Boolean>> results, EntityRelation relation, boolean isRemove) {
  250 + if (isRemove) {
  251 + results.add(relationDao.deleteRelationAsync(relation));
  252 + }
  253 + cacheEviction(relation, relation.getTo(), cache);
  254 + }
  255 +
  256 + private void cacheEviction(EntityRelation relation, EntityId entityId, Cache cache) {
  257 + cache.evict(entityId);
  258 +
  259 + List<Object> toAndType = new ArrayList<>();
  260 + toAndType.add(entityId);
  261 + toAndType.add(relation.getType());
  262 + cache.evict(toAndType);
  263 +
  264 + List<Object> fromToAndType = new ArrayList<>();
  265 + fromToAndType.add(relation.getFrom());
  266 + fromToAndType.add(relation.getTo());
  267 + fromToAndType.add(relation.getType());
  268 + cache.evict(fromToAndType);
  269 + }
  270 +
  271 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "#from")
163 272 @Override
164 273 public ListenableFuture<List<EntityRelation>> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
165 274 log.trace("Executing findByFrom [{}][{}]", from, typeGroup);
... ... @@ -176,17 +285,18 @@ public class BaseRelationService implements RelationService {
176 285 ListenableFuture<List<EntityRelation>> relations = relationDao.findAllByFrom(from, typeGroup);
177 286 ListenableFuture<List<EntityRelationInfo>> relationsInfo = Futures.transform(relations,
178 287 (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
179   - List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
  288 + List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
180 289 relations1.stream().forEach(relation ->
181 290 futures.add(fetchRelationInfoAsync(relation,
182 291 relation2 -> relation2.getTo(),
183 292 (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setToName(entityName)))
184 293 );
185 294 return Futures.successfulAsList(futures);
186   - });
  295 + });
187 296 return relationsInfo;
188 297 }
189 298
  299 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType}")
190 300 @Override
191 301 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
192 302 log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup);
... ... @@ -196,6 +306,7 @@ public class BaseRelationService implements RelationService {
196 306 return relationDao.findAllByFromAndType(from, relationType, typeGroup);
197 307 }
198 308
  309 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "#to")
199 310 @Override
200 311 public ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup) {
201 312 log.trace("Executing findByTo [{}][{}]", to, typeGroup);
... ... @@ -214,9 +325,9 @@ public class BaseRelationService implements RelationService {
214 325 (AsyncFunction<List<EntityRelation>, List<EntityRelationInfo>>) relations1 -> {
215 326 List<ListenableFuture<EntityRelationInfo>> futures = new ArrayList<>();
216 327 relations1.stream().forEach(relation ->
217   - futures.add(fetchRelationInfoAsync(relation,
218   - relation2 -> relation2.getFrom(),
219   - (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setFromName(entityName)))
  328 + futures.add(fetchRelationInfoAsync(relation,
  329 + relation2 -> relation2.getFrom(),
  330 + (EntityRelationInfo relationInfo, String entityName) -> relationInfo.setFromName(entityName)))
220 331 );
221 332 return Futures.successfulAsList(futures);
222 333 });
... ... @@ -236,6 +347,7 @@ public class BaseRelationService implements RelationService {
236 347 return entityRelationInfo;
237 348 }
238 349
  350 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType}")
239 351 @Override
240 352 public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
241 353 log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup);
... ... @@ -417,5 +529,4 @@ public class BaseRelationService implements RelationService {
417 529 }
418 530 return relations;
419 531 }
420   -
421 532 }
... ...
... ... @@ -15,16 +15,14 @@
15 15 */
16 16 package org.thingsboard.server.dao.service;
17 17
18   -import com.hazelcast.core.HazelcastInstance;
19 18 import org.apache.commons.lang3.RandomStringUtils;
20 19 import org.junit.After;
21   -import org.junit.Assert;
22 20 import org.junit.Before;
23 21 import org.junit.Test;
24 22 import org.springframework.aop.framework.Advised;
25 23 import org.springframework.aop.support.AopUtils;
26 24 import org.springframework.beans.factory.annotation.Autowired;
27   -import org.springframework.test.context.TestPropertySource;
  25 +import org.springframework.cache.CacheManager;
28 26 import org.springframework.test.util.ReflectionTestUtils;
29 27 import org.thingsboard.server.common.data.CacheConstants;
30 28 import org.thingsboard.server.common.data.Device;
... ... @@ -40,7 +38,6 @@ import java.util.UUID;
40 38
41 39 import static org.mockito.Mockito.*;
42 40
43   -@TestPropertySource(properties = {"cache.enabled = true"})
44 41 public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest {
45 42
46 43 private static final String CREDENTIALS_ID_1 = RandomStringUtils.randomAlphanumeric(20);
... ... @@ -53,7 +50,7 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
53 50 private DeviceService deviceService;
54 51
55 52 @Autowired
56   - private HazelcastInstance hazelcastInstance;
  53 + private CacheManager cacheManager;
57 54
58 55 private UUID deviceId = UUID.randomUUID();
59 56
... ... @@ -67,7 +64,7 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
67 64
68 65 @After
69 66 public void cleanup() {
70   - hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).evictAll();
  67 + cacheManager.getCache(CacheConstants.DEVICE_CREDENTIALS_CACHE).clear();
71 68 }
72 69
73 70 @Test
... ... @@ -77,7 +74,6 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
77 74 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
78 75 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
79 76
80   - Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
81 77 verify(deviceCredentialsDao, times(1)).findByCredentialsId(CREDENTIALS_ID_1);
82 78 }
83 79
... ... @@ -88,17 +84,13 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
88 84 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
89 85 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
90 86
91   - Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
92 87 verify(deviceCredentialsDao, times(1)).findByCredentialsId(CREDENTIALS_ID_1);
93 88
94 89 deviceCredentialsService.deleteDeviceCredentials(createDummyDeviceCredentials(CREDENTIALS_ID_1, deviceId));
95 90
96   - Assert.assertEquals(0, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
97   -
98 91 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
99 92 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
100 93
101   - Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
102 94 verify(deviceCredentialsDao, times(2)).findByCredentialsId(CREDENTIALS_ID_1);
103 95 }
104 96
... ... @@ -109,7 +101,6 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
109 101 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
110 102 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
111 103
112   - Assert.assertEquals(1, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
113 104 verify(deviceCredentialsDao, times(1)).findByCredentialsId(CREDENTIALS_ID_1);
114 105
115 106 when(deviceCredentialsDao.findByDeviceId(deviceId)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1));
... ... @@ -119,13 +110,11 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
119 110 when(deviceService.findDeviceById(new DeviceId(deviceId))).thenReturn(new Device());
120 111
121 112 deviceCredentialsService.updateDeviceCredentials(createDummyDeviceCredentials(deviceCredentialsId, CREDENTIALS_ID_2, deviceId));
122   - Assert.assertEquals(0, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
123 113
124 114 when(deviceCredentialsDao.findByCredentialsId(CREDENTIALS_ID_1)).thenReturn(null);
125 115
126 116 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
127 117 deviceCredentialsService.findDeviceCredentialsByCredentialsId(CREDENTIALS_ID_1);
128   - Assert.assertEquals(0, hazelcastInstance.getMap(CacheConstants.DEVICE_CREDENTIALS_CACHE).size());
129 118
130 119 verify(deviceCredentialsDao, times(3)).findByCredentialsId(CREDENTIALS_ID_1);
131 120 }
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Test;
  22 +import org.springframework.aop.framework.Advised;
  23 +import org.springframework.aop.support.AopUtils;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.cache.CacheManager;
  26 +import org.springframework.test.util.ReflectionTestUtils;
  27 +import org.thingsboard.server.common.data.id.DeviceId;
  28 +import org.thingsboard.server.common.data.id.EntityId;
  29 +import org.thingsboard.server.common.data.relation.EntityRelation;
  30 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  31 +import org.thingsboard.server.dao.relation.RelationDao;
  32 +import org.thingsboard.server.dao.relation.RelationService;
  33 +
  34 +import java.util.UUID;
  35 +import java.util.concurrent.ExecutionException;
  36 +
  37 +import static org.mockito.Mockito.*;
  38 +import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
  39 +
  40 +public abstract class BaseRelationCacheTest extends AbstractServiceTest {
  41 +
  42 + private static final EntityId ENTITY_ID_FROM = new DeviceId(UUID.randomUUID());
  43 + private static final EntityId ENTITY_ID_TO = new DeviceId(UUID.randomUUID());
  44 + private static final String RELATION_TYPE = "Contains";
  45 +
  46 + @Autowired
  47 + private RelationService relationService;
  48 + @Autowired
  49 + private CacheManager cacheManager;
  50 +
  51 + private RelationDao relationDao;
  52 +
  53 + @Before
  54 + public void setup() throws Exception {
  55 + relationDao = mock(RelationDao.class);
  56 + ReflectionTestUtils.setField(unwrapRelationService(), "relationDao", relationDao);
  57 + }
  58 +
  59 + @After
  60 + public void cleanup() {
  61 + cacheManager.getCache(RELATIONS_CACHE).clear();
  62 + }
  63 +
  64 + private RelationService unwrapRelationService() throws Exception {
  65 + if (AopUtils.isAopProxy(relationService) && relationService instanceof Advised) {
  66 + Object target = ((Advised) relationService).getTargetSource().getTarget();
  67 + return (RelationService) target;
  68 + }
  69 + return null;
  70 + }
  71 +
  72 + @Test
  73 + public void testFindRelationByFrom_Cached() throws ExecutionException, InterruptedException {
  74 + when(relationDao.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON))
  75 + .thenReturn(Futures.immediateFuture(new EntityRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE)));
  76 +
  77 + relationService.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  78 + relationService.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  79 +
  80 + verify(relationDao, times(1)).getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  81 + }
  82 +
  83 + @Test
  84 + public void testDeleteRelations_EvictsCache() {
  85 + when(relationDao.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON))
  86 + .thenReturn(Futures.immediateFuture(new EntityRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE)));
  87 +
  88 + relationService.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  89 + relationService.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  90 +
  91 + verify(relationDao, times(1)).getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  92 +
  93 + relationService.deleteRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  94 +
  95 + relationService.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  96 + relationService.getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  97 +
  98 + verify(relationDao, times(2)).getRelation(ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON);
  99 +
  100 + }
  101 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service.nosql;
  17 +
  18 +import org.thingsboard.server.dao.service.BaseRelationCacheTest;
  19 +import org.thingsboard.server.dao.service.DaoNoSqlTest;
  20 +
  21 +@DaoNoSqlTest
  22 +public class RelationCacheNoSqlTest extends BaseRelationCacheTest {
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.service.sql;
  17 +
  18 +import org.thingsboard.server.dao.service.BaseRelationCacheTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class RelationCacheSqlTest extends BaseRelationCacheTest {
  23 +}
... ...
... ... @@ -7,4 +7,13 @@ zk.enabled=false
7 7 zk.url=localhost:2181
8 8 zk.zk_dir=/thingsboard
9 9
10   -updates.enabled=false
\ No newline at end of file
  10 +updates.enabled=false
  11 +
  12 +caching.specs.relations.timeToLiveInMinutes=1440
  13 +caching.specs.relations.maxSize=100000
  14 +
  15 +caching.specs.deviceCredentials.timeToLiveInMinutes=1440
  16 +caching.specs.deviceCredentials.maxSize=100000
  17 +
  18 +caching.specs.devices.timeToLiveInMinutes=1440
  19 +caching.specs.devices.maxSize=100000
\ No newline at end of file
... ...
... ... @@ -43,6 +43,7 @@
43 43 <cassandra-unit.version>3.0.0.1</cassandra-unit.version>
44 44 <takari-cpsuite.version>1.2.7</takari-cpsuite.version>
45 45 <guava.version>18.0</guava.version>
  46 + <caffeine.version>2.6.1</caffeine.version>
46 47 <commons-lang3.version>3.4</commons-lang3.version>
47 48 <commons-validator.version>1.5.0</commons-validator.version>
48 49 <commons-io.version>2.5</commons-io.version>
... ... @@ -645,6 +646,11 @@
645 646 <version>${guava.version}</version>
646 647 </dependency>
647 648 <dependency>
  649 + <groupId>com.github.ben-manes.caffeine</groupId>
  650 + <artifactId>caffeine</artifactId>
  651 + <version>${caffeine.version}</version>
  652 + </dependency>
  653 + <dependency>
648 654 <groupId>com.google.protobuf</groupId>
649 655 <artifactId>protobuf-java</artifactId>
650 656 <version>${protobuf.version}</version>
... ...
... ... @@ -26,17 +26,17 @@ import lombok.extern.slf4j.Slf4j;
26 26 import org.eclipse.californium.core.CoapResource;
27 27 import org.eclipse.californium.core.CoapServer;
28 28 import org.eclipse.californium.core.network.CoapEndpoint;
  29 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
29 30 import org.thingsboard.server.common.transport.SessionMsgProcessor;
30 31 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
31 32 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
32   -import org.slf4j.Logger;
33   -import org.slf4j.LoggerFactory;
34 33 import org.springframework.beans.factory.annotation.Autowired;
35 34 import org.springframework.beans.factory.annotation.Value;
36 35 import org.springframework.context.ApplicationContext;
37 36 import org.springframework.stereotype.Service;
38 37
39 38 @Service("CoapTransportService")
  39 +@ConditionalOnProperty(prefix = "coap", value = "enabled", havingValue = "true", matchIfMissing = true)
40 40 @Slf4j
41 41 public class CoapTransportService {
42 42
... ...
... ... @@ -24,6 +24,7 @@ import io.netty.util.ResourceLeakDetector;
24 24 import lombok.extern.slf4j.Slf4j;
25 25 import org.springframework.beans.factory.annotation.Autowired;
26 26 import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27 28 import org.springframework.context.ApplicationContext;
28 29 import org.springframework.stereotype.Service;
29 30 import org.thingsboard.server.common.transport.SessionMsgProcessor;
... ... @@ -39,6 +40,7 @@ import javax.annotation.PreDestroy;
39 40 * @author Andrew Shvayka
40 41 */
41 42 @Service("MqttTransportService")
  43 +@ConditionalOnProperty(prefix = "mqtt", value = "enabled", havingValue = "true", matchIfMissing = false)
42 44 @Slf4j
43 45 public class MqttTransportService {
44 46
... ...
... ... @@ -84,16 +84,15 @@ public class GatewaySessionCtx {
84 84
85 85 private void onDeviceConnect(String deviceName, String deviceType) {
86 86 if (!devices.containsKey(deviceName)) {
87   - Optional<Device> deviceOpt = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), deviceName);
88   - Device device = deviceOpt.orElseGet(() -> {
89   - Device newDevice = new Device();
90   - newDevice.setTenantId(gateway.getTenantId());
91   - newDevice.setName(deviceName);
92   - newDevice.setType(deviceType);
93   - newDevice = deviceService.saveDevice(newDevice);
94   - relationService.saveRelationAsync(new EntityRelation(gateway.getId(), newDevice.getId(), "Created"));
95   - return newDevice;
96   - });
  87 + Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), deviceName);
  88 + if (device == null) {
  89 + device = new Device();
  90 + device.setTenantId(gateway.getTenantId());
  91 + device.setName(deviceName);
  92 + device.setType(deviceType);
  93 + device = deviceService.saveDevice(device);
  94 + relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created"));
  95 + }
97 96 GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device);
98 97 devices.put(deviceName, ctx);
99 98 log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName);
... ...