Commit 2a74985e4ba3d1cde3cbbdb4dd1463f2220ded91

Authored by Igor Kulikov
2 parents 92fdbbf8 bec380c3

Merge with master. Add upgrade scripts.

Showing 56 changed files with 1705 additions and 252 deletions
  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 +
  17 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id (
  18 + tenant_id timeuuid,
  19 + id timeuuid,
  20 + customer_id timeuuid,
  21 + entity_id timeuuid,
  22 + entity_type text,
  23 + entity_name text,
  24 + user_id timeuuid,
  25 + user_name text,
  26 + action_type text,
  27 + action_data text,
  28 + action_status text,
  29 + action_failure_details text,
  30 + PRIMARY KEY ((tenant_id, entity_id, entity_type), id)
  31 +);
  32 +
  33 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id (
  34 + tenant_id timeuuid,
  35 + id timeuuid,
  36 + customer_id timeuuid,
  37 + entity_id timeuuid,
  38 + entity_type text,
  39 + entity_name text,
  40 + user_id timeuuid,
  41 + user_name text,
  42 + action_type text,
  43 + action_data text,
  44 + action_status text,
  45 + action_failure_details text,
  46 + PRIMARY KEY ((tenant_id, customer_id), id)
  47 +);
  48 +
  49 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id (
  50 + tenant_id timeuuid,
  51 + id timeuuid,
  52 + customer_id timeuuid,
  53 + entity_id timeuuid,
  54 + entity_type text,
  55 + entity_name text,
  56 + user_id timeuuid,
  57 + user_name text,
  58 + action_type text,
  59 + action_data text,
  60 + action_status text,
  61 + action_failure_details text,
  62 + PRIMARY KEY ((tenant_id, user_id), id)
  63 +);
  64 +
  65 +
  66 +
  67 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id (
  68 + tenant_id timeuuid,
  69 + id timeuuid,
  70 + partition bigint,
  71 + customer_id timeuuid,
  72 + entity_id timeuuid,
  73 + entity_type text,
  74 + entity_name text,
  75 + user_id timeuuid,
  76 + user_name text,
  77 + action_type text,
  78 + action_data text,
  79 + action_status text,
  80 + action_failure_details text,
  81 + PRIMARY KEY ((tenant_id, partition), id)
  82 +);
  83 +
  84 +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
  85 + tenant_id timeuuid,
  86 + partition bigint,
  87 + PRIMARY KEY (( tenant_id ), partition)
  88 +) WITH CLUSTERING ORDER BY ( partition ASC )
  89 +AND compaction = { 'class' : 'LeveledCompactionStrategy' };
  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 +
  17 +CREATE TABLE IF NOT EXISTS audit_log (
  18 + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY,
  19 + tenant_id varchar(31),
  20 + customer_id varchar(31),
  21 + entity_id varchar(31),
  22 + entity_type varchar(255),
  23 + entity_name varchar(255),
  24 + user_id varchar(31),
  25 + user_name varchar(255),
  26 + action_type varchar(255),
  27 + action_data varchar(255),
  28 + action_status varchar(255),
  29 + action_failure_details varchar
  30 +);
  31 +
@@ -429,12 +429,12 @@ public final class PluginProcessingContext implements PluginContext { @@ -429,12 +429,12 @@ public final class PluginProcessingContext implements PluginContext {
429 429
430 @Override 430 @Override
431 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) { 431 public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) {
432 - return this.pluginCtx.relationService.findByFromAndType(from, relationType, RelationTypeGroup.COMMON); 432 + return this.pluginCtx.relationService.findByFromAndTypeAsync(from, relationType, RelationTypeGroup.COMMON);
433 } 433 }
434 434
435 @Override 435 @Override
436 public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId from, String relationType) { 436 public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId from, String relationType) {
437 - return this.pluginCtx.relationService.findByToAndType(from, relationType, RelationTypeGroup.COMMON); 437 + return this.pluginCtx.relationService.findByToAndTypeAsync(from, relationType, RelationTypeGroup.COMMON);
438 } 438 }
439 439
440 @Override 440 @Override
@@ -130,6 +130,10 @@ public abstract class BaseController { @@ -130,6 +130,10 @@ public abstract class BaseController {
130 @Autowired 130 @Autowired
131 protected AuditLogService auditLogService; 131 protected AuditLogService auditLogService;
132 132
  133 + @ExceptionHandler(Exception.class)
  134 + public void handleException(Exception ex, HttpServletResponse response) {
  135 + errorResponseHandler.handle(ex, response);
  136 + }
133 137
134 @ExceptionHandler(ThingsboardException.class) 138 @ExceptionHandler(ThingsboardException.class)
135 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { 139 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
@@ -120,7 +120,7 @@ public class EntityRelationController extends BaseController { @@ -120,7 +120,7 @@ public class EntityRelationController extends BaseController {
120 checkEntityId(fromId); 120 checkEntityId(fromId);
121 checkEntityId(toId); 121 checkEntityId(toId);
122 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); 122 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
123 - return checkNotNull(relationService.getRelation(fromId, toId, strRelationType, typeGroup).get()); 123 + return checkNotNull(relationService.getRelation(fromId, toId, strRelationType, typeGroup));
124 } catch (Exception e) { 124 } catch (Exception e) {
125 throw handleException(e); 125 throw handleException(e);
126 } 126 }
@@ -138,7 +138,7 @@ public class EntityRelationController extends BaseController { @@ -138,7 +138,7 @@ public class EntityRelationController extends BaseController {
138 checkEntityId(entityId); 138 checkEntityId(entityId);
139 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); 139 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
140 try { 140 try {
141 - return checkNotNull(relationService.findByFrom(entityId, typeGroup).get()); 141 + return checkNotNull(relationService.findByFrom(entityId, typeGroup));
142 } catch (Exception e) { 142 } catch (Exception e) {
143 throw handleException(e); 143 throw handleException(e);
144 } 144 }
@@ -176,7 +176,7 @@ public class EntityRelationController extends BaseController { @@ -176,7 +176,7 @@ public class EntityRelationController extends BaseController {
176 checkEntityId(entityId); 176 checkEntityId(entityId);
177 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); 177 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
178 try { 178 try {
179 - return checkNotNull(relationService.findByFromAndType(entityId, strRelationType, typeGroup).get()); 179 + return checkNotNull(relationService.findByFromAndType(entityId, strRelationType, typeGroup));
180 } catch (Exception e) { 180 } catch (Exception e) {
181 throw handleException(e); 181 throw handleException(e);
182 } 182 }
@@ -194,7 +194,7 @@ public class EntityRelationController extends BaseController { @@ -194,7 +194,7 @@ public class EntityRelationController extends BaseController {
194 checkEntityId(entityId); 194 checkEntityId(entityId);
195 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); 195 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
196 try { 196 try {
197 - return checkNotNull(relationService.findByTo(entityId, typeGroup).get()); 197 + return checkNotNull(relationService.findByTo(entityId, typeGroup));
198 } catch (Exception e) { 198 } catch (Exception e) {
199 throw handleException(e); 199 throw handleException(e);
200 } 200 }
@@ -232,7 +232,7 @@ public class EntityRelationController extends BaseController { @@ -232,7 +232,7 @@ public class EntityRelationController extends BaseController {
232 checkEntityId(entityId); 232 checkEntityId(entityId);
233 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); 233 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
234 try { 234 try {
235 - return checkNotNull(relationService.findByToAndType(entityId, strRelationType, typeGroup).get()); 235 + return checkNotNull(relationService.findByToAndType(entityId, strRelationType, typeGroup));
236 } catch (Exception e) { 236 } catch (Exception e) {
237 throw handleException(e); 237 throw handleException(e);
238 } 238 }
@@ -81,6 +81,8 @@ public class ThingsboardInstallService { @@ -81,6 +81,8 @@ public class ThingsboardInstallService {
81 case "1.3.1": 81 case "1.3.1":
82 log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); 82 log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ...");
83 83
  84 + databaseUpgradeService.upgradeDatabase("1.3.1");
  85 +
84 log.info("Updating system data..."); 86 log.info("Updating system data...");
85 87
86 systemDataLoaderService.deleteSystemWidgetBundle("charts"); 88 systemDataLoaderService.deleteSystemWidgetBundle("charts");
@@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
159 break; 159 break;
160 case "1.3.0": 160 case "1.3.0":
161 break; 161 break;
  162 + case "1.3.1":
  163 + log.info("Updating schema ...");
  164 + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
  165 + loadCql(schemaUpdateFile);
  166 + log.info("Schema updated.");
  167 + break;
162 default: 168 default:
163 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); 169 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
164 } 170 }
@@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
61 } 61 }
62 log.info("Schema updated."); 62 log.info("Schema updated.");
63 break; 63 break;
  64 + case "1.3.1":
  65 + log.info("Updating schema ...");
  66 + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
  67 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  68 + String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
  69 + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  70 + }
  71 + log.info("Schema updated.");
  72 + break;
64 default: 73 default:
65 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 74 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
66 } 75 }
@@ -111,6 +111,27 @@ coap: @@ -111,6 +111,27 @@ coap:
111 adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}" 111 adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
112 timeout: "${COAP_TIMEOUT:10000}" 112 timeout: "${COAP_TIMEOUT:10000}"
113 113
  114 +#Quota parameters
  115 +quota:
  116 + host:
  117 + # Max allowed number of API requests in interval for single host
  118 + limit: "${QUOTA_HOST_LIMIT:10000}"
  119 + # Interval duration
  120 + intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
  121 + # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs
  122 + ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
  123 + # Interval for scheduled task that cleans expired records. TTL is used for expiring
  124 + cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
  125 + # Enable Host API Limits
  126 + enabled: "${QUOTA_HOST_ENABLED:false}"
  127 + # Array of whitelist hosts
  128 + whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
  129 + # Array of blacklist hosts
  130 + blacklist: "${QUOTA_HOST_BLACKLIST:}"
  131 + log:
  132 + topSize: 10
  133 + intervalMin: 2
  134 +
114 database: 135 database:
115 type: "${DATABASE_TYPE:sql}" # cassandra OR sql 136 type: "${DATABASE_TYPE:sql}" # cassandra OR sql
116 137
@@ -193,26 +214,11 @@ actors: @@ -193,26 +214,11 @@ actors:
193 enabled: "${ACTORS_STATISTICS_ENABLED:true}" 214 enabled: "${ACTORS_STATISTICS_ENABLED:true}"
194 persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}" 215 persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
195 216
196 -# Cache parameters  
197 cache: 217 cache:
198 - # Enable/disable cache functionality.  
199 - enabled: "${CACHE_ENABLED:false}"  
200 - device_credentials:  
201 - # Default time to store device credentials in cache, in seconds  
202 - time_to_live: "${CACHE_DEVICE_CREDENTIAL_TTL:3600}"  
203 - # Maximum size of the map. When maximum size is reached, the map is evicted based on the policy defined.  
204 - max_size:  
205 - # Max size policy options:  
206 - # PER_NODE: Maximum number of map entries in each JVM.  
207 - # PER_PARTITION: Maximum number of map entries within each partition.  
208 - # USED_HEAP_SIZE: Maximum used heap size in megabytes for each JVM.  
209 - # USED_HEAP_PERCENTAGE: Maximum used heap size percentage for each JVM.  
210 - # FREE_HEAP_SIZE: Minimum free heap size in megabytes for each JVM.  
211 - # FREE_HEAP_PERCENTAGE: Minimum free heap size percentage for each JVM.  
212 - policy: "${CACHE_DEVICE_CREDENTIAL_MAX_SIZE_POLICY:PER_NODE}"  
213 - size: "${CACHE_DEVICE_CREDENTIAL_MAX_SIZE_SIZE:1000000}" 218 + # caffeine or redis
  219 + type: "${CACHE_TYPE:caffeine}"
214 220
215 -caching: 221 +caffeine:
216 specs: 222 specs:
217 relations: 223 relations:
218 timeToLiveInMinutes: 1440 224 timeToLiveInMinutes: 1440
@@ -224,6 +230,15 @@ caching: @@ -224,6 +230,15 @@ caching:
224 timeToLiveInMinutes: 1440 230 timeToLiveInMinutes: 1440
225 maxSize: 100000 231 maxSize: 100000
226 232
  233 +redis:
  234 + # standalone or cluster
  235 + connection:
  236 + type: standalone
  237 + host: "${REDIS_HOST:localhost}"
  238 + port: "${REDIS_PORT:6379}"
  239 + db: "${REDIS_DB:0}"
  240 + password: "${REDIS_PASSWORD:}"
  241 +
227 # Check new version updates parameters 242 # Check new version updates parameters
228 updates: 243 updates:
229 # Enable/disable updates checking. 244 # Enable/disable updates checking.
@@ -19,7 +19,7 @@ import lombok.EqualsAndHashCode; @@ -19,7 +19,7 @@ import lombok.EqualsAndHashCode;
19 import org.thingsboard.server.common.data.id.UUIDBased; 19 import org.thingsboard.server.common.data.id.UUIDBased;
20 20
21 @EqualsAndHashCode(callSuper = true) 21 @EqualsAndHashCode(callSuper = true)
22 -public abstract class ContactBased<I extends UUIDBased> extends SearchTextBased<I> { 22 +public abstract class ContactBased<I extends UUIDBased> extends SearchTextBasedWithAdditionalInfo<I> {
23 23
24 private static final long serialVersionUID = 5047448057830660988L; 24 private static final long serialVersionUID = 5047448057830660988L;
25 25
@@ -29,8 +29,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -29,8 +29,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
29 29
30 private String title; 30 private String title;
31 private TenantId tenantId; 31 private TenantId tenantId;
32 - private transient JsonNode additionalInfo;  
33 - 32 +
34 public Customer() { 33 public Customer() {
35 super(); 34 super();
36 } 35 }
@@ -43,7 +42,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -43,7 +42,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
43 super(customer); 42 super(customer);
44 this.tenantId = customer.getTenantId(); 43 this.tenantId = customer.getTenantId();
45 this.title = customer.getTitle(); 44 this.title = customer.getTitle();
46 - this.additionalInfo = customer.getAdditionalInfo();  
47 } 45 }
48 46
49 public TenantId getTenantId() { 47 public TenantId getTenantId() {
@@ -65,7 +63,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -65,7 +63,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
65 @JsonIgnore 63 @JsonIgnore
66 public boolean isPublic() { 64 public boolean isPublic() {
67 if (getAdditionalInfo() != null && getAdditionalInfo().has("isPublic")) { 65 if (getAdditionalInfo() != null && getAdditionalInfo().has("isPublic")) {
68 - return additionalInfo.get("isPublic").asBoolean(); 66 + return getAdditionalInfo().get("isPublic").asBoolean();
69 } 67 }
70 68
71 return false; 69 return false;
@@ -77,14 +75,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -77,14 +75,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
77 return title; 75 return title;
78 } 76 }
79 77
80 - public JsonNode getAdditionalInfo() {  
81 - return additionalInfo;  
82 - }  
83 -  
84 - public void setAdditionalInfo(JsonNode additionalInfo) {  
85 - this.additionalInfo = additionalInfo;  
86 - }  
87 -  
88 @Override 78 @Override
89 public String getSearchText() { 79 public String getSearchText() {
90 return getTitle(); 80 return getTitle();
@@ -94,7 +84,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -94,7 +84,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
94 public int hashCode() { 84 public int hashCode() {
95 final int prime = 31; 85 final int prime = 31;
96 int result = super.hashCode(); 86 int result = super.hashCode();
97 - result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());  
98 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); 87 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
99 result = prime * result + ((title == null) ? 0 : title.hashCode()); 88 result = prime * result + ((title == null) ? 0 : title.hashCode());
100 return result; 89 return result;
@@ -109,11 +98,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -109,11 +98,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
109 if (getClass() != obj.getClass()) 98 if (getClass() != obj.getClass())
110 return false; 99 return false;
111 Customer other = (Customer) obj; 100 Customer other = (Customer) obj;
112 - if (additionalInfo == null) {  
113 - if (other.additionalInfo != null)  
114 - return false;  
115 - } else if (!additionalInfo.equals(other.additionalInfo))  
116 - return false;  
117 if (tenantId == null) { 101 if (tenantId == null) {
118 if (other.tenantId != null) 102 if (other.tenantId != null)
119 return false; 103 return false;
@@ -135,7 +119,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName { @@ -135,7 +119,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
135 builder.append(", tenantId="); 119 builder.append(", tenantId=");
136 builder.append(tenantId); 120 builder.append(tenantId);
137 builder.append(", additionalInfo="); 121 builder.append(", additionalInfo=");
138 - builder.append(additionalInfo); 122 + builder.append(getAdditionalInfo());
139 builder.append(", country="); 123 builder.append(", country=");
140 builder.append(country); 124 builder.append(country);
141 builder.append(", state="); 125 builder.append(", state=");
@@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 import com.fasterxml.jackson.databind.JsonNode; 23 import com.fasterxml.jackson.databind.JsonNode;
24 24
25 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
26 -public class Device extends SearchTextBased<DeviceId> implements HasName { 26 +public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName {
27 27
28 private static final long serialVersionUID = 2807343040519543363L; 28 private static final long serialVersionUID = 2807343040519543363L;
29 29
@@ -31,7 +31,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName { @@ -31,7 +31,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
31 private CustomerId customerId; 31 private CustomerId customerId;
32 private String name; 32 private String name;
33 private String type; 33 private String type;
34 - private transient JsonNode additionalInfo;  
35 34
36 public Device() { 35 public Device() {
37 super(); 36 super();
@@ -47,7 +46,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName { @@ -47,7 +46,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
47 this.customerId = device.getCustomerId(); 46 this.customerId = device.getCustomerId();
48 this.name = device.getName(); 47 this.name = device.getName();
49 this.type = device.getType(); 48 this.type = device.getType();
50 - this.additionalInfo = device.getAdditionalInfo();  
51 } 49 }
52 50
53 public TenantId getTenantId() { 51 public TenantId getTenantId() {
@@ -83,14 +81,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName { @@ -83,14 +81,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
83 this.type = type; 81 this.type = type;
84 } 82 }
85 83
86 - public JsonNode getAdditionalInfo() {  
87 - return additionalInfo;  
88 - }  
89 -  
90 - public void setAdditionalInfo(JsonNode additionalInfo) {  
91 - this.additionalInfo = additionalInfo;  
92 - }  
93 -  
94 @Override 84 @Override
95 public String getSearchText() { 85 public String getSearchText() {
96 return getName(); 86 return getName();
@@ -108,7 +98,7 @@ public class Device extends SearchTextBased<DeviceId> implements HasName { @@ -108,7 +98,7 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
108 builder.append(", type="); 98 builder.append(", type=");
109 builder.append(type); 99 builder.append(type);
110 builder.append(", additionalInfo="); 100 builder.append(", additionalInfo=");
111 - builder.append(additionalInfo); 101 + builder.append(getAdditionalInfo());
112 builder.append(", createdTime="); 102 builder.append(", createdTime=");
113 builder.append(createdTime); 103 builder.append(createdTime);
114 builder.append(", id="); 104 builder.append(", id=");
  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.common.data;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +
  20 +public interface HasAdditionalInfo {
  21 +
  22 + JsonNode getAdditionalInfo();
  23 +
  24 +}
  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.common.data;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.common.data.id.UUIDBased;
  24 +
  25 +import java.io.ByteArrayInputStream;
  26 +import java.io.IOException;
  27 +import java.util.Arrays;
  28 +import java.util.Objects;
  29 +import java.util.function.Supplier;
  30 +import java.util.function.Consumer;
  31 +
  32 +/**
  33 + * Created by ashvayka on 19.02.18.
  34 + */
  35 +@Slf4j
  36 +public abstract class SearchTextBasedWithAdditionalInfo<I extends UUIDBased> extends SearchTextBased<I> implements HasAdditionalInfo {
  37 +
  38 + private transient JsonNode additionalInfo;
  39 + @JsonIgnore
  40 + private byte[] additionalInfoBytes;
  41 +
  42 + public SearchTextBasedWithAdditionalInfo() {
  43 + super();
  44 + }
  45 +
  46 + public SearchTextBasedWithAdditionalInfo(I id) {
  47 + super(id);
  48 + }
  49 +
  50 + public SearchTextBasedWithAdditionalInfo(SearchTextBasedWithAdditionalInfo<I> searchTextBased) {
  51 + super(searchTextBased);
  52 + setAdditionalInfo(searchTextBased.getAdditionalInfo());
  53 + }
  54 +
  55 + @Override
  56 + public JsonNode getAdditionalInfo() {
  57 + return getJson(() -> additionalInfo, () -> additionalInfoBytes);
  58 + }
  59 +
  60 + public void setAdditionalInfo(JsonNode addInfo) {
  61 + setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
  62 + }
  63 +
  64 + @Override
  65 + public boolean equals(Object o) {
  66 + if (this == o) return true;
  67 + if (o == null || getClass() != o.getClass()) return false;
  68 + if (!super.equals(o)) return false;
  69 + SearchTextBasedWithAdditionalInfo<?> that = (SearchTextBasedWithAdditionalInfo<?>) o;
  70 + return Arrays.equals(additionalInfoBytes, that.additionalInfoBytes);
  71 + }
  72 +
  73 + @Override
  74 + public int hashCode() {
  75 + return Objects.hash(super.hashCode(), additionalInfoBytes);
  76 + }
  77 +
  78 + public static JsonNode getJson(Supplier<JsonNode> jsonData, Supplier<byte[]> binaryData) {
  79 + JsonNode json = jsonData.get();
  80 + if (json != null) {
  81 + return json;
  82 + } else {
  83 + byte[] data = binaryData.get();
  84 + if (data != null) {
  85 + try {
  86 + return new ObjectMapper().readTree(new ByteArrayInputStream(data));
  87 + } catch (IOException e) {
  88 + log.warn("Can't deserialize json data: ", e);
  89 + return null;
  90 + }
  91 + } else {
  92 + return null;
  93 + }
  94 + }
  95 + }
  96 +
  97 + public static void setJson(JsonNode json, Consumer<JsonNode> jsonConsumer, Consumer<byte[]> bytesConsumer) {
  98 + jsonConsumer.accept(json);
  99 + try {
  100 + bytesConsumer.accept(new ObjectMapper().writeValueAsBytes(json));
  101 + } catch (JsonProcessingException e) {
  102 + log.warn("Can't serialize json data: ", e);
  103 + }
  104 + }
  105 +}
@@ -28,8 +28,7 @@ public class Tenant extends ContactBased<TenantId> implements HasName { @@ -28,8 +28,7 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
28 28
29 private String title; 29 private String title;
30 private String region; 30 private String region;
31 - private transient JsonNode additionalInfo;  
32 - 31 +
33 public Tenant() { 32 public Tenant() {
34 super(); 33 super();
35 } 34 }
@@ -42,7 +41,6 @@ public class Tenant extends ContactBased<TenantId> implements HasName { @@ -42,7 +41,6 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
42 super(tenant); 41 super(tenant);
43 this.title = tenant.getTitle(); 42 this.title = tenant.getTitle();
44 this.region = tenant.getRegion(); 43 this.region = tenant.getRegion();
45 - this.additionalInfo = tenant.getAdditionalInfo();  
46 } 44 }
47 45
48 public String getTitle() { 46 public String getTitle() {
@@ -67,14 +65,6 @@ public class Tenant extends ContactBased<TenantId> implements HasName { @@ -67,14 +65,6 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
67 this.region = region; 65 this.region = region;
68 } 66 }
69 67
70 - public JsonNode getAdditionalInfo() {  
71 - return additionalInfo;  
72 - }  
73 -  
74 - public void setAdditionalInfo(JsonNode additionalInfo) {  
75 - this.additionalInfo = additionalInfo;  
76 - }  
77 -  
78 @Override 68 @Override
79 public String getSearchText() { 69 public String getSearchText() {
80 return getTitle(); 70 return getTitle();
@@ -88,7 +78,7 @@ public class Tenant extends ContactBased<TenantId> implements HasName { @@ -88,7 +78,7 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
88 builder.append(", region="); 78 builder.append(", region=");
89 builder.append(region); 79 builder.append(region);
90 builder.append(", additionalInfo="); 80 builder.append(", additionalInfo=");
91 - builder.append(additionalInfo); 81 + builder.append(getAdditionalInfo());
92 builder.append(", country="); 82 builder.append(", country=");
93 builder.append(country); 83 builder.append(country);
94 builder.append(", state="); 84 builder.append(", state=");
@@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.security.Authority; @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.security.Authority;
25 import com.fasterxml.jackson.databind.JsonNode; 25 import com.fasterxml.jackson.databind.JsonNode;
26 26
27 @EqualsAndHashCode(callSuper = true) 27 @EqualsAndHashCode(callSuper = true)
28 -public class User extends SearchTextBased<UserId> implements HasName { 28 +public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName {
29 29
30 private static final long serialVersionUID = 8250339805336035966L; 30 private static final long serialVersionUID = 8250339805336035966L;
31 31
@@ -35,7 +35,6 @@ public class User extends SearchTextBased<UserId> implements HasName { @@ -35,7 +35,6 @@ public class User extends SearchTextBased<UserId> implements HasName {
35 private Authority authority; 35 private Authority authority;
36 private String firstName; 36 private String firstName;
37 private String lastName; 37 private String lastName;
38 - private transient JsonNode additionalInfo;  
39 38
40 public User() { 39 public User() {
41 super(); 40 super();
@@ -53,7 +52,6 @@ public class User extends SearchTextBased<UserId> implements HasName { @@ -53,7 +52,6 @@ public class User extends SearchTextBased<UserId> implements HasName {
53 this.authority = user.getAuthority(); 52 this.authority = user.getAuthority();
54 this.firstName = user.getFirstName(); 53 this.firstName = user.getFirstName();
55 this.lastName = user.getLastName(); 54 this.lastName = user.getLastName();
56 - this.additionalInfo = user.getAdditionalInfo();  
57 } 55 }
58 56
59 public TenantId getTenantId() { 57 public TenantId getTenantId() {
@@ -110,14 +108,6 @@ public class User extends SearchTextBased<UserId> implements HasName { @@ -110,14 +108,6 @@ public class User extends SearchTextBased<UserId> implements HasName {
110 this.lastName = lastName; 108 this.lastName = lastName;
111 } 109 }
112 110
113 - public JsonNode getAdditionalInfo() {  
114 - return additionalInfo;  
115 - }  
116 -  
117 - public void setAdditionalInfo(JsonNode additionalInfo) {  
118 - this.additionalInfo = additionalInfo;  
119 - }  
120 -  
121 @Override 111 @Override
122 public String getSearchText() { 112 public String getSearchText() {
123 return getEmail(); 113 return getEmail();
@@ -139,7 +129,7 @@ public class User extends SearchTextBased<UserId> implements HasName { @@ -139,7 +129,7 @@ public class User extends SearchTextBased<UserId> implements HasName {
139 builder.append(", lastName="); 129 builder.append(", lastName=");
140 builder.append(lastName); 130 builder.append(lastName);
141 builder.append(", additionalInfo="); 131 builder.append(", additionalInfo=");
142 - builder.append(additionalInfo); 132 + builder.append(getAdditionalInfo());
143 builder.append(", createdTime="); 133 builder.append(", createdTime=");
144 builder.append(createdTime); 134 builder.append(createdTime);
145 builder.append(", id="); 135 builder.append(", id=");
@@ -17,14 +17,16 @@ package org.thingsboard.server.common.data.asset; @@ -17,14 +17,16 @@ package org.thingsboard.server.common.data.asset;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 import lombok.EqualsAndHashCode; 19 import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.HasAdditionalInfo;
20 import org.thingsboard.server.common.data.HasName; 21 import org.thingsboard.server.common.data.HasName;
21 import org.thingsboard.server.common.data.SearchTextBased; 22 import org.thingsboard.server.common.data.SearchTextBased;
  23 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
22 import org.thingsboard.server.common.data.id.AssetId; 24 import org.thingsboard.server.common.data.id.AssetId;
23 import org.thingsboard.server.common.data.id.CustomerId; 25 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
25 27
26 @EqualsAndHashCode(callSuper = true) 28 @EqualsAndHashCode(callSuper = true)
27 -public class Asset extends SearchTextBased<AssetId> implements HasName { 29 +public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName {
28 30
29 private static final long serialVersionUID = 2807343040519543363L; 31 private static final long serialVersionUID = 2807343040519543363L;
30 32
@@ -32,7 +34,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName { @@ -32,7 +34,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
32 private CustomerId customerId; 34 private CustomerId customerId;
33 private String name; 35 private String name;
34 private String type; 36 private String type;
35 - private transient JsonNode additionalInfo;  
36 37
37 public Asset() { 38 public Asset() {
38 super(); 39 super();
@@ -48,7 +49,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName { @@ -48,7 +49,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
48 this.customerId = asset.getCustomerId(); 49 this.customerId = asset.getCustomerId();
49 this.name = asset.getName(); 50 this.name = asset.getName();
50 this.type = asset.getType(); 51 this.type = asset.getType();
51 - this.additionalInfo = asset.getAdditionalInfo();  
52 } 52 }
53 53
54 public TenantId getTenantId() { 54 public TenantId getTenantId() {
@@ -84,14 +84,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName { @@ -84,14 +84,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
84 this.type = type; 84 this.type = type;
85 } 85 }
86 86
87 - public JsonNode getAdditionalInfo() {  
88 - return additionalInfo;  
89 - }  
90 -  
91 - public void setAdditionalInfo(JsonNode additionalInfo) {  
92 - this.additionalInfo = additionalInfo;  
93 - }  
94 -  
95 @Override 87 @Override
96 public String getSearchText() { 88 public String getSearchText() {
97 return getName(); 89 return getName();
@@ -109,7 +101,7 @@ public class Asset extends SearchTextBased<AssetId> implements HasName { @@ -109,7 +101,7 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
109 builder.append(", type="); 101 builder.append(", type=");
110 builder.append(type); 102 builder.append(type);
111 builder.append(", additionalInfo="); 103 builder.append(", additionalInfo=");
112 - builder.append(additionalInfo); 104 + builder.append(getAdditionalInfo());
113 builder.append(", createdTime="); 105 builder.append(", createdTime=");
114 builder.append(createdTime); 106 builder.append(createdTime);
115 builder.append(", id="); 107 builder.append(", id=");
@@ -15,16 +15,24 @@ @@ -15,16 +15,24 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.plugin; 16 package org.thingsboard.server.common.data.plugin;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
18 import lombok.EqualsAndHashCode; 21 import lombok.EqualsAndHashCode;
  22 +import lombok.extern.slf4j.Slf4j;
19 import org.thingsboard.server.common.data.HasName; 23 import org.thingsboard.server.common.data.HasName;
20 -import org.thingsboard.server.common.data.SearchTextBased; 24 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
21 import org.thingsboard.server.common.data.id.PluginId; 25 import org.thingsboard.server.common.data.id.PluginId;
22 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
23 27
24 import com.fasterxml.jackson.databind.JsonNode; 28 import com.fasterxml.jackson.databind.JsonNode;
25 29
  30 +import java.io.ByteArrayInputStream;
  31 +import java.io.IOException;
  32 +
26 @EqualsAndHashCode(callSuper = true) 33 @EqualsAndHashCode(callSuper = true)
27 -public class PluginMetaData extends SearchTextBased<PluginId> implements HasName { 34 +@Slf4j
  35 +public class PluginMetaData extends SearchTextBasedWithAdditionalInfo<PluginId> implements HasName {
28 36
29 private static final long serialVersionUID = 1L; 37 private static final long serialVersionUID = 1L;
30 38
@@ -35,7 +43,9 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName @@ -35,7 +43,9 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
35 private boolean publicAccess; 43 private boolean publicAccess;
36 private ComponentLifecycleState state; 44 private ComponentLifecycleState state;
37 private transient JsonNode configuration; 45 private transient JsonNode configuration;
38 - private transient JsonNode additionalInfo; 46 + @JsonIgnore
  47 + private byte[] configurationBytes;
  48 +
39 49
40 public PluginMetaData() { 50 public PluginMetaData() {
41 super(); 51 super();
@@ -54,7 +64,6 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName @@ -54,7 +64,6 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
54 this.publicAccess = plugin.isPublicAccess(); 64 this.publicAccess = plugin.isPublicAccess();
55 this.state = plugin.getState(); 65 this.state = plugin.getState();
56 this.configuration = plugin.getConfiguration(); 66 this.configuration = plugin.getConfiguration();
57 - this.additionalInfo = plugin.getAdditionalInfo();  
58 } 67 }
59 68
60 @Override 69 @Override
@@ -96,11 +105,11 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName @@ -96,11 +105,11 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
96 } 105 }
97 106
98 public JsonNode getConfiguration() { 107 public JsonNode getConfiguration() {
99 - return configuration; 108 + return getJson(() -> configuration, () -> configurationBytes);
100 } 109 }
101 110
102 - public void setConfiguration(JsonNode configuration) {  
103 - this.configuration = configuration; 111 + public void setConfiguration(JsonNode data) {
  112 + setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes);
104 } 113 }
105 114
106 public boolean isPublicAccess() { 115 public boolean isPublicAccess() {
@@ -119,14 +128,6 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName @@ -119,14 +128,6 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
119 return state; 128 return state;
120 } 129 }
121 130
122 - public JsonNode getAdditionalInfo() {  
123 - return additionalInfo;  
124 - }  
125 -  
126 - public void setAdditionalInfo(JsonNode additionalInfo) {  
127 - this.additionalInfo = additionalInfo;  
128 - }  
129 -  
130 @Override 131 @Override
131 public String toString() { 132 public String toString() {
132 return "PluginMetaData [apiToken=" + apiToken + ", tenantId=" + tenantId + ", name=" + name + ", clazz=" + clazz + ", publicAccess=" + publicAccess 133 return "PluginMetaData [apiToken=" + apiToken + ", tenantId=" + tenantId + ", name=" + name + ", clazz=" + clazz + ", publicAccess=" + publicAccess
@@ -15,10 +15,20 @@ @@ -15,10 +15,20 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.relation; 16 package org.thingsboard.server.common.data.relation;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
18 import com.fasterxml.jackson.databind.JsonNode; 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
19 import org.thingsboard.server.common.data.id.EntityId; 24 import org.thingsboard.server.common.data.id.EntityId;
20 25
21 -public class EntityRelation { 26 +import java.io.ByteArrayInputStream;
  27 +import java.io.IOException;
  28 +import java.io.Serializable;
  29 +
  30 +@Slf4j
  31 +public class EntityRelation implements Serializable {
22 32
23 private static final long serialVersionUID = 2807343040519543363L; 33 private static final long serialVersionUID = 2807343040519543363L;
24 34
@@ -29,7 +39,9 @@ public class EntityRelation { @@ -29,7 +39,9 @@ public class EntityRelation {
29 private EntityId to; 39 private EntityId to;
30 private String type; 40 private String type;
31 private RelationTypeGroup typeGroup; 41 private RelationTypeGroup typeGroup;
32 - private JsonNode additionalInfo; 42 + private transient JsonNode additionalInfo;
  43 + @JsonIgnore
  44 + private byte[] additionalInfoBytes;
33 45
34 public EntityRelation() { 46 public EntityRelation() {
35 super(); 47 super();
@@ -92,11 +104,11 @@ public class EntityRelation { @@ -92,11 +104,11 @@ public class EntityRelation {
92 } 104 }
93 105
94 public JsonNode getAdditionalInfo() { 106 public JsonNode getAdditionalInfo() {
95 - return additionalInfo; 107 + return SearchTextBasedWithAdditionalInfo.getJson(() -> additionalInfo, () -> additionalInfoBytes);
96 } 108 }
97 109
98 - public void setAdditionalInfo(JsonNode additionalInfo) {  
99 - this.additionalInfo = additionalInfo; 110 + public void setAdditionalInfo(JsonNode addInfo) {
  111 + SearchTextBasedWithAdditionalInfo.setJson(addInfo, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes);
100 } 112 }
101 113
102 @Override 114 @Override
@@ -15,18 +15,23 @@ @@ -15,18 +15,23 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.rule; 16 package org.thingsboard.server.common.data.rule;
17 17
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
18 import com.fasterxml.jackson.databind.JsonNode; 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
19 import lombok.Data; 22 import lombok.Data;
20 import lombok.EqualsAndHashCode; 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
21 import org.thingsboard.server.common.data.HasName; 25 import org.thingsboard.server.common.data.HasName;
22 -import org.thingsboard.server.common.data.SearchTextBased; 26 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
23 import org.thingsboard.server.common.data.id.RuleId; 27 import org.thingsboard.server.common.data.id.RuleId;
24 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
25 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; 29 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
26 30
27 @Data 31 @Data
28 @EqualsAndHashCode(callSuper = true) 32 @EqualsAndHashCode(callSuper = true)
29 -public class RuleMetaData extends SearchTextBased<RuleId> implements HasName { 33 +@Slf4j
  34 +public class RuleMetaData extends SearchTextBasedWithAdditionalInfo<RuleId> implements HasName {
30 35
31 private static final long serialVersionUID = -5656679015122935465L; 36 private static final long serialVersionUID = -5656679015122935465L;
32 37
@@ -38,7 +43,13 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName { @@ -38,7 +43,13 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
38 private transient JsonNode filters; 43 private transient JsonNode filters;
39 private transient JsonNode processor; 44 private transient JsonNode processor;
40 private transient JsonNode action; 45 private transient JsonNode action;
41 - private transient JsonNode additionalInfo; 46 + @JsonIgnore
  47 + private byte[] filtersBytes;
  48 + @JsonIgnore
  49 + private byte[] processorBytes;
  50 + @JsonIgnore
  51 + private byte[] actionBytes;
  52 +
42 53
43 public RuleMetaData() { 54 public RuleMetaData() {
44 super(); 55 super();
@@ -55,10 +66,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName { @@ -55,10 +66,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
55 this.state = rule.getState(); 66 this.state = rule.getState();
56 this.weight = rule.getWeight(); 67 this.weight = rule.getWeight();
57 this.pluginToken = rule.getPluginToken(); 68 this.pluginToken = rule.getPluginToken();
58 - this.filters = rule.getFilters();  
59 - this.processor = rule.getProcessor();  
60 - this.action = rule.getAction();  
61 - this.additionalInfo = rule.getAdditionalInfo(); 69 + this.setFilters(rule.getFilters());
  70 + this.setProcessor(rule.getProcessor());
  71 + this.setAction(rule.getAction());
62 } 72 }
63 73
64 @Override 74 @Override
@@ -71,4 +81,29 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName { @@ -71,4 +81,29 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
71 return name; 81 return name;
72 } 82 }
73 83
  84 + public JsonNode getFilters() {
  85 + return SearchTextBasedWithAdditionalInfo.getJson(() -> filters, () -> filtersBytes);
  86 + }
  87 +
  88 + public JsonNode getProcessor() {
  89 + return SearchTextBasedWithAdditionalInfo.getJson(() -> processor, () -> processorBytes);
  90 + }
  91 +
  92 + public JsonNode getAction() {
  93 + return SearchTextBasedWithAdditionalInfo.getJson(() -> action, () -> actionBytes);
  94 + }
  95 +
  96 + public void setFilters(JsonNode data) {
  97 + setJson(data, json -> this.filters = json, bytes -> this.filtersBytes = bytes);
  98 + }
  99 +
  100 + public void setProcessor(JsonNode data) {
  101 + setJson(data, json -> this.processor = json, bytes -> this.processorBytes = bytes);
  102 + }
  103 +
  104 + public void setAction(JsonNode data) {
  105 + setJson(data, json -> this.action = json, bytes -> this.actionBytes = bytes);
  106 + }
  107 +
  108 +
74 } 109 }
@@ -74,6 +74,18 @@ @@ -74,6 +74,18 @@
74 <artifactId>mockito-all</artifactId> 74 <artifactId>mockito-all</artifactId>
75 <scope>test</scope> 75 <scope>test</scope>
76 </dependency> 76 </dependency>
  77 + <dependency>
  78 + <groupId>org.springframework</groupId>
  79 + <artifactId>spring-context</artifactId>
  80 + </dependency>
  81 + <dependency>
  82 + <groupId>com.google.guava</groupId>
  83 + <artifactId>guava</artifactId>
  84 + </dependency>
  85 + <dependency>
  86 + <groupId>org.apache.commons</groupId>
  87 + <artifactId>commons-lang3</artifactId>
  88 + </dependency>
77 </dependencies> 89 </dependencies>
78 90
79 </project> 91 </project>
  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.common.transport.quota;
  17 +
  18 +/**
  19 + * @author Vitaliy Paromskiy
  20 + * @version 1.0
  21 + */
  22 +public final class Clock {
  23 +
  24 + private static long time = 0L;
  25 +
  26 + private Clock() {
  27 + }
  28 +
  29 +
  30 + public static long millis() {
  31 + return time == 0 ? System.currentTimeMillis() : time;
  32 + }
  33 +
  34 + public static void setMillis(long millis) {
  35 + time = millis;
  36 + }
  37 +
  38 + public static void shift(long delta) {
  39 + time += delta;
  40 + }
  41 +
  42 + public static void reset() {
  43 + time = 0;
  44 + }
  45 +}
  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.common.transport.quota;
  17 +
  18 +import org.springframework.beans.factory.annotation.Value;
  19 +import org.springframework.stereotype.Component;
  20 +
  21 +/**
  22 + * @author Vitaliy Paromskiy
  23 + * @version 1.0
  24 + */
  25 +@Component
  26 +public class HostRequestLimitPolicy {
  27 +
  28 + private final long limit;
  29 +
  30 + public HostRequestLimitPolicy(@Value("${quota.host.limit}") long limit) {
  31 + this.limit = limit;
  32 + }
  33 +
  34 + public boolean isValid(long currentValue) {
  35 + return currentValue <= limit;
  36 + }
  37 +
  38 +}
  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.common.transport.quota;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.stereotype.Service;
  21 +import org.thingsboard.server.common.transport.quota.inmemory.HostRequestIntervalRegistry;
  22 +import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner;
  23 +import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger;
  24 +
  25 +import javax.annotation.PostConstruct;
  26 +import javax.annotation.PreDestroy;
  27 +
  28 +/**
  29 + * @author Vitaliy Paromskiy
  30 + * @version 1.0
  31 + */
  32 +@Service
  33 +@Slf4j
  34 +public class HostRequestsQuotaService implements QuotaService {
  35 +
  36 + private final HostRequestIntervalRegistry requestRegistry;
  37 + private final HostRequestLimitPolicy requestsPolicy;
  38 + private final IntervalRegistryCleaner registryCleaner;
  39 + private final IntervalRegistryLogger registryLogger;
  40 + private final boolean enabled;
  41 +
  42 + public HostRequestsQuotaService(HostRequestIntervalRegistry requestRegistry, HostRequestLimitPolicy requestsPolicy,
  43 + IntervalRegistryCleaner registryCleaner, IntervalRegistryLogger registryLogger,
  44 + @Value("${quota.host.enabled}") boolean enabled) {
  45 + this.requestRegistry = requestRegistry;
  46 + this.requestsPolicy = requestsPolicy;
  47 + this.registryCleaner = registryCleaner;
  48 + this.registryLogger = registryLogger;
  49 + this.enabled = enabled;
  50 + }
  51 +
  52 + @PostConstruct
  53 + public void init() {
  54 + if (enabled) {
  55 + registryCleaner.schedule();
  56 + registryLogger.schedule();
  57 + }
  58 + }
  59 +
  60 + @PreDestroy
  61 + public void close() {
  62 + if (enabled) {
  63 + registryCleaner.stop();
  64 + registryLogger.stop();
  65 + }
  66 + }
  67 +
  68 + @Override
  69 + public boolean isQuotaExceeded(String key) {
  70 + if (enabled) {
  71 + long count = requestRegistry.tick(key);
  72 + return !requestsPolicy.isValid(count);
  73 + }
  74 + return false;
  75 + }
  76 +}
  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.common.transport.quota;
  17 +
  18 +/**
  19 + * @author Vitaliy Paromskiy
  20 + * @version 1.0
  21 + */
  22 +public interface QuotaService {
  23 +
  24 + boolean isQuotaExceeded(String key);
  25 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +import com.google.common.collect.Sets;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.commons.lang3.StringUtils;
  21 +import org.springframework.beans.factory.annotation.Value;
  22 +import org.springframework.stereotype.Component;
  23 +
  24 +import javax.annotation.PostConstruct;
  25 +import java.util.Map;
  26 +import java.util.Set;
  27 +import java.util.concurrent.ConcurrentHashMap;
  28 +import java.util.stream.Collectors;
  29 +
  30 +/**
  31 + * @author Vitaliy Paromskiy
  32 + * @version 1.0
  33 + */
  34 +@Component
  35 +@Slf4j
  36 +public class HostRequestIntervalRegistry {
  37 +
  38 + private final Map<String, IntervalCount> hostCounts = new ConcurrentHashMap<>();
  39 + private final long intervalDurationMs;
  40 + private final long ttlMs;
  41 + private final Set<String> whiteList;
  42 + private final Set<String> blackList;
  43 +
  44 + public HostRequestIntervalRegistry(@Value("${quota.host.intervalMs}") long intervalDurationMs,
  45 + @Value("${quota.host.ttlMs}") long ttlMs,
  46 + @Value("${quota.host.whitelist}") String whiteList,
  47 + @Value("${quota.host.blacklist}") String blackList) {
  48 + this.intervalDurationMs = intervalDurationMs;
  49 + this.ttlMs = ttlMs;
  50 + this.whiteList = Sets.newHashSet(StringUtils.split(whiteList, ','));
  51 + this.blackList = Sets.newHashSet(StringUtils.split(blackList, ','));
  52 + }
  53 +
  54 + @PostConstruct
  55 + public void init() {
  56 + if (ttlMs < intervalDurationMs) {
  57 + log.warn("TTL for IntervalRegistry [{}] smaller than interval duration [{}]", ttlMs, intervalDurationMs);
  58 + }
  59 + log.info("Start Host Quota Service with whitelist {}", whiteList);
  60 + log.info("Start Host Quota Service with blacklist {}", blackList);
  61 + }
  62 +
  63 + public long tick(String clientHostId) {
  64 + if (whiteList.contains(clientHostId)) {
  65 + return 0;
  66 + } else if (blackList.contains(clientHostId)) {
  67 + return Long.MAX_VALUE;
  68 + }
  69 + IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs));
  70 + return intervalCount.resetIfExpiredAndTick();
  71 + }
  72 +
  73 + public void clean() {
  74 + hostCounts.entrySet().removeIf(entry -> entry.getValue().silenceDuration() > ttlMs);
  75 + }
  76 +
  77 + public Map<String, Long> getContent() {
  78 + return hostCounts.entrySet().stream()
  79 + .collect(Collectors.toMap(
  80 + Map.Entry::getKey,
  81 + interval -> interval.getValue().getCount()));
  82 + }
  83 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +
  19 +import org.thingsboard.server.common.transport.quota.Clock;
  20 +
  21 +import java.util.concurrent.atomic.LongAdder;
  22 +
  23 +/**
  24 + * @author Vitaliy Paromskiy
  25 + * @version 1.0
  26 + */
  27 +public class IntervalCount {
  28 +
  29 + private final LongAdder adder = new LongAdder();
  30 + private final long intervalDurationMs;
  31 + private volatile long startTime;
  32 + private volatile long lastTickTime;
  33 +
  34 + public IntervalCount(long intervalDurationMs) {
  35 + this.intervalDurationMs = intervalDurationMs;
  36 + startTime = Clock.millis();
  37 + }
  38 +
  39 + public long resetIfExpiredAndTick() {
  40 + if (isExpired()) {
  41 + reset();
  42 + }
  43 + tick();
  44 + return adder.sum();
  45 + }
  46 +
  47 + public long silenceDuration() {
  48 + return Clock.millis() - lastTickTime;
  49 + }
  50 +
  51 + public long getCount() {
  52 + return adder.sum();
  53 + }
  54 +
  55 + private void tick() {
  56 + adder.add(1);
  57 + lastTickTime = Clock.millis();
  58 + }
  59 +
  60 + private void reset() {
  61 + adder.reset();
  62 + startTime = Clock.millis();
  63 + }
  64 +
  65 + private boolean isExpired() {
  66 + return (Clock.millis() - startTime) > intervalDurationMs;
  67 + }
  68 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.stereotype.Component;
  21 +
  22 +import javax.annotation.PreDestroy;
  23 +import java.util.concurrent.Executors;
  24 +import java.util.concurrent.ScheduledExecutorService;
  25 +import java.util.concurrent.TimeUnit;
  26 +
  27 +/**
  28 + * @author Vitaliy Paromskiy
  29 + * @version 1.0
  30 + */
  31 +@Component
  32 +@Slf4j
  33 +public class IntervalRegistryCleaner {
  34 +
  35 + private final HostRequestIntervalRegistry intervalRegistry;
  36 + private final long cleanPeriodMs;
  37 + private ScheduledExecutorService executor;
  38 +
  39 + public IntervalRegistryCleaner(HostRequestIntervalRegistry intervalRegistry, @Value("${quota.host.cleanPeriodMs}") long cleanPeriodMs) {
  40 + this.intervalRegistry = intervalRegistry;
  41 + this.cleanPeriodMs = cleanPeriodMs;
  42 + }
  43 +
  44 + public void schedule() {
  45 + if (executor != null) {
  46 + throw new IllegalStateException("Registry Cleaner already scheduled");
  47 + }
  48 + executor = Executors.newSingleThreadScheduledExecutor();
  49 + executor.scheduleAtFixedRate(this::clean, cleanPeriodMs, cleanPeriodMs, TimeUnit.MILLISECONDS);
  50 + }
  51 +
  52 + public void stop() {
  53 + if (executor != null) {
  54 + executor.shutdown();
  55 + }
  56 + }
  57 +
  58 + public void clean() {
  59 + try {
  60 + intervalRegistry.clean();
  61 + } catch (RuntimeException ex) {
  62 + log.error("Could not clear Interval Registry", ex);
  63 + }
  64 + }
  65 +
  66 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +import com.google.common.collect.MinMaxPriorityQueue;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Value;
  21 +import org.springframework.stereotype.Component;
  22 +
  23 +import java.util.Comparator;
  24 +import java.util.Map;
  25 +import java.util.concurrent.Executors;
  26 +import java.util.concurrent.ScheduledExecutorService;
  27 +import java.util.concurrent.TimeUnit;
  28 +import java.util.function.Function;
  29 +import java.util.stream.Collectors;
  30 +
  31 +/**
  32 + * @author Vitaliy Paromskiy
  33 + * @version 1.0
  34 + */
  35 +@Component
  36 +@Slf4j
  37 +public class IntervalRegistryLogger {
  38 +
  39 + private final int topSize;
  40 + private final HostRequestIntervalRegistry intervalRegistry;
  41 + private final long logIntervalMin;
  42 + private ScheduledExecutorService executor;
  43 +
  44 + public IntervalRegistryLogger(@Value("${quota.log.topSize}") int topSize, @Value("${quota.log.intervalMin}") long logIntervalMin,
  45 + HostRequestIntervalRegistry intervalRegistry) {
  46 + this.topSize = topSize;
  47 + this.logIntervalMin = logIntervalMin;
  48 + this.intervalRegistry = intervalRegistry;
  49 + }
  50 +
  51 + public void schedule() {
  52 + if (executor != null) {
  53 + throw new IllegalStateException("Registry Cleaner already scheduled");
  54 + }
  55 + executor = Executors.newSingleThreadScheduledExecutor();
  56 + executor.scheduleAtFixedRate(this::logStatistic, logIntervalMin, logIntervalMin, TimeUnit.MINUTES);
  57 + }
  58 +
  59 + public void stop() {
  60 + if (executor != null) {
  61 + executor.shutdown();
  62 + }
  63 + }
  64 +
  65 + public void logStatistic() {
  66 + Map<String, Long> registryContent = intervalRegistry.getContent();
  67 + int uniqHosts = registryContent.size();
  68 + long requestsCount = registryContent.values().stream().mapToLong(i -> i).sum();
  69 + Map<String, Long> top = getTopElements(registryContent);
  70 + log(top, uniqHosts, requestsCount);
  71 + }
  72 +
  73 + protected Map<String, Long> getTopElements(Map<String, Long> countMap) {
  74 + MinMaxPriorityQueue<Map.Entry<String, Long>> topQueue = MinMaxPriorityQueue
  75 + .orderedBy(Comparator.comparing((Function<Map.Entry<String, Long>, Long>) Map.Entry::getValue).reversed())
  76 + .maximumSize(topSize)
  77 + .create(countMap.entrySet());
  78 +
  79 + return topQueue.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  80 + }
  81 +
  82 + private void log(Map<String, Long> top, int uniqHosts, long requestsCount) {
  83 + long rps = requestsCount / TimeUnit.MINUTES.toSeconds(logIntervalMin);
  84 + StringBuilder builder = new StringBuilder("Quota Statistic : ");
  85 + builder.append("uniqHosts : ").append(uniqHosts).append("; ");
  86 + builder.append("requestsCount : ").append(requestsCount).append("; ");
  87 + builder.append("RPS : ").append(rps).append(" ");
  88 + builder.append("top -> ");
  89 + for (Map.Entry<String, Long> host : top.entrySet()) {
  90 + builder.append(host.getKey()).append(" : ").append(host.getValue()).append("; ");
  91 + }
  92 +
  93 + log.info(builder.toString());
  94 + }
  95 +}
  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.common.transport.quota;
  17 +
  18 +import org.junit.After;
  19 +import org.junit.Before;
  20 +import org.junit.Test;
  21 +
  22 +import static org.junit.Assert.*;
  23 +
  24 +/**
  25 + * @author Vitaliy Paromskiy
  26 + * @version 1.0
  27 + */
  28 +public class ClockTest {
  29 +
  30 + @Before
  31 + public void init() {
  32 + Clock.reset();
  33 + }
  34 +
  35 + @After
  36 + public void clear() {
  37 + Clock.reset();
  38 + }
  39 +
  40 + @Test
  41 + public void defaultClockUseSystemTime() {
  42 + assertFalse(Clock.millis() > System.currentTimeMillis());
  43 + }
  44 +
  45 + @Test
  46 + public void timeCanBeSet() {
  47 + Clock.setMillis(100L);
  48 + assertEquals(100L, Clock.millis());
  49 + }
  50 +
  51 + @Test
  52 + public void clockCanBeReseted() {
  53 + Clock.setMillis(100L);
  54 + assertEquals(100L, Clock.millis());
  55 + Clock.reset();
  56 + assertFalse(Clock.millis() > System.currentTimeMillis());
  57 + }
  58 +
  59 + @Test
  60 + public void timeIsShifted() {
  61 + Clock.setMillis(100L);
  62 + Clock.shift(50L);
  63 + assertEquals(150L, Clock.millis());
  64 + }
  65 +
  66 +}
  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.common.transport.quota;
  17 +
  18 +import org.junit.Test;
  19 +
  20 +import static org.junit.Assert.assertFalse;
  21 +import static org.junit.Assert.assertTrue;
  22 +
  23 +/**
  24 + * @author Vitaliy Paromskiy
  25 + * @version 1.0
  26 + */
  27 +public class HostRequestLimitPolicyTest {
  28 +
  29 + private HostRequestLimitPolicy limitPolicy = new HostRequestLimitPolicy(10L);
  30 +
  31 + @Test
  32 + public void ifCurrentValueLessThenLimitItIsValid() {
  33 + assertTrue(limitPolicy.isValid(9));
  34 + }
  35 +
  36 + @Test
  37 + public void ifCurrentValueEqualsToLimitItIsValid() {
  38 + assertTrue(limitPolicy.isValid(10));
  39 + }
  40 +
  41 + @Test
  42 + public void ifCurrentValueGreaterThenLimitItIsValid() {
  43 + assertFalse(limitPolicy.isValid(11));
  44 + }
  45 +
  46 +}
  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.common.transport.quota;
  17 +
  18 +import org.junit.Before;
  19 +import org.junit.Test;
  20 +import org.thingsboard.server.common.transport.quota.inmemory.HostRequestIntervalRegistry;
  21 +import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner;
  22 +import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger;
  23 +
  24 +import static org.junit.Assert.assertFalse;
  25 +import static org.junit.Assert.assertTrue;
  26 +import static org.mockito.Mockito.*;
  27 +
  28 +/**
  29 + * @author Vitaliy Paromskiy
  30 + * @version 1.0
  31 + */
  32 +public class HostRequestsQuotaServiceTest {
  33 +
  34 + private HostRequestsQuotaService quotaService;
  35 +
  36 + private HostRequestIntervalRegistry requestRegistry = mock(HostRequestIntervalRegistry.class);
  37 + private HostRequestLimitPolicy requestsPolicy = mock(HostRequestLimitPolicy.class);
  38 + private IntervalRegistryCleaner registryCleaner = mock(IntervalRegistryCleaner.class);
  39 + private IntervalRegistryLogger registryLogger = mock(IntervalRegistryLogger.class);
  40 +
  41 + @Before
  42 + public void init() {
  43 + quotaService = new HostRequestsQuotaService(requestRegistry, requestsPolicy, registryCleaner, registryLogger, true);
  44 + }
  45 +
  46 + @Test
  47 + public void quotaExceededIfRequestCountBiggerThanAllowed() {
  48 + when(requestRegistry.tick("key")).thenReturn(10L);
  49 + when(requestsPolicy.isValid(10L)).thenReturn(false);
  50 +
  51 + assertTrue(quotaService.isQuotaExceeded("key"));
  52 +
  53 + verify(requestRegistry).tick("key");
  54 + verify(requestsPolicy).isValid(10L);
  55 + verifyNoMoreInteractions(requestRegistry, requestsPolicy);
  56 + }
  57 +
  58 + @Test
  59 + public void quotaNotExceededIfRequestCountLessThanAllowed() {
  60 + when(requestRegistry.tick("key")).thenReturn(10L);
  61 + when(requestsPolicy.isValid(10L)).thenReturn(true);
  62 +
  63 + assertFalse(quotaService.isQuotaExceeded("key"));
  64 +
  65 + verify(requestRegistry).tick("key");
  66 + verify(requestsPolicy).isValid(10L);
  67 + verifyNoMoreInteractions(requestRegistry, requestsPolicy);
  68 + }
  69 +
  70 + @Test
  71 + public void serviceCanBeDisabled() {
  72 + quotaService = new HostRequestsQuotaService(requestRegistry, requestsPolicy, registryCleaner, registryLogger, false);
  73 + assertFalse(quotaService.isQuotaExceeded("key"));
  74 + verifyNoMoreInteractions(requestRegistry, requestsPolicy);
  75 + }
  76 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +import com.google.common.collect.Sets;
  19 +import org.junit.Before;
  20 +import org.junit.Test;
  21 +
  22 +import java.util.Collections;
  23 +
  24 +import static org.junit.Assert.assertEquals;
  25 +
  26 +/**
  27 + * @author Vitaliy Paromskiy
  28 + * @version 1.0
  29 + */
  30 +public class HostRequestIntervalRegistryTest {
  31 +
  32 + private HostRequestIntervalRegistry registry;
  33 +
  34 + @Before
  35 + public void init() {
  36 + registry = new HostRequestIntervalRegistry(10000L, 100L,"g1,g2", "b1");
  37 + }
  38 +
  39 + @Test
  40 + public void newHostCreateNewInterval() {
  41 + assertEquals(1L, registry.tick("host1"));
  42 + }
  43 +
  44 + @Test
  45 + public void existingHostUpdated() {
  46 + registry.tick("aaa");
  47 + assertEquals(1L, registry.tick("bbb"));
  48 + assertEquals(2L, registry.tick("aaa"));
  49 + }
  50 +
  51 + @Test
  52 + public void expiredIntervalsCleaned() throws InterruptedException {
  53 + registry.tick("aaa");
  54 + Thread.sleep(150L);
  55 + registry.tick("bbb");
  56 + registry.clean();
  57 + assertEquals(1L, registry.tick("aaa"));
  58 + assertEquals(2L, registry.tick("bbb"));
  59 + }
  60 +
  61 + @Test
  62 + public void domainFromWhitelistNotCounted(){
  63 + assertEquals(0L, registry.tick("g1"));
  64 + assertEquals(0L, registry.tick("g1"));
  65 + assertEquals(0L, registry.tick("g2"));
  66 + }
  67 +
  68 + @Test
  69 + public void domainFromBlackListReturnMaxValue(){
  70 + assertEquals(Long.MAX_VALUE, registry.tick("b1"));
  71 + assertEquals(Long.MAX_VALUE, registry.tick("b1"));
  72 + }
  73 +
  74 + @Test
  75 + public void emptyWhitelistParsedOk(){
  76 + registry = new HostRequestIntervalRegistry(10000L, 100L,"", "b1");
  77 + assertEquals(1L, registry.tick("aaa"));
  78 + }
  79 +
  80 + @Test
  81 + public void emptyBlacklistParsedOk(){
  82 + registry = new HostRequestIntervalRegistry(10000L, 100L,"", "");
  83 + assertEquals(1L, registry.tick("aaa"));
  84 + }
  85 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +import org.junit.After;
  19 +import org.junit.Before;
  20 +import org.junit.Test;
  21 +import org.thingsboard.server.common.transport.quota.Clock;
  22 +
  23 +import static org.junit.Assert.assertEquals;
  24 +
  25 +/**
  26 + * @author Vitaliy Paromskiy
  27 + * @version 1.0
  28 + */
  29 +public class IntervalCountTest {
  30 +
  31 + @Before
  32 + public void init() {
  33 + Clock.setMillis(1000L);
  34 + }
  35 +
  36 + @After
  37 + public void clear() {
  38 + Clock.reset();
  39 + }
  40 +
  41 + @Test
  42 + public void ticksInSameIntervalAreSummed() {
  43 + IntervalCount intervalCount = new IntervalCount(100L);
  44 + assertEquals(1L, intervalCount.resetIfExpiredAndTick());
  45 + Clock.shift(100);
  46 + assertEquals(2L, intervalCount.resetIfExpiredAndTick());
  47 + }
  48 +
  49 + @Test
  50 + public void oldDataCleanedWhenIntervalExpired() {
  51 + IntervalCount intervalCount = new IntervalCount(100L);
  52 + assertEquals(1L, intervalCount.resetIfExpiredAndTick());
  53 + Clock.shift(101);
  54 + assertEquals(1L, intervalCount.resetIfExpiredAndTick());
  55 + }
  56 +
  57 + @Test
  58 + public void silenceDurationCalculatedFromLastTick() {
  59 + IntervalCount intervalCount = new IntervalCount(100L);
  60 + assertEquals(1L, intervalCount.resetIfExpiredAndTick());
  61 + Clock.shift(10L);
  62 + assertEquals(10L, intervalCount.silenceDuration());
  63 + }
  64 +
  65 +}
  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.common.transport.quota.inmemory;
  17 +
  18 +import com.google.common.collect.ImmutableMap;
  19 +import org.junit.Before;
  20 +import org.junit.Test;
  21 +
  22 +import java.util.Collections;
  23 +import java.util.Map;
  24 +
  25 +import static org.junit.Assert.assertEquals;
  26 +import static org.mockito.Mockito.mock;
  27 +
  28 +/**
  29 + * @author Vitaliy Paromskiy
  30 + * @version 1.0
  31 + */
  32 +public class IntervalRegistryLoggerTest {
  33 +
  34 + private IntervalRegistryLogger logger;
  35 +
  36 + private HostRequestIntervalRegistry requestRegistry = mock(HostRequestIntervalRegistry.class);
  37 +
  38 + @Before
  39 + public void init() {
  40 + logger = new IntervalRegistryLogger(3, 10, requestRegistry);
  41 + }
  42 +
  43 + @Test
  44 + public void onlyMaxHostsCollected() {
  45 + Map<String, Long> map = ImmutableMap.of("a", 8L, "b", 3L, "c", 1L, "d", 3L);
  46 + Map<String, Long> actual = logger.getTopElements(map);
  47 + Map<String, Long> expected = ImmutableMap.of("a", 8L, "b", 3L, "d", 3L);
  48 +
  49 + assertEquals(expected, actual);
  50 + }
  51 +
  52 + @Test
  53 + public void emptyMapProcessedCorrectly() {
  54 + Map<String, Long> map = Collections.emptyMap();
  55 + Map<String, Long> actual = logger.getTopElements(map);
  56 + Map<String, Long> expected = Collections.emptyMap();
  57 +
  58 + assertEquals(expected, actual);
  59 + }
  60 +
  61 +}
@@ -182,6 +182,14 @@ @@ -182,6 +182,14 @@
182 <groupId>org.springframework</groupId> 182 <groupId>org.springframework</groupId>
183 <artifactId>spring-context-support</artifactId> 183 <artifactId>spring-context-support</artifactId>
184 </dependency> 184 </dependency>
  185 + <dependency>
  186 + <groupId>org.springframework.data</groupId>
  187 + <artifactId>spring-data-redis</artifactId>
  188 + </dependency>
  189 + <dependency>
  190 + <groupId>redis.clients</groupId>
  191 + <artifactId>jedis</artifactId>
  192 + </dependency>
185 </dependencies> 193 </dependencies>
186 <build> 194 <build>
187 <plugins> 195 <plugins>
@@ -337,7 +337,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @@ -337,7 +337,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
337 337
338 private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) { 338 private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) {
339 try { 339 try {
340 - List<EntityRelation> relations = relationService.findByTo(alarm.getId(), RelationTypeGroup.ALARM).get(); 340 + List<EntityRelation> relations = relationService.findByToAsync(alarm.getId(), RelationTypeGroup.ALARM).get();
341 Set<EntityId> parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); 341 Set<EntityId> parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet());
342 for (EntityId parentId : parents) { 342 for (EntityId parentId : parents) {
343 updateAlarmRelation(parentId, alarm.getId(), oldStatus, newStatus); 343 updateAlarmRelation(parentId, alarm.getId(), oldStatus, newStatus);
dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java renamed from dao/src/main/java/org/thingsboard/server/dao/cache/ServiceCacheConfiguration.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.dao.cache; @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.cache;
18 import com.github.benmanes.caffeine.cache.Caffeine; 18 import com.github.benmanes.caffeine.cache.Caffeine;
19 import com.github.benmanes.caffeine.cache.Ticker; 19 import com.github.benmanes.caffeine.cache.Ticker;
20 import lombok.Data; 20 import lombok.Data;
  21 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 import org.springframework.boot.context.properties.ConfigurationProperties; 22 import org.springframework.boot.context.properties.ConfigurationProperties;
22 import org.springframework.cache.CacheManager; 23 import org.springframework.cache.CacheManager;
23 import org.springframework.cache.annotation.EnableCaching; 24 import org.springframework.cache.annotation.EnableCaching;
@@ -33,10 +34,11 @@ import java.util.concurrent.TimeUnit; @@ -33,10 +34,11 @@ import java.util.concurrent.TimeUnit;
33 import java.util.stream.Collectors; 34 import java.util.stream.Collectors;
34 35
35 @Configuration 36 @Configuration
36 -@ConfigurationProperties(prefix = "caching") 37 +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
  38 +@ConfigurationProperties(prefix = "caffeine")
37 @EnableCaching 39 @EnableCaching
38 @Data 40 @Data
39 -public class ServiceCacheConfiguration { 41 +public class CaffeineCacheConfiguration {
40 42
41 private Map<String, CacheSpecs> specs; 43 private Map<String, CacheSpecs> specs;
42 44
  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 +import org.springframework.beans.factory.annotation.Value;
  20 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  21 +import org.springframework.boot.context.properties.ConfigurationProperties;
  22 +import org.springframework.cache.CacheManager;
  23 +import org.springframework.cache.annotation.EnableCaching;
  24 +import org.springframework.cache.interceptor.KeyGenerator;
  25 +import org.springframework.context.annotation.Bean;
  26 +import org.springframework.context.annotation.Configuration;
  27 +import org.springframework.data.redis.cache.RedisCacheManager;
  28 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  29 +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
  30 +import org.springframework.data.redis.core.RedisTemplate;
  31 +
  32 +@Configuration
  33 +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false)
  34 +@EnableCaching
  35 +@Data
  36 +public class TBRedisCacheConfiguration {
  37 +
  38 + @Value("${redis.connection.host}")
  39 + private String host;
  40 +
  41 + @Value("${redis.connection.port}")
  42 + private Integer port;
  43 +
  44 + @Value("${redis.connection.db}")
  45 + private Integer db;
  46 +
  47 + @Value("${redis.connection.password}")
  48 + private String password;
  49 +
  50 + @Bean
  51 + public RedisConnectionFactory redisConnectionFactory() {
  52 + JedisConnectionFactory factory = new JedisConnectionFactory();
  53 + factory.setHostName(host);
  54 + factory.setPort(port);
  55 + factory.setDatabase(db);
  56 + factory.setPassword(password);
  57 + return factory;
  58 + }
  59 +
  60 + @Bean
  61 + public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
  62 + RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
  63 + redisTemplate.setConnectionFactory(cf);
  64 + return redisTemplate;
  65 + }
  66 +
  67 + @Bean
  68 + public CacheManager cacheManager(RedisTemplate redisTemplate) {
  69 + return new RedisCacheManager(redisTemplate);
  70 + }
  71 +
  72 + @Bean
  73 + public KeyGenerator previousDeviceCredentialsId() {
  74 + return new PreviousDeviceCredentialsIdKeyGenerator();
  75 + }
  76 +
  77 +
  78 +}
@@ -64,19 +64,28 @@ public class BaseRelationService implements RelationService { @@ -64,19 +64,28 @@ public class BaseRelationService implements RelationService {
64 return relationDao.checkRelation(from, to, relationType, typeGroup); 64 return relationDao.checkRelation(from, to, relationType, typeGroup);
65 } 65 }
66 66
67 - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType}") 67 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}")
68 @Override 68 @Override
69 - public ListenableFuture<EntityRelation> getRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { 69 + public EntityRelation getRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
  70 + try {
  71 + return getRelationAsync(from, to, relationType, typeGroup).get();
  72 + } catch (InterruptedException | ExecutionException e) {
  73 + throw new RuntimeException(e);
  74 + }
  75 + }
  76 +
  77 + @Override
  78 + public ListenableFuture<EntityRelation> getRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
70 log.trace("Executing EntityRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup); 79 log.trace("Executing EntityRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
71 validate(from, to, relationType, typeGroup); 80 validate(from, to, relationType, typeGroup);
72 return relationDao.getRelation(from, to, relationType, typeGroup); 81 return relationDao.getRelation(from, to, relationType, typeGroup);
73 } 82 }
74 83
75 @Caching(evict = { 84 @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}") 85 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
  86 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
  87 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
  88 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
80 }) 89 })
81 @Override 90 @Override
82 public boolean saveRelation(EntityRelation relation) { 91 public boolean saveRelation(EntityRelation relation) {
@@ -86,10 +95,10 @@ public class BaseRelationService implements RelationService { @@ -86,10 +95,10 @@ public class BaseRelationService implements RelationService {
86 } 95 }
87 96
88 @Caching(evict = { 97 @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}") 98 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
  99 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
  100 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
  101 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
93 }) 102 })
94 @Override 103 @Override
95 public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) { 104 public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
@@ -99,11 +108,11 @@ public class BaseRelationService implements RelationService { @@ -99,11 +108,11 @@ public class BaseRelationService implements RelationService {
99 } 108 }
100 109
101 @Caching(evict = { 110 @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}") 111 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
  112 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
  113 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
  114 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}"),
  115 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}")
107 }) 116 })
108 @Override 117 @Override
109 public boolean deleteRelation(EntityRelation relation) { 118 public boolean deleteRelation(EntityRelation relation) {
@@ -117,7 +126,7 @@ public class BaseRelationService implements RelationService { @@ -117,7 +126,7 @@ public class BaseRelationService implements RelationService {
117 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"), 126 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"),
118 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"), 127 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"),
119 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type}"), 128 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type}"),
120 - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type}") 129 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}")
121 }) 130 })
122 @Override 131 @Override
123 public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) { 132 public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
@@ -218,9 +227,9 @@ public class BaseRelationService implements RelationService { @@ -218,9 +227,9 @@ public class BaseRelationService implements RelationService {
218 ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo); 227 ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
219 ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo, 228 ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo,
220 (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> { 229 (AsyncFunction<List<List<EntityRelation>>, List<Boolean>>) relations -> {
221 - List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, true);  
222 - return Futures.allAsList(results);  
223 - }); 230 + List<ListenableFuture<Boolean>> results = getListenableFutures(relations, cache, true);
  231 + return Futures.allAsList(results);
  232 + });
224 233
225 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction()); 234 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
226 235
@@ -272,9 +281,18 @@ public class BaseRelationService implements RelationService { @@ -272,9 +281,18 @@ public class BaseRelationService implements RelationService {
272 cache.evict(fromToAndType); 281 cache.evict(fromToAndType);
273 } 282 }
274 283
275 - @Cacheable(cacheNames = RELATIONS_CACHE, key = "#from") 284 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
  285 + @Override
  286 + public List<EntityRelation> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
  287 + try {
  288 + return findByFromAsync(from, typeGroup).get();
  289 + } catch (InterruptedException | ExecutionException e) {
  290 + throw new RuntimeException(e);
  291 + }
  292 + }
  293 +
276 @Override 294 @Override
277 - public ListenableFuture<List<EntityRelation>> findByFrom(EntityId from, RelationTypeGroup typeGroup) { 295 + public ListenableFuture<List<EntityRelation>> findByFromAsync(EntityId from, RelationTypeGroup typeGroup) {
278 log.trace("Executing findByFrom [{}][{}]", from, typeGroup); 296 log.trace("Executing findByFrom [{}][{}]", from, typeGroup);
279 validate(from); 297 validate(from);
280 validateTypeGroup(typeGroup); 298 validateTypeGroup(typeGroup);
@@ -300,9 +318,18 @@ public class BaseRelationService implements RelationService { @@ -300,9 +318,18 @@ public class BaseRelationService implements RelationService {
300 return relationsInfo; 318 return relationsInfo;
301 } 319 }
302 320
303 - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType}") 321 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}")
  322 + @Override
  323 + public List<EntityRelation> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
  324 + try {
  325 + return findByFromAndTypeAsync(from, relationType, typeGroup).get();
  326 + } catch (InterruptedException | ExecutionException e) {
  327 + throw new RuntimeException(e);
  328 + }
  329 + }
  330 +
304 @Override 331 @Override
305 - public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) { 332 + public ListenableFuture<List<EntityRelation>> findByFromAndTypeAsync(EntityId from, String relationType, RelationTypeGroup typeGroup) {
306 log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup); 333 log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup);
307 validate(from); 334 validate(from);
308 validateType(relationType); 335 validateType(relationType);
@@ -310,9 +337,18 @@ public class BaseRelationService implements RelationService { @@ -310,9 +337,18 @@ public class BaseRelationService implements RelationService {
310 return relationDao.findAllByFromAndType(from, relationType, typeGroup); 337 return relationDao.findAllByFromAndType(from, relationType, typeGroup);
311 } 338 }
312 339
313 - @Cacheable(cacheNames = RELATIONS_CACHE, key = "#to") 340 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}")
  341 + @Override
  342 + public List<EntityRelation> findByTo(EntityId to, RelationTypeGroup typeGroup) {
  343 + try {
  344 + return findByToAsync(to, typeGroup).get();
  345 + } catch (InterruptedException | ExecutionException e) {
  346 + throw new RuntimeException(e);
  347 + }
  348 + }
  349 +
314 @Override 350 @Override
315 - public ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup) { 351 + public ListenableFuture<List<EntityRelation>> findByToAsync(EntityId to, RelationTypeGroup typeGroup) {
316 log.trace("Executing findByTo [{}][{}]", to, typeGroup); 352 log.trace("Executing findByTo [{}][{}]", to, typeGroup);
317 validate(to); 353 validate(to);
318 validateTypeGroup(typeGroup); 354 validateTypeGroup(typeGroup);
@@ -351,9 +387,18 @@ public class BaseRelationService implements RelationService { @@ -351,9 +387,18 @@ public class BaseRelationService implements RelationService {
351 return entityRelationInfo; 387 return entityRelationInfo;
352 } 388 }
353 389
354 - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType}") 390 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
  391 + @Override
  392 + public List<EntityRelation> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
  393 + try {
  394 + return findByToAndTypeAsync(to, relationType, typeGroup).get();
  395 + } catch (InterruptedException | ExecutionException e) {
  396 + throw new RuntimeException(e);
  397 + }
  398 + }
  399 +
355 @Override 400 @Override
356 - public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) { 401 + public ListenableFuture<List<EntityRelation>> findByToAndTypeAsync(EntityId to, String relationType, RelationTypeGroup typeGroup) {
357 log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup); 402 log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup);
358 validate(to); 403 validate(to);
359 validateType(relationType); 404 validateType(relationType);
@@ -527,9 +572,9 @@ public class BaseRelationService implements RelationService { @@ -527,9 +572,9 @@ public class BaseRelationService implements RelationService {
527 private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) { 572 private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) {
528 ListenableFuture<List<EntityRelation>> relations; 573 ListenableFuture<List<EntityRelation>> relations;
529 if (direction == EntitySearchDirection.FROM) { 574 if (direction == EntitySearchDirection.FROM) {
530 - relations = findByFrom(rootId, RelationTypeGroup.COMMON); 575 + relations = findByFromAsync(rootId, RelationTypeGroup.COMMON);
531 } else { 576 } else {
532 - relations = findByTo(rootId, RelationTypeGroup.COMMON); 577 + relations = findByToAsync(rootId, RelationTypeGroup.COMMON);
533 } 578 }
534 return relations; 579 return relations;
535 } 580 }
@@ -31,7 +31,9 @@ public interface RelationService { @@ -31,7 +31,9 @@ public interface RelationService {
31 31
32 ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); 32 ListenableFuture<Boolean> checkRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
33 33
34 - ListenableFuture<EntityRelation> getRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); 34 + EntityRelation getRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
  35 +
  36 + ListenableFuture<EntityRelation> getRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup);
35 37
36 boolean saveRelation(EntityRelation relation); 38 boolean saveRelation(EntityRelation relation);
37 39
@@ -49,17 +51,25 @@ public interface RelationService { @@ -49,17 +51,25 @@ public interface RelationService {
49 51
50 ListenableFuture<Boolean> deleteEntityRelationsAsync(EntityId entity); 52 ListenableFuture<Boolean> deleteEntityRelationsAsync(EntityId entity);
51 53
52 - ListenableFuture<List<EntityRelation>> findByFrom(EntityId from, RelationTypeGroup typeGroup); 54 + List<EntityRelation> findByFrom(EntityId from, RelationTypeGroup typeGroup);
  55 +
  56 + ListenableFuture<List<EntityRelation>> findByFromAsync(EntityId from, RelationTypeGroup typeGroup);
53 57
54 ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from, RelationTypeGroup typeGroup); 58 ListenableFuture<List<EntityRelationInfo>> findInfoByFrom(EntityId from, RelationTypeGroup typeGroup);
55 59
56 - ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup); 60 + List<EntityRelation> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup);
  61 +
  62 + ListenableFuture<List<EntityRelation>> findByFromAndTypeAsync(EntityId from, String relationType, RelationTypeGroup typeGroup);
57 63
58 - ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup); 64 + List<EntityRelation> findByTo(EntityId to, RelationTypeGroup typeGroup);
  65 +
  66 + ListenableFuture<List<EntityRelation>> findByToAsync(EntityId to, RelationTypeGroup typeGroup);
59 67
60 ListenableFuture<List<EntityRelationInfo>> findInfoByTo(EntityId to, RelationTypeGroup typeGroup); 68 ListenableFuture<List<EntityRelationInfo>> findInfoByTo(EntityId to, RelationTypeGroup typeGroup);
61 69
62 - ListenableFuture<List<EntityRelation>> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup); 70 + List<EntityRelation> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup);
  71 +
  72 + ListenableFuture<List<EntityRelation>> findByToAndTypeAsync(EntityId to, String relationType, RelationTypeGroup typeGroup);
63 73
64 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query); 74 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
65 75
@@ -59,7 +59,6 @@ CREATE TABLE IF NOT EXISTS audit_log ( @@ -59,7 +59,6 @@ CREATE TABLE IF NOT EXISTS audit_log (
59 action_type varchar(255), 59 action_type varchar(255),
60 action_data varchar(255), 60 action_data varchar(255),
61 action_status varchar(255), 61 action_status varchar(255),
62 - search_text varchar(255),  
63 action_failure_details varchar 62 action_failure_details varchar
64 ); 63 );
65 64
@@ -122,7 +122,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -122,7 +122,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
122 saveRelation(relationB1); 122 saveRelation(relationB1);
123 saveRelation(relationB2); 123 saveRelation(relationB2);
124 124
125 - List<EntityRelation> relations = relationService.findByFrom(parentA, RelationTypeGroup.COMMON).get(); 125 + List<EntityRelation> relations = relationService.findByFrom(parentA, RelationTypeGroup.COMMON);
126 Assert.assertEquals(2, relations.size()); 126 Assert.assertEquals(2, relations.size());
127 for (EntityRelation relation : relations) { 127 for (EntityRelation relation : relations) {
128 Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType()); 128 Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType());
@@ -130,13 +130,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -130,13 +130,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
130 Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo())); 130 Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
131 } 131 }
132 132
133 - relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get(); 133 + relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON);
134 Assert.assertEquals(2, relations.size()); 134 Assert.assertEquals(2, relations.size());
135 135
136 - relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get(); 136 + relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON);
137 Assert.assertEquals(0, relations.size()); 137 Assert.assertEquals(0, relations.size());
138 138
139 - relations = relationService.findByFrom(parentB, RelationTypeGroup.COMMON).get(); 139 + relations = relationService.findByFrom(parentB, RelationTypeGroup.COMMON);
140 Assert.assertEquals(2, relations.size()); 140 Assert.assertEquals(2, relations.size());
141 for (EntityRelation relation : relations) { 141 for (EntityRelation relation : relations) {
142 Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType()); 142 Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType());
@@ -144,10 +144,10 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -144,10 +144,10 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
144 Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo())); 144 Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo()));
145 } 145 }
146 146
147 - relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get(); 147 + relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON);
148 Assert.assertEquals(0, relations.size()); 148 Assert.assertEquals(0, relations.size());
149 149
150 - relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get(); 150 + relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON);
151 Assert.assertEquals(0, relations.size()); 151 Assert.assertEquals(0, relations.size());
152 } 152 }
153 153
@@ -177,26 +177,26 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -177,26 +177,26 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
177 // Data propagation to views is async 177 // Data propagation to views is async
178 Thread.sleep(3000); 178 Thread.sleep(3000);
179 179
180 - List<EntityRelation> relations = relationService.findByTo(childA, RelationTypeGroup.COMMON).get(); 180 + List<EntityRelation> relations = relationService.findByTo(childA, RelationTypeGroup.COMMON);
181 Assert.assertEquals(2, relations.size()); 181 Assert.assertEquals(2, relations.size());
182 for (EntityRelation relation : relations) { 182 for (EntityRelation relation : relations) {
183 Assert.assertEquals(childA, relation.getTo()); 183 Assert.assertEquals(childA, relation.getTo());
184 Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom())); 184 Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom()));
185 } 185 }
186 186
187 - relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get(); 187 + relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON);
188 Assert.assertEquals(1, relations.size()); 188 Assert.assertEquals(1, relations.size());
189 189
190 - relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get(); 190 + relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON);
191 Assert.assertEquals(1, relations.size()); 191 Assert.assertEquals(1, relations.size());
192 192
193 - relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get(); 193 + relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON);
194 Assert.assertEquals(0, relations.size()); 194 Assert.assertEquals(0, relations.size());
195 195
196 - relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON).get(); 196 + relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE, RelationTypeGroup.COMMON);
197 Assert.assertEquals(0, relations.size()); 197 Assert.assertEquals(0, relations.size());
198 198
199 - relations = relationService.findByTo(childB, RelationTypeGroup.COMMON).get(); 199 + relations = relationService.findByTo(childB, RelationTypeGroup.COMMON);
200 Assert.assertEquals(2, relations.size()); 200 Assert.assertEquals(2, relations.size());
201 for (EntityRelation relation : relations) { 201 for (EntityRelation relation : relations) {
202 Assert.assertEquals(childB, relation.getTo()); 202 Assert.assertEquals(childB, relation.getTo());
1 -cache.enabled=false  
2 -cache.device_credentials.time_to_live=3600  
3 -cache.device_credentials.max_size.size=1000000  
4 -cache.device_credentials.max_size.policy=PER_NODE  
5 -  
6 zk.enabled=false 1 zk.enabled=false
7 zk.url=localhost:2181 2 zk.url=localhost:2181
8 zk.zk_dir=/thingsboard 3 zk.zk_dir=/thingsboard
@@ -14,11 +9,22 @@ audit_log.exceptions.enabled=false @@ -14,11 +9,22 @@ audit_log.exceptions.enabled=false
14 audit_log.by_tenant_partitioning=MONTHS 9 audit_log.by_tenant_partitioning=MONTHS
15 audit_log.default_query_period=30 10 audit_log.default_query_period=30
16 11
17 -caching.specs.relations.timeToLiveInMinutes=1440  
18 -caching.specs.relations.maxSize=100000 12 +cache.type=caffeine
  13 +#cache.type=redis
  14 +
  15 +caffeine.specs.relations.timeToLiveInMinutes=1440
  16 +caffeine.specs.relations.maxSize=100000
19 17
20 -caching.specs.deviceCredentials.timeToLiveInMinutes=1440  
21 -caching.specs.deviceCredentials.maxSize=100000 18 +caffeine.specs.deviceCredentials.timeToLiveInMinutes=1440
  19 +caffeine.specs.deviceCredentials.maxSize=100000
  20 +
  21 +caffeine.specs.devices.timeToLiveInMinutes=1440
  22 +caffeine.specs.devices.maxSize=100000
22 23
23 caching.specs.devices.timeToLiveInMinutes=1440 24 caching.specs.devices.timeToLiveInMinutes=1440
24 caching.specs.devices.maxSize=100000 25 caching.specs.devices.maxSize=100000
  26 +
  27 +redis.connection.host=localhost
  28 +redis.connection.port=6379
  29 +redis.connection.db=0
  30 +redis.connection.password=
  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 +
  17 +version: '3.3'
  18 +services:
  19 + redis:
  20 + image: redis:4.0
  21 + networks:
  22 + - core
  23 + ports:
  24 + - "6379:6379"
  25 +
  26 +networks:
  27 + core:
@@ -32,6 +32,8 @@ @@ -32,6 +32,8 @@
32 <spring-boot.version>1.4.3.RELEASE</spring-boot.version> 32 <spring-boot.version>1.4.3.RELEASE</spring-boot.version>
33 <spring.version>4.3.4.RELEASE</spring.version> 33 <spring.version>4.3.4.RELEASE</spring.version>
34 <spring-security.version>4.2.0.RELEASE</spring-security.version> 34 <spring-security.version>4.2.0.RELEASE</spring-security.version>
  35 + <spring-data-redis.version>1.8.10.RELEASE</spring-data-redis.version>
  36 + <jedis.version>2.9.0</jedis.version>
35 <jjwt.version>0.7.0</jjwt.version> 37 <jjwt.version>0.7.0</jjwt.version>
36 <json-path.version>2.2.0</json-path.version> 38 <json-path.version>2.2.0</json-path.version>
37 <junit.version>4.12</junit.version> 39 <junit.version>4.12</junit.version>
@@ -784,6 +786,16 @@ @@ -784,6 +786,16 @@
784 <scope>test</scope> 786 <scope>test</scope>
785 </dependency> 787 </dependency>
786 <dependency> 788 <dependency>
  789 + <groupId>org.springframework.data</groupId>
  790 + <artifactId>spring-data-redis</artifactId>
  791 + <version>${spring-data-redis.version}</version>
  792 + </dependency>
  793 + <dependency>
  794 + <groupId>redis.clients</groupId>
  795 + <artifactId>jedis</artifactId>
  796 + <version>${jedis.version}</version>
  797 + </dependency>
  798 + <dependency>
787 <groupId>com.sun.winsw</groupId> 799 <groupId>com.sun.winsw</groupId>
788 <artifactId>winsw</artifactId> 800 <artifactId>winsw</artifactId>
789 <version>${winsw.version}</version> 801 <version>${winsw.version}</version>
@@ -34,6 +34,7 @@ import org.thingsboard.server.common.msg.session.*; @@ -34,6 +34,7 @@ import org.thingsboard.server.common.msg.session.*;
34 import org.thingsboard.server.common.transport.SessionMsgProcessor; 34 import org.thingsboard.server.common.transport.SessionMsgProcessor;
35 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 35 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
36 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 36 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  37 +import org.thingsboard.server.common.transport.quota.QuotaService;
37 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; 38 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
38 import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; 39 import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy;
39 import org.thingsboard.server.transport.coap.session.CoapSessionCtx; 40 import org.thingsboard.server.transport.coap.session.CoapSessionCtx;
@@ -51,13 +52,16 @@ public class CoapTransportResource extends CoapResource { @@ -51,13 +52,16 @@ public class CoapTransportResource extends CoapResource {
51 private final CoapTransportAdaptor adaptor; 52 private final CoapTransportAdaptor adaptor;
52 private final SessionMsgProcessor processor; 53 private final SessionMsgProcessor processor;
53 private final DeviceAuthService authService; 54 private final DeviceAuthService authService;
  55 + private final QuotaService quotaService;
54 private final Field observerField; 56 private final Field observerField;
55 private final long timeout; 57 private final long timeout;
56 58
57 - public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, long timeout) { 59 + public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name,
  60 + long timeout, QuotaService quotaService) {
58 super(name); 61 super(name);
59 this.processor = processor; 62 this.processor = processor;
60 this.authService = authService; 63 this.authService = authService;
  64 + this.quotaService = quotaService;
61 this.adaptor = adaptor; 65 this.adaptor = adaptor;
62 this.timeout = timeout; 66 this.timeout = timeout;
63 // This is important to turn off existing observable logic in 67 // This is important to turn off existing observable logic in
@@ -70,6 +74,12 @@ public class CoapTransportResource extends CoapResource { @@ -70,6 +74,12 @@ public class CoapTransportResource extends CoapResource {
70 74
71 @Override 75 @Override
72 public void handleGET(CoapExchange exchange) { 76 public void handleGET(CoapExchange exchange) {
  77 + if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
  78 + log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
  79 + exchange.respond(ResponseCode.BAD_REQUEST);
  80 + return;
  81 + }
  82 +
73 Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest()); 83 Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
74 if (!featureType.isPresent()) { 84 if (!featureType.isPresent()) {
75 log.trace("Missing feature type parameter"); 85 log.trace("Missing feature type parameter");
@@ -15,25 +15,25 @@ @@ -15,25 +15,25 @@
15 */ 15 */
16 package org.thingsboard.server.transport.coap; 16 package org.thingsboard.server.transport.coap;
17 17
18 -import java.net.InetAddress;  
19 -import java.net.InetSocketAddress;  
20 -import java.net.UnknownHostException;  
21 -  
22 -import javax.annotation.PostConstruct;  
23 -import javax.annotation.PreDestroy;  
24 -  
25 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
26 import org.eclipse.californium.core.CoapResource; 19 import org.eclipse.californium.core.CoapResource;
27 import org.eclipse.californium.core.CoapServer; 20 import org.eclipse.californium.core.CoapServer;
28 import org.eclipse.californium.core.network.CoapEndpoint; 21 import org.eclipse.californium.core.network.CoapEndpoint;
29 -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
30 -import org.thingsboard.server.common.transport.SessionMsgProcessor;  
31 -import org.thingsboard.server.common.transport.auth.DeviceAuthService;  
32 -import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;  
33 import org.springframework.beans.factory.annotation.Autowired; 22 import org.springframework.beans.factory.annotation.Autowired;
34 import org.springframework.beans.factory.annotation.Value; 23 import org.springframework.beans.factory.annotation.Value;
  24 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
35 import org.springframework.context.ApplicationContext; 25 import org.springframework.context.ApplicationContext;
36 import org.springframework.stereotype.Service; 26 import org.springframework.stereotype.Service;
  27 +import org.thingsboard.server.common.transport.SessionMsgProcessor;
  28 +import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  29 +import org.thingsboard.server.common.transport.quota.QuotaService;
  30 +import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
  31 +
  32 +import javax.annotation.PostConstruct;
  33 +import javax.annotation.PreDestroy;
  34 +import java.net.InetAddress;
  35 +import java.net.InetSocketAddress;
  36 +import java.net.UnknownHostException;
37 37
38 @Service("CoapTransportService") 38 @Service("CoapTransportService")
39 @ConditionalOnProperty(prefix = "coap", value = "enabled", havingValue = "true", matchIfMissing = true) 39 @ConditionalOnProperty(prefix = "coap", value = "enabled", havingValue = "true", matchIfMissing = true)
@@ -54,6 +54,9 @@ public class CoapTransportService { @@ -54,6 +54,9 @@ public class CoapTransportService {
54 @Autowired(required = false) 54 @Autowired(required = false)
55 private DeviceAuthService authService; 55 private DeviceAuthService authService;
56 56
  57 + @Autowired(required = false)
  58 + private QuotaService quotaService;
  59 +
57 60
58 @Value("${coap.bind_address}") 61 @Value("${coap.bind_address}")
59 private String host; 62 private String host;
@@ -83,7 +86,7 @@ public class CoapTransportService { @@ -83,7 +86,7 @@ public class CoapTransportService {
83 86
84 private void createResources() { 87 private void createResources() {
85 CoapResource api = new CoapResource(API); 88 CoapResource api = new CoapResource(API);
86 - api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout)); 89 + api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService));
87 server.add(api); 90 server.add(api);
88 } 91 }
89 92
@@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.session.*; @@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.session.*;
50 import org.thingsboard.server.common.transport.SessionMsgProcessor; 50 import org.thingsboard.server.common.transport.SessionMsgProcessor;
51 import org.thingsboard.server.common.transport.auth.DeviceAuthResult; 51 import org.thingsboard.server.common.transport.auth.DeviceAuthResult;
52 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 52 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  53 +import org.thingsboard.server.common.transport.quota.QuotaService;
53 54
54 import java.util.ArrayList; 55 import java.util.ArrayList;
55 import java.util.List; 56 import java.util.List;
@@ -131,6 +132,11 @@ public class CoapServerTest { @@ -131,6 +132,11 @@ public class CoapServerTest {
131 } 132 }
132 }; 133 };
133 } 134 }
  135 +
  136 + @Bean
  137 + public static QuotaService quotaService() {
  138 + return key -> false;
  139 + }
134 } 140 }
135 141
136 @Autowired 142 @Autowired
@@ -35,10 +35,11 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg; @@ -35,10 +35,11 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg;
35 import org.thingsboard.server.common.transport.SessionMsgProcessor; 35 import org.thingsboard.server.common.transport.SessionMsgProcessor;
36 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 36 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
37 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 37 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  38 +import org.thingsboard.server.common.transport.quota.QuotaService;
38 import org.thingsboard.server.transport.http.session.HttpSessionCtx; 39 import org.thingsboard.server.transport.http.session.HttpSessionCtx;
39 40
  41 +import javax.servlet.http.HttpServletRequest;
40 import java.util.Arrays; 42 import java.util.Arrays;
41 -import java.util.Collections;  
42 import java.util.HashSet; 43 import java.util.HashSet;
43 import java.util.Set; 44 import java.util.Set;
44 45
@@ -59,11 +60,18 @@ public class DeviceApiController { @@ -59,11 +60,18 @@ public class DeviceApiController {
59 @Autowired(required = false) 60 @Autowired(required = false)
60 private DeviceAuthService authService; 61 private DeviceAuthService authService;
61 62
  63 + @Autowired(required = false)
  64 + private QuotaService quotaService;
  65 +
62 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") 66 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
63 public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, 67 public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
64 @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, 68 @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
65 - @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys) { 69 + @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys,
  70 + HttpServletRequest httpRequest) {
66 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); 71 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  72 + if (quotaExceeded(httpRequest, responseWriter)) {
  73 + return responseWriter;
  74 + }
67 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 75 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
68 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 76 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
69 GetAttributesRequest request; 77 GetAttributesRequest request;
@@ -84,8 +92,11 @@ public class DeviceApiController { @@ -84,8 +92,11 @@ public class DeviceApiController {
84 92
85 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST) 93 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST)
86 public DeferredResult<ResponseEntity> postDeviceAttributes(@PathVariable("deviceToken") String deviceToken, 94 public DeferredResult<ResponseEntity> postDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
87 - @RequestBody String json) { 95 + @RequestBody String json, HttpServletRequest request) {
88 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); 96 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  97 + if (quotaExceeded(request, responseWriter)) {
  98 + return responseWriter;
  99 + }
89 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 100 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
90 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 101 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
91 try { 102 try {
@@ -101,8 +112,11 @@ public class DeviceApiController { @@ -101,8 +112,11 @@ public class DeviceApiController {
101 112
102 @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST) 113 @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST)
103 public DeferredResult<ResponseEntity> postTelemetry(@PathVariable("deviceToken") String deviceToken, 114 public DeferredResult<ResponseEntity> postTelemetry(@PathVariable("deviceToken") String deviceToken,
104 - @RequestBody String json) { 115 + @RequestBody String json, HttpServletRequest request) {
105 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); 116 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  117 + if (quotaExceeded(request, responseWriter)) {
  118 + return responseWriter;
  119 + }
106 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 120 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
107 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 121 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
108 try { 122 try {
@@ -118,15 +132,20 @@ public class DeviceApiController { @@ -118,15 +132,20 @@ public class DeviceApiController {
118 132
119 @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json") 133 @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json")
120 public DeferredResult<ResponseEntity> subscribeToCommands(@PathVariable("deviceToken") String deviceToken, 134 public DeferredResult<ResponseEntity> subscribeToCommands(@PathVariable("deviceToken") String deviceToken,
121 - @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout) {  
122 - return subscribe(deviceToken, timeout, new RpcSubscribeMsg()); 135 + @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
  136 + HttpServletRequest request) {
  137 +
  138 + return subscribe(deviceToken, timeout, new RpcSubscribeMsg(), request);
123 } 139 }
124 140
125 @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST) 141 @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST)
126 public DeferredResult<ResponseEntity> replyToCommand(@PathVariable("deviceToken") String deviceToken, 142 public DeferredResult<ResponseEntity> replyToCommand(@PathVariable("deviceToken") String deviceToken,
127 @PathVariable("requestId") Integer requestId, 143 @PathVariable("requestId") Integer requestId,
128 - @RequestBody String json) { 144 + @RequestBody String json, HttpServletRequest request) {
129 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); 145 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  146 + if (quotaExceeded(request, responseWriter)) {
  147 + return responseWriter;
  148 + }
130 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 149 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
131 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 150 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
132 try { 151 try {
@@ -143,8 +162,11 @@ public class DeviceApiController { @@ -143,8 +162,11 @@ public class DeviceApiController {
143 162
144 @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST) 163 @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST)
145 public DeferredResult<ResponseEntity> postRpcRequest(@PathVariable("deviceToken") String deviceToken, 164 public DeferredResult<ResponseEntity> postRpcRequest(@PathVariable("deviceToken") String deviceToken,
146 - @RequestBody String json) { 165 + @RequestBody String json, HttpServletRequest httpRequest) {
147 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); 166 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  167 + if (quotaExceeded(httpRequest, responseWriter)) {
  168 + return responseWriter;
  169 + }
148 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); 170 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
149 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 171 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
150 try { 172 try {
@@ -163,12 +185,17 @@ public class DeviceApiController { @@ -163,12 +185,17 @@ public class DeviceApiController {
163 185
164 @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json") 186 @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json")
165 public DeferredResult<ResponseEntity> subscribeToAttributes(@PathVariable("deviceToken") String deviceToken, 187 public DeferredResult<ResponseEntity> subscribeToAttributes(@PathVariable("deviceToken") String deviceToken,
166 - @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout) {  
167 - return subscribe(deviceToken, timeout, new AttributesSubscribeMsg()); 188 + @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
  189 + HttpServletRequest httpRequest) {
  190 +
  191 + return subscribe(deviceToken, timeout, new AttributesSubscribeMsg(), httpRequest);
168 } 192 }
169 193
170 - private DeferredResult<ResponseEntity> subscribe(String deviceToken, long timeout, FromDeviceMsg msg) { 194 + private DeferredResult<ResponseEntity> subscribe(String deviceToken, long timeout, FromDeviceMsg msg, HttpServletRequest httpRequest) {
171 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>(); 195 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  196 + if (quotaExceeded(httpRequest, responseWriter)) {
  197 + return responseWriter;
  198 + }
172 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout); 199 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout);
173 if (ctx.login(new DeviceTokenCredentials(deviceToken))) { 200 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
174 try { 201 try {
@@ -195,4 +222,13 @@ public class DeviceApiController { @@ -195,4 +222,13 @@ public class DeviceApiController {
195 processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg)); 222 processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg));
196 } 223 }
197 224
  225 + private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) {
  226 + if (quotaService.isQuotaExceeded(request.getRemoteAddr())) {
  227 + log.warn("REST Quota exceeded for [{}] . Disconnect", request.getRemoteAddr());
  228 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED));
  229 + return true;
  230 + }
  231 + return false;
  232 + }
  233 +
198 } 234 }
@@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
16 package org.thingsboard.server.transport.mqtt; 16 package org.thingsboard.server.transport.mqtt;
17 17
18 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
19 -import io.netty.channel.Channel;  
20 import io.netty.channel.ChannelHandlerContext; 19 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelInboundHandlerAdapter; 20 import io.netty.channel.ChannelInboundHandlerAdapter;
22 import io.netty.handler.codec.mqtt.*; 21 import io.netty.handler.codec.mqtt.*;
@@ -36,18 +35,18 @@ import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; @@ -36,18 +35,18 @@ import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
36 import org.thingsboard.server.common.transport.SessionMsgProcessor; 35 import org.thingsboard.server.common.transport.SessionMsgProcessor;
37 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 36 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
38 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 37 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  38 +import org.thingsboard.server.common.transport.quota.QuotaService;
39 import org.thingsboard.server.dao.EncryptionUtil; 39 import org.thingsboard.server.dao.EncryptionUtil;
40 import org.thingsboard.server.dao.device.DeviceService; 40 import org.thingsboard.server.dao.device.DeviceService;
41 import org.thingsboard.server.dao.relation.RelationService; 41 import org.thingsboard.server.dao.relation.RelationService;
42 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 42 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
43 -import org.thingsboard.server.transport.mqtt.session.GatewaySessionCtx;  
44 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; 43 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
  44 +import org.thingsboard.server.transport.mqtt.session.GatewaySessionCtx;
45 import org.thingsboard.server.transport.mqtt.util.SslUtil; 45 import org.thingsboard.server.transport.mqtt.util.SslUtil;
46 46
47 import javax.net.ssl.SSLPeerUnverifiedException; 47 import javax.net.ssl.SSLPeerUnverifiedException;
48 import javax.security.cert.X509Certificate; 48 import javax.security.cert.X509Certificate;
49 import java.net.InetSocketAddress; 49 import java.net.InetSocketAddress;
50 -import java.net.SocketAddress;  
51 import java.util.ArrayList; 50 import java.util.ArrayList;
52 import java.util.List; 51 import java.util.List;
53 52
@@ -72,13 +71,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -72,13 +71,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
72 private final DeviceService deviceService; 71 private final DeviceService deviceService;
73 private final DeviceAuthService authService; 72 private final DeviceAuthService authService;
74 private final RelationService relationService; 73 private final RelationService relationService;
  74 + private final QuotaService quotaService;
75 private final SslHandler sslHandler; 75 private final SslHandler sslHandler;
76 private volatile boolean connected; 76 private volatile boolean connected;
77 private volatile InetSocketAddress address; 77 private volatile InetSocketAddress address;
78 private volatile GatewaySessionCtx gatewaySessionCtx; 78 private volatile GatewaySessionCtx gatewaySessionCtx;
79 79
80 public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, 80 public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
81 - MqttTransportAdaptor adaptor, SslHandler sslHandler) { 81 + MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService) {
82 this.processor = processor; 82 this.processor = processor;
83 this.deviceService = deviceService; 83 this.deviceService = deviceService;
84 this.relationService = relationService; 84 this.relationService = relationService;
@@ -87,6 +87,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -87,6 +87,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
87 this.deviceSessionCtx = new DeviceSessionCtx(processor, authService, adaptor); 87 this.deviceSessionCtx = new DeviceSessionCtx(processor, authService, adaptor);
88 this.sessionId = deviceSessionCtx.getSessionId().toUidStr(); 88 this.sessionId = deviceSessionCtx.getSessionId().toUidStr();
89 this.sslHandler = sslHandler; 89 this.sslHandler = sslHandler;
  90 + this.quotaService = quotaService;
90 } 91 }
91 92
92 @Override 93 @Override
@@ -102,35 +103,43 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -102,35 +103,43 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
102 if (msg.fixedHeader() == null) { 103 if (msg.fixedHeader() == null) {
103 log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort()); 104 log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort());
104 processDisconnect(ctx); 105 processDisconnect(ctx);
105 - } else {  
106 - deviceSessionCtx.setChannel(ctx);  
107 - switch (msg.fixedHeader().messageType()) {  
108 - case CONNECT:  
109 - processConnect(ctx, (MqttConnectMessage) msg);  
110 - break;  
111 - case PUBLISH:  
112 - processPublish(ctx, (MqttPublishMessage) msg);  
113 - break;  
114 - case SUBSCRIBE:  
115 - processSubscribe(ctx, (MqttSubscribeMessage) msg);  
116 - break;  
117 - case UNSUBSCRIBE:  
118 - processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);  
119 - break;  
120 - case PINGREQ:  
121 - if (checkConnected(ctx)) {  
122 - ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));  
123 - }  
124 - break;  
125 - case DISCONNECT:  
126 - if (checkConnected(ctx)) {  
127 - processDisconnect(ctx);  
128 - }  
129 - break;  
130 - default:  
131 - break;  
132 - } 106 + return;
  107 + }
  108 +
  109 + if (quotaService.isQuotaExceeded(address.getHostName())) {
  110 + log.warn("MQTT Quota exceeded for [{}:{}] . Disconnect", address.getHostName(), address.getPort());
  111 + processDisconnect(ctx);
  112 + return;
133 } 113 }
  114 +
  115 + deviceSessionCtx.setChannel(ctx);
  116 + switch (msg.fixedHeader().messageType()) {
  117 + case CONNECT:
  118 + processConnect(ctx, (MqttConnectMessage) msg);
  119 + break;
  120 + case PUBLISH:
  121 + processPublish(ctx, (MqttPublishMessage) msg);
  122 + break;
  123 + case SUBSCRIBE:
  124 + processSubscribe(ctx, (MqttSubscribeMessage) msg);
  125 + break;
  126 + case UNSUBSCRIBE:
  127 + processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
  128 + break;
  129 + case PINGREQ:
  130 + if (checkConnected(ctx)) {
  131 + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
  132 + }
  133 + break;
  134 + case DISCONNECT:
  135 + if (checkConnected(ctx)) {
  136 + processDisconnect(ctx);
  137 + }
  138 + break;
  139 + default:
  140 + break;
  141 + }
  142 +
134 } 143 }
135 144
136 private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) { 145 private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) {
@@ -15,27 +15,19 @@ @@ -15,27 +15,19 @@
15 */ 15 */
16 package org.thingsboard.server.transport.mqtt; 16 package org.thingsboard.server.transport.mqtt;
17 17
18 -import io.netty.buffer.ByteBufAllocator;  
19 -import io.netty.channel.ChannelHandler;  
20 import io.netty.channel.ChannelInitializer; 18 import io.netty.channel.ChannelInitializer;
21 import io.netty.channel.ChannelPipeline; 19 import io.netty.channel.ChannelPipeline;
22 import io.netty.channel.socket.SocketChannel; 20 import io.netty.channel.socket.SocketChannel;
23 import io.netty.handler.codec.mqtt.MqttDecoder; 21 import io.netty.handler.codec.mqtt.MqttDecoder;
24 import io.netty.handler.codec.mqtt.MqttEncoder; 22 import io.netty.handler.codec.mqtt.MqttEncoder;
25 -import io.netty.handler.ssl.SslContext;  
26 -import io.netty.handler.ssl.SslContextBuilder;  
27 import io.netty.handler.ssl.SslHandler; 23 import io.netty.handler.ssl.SslHandler;
28 -import io.netty.handler.ssl.util.SelfSignedCertificate;  
29 -import org.springframework.beans.factory.annotation.Value;  
30 import org.thingsboard.server.common.transport.SessionMsgProcessor; 24 import org.thingsboard.server.common.transport.SessionMsgProcessor;
31 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 25 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  26 +import org.thingsboard.server.common.transport.quota.QuotaService;
32 import org.thingsboard.server.dao.device.DeviceService; 27 import org.thingsboard.server.dao.device.DeviceService;
33 import org.thingsboard.server.dao.relation.RelationService; 28 import org.thingsboard.server.dao.relation.RelationService;
34 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 29 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
35 30
36 -import javax.net.ssl.SSLException;  
37 -import java.security.cert.CertificateException;  
38 -  
39 /** 31 /**
40 * @author Andrew Shvayka 32 * @author Andrew Shvayka
41 */ 33 */
@@ -49,16 +41,18 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha @@ -49,16 +41,18 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
49 private final RelationService relationService; 41 private final RelationService relationService;
50 private final MqttTransportAdaptor adaptor; 42 private final MqttTransportAdaptor adaptor;
51 private final MqttSslHandlerProvider sslHandlerProvider; 43 private final MqttSslHandlerProvider sslHandlerProvider;
  44 + private final QuotaService quotaService;
52 45
53 public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, 46 public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
54 - MqttTransportAdaptor adaptor,  
55 - MqttSslHandlerProvider sslHandlerProvider) { 47 + MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
  48 + QuotaService quotaService) {
56 this.processor = processor; 49 this.processor = processor;
57 this.deviceService = deviceService; 50 this.deviceService = deviceService;
58 this.authService = authService; 51 this.authService = authService;
59 this.relationService = relationService; 52 this.relationService = relationService;
60 this.adaptor = adaptor; 53 this.adaptor = adaptor;
61 this.sslHandlerProvider = sslHandlerProvider; 54 this.sslHandlerProvider = sslHandlerProvider;
  55 + this.quotaService = quotaService;
62 } 56 }
63 57
64 @Override 58 @Override
@@ -72,7 +66,9 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha @@ -72,7 +66,9 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
72 pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE)); 66 pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE));
73 pipeline.addLast("encoder", MqttEncoder.INSTANCE); 67 pipeline.addLast("encoder", MqttEncoder.INSTANCE);
74 68
75 - MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService, adaptor, sslHandler); 69 + MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
  70 + adaptor, sslHandler, quotaService);
  71 +
76 pipeline.addLast(handler); 72 pipeline.addLast(handler);
77 ch.closeFuture().addListener(handler); 73 ch.closeFuture().addListener(handler);
78 } 74 }
@@ -29,6 +29,7 @@ import org.springframework.context.ApplicationContext; @@ -29,6 +29,7 @@ import org.springframework.context.ApplicationContext;
29 import org.springframework.stereotype.Service; 29 import org.springframework.stereotype.Service;
30 import org.thingsboard.server.common.transport.SessionMsgProcessor; 30 import org.thingsboard.server.common.transport.SessionMsgProcessor;
31 import org.thingsboard.server.common.transport.auth.DeviceAuthService; 31 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  32 +import org.thingsboard.server.common.transport.quota.QuotaService;
32 import org.thingsboard.server.dao.device.DeviceService; 33 import org.thingsboard.server.dao.device.DeviceService;
33 import org.thingsboard.server.dao.relation.RelationService; 34 import org.thingsboard.server.dao.relation.RelationService;
34 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; 35 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@@ -65,6 +66,9 @@ public class MqttTransportService { @@ -65,6 +66,9 @@ public class MqttTransportService {
65 @Autowired(required = false) 66 @Autowired(required = false)
66 private MqttSslHandlerProvider sslHandlerProvider; 67 private MqttSslHandlerProvider sslHandlerProvider;
67 68
  69 + @Autowired(required = false)
  70 + private QuotaService quotaService;
  71 +
68 @Value("${mqtt.bind_address}") 72 @Value("${mqtt.bind_address}")
69 private String host; 73 private String host;
70 @Value("${mqtt.bind_port}") 74 @Value("${mqtt.bind_port}")
@@ -101,7 +105,8 @@ public class MqttTransportService { @@ -101,7 +105,8 @@ public class MqttTransportService {
101 ServerBootstrap b = new ServerBootstrap(); 105 ServerBootstrap b = new ServerBootstrap();
102 b.group(bossGroup, workerGroup) 106 b.group(bossGroup, workerGroup)
103 .channel(NioServerSocketChannel.class) 107 .channel(NioServerSocketChannel.class)
104 - .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService, adaptor, sslHandlerProvider)); 108 + .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
  109 + adaptor, sslHandlerProvider, quotaService));
105 110
106 serverChannel = b.bind(host, port).sync().channel(); 111 serverChannel = b.bind(host, port).sync().channel();
107 log.info("Mqtt transport started!"); 112 log.info("Mqtt transport started!");
@@ -13,10 +13,19 @@ @@ -13,10 +13,19 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import logoSvg from '../../svg/logo_title_white.svg';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
16 /*@ngInject*/ 23 /*@ngInject*/
17 export default function LoginController(toast, loginService, userService/*, $rootScope, $log, $translate*/) { 24 export default function LoginController(toast, loginService, userService/*, $rootScope, $log, $translate*/) {
18 var vm = this; 25 var vm = this;
19 26
  27 + vm.logoSvg = logoSvg;
  28 +
20 vm.user = { 29 vm.user = {
21 name: '', 30 name: '',
22 password: '' 31 password: ''
@@ -20,4 +20,12 @@ md-card.tb-login-card { @@ -20,4 +20,12 @@ md-card.tb-login-card {
20 @media (min-width: $layout-breakpoint-sm) { 20 @media (min-width: $layout-breakpoint-sm) {
21 width: 450px !important; 21 width: 450px !important;
22 } 22 }
  23 + md-card-title {
  24 + img.tb-login-logo {
  25 + height: 50px;
  26 + }
  27 + }
  28 + md-card-content {
  29 + margin-top: -50px;
  30 + }
23 } 31 }
@@ -18,11 +18,9 @@ @@ -18,11 +18,9 @@
18 <md-content layout="row" layout-align="center center" style="width: 100%;"> 18 <md-content layout="row" layout-align="center center" style="width: 100%;">
19 <md-card flex="initial" class="tb-login-card" md-theme="tb-dark"> 19 <md-card flex="initial" class="tb-login-card" md-theme="tb-dark">
20 <md-card-title> 20 <md-card-title>
21 - <md-card-title-text>  
22 - <span translate class="md-headline">login.sign-in</span>  
23 - </md-card-title-text> 21 + <img src="{{vm.logoSvg}}" aria-label="logo" class="tb-login-logo"/>
24 </md-card-title> 22 </md-card-title>
25 - <md-progress-linear class="md-warn" style="z-index: 1; max-height: 5px; width: inherit; position: absolute" 23 + <md-progress-linear class="md-warn" style="z-index: 1; max-height: 0px; width: inherit;"
26 md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear> 24 md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
27 <md-card-content> 25 <md-card-content>
28 <form class="login-form" ng-submit="vm.login()"> 26 <form class="login-form" ng-submit="vm.login()">