diff --git a/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java b/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java index 5da54eb8b5..eb082c92c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java +++ b/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java @@ -68,6 +68,7 @@ import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService; import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService; import org.thingsboard.server.service.entitiy.widgets.type.TbWidgetTypeService; +import org.thingsboard.server.service.install.ProjectInfo; import org.thingsboard.server.service.rule.TbRuleChainService; import org.thingsboard.server.service.security.model.SecurityUser; @@ -117,6 +118,7 @@ public class DefaultIotHubService implements IotHubService { private final DeviceService deviceService; private final TbDeviceService tbDeviceService; private final SolutionService solutionService; + private final ProjectInfo projectInfo; // Field names of the marketplace version JSON payload. Both the install path and the // install-plan resolver parse the same shape, so the contract lives here in one place. @@ -203,10 +205,11 @@ public class DefaultIotHubService implements IotHubService { } try { - iotHubRestClient.reportVersionInstalled(versionId); + InstallReport report = buildInstallReport(tenantId.getId(), user.getId().getId(), + projectInfo.getProjectVersion(), projectInfo.getProductType()); + iotHubRestClient.reportVersionInstalled(versionId, report); } catch (Exception e) { - // Counter ping is best-effort — do not fail the install if it errors. - log.warn("[{}] Failed to report install counter for version {}: {}", tenantId, versionId, e.getMessage()); + log.warn("[{}] Failed to report install for version {}: {}", tenantId, versionId, e.getMessage()); } log.info("[{}] Successfully installed IoT Hub item version: {} (type: {})", tenantId, itemName, itemType); return installedItem; @@ -665,6 +668,13 @@ public class DefaultIotHubService implements IotHubService { } } + static InstallReport buildInstallReport(UUID tenantId, UUID userId, String tbVersion, String edition) { + String salt = tenantId.toString(); + String tenantHash = sha256(salt + tenantId); + String userHash = sha256(salt + userId); + return new InstallReport(tenantHash, userHash, tbVersion, edition); + } + @Override public InstallItemVersionResult registerDeviceInstall(SecurityUser user, String versionId, DeviceInstalledItemDescriptor descriptor) { TenantId tenantId = user.getTenantId(); @@ -704,10 +714,11 @@ public class DefaultIotHubService implements IotHubService { } try { - iotHubRestClient.reportVersionInstalled(versionId); + InstallReport report = buildInstallReport(tenantId.getId(), user.getId().getId(), + projectInfo.getProjectVersion(), projectInfo.getProductType()); + iotHubRestClient.reportVersionInstalled(versionId, report); } catch (Exception e) { - // Counter ping is best-effort — do not fail the install if it errors. - log.warn("[{}] Failed to report install counter for version {}: {}", tenantId, versionId, e.getMessage()); + log.warn("[{}] Failed to report install for version {}: {}", tenantId, versionId, e.getMessage()); } log.info("[{}] Registered device package install: {} (version {})", tenantId, itemName, version); diff --git a/application/src/main/java/org/thingsboard/server/service/iot_hub/InstallReport.java b/application/src/main/java/org/thingsboard/server/service/iot_hub/InstallReport.java new file mode 100644 index 0000000000..3b253e426c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/iot_hub/InstallReport.java @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2026 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.iot_hub; + +public record InstallReport(String tenantHash, String userHash, String tbVersion, String edition) { +} diff --git a/application/src/main/java/org/thingsboard/server/service/iot_hub/IotHubRestClient.java b/application/src/main/java/org/thingsboard/server/service/iot_hub/IotHubRestClient.java index c67fef55a8..ae3cf5b07e 100644 --- a/application/src/main/java/org/thingsboard/server/service/iot_hub/IotHubRestClient.java +++ b/application/src/main/java/org/thingsboard/server/service/iot_hub/IotHubRestClient.java @@ -120,9 +120,9 @@ public class IotHubRestClient { }); } - public void reportVersionInstalled(String versionId) { + public void reportVersionInstalled(String versionId, InstallReport report) { String url = baseUrl + "/api/versions/" + versionId + "/install"; log.debug("Reporting IoT Hub version installed: {}", url); - restTemplate.postForObject(url, null, Void.class); + restTemplate.postForObject(url, report, Void.class); } } diff --git a/application/src/main/java/org/thingsboard/server/service/solutions/DefaultSolutionService.java b/application/src/main/java/org/thingsboard/server/service/solutions/DefaultSolutionService.java index 17a018bdfa..2d722e6483 100644 --- a/application/src/main/java/org/thingsboard/server/service/solutions/DefaultSolutionService.java +++ b/application/src/main/java/org/thingsboard/server/service/solutions/DefaultSolutionService.java @@ -395,12 +395,12 @@ public class DefaultSolutionService implements SolutionService { provisionAlarmRules(ctx); + provisionCalculatedFields(ctx); + Set> telemetryLoading = launchEmulators(ctx, devices, assets); waitForTelemetryCompletion(telemetryLoading); - provisionCalculatedFields(ctx); - ctx.getSolutionInstructions().setDetails(prepareInstructions(ctx, request)); List ruleChainDefs = loadListOfEntitiesIfFileExists(ctx.getTempDir(), "rule_chains.json", new TypeReference<>() {}); @@ -1302,27 +1302,7 @@ public class DefaultSolutionService implements SolutionService { List cfs = loadListOfEntitiesIfFileExists(ctx.getTempDir(), "calculated_fields.json", new TypeReference<>() { }); cfs.addAll(loadListOfEntitiesFromDirectory(ctx.getTempDir(), "calculated_fields", CalculatedFieldDefinition.class)); - - List createOnly = new ArrayList<>(); - TreeMap> ordered = new TreeMap<>(); - - for (CalculatedFieldDefinition cf : cfs) { - if (cf.getReprocessingOrder() == null || cf.getReprocessingOrder() < 0) { - createOnly.add(cf); - } else { - ordered.computeIfAbsent(cf.getReprocessingOrder(), integer -> new ArrayList<>()).add(cf); - } - } - - createOnly.forEach(cf -> ctx.register(createCalculatedField(cf, ctx))); - - for (Map.Entry> entry : ordered.entrySet()) { - List cfDefs = entry.getValue(); - for (CalculatedFieldDefinition cfDef : cfDefs) { - CalculatedField calculatedField = createCalculatedField(cfDef, ctx); - ctx.register(calculatedField); - } - } + cfs.forEach(cf -> ctx.register(createCalculatedField(cf, ctx))); } private CalculatedField createCalculatedField(CalculatedField cf, SolutionInstallContext ctx) { diff --git a/application/src/main/java/org/thingsboard/server/service/solutions/data/definition/CalculatedFieldDefinition.java b/application/src/main/java/org/thingsboard/server/service/solutions/data/definition/CalculatedFieldDefinition.java index 1dcc7a6bbd..536d3ce279 100644 --- a/application/src/main/java/org/thingsboard/server/service/solutions/data/definition/CalculatedFieldDefinition.java +++ b/application/src/main/java/org/thingsboard/server/service/solutions/data/definition/CalculatedFieldDefinition.java @@ -23,6 +23,4 @@ import org.thingsboard.server.common.data.cf.CalculatedField; @EqualsAndHashCode(callSuper = true) public class CalculatedFieldDefinition extends CalculatedField { - private Integer reprocessingOrder; - } diff --git a/application/src/test/java/org/thingsboard/server/service/iot_hub/InstallReportTest.java b/application/src/test/java/org/thingsboard/server/service/iot_hub/InstallReportTest.java new file mode 100644 index 0000000000..73f968fc01 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/iot_hub/InstallReportTest.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2026 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.iot_hub; + +import org.junit.jupiter.api.Test; + +import java.security.MessageDigest; +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class InstallReportTest { + + private static String sha256(String s) throws Exception { + MessageDigest d = MessageDigest.getInstance("SHA-256"); + return HexFormat.of().formatHex(d.digest(s.getBytes(StandardCharsets.UTF_8))); + } + + @Test + void buildInstallReport_hashesMatchSaltedFormula() throws Exception { + UUID tenantId = UUID.randomUUID(); + UUID userId = UUID.randomUUID(); + + InstallReport r = DefaultIotHubService.buildInstallReport(tenantId, userId, "4.2.0", "CE"); + + assertEquals(sha256(tenantId.toString() + tenantId), r.tenantHash()); + assertEquals(sha256(tenantId.toString() + userId), r.userHash()); + assertEquals("4.2.0", r.tbVersion()); + assertEquals("CE", r.edition()); + } +} diff --git a/pom.xml b/pom.xml index 069dc0954e..146f568cd9 100755 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ ${project.name} /var/log/${pkg.name} /usr/share/${pkg.name} - 4.3.1.2 + 4.3.1.3 3.5.15