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