Commit 201476f912e317ce7d7c466c0bffec72189611bb
Committed by
Andrew Shvayka
1 parent
3420eeb9
lwm2m: add objectKeyId to back and front profile
Showing
26 changed files
with
458 additions
and
140 deletions
... | ... | @@ -27,11 +27,14 @@ import org.thingsboard.server.common.data.Resource; |
27 | 27 | import org.thingsboard.server.common.data.ResourceType; |
28 | 28 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
29 | 29 | import org.thingsboard.server.common.data.id.TenantId; |
30 | +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; | |
30 | 31 | import org.thingsboard.server.common.data.page.PageData; |
31 | 32 | import org.thingsboard.server.common.data.page.PageLink; |
32 | 33 | import org.thingsboard.server.dao.resource.ResourceService; |
33 | 34 | import org.thingsboard.server.queue.util.TbCoreComponent; |
34 | 35 | |
36 | +import java.util.List; | |
37 | + | |
35 | 38 | @Slf4j |
36 | 39 | @RestController |
37 | 40 | @TbCoreComponent |
... | ... | @@ -59,21 +62,55 @@ public class ResourceController extends BaseController { |
59 | 62 | } |
60 | 63 | |
61 | 64 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
62 | - @RequestMapping(value = "/resource", method = RequestMethod.GET) | |
65 | + @RequestMapping(value = "/resource/page", method = RequestMethod.GET) | |
63 | 66 | @ResponseBody |
64 | 67 | public PageData<Resource> getResources(@RequestParam(required = false) boolean system, |
65 | 68 | @RequestParam int pageSize, |
66 | 69 | @RequestParam int page, |
70 | + @RequestParam(required = false) String textSearch, | |
67 | 71 | @RequestParam(required = false) String sortProperty, |
68 | 72 | @RequestParam(required = false) String sortOrder) throws ThingsboardException { |
69 | 73 | try { |
70 | - PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder); | |
74 | +// int[] objectIds; | |
75 | +// ResourceType resourceType | |
76 | + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | |
71 | 77 | return checkNotNull(resourceService.findResourcesByTenantId(system ? TenantId.SYS_TENANT_ID : getTenantId(), pageLink)); |
72 | 78 | } catch (Exception e) { |
73 | 79 | throw handleException(e); |
74 | 80 | } |
75 | 81 | } |
76 | 82 | |
83 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | |
84 | + @RequestMapping(value = "/resource/lwm2m/page", method = RequestMethod.GET) | |
85 | + @ResponseBody | |
86 | + public List<LwM2mObject> getLwm2mListObjectsPage(@RequestParam int pageSize, | |
87 | + @RequestParam int page, | |
88 | + @RequestParam(required = false) String textSearch, | |
89 | + @RequestParam(required = false) String sortProperty, | |
90 | + @RequestParam(required = false) String sortOrder) throws ThingsboardException { | |
91 | + try { | |
92 | + PageLink pageLink = new PageLink(pageSize, page, textSearch); | |
93 | + return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink)); | |
94 | + } catch (Exception e) { | |
95 | + throw handleException(e); | |
96 | + } | |
97 | + } | |
98 | + | |
99 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | |
100 | + @RequestMapping(value = "/resource/lwm2m", method = RequestMethod.GET) | |
101 | + @ResponseBody | |
102 | + public List<LwM2mObject> getLwm2mListObjects(@RequestParam String sortOrder, | |
103 | + @RequestParam String sortProperty, | |
104 | + @RequestParam(required = false) String[] objectIds, | |
105 | + @RequestParam(required = false) String searchText) | |
106 | + throws ThingsboardException { | |
107 | + try { | |
108 | + return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds, searchText)); | |
109 | + } catch (Exception e) { | |
110 | + throw handleException(e); | |
111 | + } | |
112 | + } | |
113 | + | |
77 | 114 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
78 | 115 | @RequestMapping(value = "/resource/{resourceType}/{resourceId}", method = RequestMethod.DELETE) |
79 | 116 | @ResponseBody | ... | ... |
... | ... | @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
37 | 37 | import org.thingsboard.server.common.data.widget.WidgetType; |
38 | 38 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
39 | 39 | import org.thingsboard.server.dao.dashboard.DashboardService; |
40 | +import org.thingsboard.server.dao.exception.DataValidationException; | |
40 | 41 | import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; |
41 | 42 | import org.thingsboard.server.dao.resource.ResourceService; |
42 | 43 | import org.thingsboard.server.dao.rule.RuleChainService; |
... | ... | @@ -202,7 +203,6 @@ public class InstallScripts { |
202 | 203 | } |
203 | 204 | |
204 | 205 | public void loadSystemLwm2mResources() throws Exception { |
205 | -// Path modelsDir = Paths.get("/home/nick/Igor_project/thingsboard_ce_3_2_docker/thingsboard/common/transport/lwm2m/src/main/resources/models/"); | |
206 | 206 | Path modelsDir = Paths.get(getDataDir(), MODELS_DIR); |
207 | 207 | if (Files.isDirectory(modelsDir)) { |
208 | 208 | try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) { |
... | ... | @@ -210,18 +210,16 @@ public class InstallScripts { |
210 | 210 | path -> { |
211 | 211 | try { |
212 | 212 | byte[] fileBytes = Files.readAllBytes(path); |
213 | - String key = getObjectModelLwm2mValid(fileBytes, path.getFileName().toString(), new DefaultDDFFileValidator()); | |
214 | - if (key != null) { | |
215 | - Resource resource = new Resource(); | |
216 | - resource.setTenantId(TenantId.SYS_TENANT_ID); | |
217 | - resource.setResourceType(ResourceType.LWM2M_MODEL); | |
218 | - resource.setResourceId(key); | |
219 | - resource.setValue(Base64.getEncoder().encodeToString(fileBytes)); | |
220 | - resourceService.saveResource(resource); | |
221 | - } | |
213 | + String source = new String(fileBytes); | |
214 | + Resource resource = new Resource(); | |
215 | + resource.setTenantId(TenantId.SYS_TENANT_ID); | |
216 | + resource.setResourceType(ResourceType.LWM2M_MODEL); | |
217 | + resource.setResourceId(getValueByTeg(source, "ObjectID") + "_" + getValueByTeg(source, "ObjectVersion")); | |
218 | + resource.setTextSearch(resource.getResourceId() + ":" + getValueByTeg(source, "Name")); | |
219 | + resource.setValue(Base64.getEncoder().encodeToString(fileBytes)); | |
220 | + resourceService.saveResource(resource); | |
222 | 221 | } catch (Exception e) { |
223 | - log.error("Unable to load lwm2m model [{}]", path.toString()); | |
224 | - throw new RuntimeException("Unable to load lwm2m model", e); | |
222 | + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", path.toString())); | |
225 | 223 | } |
226 | 224 | } |
227 | 225 | ); |
... | ... | @@ -234,6 +232,7 @@ public class InstallScripts { |
234 | 232 | resource.setTenantId(TenantId.SYS_TENANT_ID); |
235 | 233 | resource.setResourceType(ResourceType.JKS); |
236 | 234 | resource.setResourceId(jksPath.getFileName().toString()); |
235 | + resource.setTextSearch(jksPath.getFileName().toString()); | |
237 | 236 | resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(jksPath))); |
238 | 237 | resourceService.saveResource(resource); |
239 | 238 | } catch (Exception e) { |
... | ... | @@ -242,11 +241,18 @@ public class InstallScripts { |
242 | 241 | } |
243 | 242 | } |
244 | 243 | |
245 | - private String getObjectModelLwm2mValid(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) { | |
244 | + private String getValueByTeg(String source, String tagHtml) { | |
245 | + int lenTag = ("<" + tagHtml + ">").length(); | |
246 | + int indStart = source.indexOf("<" + tagHtml + ">"); | |
247 | + int indEnd = source.indexOf("</" + tagHtml + ">"); | |
248 | + return (indStart > 0 && indEnd > 0) ? source.substring(indStart + lenTag, indEnd) : null; | |
249 | + | |
250 | + } | |
251 | + | |
252 | + private ObjectModel getObjectModelLwm2mValid(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) { | |
246 | 253 | try { |
247 | 254 | DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator); |
248 | - ObjectModel objectModel = ddfFileParser.parseEx(new ByteArrayInputStream(xmlByte), streamName).get(0); | |
249 | - return objectModel.id + "##" + objectModel.getVersion(); | |
255 | + return ddfFileParser.parseEx(new ByteArrayInputStream(xmlByte), streamName).get(0); | |
250 | 256 | } catch (IOException | InvalidDDFFileException e) { |
251 | 257 | log.error("Could not parse the XML file [{}]", streamName, e); |
252 | 258 | return null; | ... | ... |
... | ... | @@ -442,6 +442,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService |
442 | 442 | " tenant_id uuid NOT NULL," + |
443 | 443 | " resource_type varchar(32) NOT NULL," + |
444 | 444 | " resource_id varchar(255) NOT NULL," + |
445 | + " text_search varchar(255)," + | |
445 | 446 | " resource_value varchar," + |
446 | 447 | " CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id)" + |
447 | 448 | " );"); | ... | ... |
... | ... | @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.resource; |
18 | 18 | import org.thingsboard.server.common.data.Resource; |
19 | 19 | import org.thingsboard.server.common.data.ResourceType; |
20 | 20 | import org.thingsboard.server.common.data.id.TenantId; |
21 | +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; | |
21 | 22 | import org.thingsboard.server.common.data.page.PageData; |
22 | 23 | import org.thingsboard.server.common.data.page.PageLink; |
23 | 24 | |
... | ... | @@ -31,7 +32,18 @@ public interface ResourceService { |
31 | 32 | |
32 | 33 | PageData<Resource> findResourcesByTenantId(TenantId tenantId, PageLink pageLink); |
33 | 34 | |
34 | - List<Resource> findResourcesByTenantIdResourceType(TenantId tenantId, ResourceType resourceType); | |
35 | + List<LwM2mObject> findLwM2mObject(TenantId tenantId, | |
36 | + String sortOrder, | |
37 | + String sortProperty, | |
38 | + String[] objectIds, | |
39 | + String searchText); | |
40 | + | |
41 | + List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, | |
42 | + String sortProperty, | |
43 | + String sortOrder, | |
44 | + PageLink pageLink); | |
45 | + | |
46 | + List<Resource> findAllByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType); | |
35 | 47 | |
36 | 48 | void deleteResource(TenantId tenantId, ResourceType resourceType, String resourceId); |
37 | 49 | ... | ... |
... | ... | @@ -79,6 +79,10 @@ |
79 | 79 | <groupId>org.thingsboard</groupId> |
80 | 80 | <artifactId>protobuf-dynamic</artifactId> |
81 | 81 | </dependency> |
82 | + <dependency> | |
83 | + <groupId>org.eclipse.leshan</groupId> | |
84 | + <artifactId>leshan-core</artifactId> | |
85 | + </dependency> | |
82 | 86 | </dependencies> |
83 | 87 | |
84 | 88 | <build> | ... | ... |
... | ... | @@ -16,10 +16,24 @@ |
16 | 16 | package org.thingsboard.server.common.data; |
17 | 17 | |
18 | 18 | import lombok.Data; |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.eclipse.leshan.core.model.DDFFileParser; | |
21 | +import org.eclipse.leshan.core.model.DefaultDDFFileValidator; | |
22 | +import org.eclipse.leshan.core.model.InvalidDDFFileException; | |
23 | +import org.eclipse.leshan.core.model.ObjectModel; | |
19 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
25 | +import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; | |
26 | +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; | |
27 | +import org.thingsboard.server.common.data.lwm2m.LwM2mResource; | |
20 | 28 | |
29 | +import java.io.ByteArrayInputStream; | |
30 | +import java.io.IOException; | |
21 | 31 | import java.io.Serializable; |
32 | +import java.util.ArrayList; | |
33 | +import java.util.Base64; | |
34 | +import java.util.List; | |
22 | 35 | |
36 | +@Slf4j | |
23 | 37 | @Data |
24 | 38 | public class Resource implements HasTenantId, Serializable { |
25 | 39 | |
... | ... | @@ -28,14 +42,51 @@ public class Resource implements HasTenantId, Serializable { |
28 | 42 | private TenantId tenantId; |
29 | 43 | private ResourceType resourceType; |
30 | 44 | private String resourceId; |
45 | + private String textSearch; | |
31 | 46 | private String value; |
32 | 47 | |
33 | 48 | @Override |
34 | 49 | public String toString() { |
35 | - return "Resource{" + | |
36 | - "tenantId=" + tenantId + | |
37 | - ", resourceType=" + resourceType + | |
38 | - ", resourceId='" + resourceId + '\'' + | |
39 | - '}'; | |
50 | + final StringBuilder res = new StringBuilder("Resource{"); | |
51 | + res.append("tenantId=").append(tenantId); | |
52 | + res.append(", resourceType='").append(resourceType).append('\''); | |
53 | + res.append(", resourceId='").append(resourceId).append('\''); | |
54 | + res.append(", textSearch='").append(textSearch).append('\''); | |
55 | + res.append('}'); | |
56 | + return res.toString(); | |
57 | + } | |
58 | + | |
59 | + public LwM2mObject toLwM2mObject () { | |
60 | + try { | |
61 | + DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); | |
62 | + List<ObjectModel> objectModels = ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(this.value)), this.textSearch); | |
63 | + if (objectModels.size() == 0) { | |
64 | + return null; | |
65 | + } | |
66 | + else { | |
67 | + ObjectModel obj = objectModels.get(0); | |
68 | + LwM2mObject lwM2mObject = new LwM2mObject(); | |
69 | + lwM2mObject.setId(obj.id); | |
70 | + lwM2mObject.setKeyId(this.resourceId); | |
71 | + lwM2mObject.setName(obj.name); | |
72 | + lwM2mObject.setMultiple(obj.multiple); | |
73 | + lwM2mObject.setMandatory(obj.mandatory); | |
74 | + LwM2mInstance instance = new LwM2mInstance(); | |
75 | + instance.setId(0); | |
76 | + List<LwM2mResource> resources = new ArrayList<>(); | |
77 | + obj.resources.forEach((k, v) -> { | |
78 | + if (!v.operations.isExecutable()) { | |
79 | + LwM2mResource resource = new LwM2mResource(k, v.name, false, false, false); | |
80 | + resources.add(resource); | |
81 | + } | |
82 | + }); | |
83 | + instance.setResources(resources.stream().toArray(LwM2mResource[]::new)); | |
84 | + lwM2mObject.setInstances(new LwM2mInstance[]{instance}); | |
85 | + return lwM2mObject; | |
86 | + } | |
87 | + } catch (IOException | InvalidDDFFileException e) { | |
88 | + log.error("Could not parse the XML of objectModel with name [{}]", this.textSearch, e); | |
89 | + return null; | |
90 | + } | |
40 | 91 | } |
41 | 92 | } | ... | ... |
... | ... | @@ -214,6 +214,10 @@ |
214 | 214 | <groupId>org.elasticsearch.client</groupId> |
215 | 215 | <artifactId>rest</artifactId> |
216 | 216 | </dependency> |
217 | + <dependency> | |
218 | + <groupId>org.eclipse.leshan</groupId> | |
219 | + <artifactId>leshan-core</artifactId> | |
220 | + </dependency> | |
217 | 221 | </dependencies> |
218 | 222 | <build> |
219 | 223 | <plugins> | ... | ... |
... | ... | @@ -460,8 +460,10 @@ public class ModelConstants { |
460 | 460 | public static final String RESOURCE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; |
461 | 461 | public static final String RESOURCE_TYPE_COLUMN = "resource_type"; |
462 | 462 | public static final String RESOURCE_ID_COLUMN = "resource_id"; |
463 | + public static final String TEXT_SEARCH_COLUMN = "text_search"; | |
463 | 464 | public static final String RESOURCE_VALUE_COLUMN = "resource_value"; |
464 | 465 | |
466 | + | |
465 | 467 | /** |
466 | 468 | * Cassandra attributes and timeseries constants. |
467 | 469 | */ | ... | ... |
... | ... | @@ -33,6 +33,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TABLE_NAM |
33 | 33 | import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TENANT_ID_COLUMN; |
34 | 34 | import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_TYPE_COLUMN; |
35 | 35 | import static org.thingsboard.server.dao.model.ModelConstants.RESOURCE_VALUE_COLUMN; |
36 | +import static org.thingsboard.server.dao.model.ModelConstants.TEXT_SEARCH_COLUMN; | |
36 | 37 | |
37 | 38 | @Data |
38 | 39 | @Entity |
... | ... | @@ -52,6 +53,9 @@ public class ResourceEntity implements ToData<Resource> { |
52 | 53 | @Column(name = RESOURCE_ID_COLUMN) |
53 | 54 | private String resourceId; |
54 | 55 | |
56 | + @Column(name = TEXT_SEARCH_COLUMN) | |
57 | + private String textSearch; | |
58 | + | |
55 | 59 | @Column(name = RESOURCE_VALUE_COLUMN) |
56 | 60 | private String value; |
57 | 61 | |
... | ... | @@ -62,6 +66,7 @@ public class ResourceEntity implements ToData<Resource> { |
62 | 66 | this.tenantId = resource.getTenantId().getId(); |
63 | 67 | this.resourceType = resource.getResourceType().name(); |
64 | 68 | this.resourceId = resource.getResourceId(); |
69 | + this.textSearch = resource.getTextSearch(); | |
65 | 70 | this.value = resource.getValue(); |
66 | 71 | } |
67 | 72 | |
... | ... | @@ -71,6 +76,7 @@ public class ResourceEntity implements ToData<Resource> { |
71 | 76 | resource.setTenantId(new TenantId(tenantId)); |
72 | 77 | resource.setResourceType(ResourceType.valueOf(resourceType)); |
73 | 78 | resource.setResourceId(resourceId); |
79 | + resource.setTextSearch(textSearch); | |
74 | 80 | resource.setValue(value); |
75 | 81 | return resource; |
76 | 82 | } | ... | ... |
... | ... | @@ -16,15 +16,28 @@ |
16 | 16 | package org.thingsboard.server.dao.resource; |
17 | 17 | |
18 | 18 | import lombok.extern.slf4j.Slf4j; |
19 | +import org.eclipse.leshan.core.model.DDFFileParser; | |
20 | +import org.eclipse.leshan.core.model.DefaultDDFFileValidator; | |
21 | +import org.eclipse.leshan.core.model.InvalidDDFFileException; | |
22 | +import org.eclipse.leshan.core.model.ObjectModel; | |
19 | 23 | import org.springframework.stereotype.Service; |
20 | 24 | import org.thingsboard.server.common.data.Resource; |
21 | 25 | import org.thingsboard.server.common.data.ResourceType; |
22 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
27 | +import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; | |
28 | +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; | |
29 | +import org.thingsboard.server.common.data.lwm2m.LwM2mResource; | |
23 | 30 | import org.thingsboard.server.common.data.page.PageData; |
24 | 31 | import org.thingsboard.server.common.data.page.PageLink; |
25 | 32 | import org.thingsboard.server.dao.exception.DataValidationException; |
26 | 33 | |
34 | +import java.io.ByteArrayInputStream; | |
35 | +import java.io.IOException; | |
36 | +import java.util.ArrayList; | |
37 | +import java.util.Base64; | |
38 | +import java.util.Comparator; | |
27 | 39 | import java.util.List; |
40 | +import java.util.stream.Collectors; | |
28 | 41 | |
29 | 42 | import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID; |
30 | 43 | import static org.thingsboard.server.dao.service.Validator.validateId; |
... | ... | @@ -69,10 +82,35 @@ public class BaseResourceService implements ResourceService { |
69 | 82 | |
70 | 83 | |
71 | 84 | @Override |
72 | - public List<Resource> findResourcesByTenantIdResourceType(TenantId tenantId, ResourceType resourceType) { | |
85 | + public List<Resource> findAllByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType) { | |
73 | 86 | log.trace("Executing findByTenantId [{}]", tenantId); |
74 | 87 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
75 | - return resourceDao.findAllByTenantIdResourceType(tenantId, resourceType); | |
88 | + return resourceDao.findAllByTenantIdAndResourceType(tenantId, resourceType); | |
89 | + } | |
90 | + | |
91 | + @Override | |
92 | + public List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) { | |
93 | + log.trace("Executing findByTenantId [{}]", tenantId); | |
94 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
95 | + PageData<Resource> resourcePageData = resourceDao.findResourcesByTenantIdAndResourceType( | |
96 | + tenantId, | |
97 | + ResourceType.LWM2M_MODEL, pageLink); | |
98 | + List<LwM2mObject> lwM2mObjects = resourcePageData.getData().stream().map(this::toLwM2mObject).collect(Collectors.toList()); | |
99 | + return lwM2mObjects.size() > 1 ? this.sortList (lwM2mObjects, sortProperty, sortOrder) : lwM2mObjects; | |
100 | + } | |
101 | + | |
102 | + @Override | |
103 | + public List<LwM2mObject> findLwM2mObject(TenantId tenantId, String sortOrder, | |
104 | + String sortProperty, | |
105 | + String[] objectIds, | |
106 | + String searchText) { | |
107 | + log.trace("Executing findByTenantId [{}]", tenantId); | |
108 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
109 | + List<Resource> resources = resourceDao.findResourcesByTenantIdAndResourceType(tenantId, ResourceType.LWM2M_MODEL, | |
110 | + objectIds, | |
111 | + searchText); | |
112 | + List<LwM2mObject> lwM2mObjects = resources.stream().map(this::toLwM2mObject).collect(Collectors.toList()); | |
113 | + return lwM2mObjects.size() > 1 ? this.sortList (lwM2mObjects, sortProperty, sortOrder) : lwM2mObjects; | |
76 | 114 | } |
77 | 115 | |
78 | 116 | @Override |
... | ... | @@ -86,11 +124,13 @@ public class BaseResourceService implements ResourceService { |
86 | 124 | if (resource == null) { |
87 | 125 | throw new DataValidationException("Resource should be specified!"); |
88 | 126 | } |
89 | - | |
90 | 127 | if (resource.getValue() == null) { |
91 | 128 | throw new DataValidationException("Resource value should be specified!"); |
92 | 129 | } |
93 | 130 | validate(resource.getTenantId(), resource.getResourceType(), resource.getResourceId()); |
131 | + if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && resource.toLwM2mObject() == null) { | |
132 | + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getTextSearch())); | |
133 | + } | |
94 | 134 | } |
95 | 135 | |
96 | 136 | protected void validate(TenantId tenantId, ResourceType resourceType, String resourceId) { |
... | ... | @@ -103,4 +143,61 @@ public class BaseResourceService implements ResourceService { |
103 | 143 | validateId(tenantId, "Incorrect tenantId "); |
104 | 144 | } |
105 | 145 | |
146 | + private LwM2mObject toLwM2mObject(Resource resource) { | |
147 | + try { | |
148 | + DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); | |
149 | + List<ObjectModel> objectModels = | |
150 | + ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getValue())), resource.getTextSearch()); | |
151 | + if (objectModels.size() == 0) { | |
152 | + return null; | |
153 | + } else { | |
154 | + ObjectModel obj = objectModels.get(0); | |
155 | + LwM2mObject lwM2mObject = new LwM2mObject(); | |
156 | + lwM2mObject.setId(obj.id); | |
157 | + lwM2mObject.setKeyId(resource.getResourceId()); | |
158 | + lwM2mObject.setName(obj.name); | |
159 | + lwM2mObject.setMultiple(obj.multiple); | |
160 | + lwM2mObject.setMandatory(obj.mandatory); | |
161 | + LwM2mInstance instance = new LwM2mInstance(); | |
162 | + instance.setId(0); | |
163 | + List<LwM2mResource> resources = new ArrayList<>(); | |
164 | + obj.resources.forEach((k, v) -> { | |
165 | + if (!v.operations.isExecutable()) { | |
166 | + LwM2mResource lwM2mResource = new LwM2mResource(k, v.name, false, false, false); | |
167 | + resources.add(lwM2mResource); | |
168 | + } | |
169 | + }); | |
170 | + instance.setResources(resources.stream().toArray(LwM2mResource[]::new)); | |
171 | + lwM2mObject.setInstances(new LwM2mInstance[]{instance}); | |
172 | + return lwM2mObject; | |
173 | + } | |
174 | + } catch (IOException | InvalidDDFFileException e) { | |
175 | + log.error("Could not parse the XML of objectModel with name [{}]", resource.getTextSearch(), e); | |
176 | + return null; | |
177 | + } | |
178 | + } | |
179 | + | |
180 | + private List<LwM2mObject> sortList (List<LwM2mObject> lwM2mObjects, String sortProperty, String sortOrder) { | |
181 | + switch (sortProperty) { | |
182 | + case "name": | |
183 | + switch (sortOrder) { | |
184 | + case "ASC": | |
185 | + lwM2mObjects.sort((o1, o2) -> o1.getName().compareTo(o2.getName())); | |
186 | + break; | |
187 | + case "DESC": | |
188 | + lwM2mObjects.stream().sorted(Comparator.comparing(LwM2mObject::getName).reversed()); | |
189 | + break; | |
190 | + } | |
191 | + case "id": | |
192 | + switch (sortOrder) { | |
193 | + case "ASC": | |
194 | + lwM2mObjects.sort((o1, o2) -> Long.compare(o1.getId(), o2.getId())); | |
195 | + break; | |
196 | + case "DESC": | |
197 | + lwM2mObjects.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId())); | |
198 | + } | |
199 | + } | |
200 | + return lwM2mObjects; | |
201 | + } | |
202 | + | |
106 | 203 | } | ... | ... |
... | ... | @@ -33,8 +33,15 @@ public interface ResourceDao { |
33 | 33 | |
34 | 34 | PageData<Resource> findAllByTenantId(TenantId tenantId, PageLink pageLink); |
35 | 35 | |
36 | + PageData<Resource> findResourcesByTenantIdAndResourceType(TenantId tenantId, | |
37 | + ResourceType resourceType, | |
38 | + PageLink pageLink); | |
36 | 39 | |
37 | - List<Resource> findAllByTenantIdResourceType(TenantId tenantId, ResourceType resourceType); | |
40 | + List<Resource> findAllByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType); | |
38 | 41 | |
42 | + List<Resource> findResourcesByTenantIdAndResourceType(TenantId tenantId, | |
43 | + ResourceType resourceType, | |
44 | + String[] objectIds, | |
45 | + String searchText); | |
39 | 46 | void removeAllByTenantId(TenantId tenantId); |
40 | 47 | } | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.model.sql.ResourceEntity; |
29 | 29 | import org.thingsboard.server.dao.resource.ResourceDao; |
30 | 30 | |
31 | 31 | import java.util.List; |
32 | +import java.util.Objects; | |
32 | 33 | |
33 | 34 | @Slf4j |
34 | 35 | @Component |
... | ... | @@ -73,11 +74,40 @@ public class ResourceDaoImpl implements ResourceDao { |
73 | 74 | } |
74 | 75 | |
75 | 76 | @Override |
76 | - public List<Resource> findAllByTenantIdResourceType(TenantId tenantId, ResourceType resourceType) { | |
77 | + public List<Resource> findAllByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType) { | |
77 | 78 | return DaoUtil.convertDataList(resourceRepository.findAllByTenantIdAndResourceType(tenantId.getId(), resourceType.name())); |
78 | 79 | } |
79 | 80 | |
80 | 81 | @Override |
82 | + public PageData<Resource> findResourcesByTenantIdAndResourceType(TenantId tenantId, | |
83 | + ResourceType resourceType, | |
84 | + PageLink pageLink) { | |
85 | + return DaoUtil.toPageData(resourceRepository.findResourcesPage( | |
86 | + tenantId.getId(), | |
87 | + TenantId.SYS_TENANT_ID.getId(), | |
88 | + resourceType.name(), | |
89 | + Objects.toString(pageLink.getTextSearch(), ""), | |
90 | + DaoUtil.toPageable(pageLink) | |
91 | + )); | |
92 | + } | |
93 | + | |
94 | + @Override | |
95 | + public List<Resource> findResourcesByTenantIdAndResourceType(TenantId tenantId, ResourceType resourceType, | |
96 | + String[] objectIds, | |
97 | + String searchText) { | |
98 | + return objectIds == null ? | |
99 | + DaoUtil.convertDataList(resourceRepository.findResources( | |
100 | + tenantId.getId(), | |
101 | + TenantId.SYS_TENANT_ID.getId(), | |
102 | + resourceType.name(), | |
103 | + Objects.toString(searchText, ""))) : | |
104 | + DaoUtil.convertDataList(resourceRepository.findResourcesByIds( | |
105 | + tenantId.getId(), | |
106 | + TenantId.SYS_TENANT_ID.getId(), | |
107 | + resourceType.name(), objectIds)); | |
108 | + } | |
109 | + | |
110 | + @Override | |
81 | 111 | public void removeAllByTenantId(TenantId tenantId) { |
82 | 112 | resourceRepository.removeAllByTenantId(tenantId.getId()); |
83 | 113 | } | ... | ... |
... | ... | @@ -17,7 +17,9 @@ package org.thingsboard.server.dao.sql.resource; |
17 | 17 | |
18 | 18 | import org.springframework.data.domain.Page; |
19 | 19 | import org.springframework.data.domain.Pageable; |
20 | +import org.springframework.data.jpa.repository.Query; | |
20 | 21 | import org.springframework.data.repository.CrudRepository; |
22 | +import org.springframework.data.repository.query.Param; | |
21 | 23 | import org.thingsboard.server.dao.model.sql.ResourceCompositeKey; |
22 | 24 | import org.thingsboard.server.dao.model.sql.ResourceEntity; |
23 | 25 | |
... | ... | @@ -26,10 +28,57 @@ import java.util.UUID; |
26 | 28 | |
27 | 29 | public interface ResourceRepository extends CrudRepository<ResourceEntity, ResourceCompositeKey> { |
28 | 30 | |
31 | + | |
29 | 32 | Page<ResourceEntity> findAllByTenantId(UUID tenantId, Pageable pageable); |
30 | 33 | |
34 | + @Query("SELECT tr FROM ResourceEntity tr " + | |
35 | + "WHERE tr.resourceType = :resourceType " + | |
36 | + "AND LOWER(tr.textSearch) LIKE LOWER(CONCAT('%', :searchText, '%')) " + | |
37 | + "AND (tr.tenantId = :tenantId " + | |
38 | + "OR (tr.tenantId = :systemAdminId " + | |
39 | + "AND NOT EXISTS " + | |
40 | + "(SELECT sr FROM ResourceEntity sr " + | |
41 | + "WHERE sr.tenantId = :tenantId " + | |
42 | + "AND sr.resourceType = :resourceType " + | |
43 | + "AND tr.resourceId = sr.resourceId)))") | |
44 | + Page<ResourceEntity> findResourcesPage( | |
45 | + @Param("tenantId") UUID tenantId, | |
46 | + @Param("systemAdminId") UUID sysAdminId, | |
47 | + @Param("resourceType") String resourceType, | |
48 | + @Param("searchText") String search, | |
49 | + Pageable pageable); | |
31 | 50 | |
32 | 51 | List<ResourceEntity> findAllByTenantIdAndResourceType(UUID tenantId, String resourceType); |
33 | 52 | |
34 | 53 | void removeAllByTenantId(UUID tenantId); |
54 | + | |
55 | + @Query("SELECT tr FROM ResourceEntity tr " + | |
56 | + "WHERE tr.resourceType = :resourceType " + | |
57 | + "AND LOWER(tr.textSearch) LIKE LOWER(CONCAT('%', :searchText, '%')) " + | |
58 | + "AND (tr.tenantId = :tenantId " + | |
59 | + "OR (tr.tenantId = :systemAdminId " + | |
60 | + "AND NOT EXISTS " + | |
61 | + "(SELECT sr FROM ResourceEntity sr " + | |
62 | + "WHERE sr.tenantId = :tenantId " + | |
63 | + "AND sr.resourceType = :resourceType " + | |
64 | + "AND tr.resourceId = sr.resourceId)))") | |
65 | + List<ResourceEntity> findResources(@Param("tenantId") UUID tenantId, | |
66 | + @Param("systemAdminId") UUID sysAdminId, | |
67 | + @Param("resourceType") String resourceType, | |
68 | + @Param("searchText") String search); | |
69 | + | |
70 | + @Query("SELECT tr FROM ResourceEntity tr " + | |
71 | + "WHERE tr.resourceType = :resourceType " + | |
72 | + "AND tr.resourceId in (:resourceIds) " + | |
73 | + "AND (tr.tenantId = :tenantId " + | |
74 | + "OR (tr.tenantId = :systemAdminId " + | |
75 | + "AND NOT EXISTS " + | |
76 | + "(SELECT sr FROM ResourceEntity sr " + | |
77 | + "WHERE sr.tenantId = :tenantId " + | |
78 | + "AND sr.resourceType = :resourceType " + | |
79 | + "AND tr.resourceId = sr.resourceId)))") | |
80 | + List<ResourceEntity> findResourcesByIds(@Param("tenantId") UUID tenantId, | |
81 | + @Param("systemAdminId") UUID sysAdminId, | |
82 | + @Param("resourceType") String resourceType, | |
83 | + @Param("resourceIds") String[] objectIds); | |
35 | 84 | } | ... | ... |
... | ... | @@ -425,6 +425,7 @@ CREATE TABLE IF NOT EXISTS resource ( |
425 | 425 | tenant_id uuid NOT NULL, |
426 | 426 | resource_type varchar(32) NOT NULL, |
427 | 427 | resource_id varchar(255) NOT NULL, |
428 | + text_search varchar(255), | |
428 | 429 | resource_value varchar, |
429 | 430 | CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id) |
430 | 431 | ); | ... | ... |
... | ... | @@ -451,6 +451,7 @@ CREATE TABLE IF NOT EXISTS resource ( |
451 | 451 | tenant_id uuid NOT NULL, |
452 | 452 | resource_type varchar(32) NOT NULL, |
453 | 453 | resource_id varchar(255) NOT NULL, |
454 | + text_search varchar(255), | |
454 | 455 | resource_value varchar, |
455 | 456 | CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id) |
456 | 457 | ); | ... | ... |
... | ... | @@ -14,16 +14,16 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Injectable } from '@angular/core'; | |
18 | -import { HttpClient } from '@angular/common/http'; | |
19 | -import { PageLink } from '@shared/models/page/page-link'; | |
20 | -import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; | |
21 | -import { Observable } from 'rxjs'; | |
22 | -import { PageData } from '@shared/models/page/page-data'; | |
23 | -import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; | |
24 | -import { isDefinedAndNotNull, isEmptyStr } from '@core/utils'; | |
25 | -import { ObjectLwM2M, ServerSecurityConfig } from '@home/components/profile/device/lwm2m/profile-config.models'; | |
26 | -import { SortOrder } from '@shared/models/page/sort-order'; | |
17 | +import {Injectable} from '@angular/core'; | |
18 | +import {HttpClient} from '@angular/common/http'; | |
19 | +import {PageLink} from '@shared/models/page/page-link'; | |
20 | +import {defaultHttpOptionsFromConfig, RequestConfig} from './http-utils'; | |
21 | +import {Observable} from 'rxjs'; | |
22 | +import {PageData} from '@shared/models/page/page-data'; | |
23 | +import {DeviceProfile, DeviceProfileInfo, DeviceTransportType} from '@shared/models/device.models'; | |
24 | +import {isDefinedAndNotNull, isEmptyStr} from '@core/utils'; | |
25 | +import {ObjectLwM2M, ServerSecurityConfig} from '@home/components/profile/device/lwm2m/profile-config.models'; | |
26 | +import {SortOrder} from '@shared/models/page/sort-order'; | |
27 | 27 | |
28 | 28 | @Injectable({ |
29 | 29 | providedIn: 'root' |
... | ... | @@ -43,9 +43,9 @@ export class DeviceProfileService { |
43 | 43 | return this.http.get<DeviceProfile>(`/api/deviceProfile/${deviceProfileId}`, defaultHttpOptionsFromConfig(config)); |
44 | 44 | } |
45 | 45 | |
46 | - public getLwm2mObjects(sortOrder: SortOrder, objectIds?: number[], searchText?: string, config?: RequestConfig): | |
46 | + public getLwm2mObjects(sortOrder: SortOrder, objectIds?: string[], searchText?: string, config?: RequestConfig): | |
47 | 47 | Observable<Array<ObjectLwM2M>> { |
48 | - let url = `/api/lwm2m/deviceProfile/?sortProperty=${sortOrder.property}&sortOrder=${sortOrder.direction}`; | |
48 | + let url = `/api/resource/lwm2m/?sortProperty=${sortOrder.property}&sortOrder=${sortOrder.direction}`; | |
49 | 49 | if (isDefinedAndNotNull(objectIds) && objectIds.length > 0) { |
50 | 50 | url += `&objectIds=${objectIds}`; |
51 | 51 | } |
... | ... | @@ -63,9 +63,9 @@ export class DeviceProfileService { |
63 | 63 | ); |
64 | 64 | } |
65 | 65 | |
66 | - public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ObjectLwM2M>> { | |
67 | - return this.http.get<PageData<ObjectLwM2M>>( | |
68 | - `/api/lwm2m/deviceProfile/objects${pageLink.toQuery()}`, | |
66 | + public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable<Array<ObjectLwM2M>> { | |
67 | + return this.http.get<Array<ObjectLwM2M>>( | |
68 | + `/api/resource/lwm2m/page${pageLink.toQuery()}`, | |
69 | 69 | defaultHttpOptionsFromConfig(config) |
70 | 70 | ); |
71 | 71 | } | ... | ... |
... | ... | @@ -172,7 +172,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
172 | 172 | |
173 | 173 | private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => { |
174 | 174 | this.lwm2mDeviceProfileFormGroup.patchValue({ |
175 | - observeAttrTelemetry: this.getObserveAttrTelemetryObjects(objectsList) | |
175 | + observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList)) | |
176 | 176 | }, |
177 | 177 | {emitEvent: false}); |
178 | 178 | } |
... | ... | @@ -195,32 +195,32 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
195 | 195 | } |
196 | 196 | } |
197 | 197 | |
198 | - private getObserveAttrTelemetryObjects = (listObject: ObjectLwM2M[]): object => { | |
199 | - const clientObserveAttrTelemetry = listObject; | |
200 | - if (this.configurationValue.observeAttr) { | |
198 | + private getObserveAttrTelemetryObjects = (objectList: ObjectLwM2M[]): object => { | |
199 | + const objectLwM2MS = deepClone(objectList); | |
200 | + if (this.configurationValue.observeAttr && objectLwM2MS.length > 0) { | |
201 | 201 | const observeArray = this.configurationValue.observeAttr.observe; |
202 | 202 | const attributeArray = this.configurationValue.observeAttr.attribute; |
203 | 203 | const telemetryArray = this.configurationValue.observeAttr.telemetry; |
204 | 204 | let keyNameJson = this.configurationValue.observeAttr.keyName; |
205 | 205 | if (this.includesNotZeroInstance(attributeArray, telemetryArray)) { |
206 | - this.addInstances(attributeArray, telemetryArray, clientObserveAttrTelemetry); | |
206 | + this.addInstances(attributeArray, telemetryArray, objectLwM2MS); | |
207 | 207 | } |
208 | 208 | if (isDefinedAndNotNull(observeArray)) { |
209 | - this.updateObserveAttrTelemetryObjects(observeArray, clientObserveAttrTelemetry, OBSERVE); | |
209 | + this.updateObserveAttrTelemetryObjects(observeArray, objectLwM2MS, OBSERVE); | |
210 | 210 | } |
211 | 211 | if (isDefinedAndNotNull(attributeArray)) { |
212 | - this.updateObserveAttrTelemetryObjects(attributeArray, clientObserveAttrTelemetry, ATTRIBUTE); | |
212 | + this.updateObserveAttrTelemetryObjects(attributeArray, objectLwM2MS, ATTRIBUTE); | |
213 | 213 | } |
214 | 214 | if (isDefinedAndNotNull(telemetryArray)) { |
215 | - this.updateObserveAttrTelemetryObjects(telemetryArray, clientObserveAttrTelemetry, TELEMETRY); | |
215 | + this.updateObserveAttrTelemetryObjects(telemetryArray, objectLwM2MS, TELEMETRY); | |
216 | 216 | } |
217 | 217 | if (isDefinedAndNotNull(keyNameJson)) { |
218 | 218 | this.configurationValue.observeAttr.keyName = this.validateKeyNameObjects(keyNameJson, attributeArray, telemetryArray); |
219 | 219 | this.upDateJsonAllConfig(); |
220 | - this.updateKeyNameObjects(clientObserveAttrTelemetry); | |
220 | + this.updateKeyNameObjects(objectLwM2MS); | |
221 | 221 | } |
222 | 222 | } |
223 | - return {clientLwM2M: clientObserveAttrTelemetry}; | |
223 | + return {clientLwM2M: objectLwM2MS}; | |
224 | 224 | } |
225 | 225 | |
226 | 226 | private includesNotZeroInstance = (attribute: string[], telemetry: string[]): boolean => { |
... | ... | @@ -235,35 +235,39 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
235 | 235 | .sort(this.sortPath); |
236 | 236 | |
237 | 237 | new Set(instancesPath).forEach(path => { |
238 | - const pathParameter = Array.from(path.split('/'), Number); | |
239 | - const objectLwM2M = clientObserveAttrTelemetry.find(x => x.id === pathParameter[0]); | |
238 | + const pathParameter = Array.from(path.split('/'), String); | |
239 | + const objectLwM2M = clientObserveAttrTelemetry.find(x => x.keyId === pathParameter[0]); | |
240 | 240 | if (objectLwM2M) { |
241 | 241 | const instance = deepClone(objectLwM2M.instances[0]); |
242 | - instance.id = pathParameter[1]; | |
242 | + instance.id = +pathParameter[1]; | |
243 | 243 | objectLwM2M.instances.push(instance); |
244 | 244 | } |
245 | 245 | }); |
246 | 246 | } |
247 | 247 | |
248 | - private updateObserveAttrTelemetryObjects = (parameters: string[], clientObserveAttrTelemetry: ObjectLwM2M[], | |
248 | + private updateObserveAttrTelemetryObjects = (parameters: string[], objectLwM2MS: ObjectLwM2M[], | |
249 | 249 | nameParameter: string): void => { |
250 | 250 | parameters.forEach(parameter => { |
251 | - const [objectId, instanceId, resourceId] = Array.from(parameter.substring(1).split('/'), Number); | |
252 | - clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.id === objectId) | |
253 | - .instances.find(itrInstance => itrInstance.id === instanceId) | |
254 | - .resources.find(resource => resource.id === resourceId) | |
255 | - [nameParameter] = true; | |
251 | + const [objectKeyId, instanceId, resourceId] = Array.from(parameter.substring(1).split('/'), String); | |
252 | + const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId); | |
253 | + if (objectLwM2M) { | |
254 | + objectLwM2M.instances.find(itrInstance => itrInstance.id === +instanceId) | |
255 | + .resources.find(resource => resource.id === +resourceId) | |
256 | + [nameParameter] = true; | |
257 | + } | |
256 | 258 | }); |
259 | + | |
257 | 260 | } |
258 | 261 | |
259 | 262 | private updateKeyNameObjects = (clientObserveAttrTelemetry: ObjectLwM2M[]): void => { |
260 | 263 | Object.keys(this.configurationValue.observeAttr.keyName).forEach(key => { |
261 | - const [objectId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), Number); | |
262 | - clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.id === objectId) | |
263 | - .instances.find(instance => instance.id === instanceId) | |
264 | - .resources.find(resource => resource.id === resourceId) | |
264 | + const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String); | |
265 | + const objectLwM2M = clientObserveAttrTelemetry.find(objectLwm2m => objectLwm2m.keyId === objectKeyId) | |
266 | + if (objectLwM2M) { | |
267 | + objectLwM2M.instances.find(instance => instance.id === +instanceId) | |
268 | + .resources.find(resource => resource.id === +resourceId) | |
265 | 269 | .keyName = this.configurationValue.observeAttr.keyName[key]; |
266 | - }); | |
270 | + }}); | |
267 | 271 | } |
268 | 272 | |
269 | 273 | private validateKeyNameObjects = (nameJson: JsonObject, attributeArray: JsonArray, telemetryArray: JsonArray): {} => { |
... | ... | @@ -290,7 +294,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
290 | 294 | if (instance.hasOwnProperty(RESOURCES) && Array.isArray(instance.resources)) { |
291 | 295 | instance.resources.forEach(resource => { |
292 | 296 | if (resource.attribute || resource.telemetry) { |
293 | - let pathRes = `/${obj.id}/${instance.id}/${resource.id}`; | |
297 | + let pathRes = `/${obj.keyId}/${instance.id}/${resource.id}`; | |
294 | 298 | if (resource.observe) { |
295 | 299 | observeArray.push(pathRes); |
296 | 300 | } |
... | ... | @@ -345,22 +349,22 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
345 | 349 | }); |
346 | 350 | } |
347 | 351 | |
348 | - private getObjectsFromJsonAllConfig = (): number[] => { | |
349 | - const objectsIds = new Set<number>(); | |
352 | + private getObjectsFromJsonAllConfig = (): string[] => { | |
353 | + const objectsIds = new Set<string>(); | |
350 | 354 | if (this.configurationValue.observeAttr) { |
351 | 355 | if (this.configurationValue.observeAttr.observe) { |
352 | 356 | this.configurationValue.observeAttr.observe.forEach(obj => { |
353 | - objectsIds.add(Array.from(obj.substring(1).split('/'), Number)[0]); | |
357 | + objectsIds.add(Array.from(obj.substring(1).split('/'), String)[0]); | |
354 | 358 | }); |
355 | 359 | } |
356 | 360 | if (this.configurationValue.observeAttr.attribute) { |
357 | 361 | this.configurationValue.observeAttr.attribute.forEach(obj => { |
358 | - objectsIds.add(Array.from(obj.substring(1).split('/'), Number)[0]); | |
362 | + objectsIds.add(Array.from(obj.substring(1).split('/'), String)[0]); | |
359 | 363 | }); |
360 | 364 | } |
361 | 365 | if (this.configurationValue.observeAttr.telemetry) { |
362 | 366 | this.configurationValue.observeAttr.telemetry.forEach(obj => { |
363 | - objectsIds.add(Array.from(obj.substring(1).split('/'), Number)[0]); | |
367 | + objectsIds.add(Array.from(obj.substring(1).split('/'), String)[0]); | |
364 | 368 | }); |
365 | 369 | } |
366 | 370 | } |
... | ... | @@ -379,21 +383,21 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
379 | 383 | |
380 | 384 | removeObjectsList = (value: ObjectLwM2M): void => { |
381 | 385 | const objectsOld = this.lwm2mDeviceProfileFormGroup.get(OBSERVE_ATTR_TELEMETRY).value.clientLwM2M; |
382 | - const isIdIndex = (element) => element.id === value.id; | |
386 | + const isIdIndex = (element) => element.keyId === value.keyId; | |
383 | 387 | const index = objectsOld.findIndex(isIdIndex); |
384 | 388 | if (index >= 0) { |
385 | 389 | objectsOld.splice(index, 1); |
386 | 390 | } |
387 | - this.removeObserveAttrTelemetryFromJson(OBSERVE, value.id); | |
388 | - this.removeObserveAttrTelemetryFromJson(TELEMETRY, value.id); | |
389 | - this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.id); | |
390 | - this.removeKeyNameFromJson(value.id); | |
391 | + this.removeObserveAttrTelemetryFromJson(OBSERVE, value.keyId); | |
392 | + this.removeObserveAttrTelemetryFromJson(TELEMETRY, value.keyId); | |
393 | + this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.keyId); | |
394 | + this.removeKeyNameFromJson(value.keyId); | |
391 | 395 | this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); |
392 | 396 | this.upDateJsonAllConfig(); |
393 | 397 | } |
394 | 398 | |
395 | - private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, id: number): void => { | |
396 | - const isIdIndex = (element) => element.startsWith(`/${id}`); | |
399 | + private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => { | |
400 | + const isIdIndex = (element) => element.startsWith(`/${keyId}`); | |
397 | 401 | let index = this.configurationValue.observeAttr[observeAttrTel].findIndex(isIdIndex); |
398 | 402 | while (index >= 0) { |
399 | 403 | this.configurationValue.observeAttr[observeAttrTel].splice(index, 1); |
... | ... | @@ -401,10 +405,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro |
401 | 405 | } |
402 | 406 | } |
403 | 407 | |
404 | - private removeKeyNameFromJson = (id: number): void => { | |
408 | + private removeKeyNameFromJson = (keyId: string): void => { | |
405 | 409 | const keyNameJson = this.configurationValue.observeAttr.keyName; |
406 | 410 | Object.keys(keyNameJson).forEach(key => { |
407 | - if (key.startsWith(`/${id}`)) { | |
411 | + if (key.startsWith(`/${keyId}`)) { | |
408 | 412 | delete keyNameJson[key]; |
409 | 413 | } |
410 | 414 | }); | ... | ... |
... | ... | @@ -14,12 +14,11 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, forwardRef, } from '@angular/core'; | |
18 | -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | -import { Store } from '@ngrx/store'; | |
20 | -import { AppState } from '@core/core.state'; | |
21 | -import { INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER } from './profile-config.models'; | |
22 | -import { DeviceProfileService } from '@core/http/device-profile.service'; | |
17 | +import {Component, forwardRef,} from '@angular/core'; | |
18 | +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; | |
19 | +import {Store} from '@ngrx/store'; | |
20 | +import {AppState} from '@core/core.state'; | |
21 | +import {INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER} from './profile-config.models'; | |
23 | 22 | |
24 | 23 | @Component({ |
25 | 24 | selector: 'tb-profile-lwm2m-object-add-instances-list', |
... | ... | @@ -43,7 +42,6 @@ export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccesso |
43 | 42 | private propagateChange = (v: any) => { }; |
44 | 43 | |
45 | 44 | constructor(private store: Store<AppState>, |
46 | - private deviceProfileService: DeviceProfileService, | |
47 | 45 | private fb: FormBuilder) { |
48 | 46 | this.instancesListFormGroup = this.fb.group({ |
49 | 47 | instanceIdInput: [null, [ | ... | ... |
... | ... | @@ -17,7 +17,7 @@ |
17 | 17 | --> |
18 | 18 | <form [formGroup]="instancesFormGroup" (ngSubmit)="add()" style="min-width: 400px;"> |
19 | 19 | <mat-toolbar fxLayout="row" color="primary"> |
20 | - <b><i>{{data.objectName}}</i></b> (object [<b>{{data.objectId}}</b>]) | |
20 | + <b><i>{{data.objectName}}</i></b> (object [<b>{{data.objectKeyId}}</b>]) | |
21 | 21 | <span fxFlex></span> |
22 | 22 | <button mat-button mat-icon-button |
23 | 23 | (click)="cancel()" | ... | ... |
... | ... | @@ -14,18 +14,18 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, Inject, OnInit } from '@angular/core'; | |
18 | -import { DialogComponent } from '@shared/components/dialog.component'; | |
19 | -import { FormBuilder, FormGroup } from '@angular/forms'; | |
20 | -import { Store } from '@ngrx/store'; | |
21 | -import { AppState } from '@core/core.state'; | |
22 | -import { Router } from '@angular/router'; | |
23 | -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | |
17 | +import {Component, Inject, OnInit} from '@angular/core'; | |
18 | +import {DialogComponent} from '@shared/components/dialog.component'; | |
19 | +import {FormBuilder, FormGroup} from '@angular/forms'; | |
20 | +import {Store} from '@ngrx/store'; | |
21 | +import {AppState} from '@core/core.state'; | |
22 | +import {Router} from '@angular/router'; | |
23 | +import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; | |
24 | 24 | |
25 | 25 | export interface Lwm2mObjectAddInstancesData { |
26 | 26 | instancesIds: Set<number>; |
27 | 27 | objectName?: string; |
28 | - objectId?: number; | |
28 | + objectKeyId?: string; | |
29 | 29 | } |
30 | 30 | |
31 | 31 | @Component({ | ... | ... |
... | ... | @@ -40,7 +40,7 @@ |
40 | 40 | class="tb-autocomplete" |
41 | 41 | [displayWith]="displayObjectLwm2mFn"> |
42 | 42 | <mat-option *ngFor="let objectLwm2m of filteredObjectsList | async" [value]="objectLwm2m"> |
43 | - <span [innerHTML]="objectLwm2m.id + ': ' + objectLwm2m.name | highlight:searchText"></span> | |
43 | + <span [innerHTML]="objectLwm2m.keyId + ': ' + objectLwm2m.name | highlight:searchText"></span> | |
44 | 44 | </mat-option> |
45 | 45 | <mat-option *ngIf="!(filteredObjectsList | async)?.length" [value]="null"> |
46 | 46 | <span> | ... | ... |
... | ... | @@ -14,17 +14,18 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; | |
18 | -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; | |
19 | -import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
20 | -import { Store } from '@ngrx/store'; | |
21 | -import { AppState } from '@core/core.state'; | |
22 | -import { Observable } from 'rxjs'; | |
23 | -import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; | |
24 | -import { ModelValue, ObjectLwM2M } from './profile-config.models'; | |
25 | -import { DeviceProfileService } from '@core/http/device-profile.service'; | |
26 | -import { Direction } from '@shared/models/page/sort-order'; | |
27 | -import { isDefined, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils'; | |
17 | +import {Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild} from '@angular/core'; | |
18 | +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; | |
19 | +import {coerceBooleanProperty} from '@angular/cdk/coercion'; | |
20 | +import {Store} from '@ngrx/store'; | |
21 | +import {AppState} from '@core/core.state'; | |
22 | +import {Observable} from 'rxjs'; | |
23 | +import {filter, map, mergeMap, publishReplay, refCount, tap} from 'rxjs/operators'; | |
24 | +import {ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT} from './profile-config.models'; | |
25 | +import {DeviceProfileService} from '@core/http/device-profile.service'; | |
26 | +import {Direction} from '@shared/models/page/sort-order'; | |
27 | +import {isDefined, isDefinedAndNotNull, isString} from '@core/utils'; | |
28 | +import {PageLink} from "@shared/models/page/page-link"; | |
28 | 29 | |
29 | 30 | @Component({ |
30 | 31 | selector: 'tb-profile-lwm2m-object-list', |
... | ... | @@ -41,7 +42,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V |
41 | 42 | private requiredValue: boolean; |
42 | 43 | private dirty = false; |
43 | 44 | private lw2mModels: Observable<Array<ObjectLwM2M>>; |
44 | - private modelValue: Array<number> = []; | |
45 | + private modelValue: Array<string> = []; | |
45 | 46 | |
46 | 47 | lwm2mListFormGroup: FormGroup; |
47 | 48 | objectsList: Array<ObjectLwM2M> = []; |
... | ... | @@ -134,8 +135,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V |
134 | 135 | } |
135 | 136 | |
136 | 137 | private add(object: ObjectLwM2M): void { |
137 | - if (isDefinedAndNotNull(this.modelValue) && this.modelValue.indexOf(object.id) === -1) { | |
138 | - this.modelValue.push(object.id); | |
138 | + if (isDefinedAndNotNull(this.modelValue) && this.modelValue.indexOf(object.keyId) === -1) { | |
139 | + this.modelValue.push(object.keyId); | |
139 | 140 | this.objectsList.push(object); |
140 | 141 | this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList); |
141 | 142 | this.addList.next(this.objectsList); |
... | ... | @@ -148,7 +149,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V |
148 | 149 | if (index >= 0) { |
149 | 150 | this.objectsList.splice(index, 1); |
150 | 151 | this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList); |
151 | - index = this.modelValue.indexOf(object.id); | |
152 | + index = this.modelValue.indexOf(object.keyId); | |
152 | 153 | this.modelValue.splice(index, 1); |
153 | 154 | this.removeList.next(object); |
154 | 155 | this.clear(); |
... | ... | @@ -159,25 +160,29 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V |
159 | 160 | return object ? object.name : undefined; |
160 | 161 | } |
161 | 162 | |
162 | - private fetchListObjects = (searchText?: string): Observable<Array<ObjectLwM2M>> => { | |
163 | + private fetchListObjects = (searchText?: string): Observable<Array<ObjectLwM2M>> => { | |
163 | 164 | this.searchText = searchText; |
164 | - const filters = {names: [], ids: []}; | |
165 | - if (isDefinedAndNotNull(searchText) && !isEmptyStr(searchText)) { | |
166 | - const ids = searchText.match(/\d+/g); | |
167 | - filters.ids = ids !== null ? ids.map(Number) : filters.ids; | |
168 | - filters.names = searchText.trim().toUpperCase().split(' '); | |
169 | - } | |
170 | - const predicate = objectLwM2M => filters.names.find(word => objectLwM2M.name.toUpperCase().includes(word)) | |
171 | - || filters.ids.includes(objectLwM2M.id); | |
172 | - return this.getLwM2mModels().pipe( | |
173 | - map(objectLwM2Ms => searchText ? objectLwM2Ms.filter(predicate) : objectLwM2Ms) | |
174 | - ); | |
165 | + return this.getLwM2mModelsPage().pipe( | |
166 | + map(objectLwM2Ms => objectLwM2Ms) | |
167 | + ); | |
168 | + } | |
169 | + | |
170 | + private getLwM2mModelsPage(): Observable<Array<ObjectLwM2M>> { | |
171 | + const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, { | |
172 | + property: 'id', | |
173 | + direction: Direction.ASC | |
174 | + }); | |
175 | + this.lw2mModels = this.deviceProfileService.getLwm2mObjectsPage(pageLink).pipe( | |
176 | + publishReplay(1), | |
177 | + refCount() | |
178 | + ); | |
179 | + return this.lw2mModels; | |
175 | 180 | } |
176 | 181 | |
177 | 182 | private getLwM2mModels(): Observable<Array<ObjectLwM2M>> { |
178 | 183 | if (!this.lw2mModels) { |
179 | 184 | const sortOrder = { |
180 | - property: 'name', | |
185 | + property: 'id', | |
181 | 186 | direction: Direction.ASC |
182 | 187 | }; |
183 | 188 | this.lw2mModels = this.deviceProfileService.getLwm2mObjects(sortOrder).pipe( | ... | ... |
... | ... | @@ -22,7 +22,7 @@ |
22 | 22 | [formGroupName]="i"> |
23 | 23 | <mat-expansion-panel-header> |
24 | 24 | <mat-panel-title fxLayoutAlign="start center"> |
25 | - <b><i>{{ objectLwM2M.get('name').value}}</i></b> <id: {{ objectLwM2M.get('id').value}}> | |
25 | + <b><i>{{ objectLwM2M.get('name').value}}</i></b> <id: {{ objectLwM2M.get('keyId').value}}> | |
26 | 26 | </mat-panel-title> |
27 | 27 | <mat-panel-description fxLayoutAlign="end center" *ngIf="!disabled"> |
28 | 28 | <button type="button" | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 17 | |
18 | -import { Component, forwardRef, Input } from '@angular/core'; | |
18 | +import {Component, forwardRef, Input} from '@angular/core'; | |
19 | 19 | import { |
20 | 20 | AbstractControl, |
21 | 21 | ControlValueAccessor, |
... | ... | @@ -25,13 +25,13 @@ import { |
25 | 25 | NG_VALUE_ACCESSOR, |
26 | 26 | Validators |
27 | 27 | } from '@angular/forms'; |
28 | -import { Store } from '@ngrx/store'; | |
29 | -import { AppState } from '@core/core.state'; | |
30 | -import { coerceBooleanProperty } from '@angular/cdk/coercion'; | |
31 | -import { CLIENT_LWM2M, Instance, INSTANCES, ObjectLwM2M, ResourceLwM2M, RESOURCES } from './profile-config.models'; | |
32 | -import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils'; | |
33 | -import { MatDialog } from '@angular/material/dialog'; | |
34 | -import { TranslateService } from '@ngx-translate/core'; | |
28 | +import {Store} from '@ngrx/store'; | |
29 | +import {AppState} from '@core/core.state'; | |
30 | +import {coerceBooleanProperty} from '@angular/cdk/coercion'; | |
31 | +import {CLIENT_LWM2M, Instance, INSTANCES, ObjectLwM2M, ResourceLwM2M, RESOURCES} from './profile-config.models'; | |
32 | +import {deepClone, isDefinedAndNotNull, isEqual, isUndefined} from '@core/utils'; | |
33 | +import {MatDialog} from '@angular/material/dialog'; | |
34 | +import {TranslateService} from '@ngx-translate/core'; | |
35 | 35 | import { |
36 | 36 | Lwm2mObjectAddInstancesComponent, |
37 | 37 | Lwm2mObjectAddInstancesData |
... | ... | @@ -139,7 +139,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor |
139 | 139 | private createObjectsLwM2M = (objectsLwM2M: ObjectLwM2M[]): FormArray => { |
140 | 140 | return this.fb.array(objectsLwM2M.map((objectLwM2M) => { |
141 | 141 | return this.fb.group({ |
142 | - id: objectLwM2M.id, | |
142 | + keyId: objectLwM2M.keyId, | |
143 | 143 | name: objectLwM2M.name, |
144 | 144 | multiple: objectLwM2M.multiple, |
145 | 145 | mandatory: objectLwM2M.mandatory, |
... | ... | @@ -228,7 +228,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor |
228 | 228 | data: { |
229 | 229 | instancesIds: this.instancesToSetId(object.instances), |
230 | 230 | objectName: object.name, |
231 | - objectId: object.id | |
231 | + objectKeyId: object.keyId | |
232 | 232 | } |
233 | 233 | }).afterClosed().subscribe( |
234 | 234 | (res: Lwm2mObjectAddInstancesData | undefined) => { |
... | ... | @@ -241,7 +241,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor |
241 | 241 | |
242 | 242 | private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => { |
243 | 243 | const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls |
244 | - .find(e => e.value.id === data.objectId) as FormGroup; | |
244 | + .find(e => e.value.keyId === data.objectKeyId) as FormGroup; | |
245 | 245 | const instancesArray = objectLwM2MFormGroup.value.instances as Instance []; |
246 | 246 | const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray; |
247 | 247 | const instance0 = deepClone(instancesFormArray.at(0).value as Instance); | ... | ... |
... | ... | @@ -14,6 +14,7 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | +export const PAGE_SIZE_LIMIT = 50; | |
17 | 18 | export const INSTANCES = 'instances'; |
18 | 19 | export const RESOURCES = 'resources'; |
19 | 20 | export const CLIENT_LWM2M = 'clientLwM2M'; |
... | ... | @@ -58,7 +59,7 @@ export const SECURITY_CONFIG_MODE_NAMES = new Map<SECURITY_CONFIG_MODE, string>( |
58 | 59 | ); |
59 | 60 | |
60 | 61 | export interface ModelValue { |
61 | - objectIds: number[] | null, | |
62 | + objectIds: string[] | null, | |
62 | 63 | objectsList: ObjectLwM2M[] |
63 | 64 | } |
64 | 65 | |
... | ... | @@ -190,6 +191,7 @@ export interface Instance { |
190 | 191 | */ |
191 | 192 | export interface ObjectLwM2M { |
192 | 193 | id: number; |
194 | + keyId: string; | |
193 | 195 | name: string; |
194 | 196 | multiple?: boolean; |
195 | 197 | mandatory?: boolean; | ... | ... |