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 429
430 430 @Override
431 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 435 @Override
436 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 440 @Override
... ...
... ... @@ -130,6 +130,10 @@ public abstract class BaseController {
130 130 @Autowired
131 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 138 @ExceptionHandler(ThingsboardException.class)
135 139 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
... ...
... ... @@ -120,7 +120,7 @@ public class EntityRelationController extends BaseController {
120 120 checkEntityId(fromId);
121 121 checkEntityId(toId);
122 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 124 } catch (Exception e) {
125 125 throw handleException(e);
126 126 }
... ... @@ -138,7 +138,7 @@ public class EntityRelationController extends BaseController {
138 138 checkEntityId(entityId);
139 139 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
140 140 try {
141   - return checkNotNull(relationService.findByFrom(entityId, typeGroup).get());
  141 + return checkNotNull(relationService.findByFrom(entityId, typeGroup));
142 142 } catch (Exception e) {
143 143 throw handleException(e);
144 144 }
... ... @@ -176,7 +176,7 @@ public class EntityRelationController extends BaseController {
176 176 checkEntityId(entityId);
177 177 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
178 178 try {
179   - return checkNotNull(relationService.findByFromAndType(entityId, strRelationType, typeGroup).get());
  179 + return checkNotNull(relationService.findByFromAndType(entityId, strRelationType, typeGroup));
180 180 } catch (Exception e) {
181 181 throw handleException(e);
182 182 }
... ... @@ -194,7 +194,7 @@ public class EntityRelationController extends BaseController {
194 194 checkEntityId(entityId);
195 195 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
196 196 try {
197   - return checkNotNull(relationService.findByTo(entityId, typeGroup).get());
  197 + return checkNotNull(relationService.findByTo(entityId, typeGroup));
198 198 } catch (Exception e) {
199 199 throw handleException(e);
200 200 }
... ... @@ -232,7 +232,7 @@ public class EntityRelationController extends BaseController {
232 232 checkEntityId(entityId);
233 233 RelationTypeGroup typeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
234 234 try {
235   - return checkNotNull(relationService.findByToAndType(entityId, strRelationType, typeGroup).get());
  235 + return checkNotNull(relationService.findByToAndType(entityId, strRelationType, typeGroup));
236 236 } catch (Exception e) {
237 237 throw handleException(e);
238 238 }
... ...
... ... @@ -81,6 +81,8 @@ public class ThingsboardInstallService {
81 81 case "1.3.1":
82 82 log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ...");
83 83
  84 + databaseUpgradeService.upgradeDatabase("1.3.1");
  85 +
84 86 log.info("Updating system data...");
85 87
86 88 systemDataLoaderService.deleteSystemWidgetBundle("charts");
... ...
... ... @@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
159 159 break;
160 160 case "1.3.0":
161 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 168 default:
163 169 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
164 170 }
... ...
... ... @@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
61 61 }
62 62 log.info("Schema updated.");
63 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 73 default:
65 74 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
66 75 }
... ...
... ... @@ -111,6 +111,27 @@ coap:
111 111 adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}"
112 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 135 database:
115 136 type: "${DATABASE_TYPE:sql}" # cassandra OR sql
116 137
... ... @@ -193,26 +214,11 @@ actors:
193 214 enabled: "${ACTORS_STATISTICS_ENABLED:true}"
194 215 persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
195 216
196   -# Cache parameters
197 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 222 specs:
217 223 relations:
218 224 timeToLiveInMinutes: 1440
... ... @@ -224,6 +230,15 @@ caching:
224 230 timeToLiveInMinutes: 1440
225 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 242 # Check new version updates parameters
228 243 updates:
229 244 # Enable/disable updates checking.
... ...
... ... @@ -19,7 +19,7 @@ import lombok.EqualsAndHashCode;
19 19 import org.thingsboard.server.common.data.id.UUIDBased;
20 20
21 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 24 private static final long serialVersionUID = 5047448057830660988L;
25 25
... ...
... ... @@ -29,8 +29,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
29 29
30 30 private String title;
31 31 private TenantId tenantId;
32   - private transient JsonNode additionalInfo;
33   -
  32 +
34 33 public Customer() {
35 34 super();
36 35 }
... ... @@ -43,7 +42,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
43 42 super(customer);
44 43 this.tenantId = customer.getTenantId();
45 44 this.title = customer.getTitle();
46   - this.additionalInfo = customer.getAdditionalInfo();
47 45 }
48 46
49 47 public TenantId getTenantId() {
... ... @@ -65,7 +63,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
65 63 @JsonIgnore
66 64 public boolean isPublic() {
67 65 if (getAdditionalInfo() != null && getAdditionalInfo().has("isPublic")) {
68   - return additionalInfo.get("isPublic").asBoolean();
  66 + return getAdditionalInfo().get("isPublic").asBoolean();
69 67 }
70 68
71 69 return false;
... ... @@ -77,14 +75,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
77 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 78 @Override
89 79 public String getSearchText() {
90 80 return getTitle();
... ... @@ -94,7 +84,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
94 84 public int hashCode() {
95 85 final int prime = 31;
96 86 int result = super.hashCode();
97   - result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode());
98 87 result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
99 88 result = prime * result + ((title == null) ? 0 : title.hashCode());
100 89 return result;
... ... @@ -109,11 +98,6 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
109 98 if (getClass() != obj.getClass())
110 99 return false;
111 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 101 if (tenantId == null) {
118 102 if (other.tenantId != null)
119 103 return false;
... ... @@ -135,7 +119,7 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
135 119 builder.append(", tenantId=");
136 120 builder.append(tenantId);
137 121 builder.append(", additionalInfo=");
138   - builder.append(additionalInfo);
  122 + builder.append(getAdditionalInfo());
139 123 builder.append(", country=");
140 124 builder.append(country);
141 125 builder.append(", state=");
... ...
... ... @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
23 23 import com.fasterxml.jackson.databind.JsonNode;
24 24
25 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 28 private static final long serialVersionUID = 2807343040519543363L;
29 29
... ... @@ -31,7 +31,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
31 31 private CustomerId customerId;
32 32 private String name;
33 33 private String type;
34   - private transient JsonNode additionalInfo;
35 34
36 35 public Device() {
37 36 super();
... ... @@ -47,7 +46,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
47 46 this.customerId = device.getCustomerId();
48 47 this.name = device.getName();
49 48 this.type = device.getType();
50   - this.additionalInfo = device.getAdditionalInfo();
51 49 }
52 50
53 51 public TenantId getTenantId() {
... ... @@ -83,14 +81,6 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
83 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 84 @Override
95 85 public String getSearchText() {
96 86 return getName();
... ... @@ -108,7 +98,7 @@ public class Device extends SearchTextBased<DeviceId> implements HasName {
108 98 builder.append(", type=");
109 99 builder.append(type);
110 100 builder.append(", additionalInfo=");
111   - builder.append(additionalInfo);
  101 + builder.append(getAdditionalInfo());
112 102 builder.append(", createdTime=");
113 103 builder.append(createdTime);
114 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 28
29 29 private String title;
30 30 private String region;
31   - private transient JsonNode additionalInfo;
32   -
  31 +
33 32 public Tenant() {
34 33 super();
35 34 }
... ... @@ -42,7 +41,6 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
42 41 super(tenant);
43 42 this.title = tenant.getTitle();
44 43 this.region = tenant.getRegion();
45   - this.additionalInfo = tenant.getAdditionalInfo();
46 44 }
47 45
48 46 public String getTitle() {
... ... @@ -67,14 +65,6 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
67 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 68 @Override
79 69 public String getSearchText() {
80 70 return getTitle();
... ... @@ -88,7 +78,7 @@ public class Tenant extends ContactBased<TenantId> implements HasName {
88 78 builder.append(", region=");
89 79 builder.append(region);
90 80 builder.append(", additionalInfo=");
91   - builder.append(additionalInfo);
  81 + builder.append(getAdditionalInfo());
92 82 builder.append(", country=");
93 83 builder.append(country);
94 84 builder.append(", state=");
... ...
... ... @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.security.Authority;
25 25 import com.fasterxml.jackson.databind.JsonNode;
26 26
27 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 30 private static final long serialVersionUID = 8250339805336035966L;
31 31
... ... @@ -35,7 +35,6 @@ public class User extends SearchTextBased<UserId> implements HasName {
35 35 private Authority authority;
36 36 private String firstName;
37 37 private String lastName;
38   - private transient JsonNode additionalInfo;
39 38
40 39 public User() {
41 40 super();
... ... @@ -53,7 +52,6 @@ public class User extends SearchTextBased<UserId> implements HasName {
53 52 this.authority = user.getAuthority();
54 53 this.firstName = user.getFirstName();
55 54 this.lastName = user.getLastName();
56   - this.additionalInfo = user.getAdditionalInfo();
57 55 }
58 56
59 57 public TenantId getTenantId() {
... ... @@ -110,14 +108,6 @@ public class User extends SearchTextBased<UserId> implements HasName {
110 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 111 @Override
122 112 public String getSearchText() {
123 113 return getEmail();
... ... @@ -139,7 +129,7 @@ public class User extends SearchTextBased<UserId> implements HasName {
139 129 builder.append(", lastName=");
140 130 builder.append(lastName);
141 131 builder.append(", additionalInfo=");
142   - builder.append(additionalInfo);
  132 + builder.append(getAdditionalInfo());
143 133 builder.append(", createdTime=");
144 134 builder.append(createdTime);
145 135 builder.append(", id=");
... ...
... ... @@ -17,14 +17,16 @@ package org.thingsboard.server.common.data.asset;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.HasAdditionalInfo;
20 21 import org.thingsboard.server.common.data.HasName;
21 22 import org.thingsboard.server.common.data.SearchTextBased;
  23 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
22 24 import org.thingsboard.server.common.data.id.AssetId;
23 25 import org.thingsboard.server.common.data.id.CustomerId;
24 26 import org.thingsboard.server.common.data.id.TenantId;
25 27
26 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 31 private static final long serialVersionUID = 2807343040519543363L;
30 32
... ... @@ -32,7 +34,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
32 34 private CustomerId customerId;
33 35 private String name;
34 36 private String type;
35   - private transient JsonNode additionalInfo;
36 37
37 38 public Asset() {
38 39 super();
... ... @@ -48,7 +49,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
48 49 this.customerId = asset.getCustomerId();
49 50 this.name = asset.getName();
50 51 this.type = asset.getType();
51   - this.additionalInfo = asset.getAdditionalInfo();
52 52 }
53 53
54 54 public TenantId getTenantId() {
... ... @@ -84,14 +84,6 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
84 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 87 @Override
96 88 public String getSearchText() {
97 89 return getName();
... ... @@ -109,7 +101,7 @@ public class Asset extends SearchTextBased<AssetId> implements HasName {
109 101 builder.append(", type=");
110 102 builder.append(type);
111 103 builder.append(", additionalInfo=");
112   - builder.append(additionalInfo);
  104 + builder.append(getAdditionalInfo());
113 105 builder.append(", createdTime=");
114 106 builder.append(createdTime);
115 107 builder.append(", id=");
... ...
... ... @@ -15,16 +15,24 @@
15 15 */
16 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 21 import lombok.EqualsAndHashCode;
  22 +import lombok.extern.slf4j.Slf4j;
19 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 25 import org.thingsboard.server.common.data.id.PluginId;
22 26 import org.thingsboard.server.common.data.id.TenantId;
23 27
24 28 import com.fasterxml.jackson.databind.JsonNode;
25 29
  30 +import java.io.ByteArrayInputStream;
  31 +import java.io.IOException;
  32 +
26 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 37 private static final long serialVersionUID = 1L;
30 38
... ... @@ -35,7 +43,9 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
35 43 private boolean publicAccess;
36 44 private ComponentLifecycleState state;
37 45 private transient JsonNode configuration;
38   - private transient JsonNode additionalInfo;
  46 + @JsonIgnore
  47 + private byte[] configurationBytes;
  48 +
39 49
40 50 public PluginMetaData() {
41 51 super();
... ... @@ -54,7 +64,6 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
54 64 this.publicAccess = plugin.isPublicAccess();
55 65 this.state = plugin.getState();
56 66 this.configuration = plugin.getConfiguration();
57   - this.additionalInfo = plugin.getAdditionalInfo();
58 67 }
59 68
60 69 @Override
... ... @@ -96,11 +105,11 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
96 105 }
97 106
98 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 115 public boolean isPublicAccess() {
... ... @@ -119,14 +128,6 @@ public class PluginMetaData extends SearchTextBased<PluginId> implements HasName
119 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 131 @Override
131 132 public String toString() {
132 133 return "PluginMetaData [apiToken=" + apiToken + ", tenantId=" + tenantId + ", name=" + name + ", clazz=" + clazz + ", publicAccess=" + publicAccess
... ...
... ... @@ -15,10 +15,20 @@
15 15 */
16 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 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 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 33 private static final long serialVersionUID = 2807343040519543363L;
24 34
... ... @@ -29,7 +39,9 @@ public class EntityRelation {
29 39 private EntityId to;
30 40 private String type;
31 41 private RelationTypeGroup typeGroup;
32   - private JsonNode additionalInfo;
  42 + private transient JsonNode additionalInfo;
  43 + @JsonIgnore
  44 + private byte[] additionalInfoBytes;
33 45
34 46 public EntityRelation() {
35 47 super();
... ... @@ -92,11 +104,11 @@ public class EntityRelation {
92 104 }
93 105
94 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 114 @Override
... ...
... ... @@ -15,18 +15,23 @@
15 15 */
16 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 20 import com.fasterxml.jackson.databind.JsonNode;
  21 +import com.fasterxml.jackson.databind.ObjectMapper;
19 22 import lombok.Data;
20 23 import lombok.EqualsAndHashCode;
  24 +import lombok.extern.slf4j.Slf4j;
21 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 27 import org.thingsboard.server.common.data.id.RuleId;
24 28 import org.thingsboard.server.common.data.id.TenantId;
25 29 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
26 30
27 31 @Data
28 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 36 private static final long serialVersionUID = -5656679015122935465L;
32 37
... ... @@ -38,7 +43,13 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
38 43 private transient JsonNode filters;
39 44 private transient JsonNode processor;
40 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 54 public RuleMetaData() {
44 55 super();
... ... @@ -55,10 +66,9 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
55 66 this.state = rule.getState();
56 67 this.weight = rule.getWeight();
57 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 74 @Override
... ... @@ -71,4 +81,29 @@ public class RuleMetaData extends SearchTextBased<RuleId> implements HasName {
71 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 74 <artifactId>mockito-all</artifactId>
75 75 <scope>test</scope>
76 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 89 </dependencies>
78 90
79 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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
... ... @@ -182,6 +182,14 @@
182 182 <groupId>org.springframework</groupId>
183 183 <artifactId>spring-context-support</artifactId>
184 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 193 </dependencies>
186 194 <build>
187 195 <plugins>
... ...
... ... @@ -337,7 +337,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
337 337
338 338 private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) {
339 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 341 Set<EntityId> parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet());
342 342 for (EntityId parentId : parents) {
343 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 18 import com.github.benmanes.caffeine.cache.Caffeine;
19 19 import com.github.benmanes.caffeine.cache.Ticker;
20 20 import lombok.Data;
  21 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 22 import org.springframework.boot.context.properties.ConfigurationProperties;
22 23 import org.springframework.cache.CacheManager;
23 24 import org.springframework.cache.annotation.EnableCaching;
... ... @@ -33,10 +34,11 @@ import java.util.concurrent.TimeUnit;
33 34 import java.util.stream.Collectors;
34 35
35 36 @Configuration
36   -@ConfigurationProperties(prefix = "caching")
  37 +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
  38 +@ConfigurationProperties(prefix = "caffeine")
37 39 @EnableCaching
38 40 @Data
39   -public class ServiceCacheConfiguration {
  41 +public class CaffeineCacheConfiguration {
40 42
41 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 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 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 79 log.trace("Executing EntityRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup);
71 80 validate(from, to, relationType, typeGroup);
72 81 return relationDao.getRelation(from, to, relationType, typeGroup);
73 82 }
74 83
75 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 90 @Override
82 91 public boolean saveRelation(EntityRelation relation) {
... ... @@ -86,10 +95,10 @@ public class BaseRelationService implements RelationService {
86 95 }
87 96
88 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 103 @Override
95 104 public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
... ... @@ -99,11 +108,11 @@ public class BaseRelationService implements RelationService {
99 108 }
100 109
101 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 117 @Override
109 118 public boolean deleteRelation(EntityRelation relation) {
... ... @@ -117,7 +126,7 @@ public class BaseRelationService implements RelationService {
117 126 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type}"),
118 127 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "#relation.to"),
119 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 131 @Override
123 132 public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
... ... @@ -218,9 +227,9 @@ public class BaseRelationService implements RelationService {
218 227 ListenableFuture<List<List<EntityRelation>>> inboundRelationsTo = Futures.allAsList(inboundRelationsListTo);
219 228 ListenableFuture<List<Boolean>> inboundDeletions = Futures.transform(inboundRelationsTo,
220 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 234 ListenableFuture<Boolean> inboundFuture = Futures.transform(inboundDeletions, getListToBooleanFunction());
226 235
... ... @@ -272,9 +281,18 @@ public class BaseRelationService implements RelationService {
272 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 294 @Override
277   - public ListenableFuture<List<EntityRelation>> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
  295 + public ListenableFuture<List<EntityRelation>> findByFromAsync(EntityId from, RelationTypeGroup typeGroup) {
278 296 log.trace("Executing findByFrom [{}][{}]", from, typeGroup);
279 297 validate(from);
280 298 validateTypeGroup(typeGroup);
... ... @@ -300,9 +318,18 @@ public class BaseRelationService implements RelationService {
300 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 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 333 log.trace("Executing findByFromAndType [{}][{}][{}]", from, relationType, typeGroup);
307 334 validate(from);
308 335 validateType(relationType);
... ... @@ -310,9 +337,18 @@ public class BaseRelationService implements RelationService {
310 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 350 @Override
315   - public ListenableFuture<List<EntityRelation>> findByTo(EntityId to, RelationTypeGroup typeGroup) {
  351 + public ListenableFuture<List<EntityRelation>> findByToAsync(EntityId to, RelationTypeGroup typeGroup) {
316 352 log.trace("Executing findByTo [{}][{}]", to, typeGroup);
317 353 validate(to);
318 354 validateTypeGroup(typeGroup);
... ... @@ -351,9 +387,18 @@ public class BaseRelationService implements RelationService {
351 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 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 402 log.trace("Executing findByToAndType [{}][{}][{}]", to, relationType, typeGroup);
358 403 validate(to);
359 404 validateType(relationType);
... ... @@ -527,9 +572,9 @@ public class BaseRelationService implements RelationService {
527 572 private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) {
528 573 ListenableFuture<List<EntityRelation>> relations;
529 574 if (direction == EntitySearchDirection.FROM) {
530   - relations = findByFrom(rootId, RelationTypeGroup.COMMON);
  575 + relations = findByFromAsync(rootId, RelationTypeGroup.COMMON);
531 576 } else {
532   - relations = findByTo(rootId, RelationTypeGroup.COMMON);
  577 + relations = findByToAsync(rootId, RelationTypeGroup.COMMON);
533 578 }
534 579 return relations;
535 580 }
... ...
... ... @@ -31,7 +31,9 @@ public interface RelationService {
31 31
32 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 38 boolean saveRelation(EntityRelation relation);
37 39
... ... @@ -49,17 +51,25 @@ public interface RelationService {
49 51
50 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 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 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 74 ListenableFuture<List<EntityRelation>> findByQuery(EntityRelationsQuery query);
65 75
... ...
... ... @@ -59,7 +59,6 @@ CREATE TABLE IF NOT EXISTS audit_log (
59 59 action_type varchar(255),
60 60 action_data varchar(255),
61 61 action_status varchar(255),
62   - search_text varchar(255),
63 62 action_failure_details varchar
64 63 );
65 64
... ...
... ... @@ -122,7 +122,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
122 122 saveRelation(relationB1);
123 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 126 Assert.assertEquals(2, relations.size());
127 127 for (EntityRelation relation : relations) {
128 128 Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType());
... ... @@ -130,13 +130,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
130 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 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 137 Assert.assertEquals(0, relations.size());
138 138
139   - relations = relationService.findByFrom(parentB, RelationTypeGroup.COMMON).get();
  139 + relations = relationService.findByFrom(parentB, RelationTypeGroup.COMMON);
140 140 Assert.assertEquals(2, relations.size());
141 141 for (EntityRelation relation : relations) {
142 142 Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType());
... ... @@ -144,10 +144,10 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
144 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 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 151 Assert.assertEquals(0, relations.size());
152 152 }
153 153
... ... @@ -177,26 +177,26 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
177 177 // Data propagation to views is async
178 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 181 Assert.assertEquals(2, relations.size());
182 182 for (EntityRelation relation : relations) {
183 183 Assert.assertEquals(childA, relation.getTo());
184 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 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 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 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 197 Assert.assertEquals(0, relations.size());
198 198
199   - relations = relationService.findByTo(childB, RelationTypeGroup.COMMON).get();
  199 + relations = relationService.findByTo(childB, RelationTypeGroup.COMMON);
200 200 Assert.assertEquals(2, relations.size());
201 201 for (EntityRelation relation : relations) {
202 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 1 zk.enabled=false
7 2 zk.url=localhost:2181
8 3 zk.zk_dir=/thingsboard
... ... @@ -14,11 +9,22 @@ audit_log.exceptions.enabled=false
14 9 audit_log.by_tenant_partitioning=MONTHS
15 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 24 caching.specs.devices.timeToLiveInMinutes=1440
24 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 32 <spring-boot.version>1.4.3.RELEASE</spring-boot.version>
33 33 <spring.version>4.3.4.RELEASE</spring.version>
34 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 37 <jjwt.version>0.7.0</jjwt.version>
36 38 <json-path.version>2.2.0</json-path.version>
37 39 <junit.version>4.12</junit.version>
... ... @@ -784,6 +786,16 @@
784 786 <scope>test</scope>
785 787 </dependency>
786 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 799 <groupId>com.sun.winsw</groupId>
788 800 <artifactId>winsw</artifactId>
789 801 <version>${winsw.version}</version>
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.common.msg.session.*;
34 34 import org.thingsboard.server.common.transport.SessionMsgProcessor;
35 35 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
36 36 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  37 +import org.thingsboard.server.common.transport.quota.QuotaService;
37 38 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
38 39 import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy;
39 40 import org.thingsboard.server.transport.coap.session.CoapSessionCtx;
... ... @@ -51,13 +52,16 @@ public class CoapTransportResource extends CoapResource {
51 52 private final CoapTransportAdaptor adaptor;
52 53 private final SessionMsgProcessor processor;
53 54 private final DeviceAuthService authService;
  55 + private final QuotaService quotaService;
54 56 private final Field observerField;
55 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 61 super(name);
59 62 this.processor = processor;
60 63 this.authService = authService;
  64 + this.quotaService = quotaService;
61 65 this.adaptor = adaptor;
62 66 this.timeout = timeout;
63 67 // This is important to turn off existing observable logic in
... ... @@ -70,6 +74,12 @@ public class CoapTransportResource extends CoapResource {
70 74
71 75 @Override
72 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 83 Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
74 84 if (!featureType.isPresent()) {
75 85 log.trace("Missing feature type parameter");
... ...
... ... @@ -15,25 +15,25 @@
15 15 */
16 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 18 import lombok.extern.slf4j.Slf4j;
26 19 import org.eclipse.californium.core.CoapResource;
27 20 import org.eclipse.californium.core.CoapServer;
28 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 22 import org.springframework.beans.factory.annotation.Autowired;
34 23 import org.springframework.beans.factory.annotation.Value;
  24 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
35 25 import org.springframework.context.ApplicationContext;
36 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 38 @Service("CoapTransportService")
39 39 @ConditionalOnProperty(prefix = "coap", value = "enabled", havingValue = "true", matchIfMissing = true)
... ... @@ -54,6 +54,9 @@ public class CoapTransportService {
54 54 @Autowired(required = false)
55 55 private DeviceAuthService authService;
56 56
  57 + @Autowired(required = false)
  58 + private QuotaService quotaService;
  59 +
57 60
58 61 @Value("${coap.bind_address}")
59 62 private String host;
... ... @@ -83,7 +86,7 @@ public class CoapTransportService {
83 86
84 87 private void createResources() {
85 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 90 server.add(api);
88 91 }
89 92
... ...
... ... @@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.session.*;
50 50 import org.thingsboard.server.common.transport.SessionMsgProcessor;
51 51 import org.thingsboard.server.common.transport.auth.DeviceAuthResult;
52 52 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  53 +import org.thingsboard.server.common.transport.quota.QuotaService;
53 54
54 55 import java.util.ArrayList;
55 56 import java.util.List;
... ... @@ -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 142 @Autowired
... ...
... ... @@ -35,10 +35,11 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg;
35 35 import org.thingsboard.server.common.transport.SessionMsgProcessor;
36 36 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
37 37 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  38 +import org.thingsboard.server.common.transport.quota.QuotaService;
38 39 import org.thingsboard.server.transport.http.session.HttpSessionCtx;
39 40
  41 +import javax.servlet.http.HttpServletRequest;
40 42 import java.util.Arrays;
41   -import java.util.Collections;
42 43 import java.util.HashSet;
43 44 import java.util.Set;
44 45
... ... @@ -59,11 +60,18 @@ public class DeviceApiController {
59 60 @Autowired(required = false)
60 61 private DeviceAuthService authService;
61 62
  63 + @Autowired(required = false)
  64 + private QuotaService quotaService;
  65 +
62 66 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
63 67 public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
64 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 71 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  72 + if (quotaExceeded(httpRequest, responseWriter)) {
  73 + return responseWriter;
  74 + }
67 75 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
68 76 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
69 77 GetAttributesRequest request;
... ... @@ -84,8 +92,11 @@ public class DeviceApiController {
84 92
85 93 @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST)
86 94 public DeferredResult<ResponseEntity> postDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
87   - @RequestBody String json) {
  95 + @RequestBody String json, HttpServletRequest request) {
88 96 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  97 + if (quotaExceeded(request, responseWriter)) {
  98 + return responseWriter;
  99 + }
89 100 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
90 101 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
91 102 try {
... ... @@ -101,8 +112,11 @@ public class DeviceApiController {
101 112
102 113 @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST)
103 114 public DeferredResult<ResponseEntity> postTelemetry(@PathVariable("deviceToken") String deviceToken,
104   - @RequestBody String json) {
  115 + @RequestBody String json, HttpServletRequest request) {
105 116 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  117 + if (quotaExceeded(request, responseWriter)) {
  118 + return responseWriter;
  119 + }
106 120 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
107 121 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
108 122 try {
... ... @@ -118,15 +132,20 @@ public class DeviceApiController {
118 132
119 133 @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json")
120 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 141 @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST)
126 142 public DeferredResult<ResponseEntity> replyToCommand(@PathVariable("deviceToken") String deviceToken,
127 143 @PathVariable("requestId") Integer requestId,
128   - @RequestBody String json) {
  144 + @RequestBody String json, HttpServletRequest request) {
129 145 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  146 + if (quotaExceeded(request, responseWriter)) {
  147 + return responseWriter;
  148 + }
130 149 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
131 150 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
132 151 try {
... ... @@ -143,8 +162,11 @@ public class DeviceApiController {
143 162
144 163 @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST)
145 164 public DeferredResult<ResponseEntity> postRpcRequest(@PathVariable("deviceToken") String deviceToken,
146   - @RequestBody String json) {
  165 + @RequestBody String json, HttpServletRequest httpRequest) {
147 166 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  167 + if (quotaExceeded(httpRequest, responseWriter)) {
  168 + return responseWriter;
  169 + }
148 170 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
149 171 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
150 172 try {
... ... @@ -163,12 +185,17 @@ public class DeviceApiController {
163 185
164 186 @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json")
165 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 195 DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
  196 + if (quotaExceeded(httpRequest, responseWriter)) {
  197 + return responseWriter;
  198 + }
172 199 HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout);
173 200 if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
174 201 try {
... ... @@ -195,4 +222,13 @@ public class DeviceApiController {
195 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 16 package org.thingsboard.server.transport.mqtt;
17 17
18 18 import com.fasterxml.jackson.databind.JsonNode;
19   -import io.netty.channel.Channel;
20 19 import io.netty.channel.ChannelHandlerContext;
21 20 import io.netty.channel.ChannelInboundHandlerAdapter;
22 21 import io.netty.handler.codec.mqtt.*;
... ... @@ -36,18 +35,18 @@ import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
36 35 import org.thingsboard.server.common.transport.SessionMsgProcessor;
37 36 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
38 37 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  38 +import org.thingsboard.server.common.transport.quota.QuotaService;
39 39 import org.thingsboard.server.dao.EncryptionUtil;
40 40 import org.thingsboard.server.dao.device.DeviceService;
41 41 import org.thingsboard.server.dao.relation.RelationService;
42 42 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
43   -import org.thingsboard.server.transport.mqtt.session.GatewaySessionCtx;
44 43 import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx;
  44 +import org.thingsboard.server.transport.mqtt.session.GatewaySessionCtx;
45 45 import org.thingsboard.server.transport.mqtt.util.SslUtil;
46 46
47 47 import javax.net.ssl.SSLPeerUnverifiedException;
48 48 import javax.security.cert.X509Certificate;
49 49 import java.net.InetSocketAddress;
50   -import java.net.SocketAddress;
51 50 import java.util.ArrayList;
52 51 import java.util.List;
53 52
... ... @@ -72,13 +71,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
72 71 private final DeviceService deviceService;
73 72 private final DeviceAuthService authService;
74 73 private final RelationService relationService;
  74 + private final QuotaService quotaService;
75 75 private final SslHandler sslHandler;
76 76 private volatile boolean connected;
77 77 private volatile InetSocketAddress address;
78 78 private volatile GatewaySessionCtx gatewaySessionCtx;
79 79
80 80 public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
81   - MqttTransportAdaptor adaptor, SslHandler sslHandler) {
  81 + MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService) {
82 82 this.processor = processor;
83 83 this.deviceService = deviceService;
84 84 this.relationService = relationService;
... ... @@ -87,6 +87,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
87 87 this.deviceSessionCtx = new DeviceSessionCtx(processor, authService, adaptor);
88 88 this.sessionId = deviceSessionCtx.getSessionId().toUidStr();
89 89 this.sslHandler = sslHandler;
  90 + this.quotaService = quotaService;
90 91 }
91 92
92 93 @Override
... ... @@ -102,35 +103,43 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
102 103 if (msg.fixedHeader() == null) {
103 104 log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort());
104 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 145 private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) {
... ...
... ... @@ -15,27 +15,19 @@
15 15 */
16 16 package org.thingsboard.server.transport.mqtt;
17 17
18   -import io.netty.buffer.ByteBufAllocator;
19   -import io.netty.channel.ChannelHandler;
20 18 import io.netty.channel.ChannelInitializer;
21 19 import io.netty.channel.ChannelPipeline;
22 20 import io.netty.channel.socket.SocketChannel;
23 21 import io.netty.handler.codec.mqtt.MqttDecoder;
24 22 import io.netty.handler.codec.mqtt.MqttEncoder;
25   -import io.netty.handler.ssl.SslContext;
26   -import io.netty.handler.ssl.SslContextBuilder;
27 23 import io.netty.handler.ssl.SslHandler;
28   -import io.netty.handler.ssl.util.SelfSignedCertificate;
29   -import org.springframework.beans.factory.annotation.Value;
30 24 import org.thingsboard.server.common.transport.SessionMsgProcessor;
31 25 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  26 +import org.thingsboard.server.common.transport.quota.QuotaService;
32 27 import org.thingsboard.server.dao.device.DeviceService;
33 28 import org.thingsboard.server.dao.relation.RelationService;
34 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 32 * @author Andrew Shvayka
41 33 */
... ... @@ -49,16 +41,18 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
49 41 private final RelationService relationService;
50 42 private final MqttTransportAdaptor adaptor;
51 43 private final MqttSslHandlerProvider sslHandlerProvider;
  44 + private final QuotaService quotaService;
52 45
53 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 49 this.processor = processor;
57 50 this.deviceService = deviceService;
58 51 this.authService = authService;
59 52 this.relationService = relationService;
60 53 this.adaptor = adaptor;
61 54 this.sslHandlerProvider = sslHandlerProvider;
  55 + this.quotaService = quotaService;
62 56 }
63 57
64 58 @Override
... ... @@ -72,7 +66,9 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
72 66 pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE));
73 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 72 pipeline.addLast(handler);
77 73 ch.closeFuture().addListener(handler);
78 74 }
... ...
... ... @@ -29,6 +29,7 @@ import org.springframework.context.ApplicationContext;
29 29 import org.springframework.stereotype.Service;
30 30 import org.thingsboard.server.common.transport.SessionMsgProcessor;
31 31 import org.thingsboard.server.common.transport.auth.DeviceAuthService;
  32 +import org.thingsboard.server.common.transport.quota.QuotaService;
32 33 import org.thingsboard.server.dao.device.DeviceService;
33 34 import org.thingsboard.server.dao.relation.RelationService;
34 35 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
... ... @@ -65,6 +66,9 @@ public class MqttTransportService {
65 66 @Autowired(required = false)
66 67 private MqttSslHandlerProvider sslHandlerProvider;
67 68
  69 + @Autowired(required = false)
  70 + private QuotaService quotaService;
  71 +
68 72 @Value("${mqtt.bind_address}")
69 73 private String host;
70 74 @Value("${mqtt.bind_port}")
... ... @@ -101,7 +105,8 @@ public class MqttTransportService {
101 105 ServerBootstrap b = new ServerBootstrap();
102 106 b.group(bossGroup, workerGroup)
103 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 111 serverChannel = b.bind(host, port).sync().channel();
107 112 log.info("Mqtt transport started!");
... ...
... ... @@ -13,10 +13,19 @@
13 13 * See the License for the specific language governing permissions and
14 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 23 /*@ngInject*/
17 24 export default function LoginController(toast, loginService, userService/*, $rootScope, $log, $translate*/) {
18 25 var vm = this;
19 26
  27 + vm.logoSvg = logoSvg;
  28 +
20 29 vm.user = {
21 30 name: '',
22 31 password: ''
... ...
... ... @@ -20,4 +20,12 @@ md-card.tb-login-card {
20 20 @media (min-width: $layout-breakpoint-sm) {
21 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 18 <md-content layout="row" layout-align="center center" style="width: 100%;">
19 19 <md-card flex="initial" class="tb-login-card" md-theme="tb-dark">
20 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 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 24 md-mode="indeterminate" ng-disabled="!$root.loading" ng-show="$root.loading"></md-progress-linear>
27 25 <md-card-content>
28 26 <form class="login-form" ng-submit="vm.login()">
... ...