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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java
0 → 100644
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 | +} | ... | ... |
common/transport/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java
0 → 100644
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 | +} | ... | ... |
common/transport/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java
0 → 100644
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 | ... | ... |
... | ... | @@ -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= | ... | ... |
docker/docker-compose-tests.yml
0 → 100644
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: '' | ... | ... |
... | ... | @@ -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()"> | ... | ... |