Showing
8 changed files
with
199 additions
and
108 deletions
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
26 | 26 | import org.thingsboard.server.queue.util.TbCoreComponent; |
27 | 27 | import org.thingsboard.server.service.action.EntityActionService; |
28 | 28 | import org.thingsboard.server.service.importing.AbstractBulkImportService; |
29 | +import org.thingsboard.server.service.importing.BulkImportColumnType; | |
29 | 30 | import org.thingsboard.server.service.importing.BulkImportRequest; |
30 | 31 | import org.thingsboard.server.service.importing.ImportedEntityInfo; |
31 | 32 | import org.thingsboard.server.service.security.AccessValidator; |
... | ... | @@ -49,12 +50,12 @@ public class AssetBulkImportService extends AbstractBulkImportService<Asset> { |
49 | 50 | } |
50 | 51 | |
51 | 52 | @Override |
52 | - protected ImportedEntityInfo<Asset> saveEntity(BulkImportRequest importRequest, Map<BulkImportRequest.ColumnMapping, String> entityData, SecurityUser user) { | |
53 | + protected ImportedEntityInfo<Asset> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { | |
53 | 54 | ImportedEntityInfo<Asset> importedEntityInfo = new ImportedEntityInfo<>(); |
54 | 55 | |
55 | 56 | Asset asset = new Asset(); |
56 | 57 | asset.setTenantId(user.getTenantId()); |
57 | - setAssetFields(asset, entityData); | |
58 | + setAssetFields(asset, fields); | |
58 | 59 | |
59 | 60 | Asset existingAsset = assetService.findAssetByTenantIdAndName(user.getTenantId(), asset.getName()); |
60 | 61 | if (existingAsset != null && importRequest.getMapping().getUpdate()) { |
... | ... | @@ -69,10 +70,10 @@ public class AssetBulkImportService extends AbstractBulkImportService<Asset> { |
69 | 70 | return importedEntityInfo; |
70 | 71 | } |
71 | 72 | |
72 | - private void setAssetFields(Asset asset, Map<BulkImportRequest.ColumnMapping, String> data) { | |
73 | + private void setAssetFields(Asset asset, Map<BulkImportColumnType, String> fields) { | |
73 | 74 | ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(asset.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); |
74 | - data.forEach((columnMapping, value) -> { | |
75 | - switch (columnMapping.getType()) { | |
75 | + fields.forEach((columnType, value) -> { | |
76 | + switch (columnType) { | |
76 | 77 | case NAME: |
77 | 78 | asset.setName(value); |
78 | 79 | break; | ... | ... |
... | ... | @@ -58,7 +58,6 @@ import java.util.EnumSet; |
58 | 58 | import java.util.Map; |
59 | 59 | import java.util.Optional; |
60 | 60 | import java.util.Set; |
61 | -import java.util.stream.Collectors; | |
62 | 61 | import java.util.stream.Stream; |
63 | 62 | |
64 | 63 | @Service |
... | ... | @@ -80,12 +79,12 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
80 | 79 | } |
81 | 80 | |
82 | 81 | @Override |
83 | - protected ImportedEntityInfo<Device> saveEntity(BulkImportRequest importRequest, Map<BulkImportRequest.ColumnMapping, String> entityData, SecurityUser user) { | |
82 | + protected ImportedEntityInfo<Device> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { | |
84 | 83 | ImportedEntityInfo<Device> importedEntityInfo = new ImportedEntityInfo<>(); |
85 | 84 | |
86 | 85 | Device device = new Device(); |
87 | 86 | device.setTenantId(user.getTenantId()); |
88 | - setDeviceFields(device, entityData); | |
87 | + setDeviceFields(device, fields); | |
89 | 88 | |
90 | 89 | Device existingDevice = deviceService.findDeviceByTenantIdAndName(user.getTenantId(), device.getName()); |
91 | 90 | if (existingDevice != null && importRequest.getMapping().getUpdate()) { |
... | ... | @@ -95,7 +94,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
95 | 94 | device = existingDevice; |
96 | 95 | } |
97 | 96 | |
98 | - DeviceCredentials deviceCredentials = createDeviceCredentials(entityData); | |
97 | + DeviceCredentials deviceCredentials = createDeviceCredentials(fields); | |
99 | 98 | if (deviceCredentials.getCredentialsType() != null) { |
100 | 99 | if (deviceCredentials.getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) { |
101 | 100 | setUpLwM2mDeviceProfile(user.getTenantId(), device); |
... | ... | @@ -121,10 +120,10 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
121 | 120 | return importedEntityInfo; |
122 | 121 | } |
123 | 122 | |
124 | - private void setDeviceFields(Device device, Map<BulkImportRequest.ColumnMapping, String> data) { | |
123 | + private void setDeviceFields(Device device, Map<BulkImportColumnType, String> fields) { | |
125 | 124 | ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(device.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); |
126 | - data.forEach((columnMapping, value) -> { | |
127 | - switch (columnMapping.getType()) { | |
125 | + fields.forEach((columnType, value) -> { | |
126 | + switch (columnType) { | |
128 | 127 | case NAME: |
129 | 128 | device.setName(value); |
130 | 129 | break; |
... | ... | @@ -146,25 +145,25 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
146 | 145 | } |
147 | 146 | |
148 | 147 | @SneakyThrows |
149 | - private DeviceCredentials createDeviceCredentials(Map<BulkImportRequest.ColumnMapping, String> data) { | |
150 | - Set<BulkImportColumnType> columns = data.keySet().stream().map(BulkImportRequest.ColumnMapping::getType).collect(Collectors.toSet()); | |
148 | + private DeviceCredentials createDeviceCredentials(Map<BulkImportColumnType, String> fields) { | |
149 | + Set<BulkImportColumnType> columns = fields.keySet(); | |
151 | 150 | |
152 | 151 | DeviceCredentials credentials = new DeviceCredentials(); |
153 | 152 | |
154 | 153 | if (columns.contains(BulkImportColumnType.ACCESS_TOKEN)) { |
155 | 154 | credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
156 | - credentials.setCredentialsId(getByColumnType(BulkImportColumnType.ACCESS_TOKEN, data)); | |
155 | + credentials.setCredentialsId(fields.get(BulkImportColumnType.ACCESS_TOKEN)); | |
157 | 156 | } else if (CollectionUtils.containsAny(columns, EnumSet.of(BulkImportColumnType.MQTT_CLIENT_ID, BulkImportColumnType.MQTT_USER_NAME, BulkImportColumnType.MQTT_PASSWORD))) { |
158 | 157 | credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC); |
159 | 158 | |
160 | 159 | BasicMqttCredentials basicMqttCredentials = new BasicMqttCredentials(); |
161 | - basicMqttCredentials.setClientId(getByColumnType(BulkImportColumnType.MQTT_CLIENT_ID, data)); | |
162 | - basicMqttCredentials.setUserName(getByColumnType(BulkImportColumnType.MQTT_USER_NAME, data)); | |
163 | - basicMqttCredentials.setPassword(getByColumnType(BulkImportColumnType.MQTT_PASSWORD, data)); | |
160 | + basicMqttCredentials.setClientId(fields.get(BulkImportColumnType.MQTT_CLIENT_ID)); | |
161 | + basicMqttCredentials.setUserName(fields.get(BulkImportColumnType.MQTT_USER_NAME)); | |
162 | + basicMqttCredentials.setPassword(fields.get(BulkImportColumnType.MQTT_PASSWORD)); | |
164 | 163 | credentials.setCredentialsValue(JacksonUtil.toString(basicMqttCredentials)); |
165 | 164 | } else if (columns.contains(BulkImportColumnType.X509)) { |
166 | 165 | credentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); |
167 | - credentials.setCredentialsValue(getByColumnType(BulkImportColumnType.X509, data)); | |
166 | + credentials.setCredentialsValue(fields.get(BulkImportColumnType.X509)); | |
168 | 167 | } else if (columns.contains(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT)) { |
169 | 168 | credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); |
170 | 169 | ObjectNode lwm2mCredentials = JacksonUtil.newObjectNode(); |
... | ... | @@ -173,7 +172,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
173 | 172 | Stream.of(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE, |
174 | 173 | BulkImportColumnType.LWM2M_CLIENT_IDENTITY, BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT) |
175 | 174 | .forEach(lwm2mClientProperty -> { |
176 | - String value = getByColumnType(lwm2mClientProperty, data); | |
175 | + String value = fields.get(lwm2mClientProperty); | |
177 | 176 | if (value != null) { |
178 | 177 | client.set(lwm2mClientProperty.getKey(), new TextNode(value)); |
179 | 178 | } |
... | ... | @@ -187,7 +186,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
187 | 186 | Stream.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID, |
188 | 187 | BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECRET_KEY) |
189 | 188 | .forEach(lwm2mBootstrapServerProperty -> { |
190 | - String value = getByColumnType(lwm2mBootstrapServerProperty, data); | |
189 | + String value = fields.get(lwm2mBootstrapServerProperty); | |
191 | 190 | if (value != null) { |
192 | 191 | bootstrapServer.set(lwm2mBootstrapServerProperty.getKey(), new TextNode(value)); |
193 | 192 | } |
... | ... | @@ -197,7 +196,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> { |
197 | 196 | Stream.of(BulkImportColumnType.LWM2M_SERVER_SECURITY_MODE, BulkImportColumnType.LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID, |
198 | 197 | BulkImportColumnType.LWM2M_SERVER_CLIENT_SECRET_KEY) |
199 | 198 | .forEach(lwm2mServerProperty -> { |
200 | - String value = getByColumnType(lwm2mServerProperty, data); | |
199 | + String value = fields.get(lwm2mServerProperty); | |
201 | 200 | if (value != null) { |
202 | 201 | lwm2mServer.set(lwm2mServerProperty.getKey(), new TextNode(value)); |
203 | 202 | } | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
26 | 26 | import org.thingsboard.server.queue.util.TbCoreComponent; |
27 | 27 | import org.thingsboard.server.service.action.EntityActionService; |
28 | 28 | import org.thingsboard.server.service.importing.AbstractBulkImportService; |
29 | +import org.thingsboard.server.service.importing.BulkImportColumnType; | |
29 | 30 | import org.thingsboard.server.service.importing.BulkImportRequest; |
30 | 31 | import org.thingsboard.server.service.importing.ImportedEntityInfo; |
31 | 32 | import org.thingsboard.server.service.security.AccessValidator; |
... | ... | @@ -49,12 +50,12 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> { |
49 | 50 | } |
50 | 51 | |
51 | 52 | @Override |
52 | - protected ImportedEntityInfo<Edge> saveEntity(BulkImportRequest importRequest, Map<BulkImportRequest.ColumnMapping, String> entityData, SecurityUser user) { | |
53 | + protected ImportedEntityInfo<Edge> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user) { | |
53 | 54 | ImportedEntityInfo<Edge> importedEntityInfo = new ImportedEntityInfo<>(); |
54 | 55 | |
55 | 56 | Edge edge = new Edge(); |
56 | 57 | edge.setTenantId(user.getTenantId()); |
57 | - setEdgeFields(edge, entityData); | |
58 | + setEdgeFields(edge, fields); | |
58 | 59 | |
59 | 60 | Edge existingEdge = edgeService.findEdgeByTenantIdAndName(user.getTenantId(), edge.getName()); |
60 | 61 | if (existingEdge != null && importRequest.getMapping().getUpdate()) { |
... | ... | @@ -69,10 +70,10 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> { |
69 | 70 | return importedEntityInfo; |
70 | 71 | } |
71 | 72 | |
72 | - private void setEdgeFields(Edge edge, Map<BulkImportRequest.ColumnMapping, String> data) { | |
73 | + private void setEdgeFields(Edge edge, Map<BulkImportColumnType, String> fields) { | |
73 | 74 | ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(edge.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); |
74 | - data.forEach((columnMapping, value) -> { | |
75 | - switch (columnMapping.getType()) { | |
75 | + fields.forEach((columnType, value) -> { | |
76 | + switch (columnType) { | |
76 | 77 | case NAME: |
77 | 78 | edge.setName(value); |
78 | 79 | break; | ... | ... |
... | ... | @@ -18,6 +18,7 @@ package org.thingsboard.server.service.importing; |
18 | 18 | import com.google.common.util.concurrent.FutureCallback; |
19 | 19 | import com.google.gson.JsonObject; |
20 | 20 | import com.google.gson.JsonPrimitive; |
21 | +import lombok.Data; | |
21 | 22 | import lombok.RequiredArgsConstructor; |
22 | 23 | import lombok.SneakyThrows; |
23 | 24 | import org.apache.commons.lang3.StringUtils; |
... | ... | @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.id.EntityId; |
29 | 30 | import org.thingsboard.server.common.data.id.UUIDBased; |
30 | 31 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
31 | 32 | import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
33 | +import org.thingsboard.server.common.data.kv.DataType; | |
32 | 34 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
33 | 35 | import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
34 | 36 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
... | ... | @@ -42,9 +44,12 @@ import org.thingsboard.server.service.security.permission.AccessControlService; |
42 | 44 | import org.thingsboard.server.service.security.permission.Operation; |
43 | 45 | import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
44 | 46 | import org.thingsboard.server.utils.CsvUtils; |
47 | +import org.thingsboard.server.utils.TypeCastUtil; | |
45 | 48 | |
46 | 49 | import javax.annotation.Nullable; |
47 | 50 | import java.util.ArrayList; |
51 | +import java.util.Arrays; | |
52 | +import java.util.HashMap; | |
48 | 53 | import java.util.List; |
49 | 54 | import java.util.Map; |
50 | 55 | import java.util.concurrent.TimeUnit; |
... | ... | @@ -73,12 +78,12 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent |
73 | 78 | parseData(request).forEach(entityData -> { |
74 | 79 | i.incrementAndGet(); |
75 | 80 | try { |
76 | - ImportedEntityInfo<E> importedEntityInfo = saveEntity(request, entityData, user); | |
81 | + ImportedEntityInfo<E> importedEntityInfo = saveEntity(request, entityData.getFields(), user); | |
77 | 82 | onEntityImported.accept(importedEntityInfo); |
78 | 83 | |
79 | 84 | E entity = importedEntityInfo.getEntity(); |
80 | 85 | |
81 | - saveKvs(user, entity, entityData); | |
86 | + saveKvs(user, entity, entityData.getKvs()); | |
82 | 87 | |
83 | 88 | if (importedEntityInfo.getRelatedError() != null) { |
84 | 89 | throw new RuntimeException(importedEntityInfo.getRelatedError()); |
... | ... | @@ -98,20 +103,21 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent |
98 | 103 | return result; |
99 | 104 | } |
100 | 105 | |
101 | - protected abstract ImportedEntityInfo<E> saveEntity(BulkImportRequest importRequest, Map<ColumnMapping, String> entityData, SecurityUser user); | |
106 | + protected abstract ImportedEntityInfo<E> saveEntity(BulkImportRequest importRequest, Map<BulkImportColumnType, String> fields, SecurityUser user); | |
102 | 107 | |
103 | 108 | /* |
104 | 109 | * Attributes' values are firstly added to JsonObject in order to then make some type cast, |
105 | 110 | * because we get all values as strings from CSV |
106 | 111 | * */ |
107 | - private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, String> data) { | |
108 | - Stream.of(BulkImportColumnType.SHARED_ATTRIBUTE, BulkImportColumnType.SERVER_ATTRIBUTE, BulkImportColumnType.TIMESERIES) | |
112 | + private void saveKvs(SecurityUser user, E entity, Map<ColumnMapping, ParsedValue> data) { | |
113 | + Arrays.stream(BulkImportColumnType.values()) | |
114 | + .filter(BulkImportColumnType::isKv) | |
109 | 115 | .map(kvType -> { |
110 | 116 | JsonObject kvs = new JsonObject(); |
111 | 117 | data.entrySet().stream() |
112 | 118 | .filter(dataEntry -> dataEntry.getKey().getType() == kvType && |
113 | 119 | StringUtils.isNotEmpty(dataEntry.getKey().getKey())) |
114 | - .forEach(dataEntry -> kvs.add(dataEntry.getKey().getKey(), new JsonPrimitive(dataEntry.getValue()))); | |
120 | + .forEach(dataEntry -> kvs.add(dataEntry.getKey().getKey(), dataEntry.getValue().toJsonPrimitive())); | |
115 | 121 | return Map.entry(kvType, kvs); |
116 | 122 | }) |
117 | 123 | .filter(kvsEntry -> kvsEntry.getValue().entrySet().size() > 0) |
... | ... | @@ -127,7 +133,7 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent |
127 | 133 | |
128 | 134 | @SneakyThrows |
129 | 135 | private void saveTelemetry(SecurityUser user, E entity, Map.Entry<BulkImportColumnType, JsonObject> kvsEntry) { |
130 | - List<TsKvEntry> timeseries = JsonConverter.convertToTelemetry(kvsEntry.getValue(), System.currentTimeMillis(), false, true) | |
136 | + List<TsKvEntry> timeseries = JsonConverter.convertToTelemetry(kvsEntry.getValue(), System.currentTimeMillis()) | |
131 | 137 | .entrySet().stream() |
132 | 138 | .flatMap(entry -> entry.getValue().stream().map(kvEntry -> new BasicTsKvEntry(entry.getKey(), kvEntry))) |
133 | 139 | .collect(Collectors.toList()); |
... | ... | @@ -155,7 +161,7 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent |
155 | 161 | @SneakyThrows |
156 | 162 | private void saveAttributes(SecurityUser user, E entity, Map.Entry<BulkImportColumnType, JsonObject> kvsEntry, BulkImportColumnType kvType) { |
157 | 163 | String scope = kvType.getKey(); |
158 | - List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue(), true)); | |
164 | + List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue())); | |
159 | 165 | |
160 | 166 | accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> { |
161 | 167 | tsSubscriptionService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<>() { |
... | ... | @@ -178,24 +184,62 @@ public abstract class AbstractBulkImportService<E extends BaseData<? extends Ent |
178 | 184 | }); |
179 | 185 | } |
180 | 186 | |
181 | - protected final String getByColumnType(BulkImportColumnType bulkImportColumnType, Map<ColumnMapping, String> data) { | |
182 | - return data.entrySet().stream().filter(entry -> entry.getKey().getType() == bulkImportColumnType).findFirst().map(Map.Entry::getValue).orElse(null); | |
183 | - } | |
184 | - | |
185 | - private List<Map<ColumnMapping, String>> parseData(BulkImportRequest request) throws Exception { | |
187 | + private List<EntityData> parseData(BulkImportRequest request) throws Exception { | |
186 | 188 | List<List<String>> records = CsvUtils.parseCsv(request.getFile(), request.getMapping().getDelimiter()); |
187 | 189 | if (request.getMapping().getHeader()) { |
188 | 190 | records.remove(0); |
189 | 191 | } |
190 | 192 | |
191 | 193 | List<ColumnMapping> columnsMappings = request.getMapping().getColumns(); |
192 | - | |
193 | 194 | return records.stream() |
194 | - .map(record -> Stream.iterate(0, i -> i < record.size(), i -> i + 1) | |
195 | - .map(i -> Map.entry(columnsMappings.get(i), record.get(i))) | |
196 | - .filter(entry -> StringUtils.isNotEmpty(entry.getValue())) | |
197 | - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))) | |
195 | + .map(record -> { | |
196 | + EntityData entityData = new EntityData(); | |
197 | + Stream.iterate(0, i -> i < record.size(), i -> i + 1) | |
198 | + .map(i -> Map.entry(columnsMappings.get(i), record.get(i))) | |
199 | + .filter(entry -> StringUtils.isNotEmpty(entry.getValue())) | |
200 | + .forEach(entry -> { | |
201 | + if (!entry.getKey().getType().isKv()) { | |
202 | + entityData.getFields().put(entry.getKey().getType(), entry.getValue()); | |
203 | + } else { | |
204 | + Map.Entry<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue()); | |
205 | + entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey())); | |
206 | + } | |
207 | + }); | |
208 | + return entityData; | |
209 | + }) | |
198 | 210 | .collect(Collectors.toList()); |
199 | 211 | } |
200 | 212 | |
213 | + @Data | |
214 | + protected static class EntityData { | |
215 | + private final Map<BulkImportColumnType, String> fields = new HashMap<>(); | |
216 | + private final Map<ColumnMapping, ParsedValue> kvs = new HashMap<>(); | |
217 | + } | |
218 | + | |
219 | + @Data | |
220 | + protected static class ParsedValue { | |
221 | + private final Object value; | |
222 | + private final DataType dataType; | |
223 | + | |
224 | + public JsonPrimitive toJsonPrimitive() { | |
225 | + switch (dataType) { | |
226 | + case STRING: | |
227 | + return new JsonPrimitive((String) value); | |
228 | + case LONG: | |
229 | + return new JsonPrimitive((Long) value); | |
230 | + case DOUBLE: | |
231 | + return new JsonPrimitive((Double) value); | |
232 | + case BOOLEAN: | |
233 | + return new JsonPrimitive((Boolean) value); | |
234 | + default: | |
235 | + return null; | |
236 | + } | |
237 | + } | |
238 | + | |
239 | + public String stringValue() { | |
240 | + return value.toString(); | |
241 | + } | |
242 | + | |
243 | + } | |
244 | + | |
201 | 245 | } | ... | ... |
... | ... | @@ -18,13 +18,14 @@ package org.thingsboard.server.service.importing; |
18 | 18 | import lombok.Getter; |
19 | 19 | import org.thingsboard.server.common.data.DataConstants; |
20 | 20 | |
21 | +@Getter | |
21 | 22 | public enum BulkImportColumnType { |
22 | 23 | NAME, |
23 | 24 | TYPE, |
24 | 25 | LABEL, |
25 | - SHARED_ATTRIBUTE(DataConstants.SHARED_SCOPE), | |
26 | - SERVER_ATTRIBUTE(DataConstants.SERVER_SCOPE), | |
27 | - TIMESERIES, | |
26 | + SHARED_ATTRIBUTE(DataConstants.SHARED_SCOPE, true), | |
27 | + SERVER_ATTRIBUTE(DataConstants.SERVER_SCOPE, true), | |
28 | + TIMESERIES(true), | |
28 | 29 | ACCESS_TOKEN, |
29 | 30 | X509, |
30 | 31 | MQTT_CLIENT_ID, |
... | ... | @@ -48,8 +49,8 @@ public enum BulkImportColumnType { |
48 | 49 | ROUTING_KEY, |
49 | 50 | SECRET; |
50 | 51 | |
51 | - @Getter | |
52 | 52 | private String key; |
53 | + private boolean isKv = false; | |
53 | 54 | |
54 | 55 | BulkImportColumnType() { |
55 | 56 | } |
... | ... | @@ -57,4 +58,13 @@ public enum BulkImportColumnType { |
57 | 58 | BulkImportColumnType(String key) { |
58 | 59 | this.key = key; |
59 | 60 | } |
61 | + | |
62 | + BulkImportColumnType(boolean isKv) { | |
63 | + this.isKv = isKv; | |
64 | + } | |
65 | + | |
66 | + BulkImportColumnType(String key, boolean isKv) { | |
67 | + this.key = key; | |
68 | + this.isKv = isKv; | |
69 | + } | |
60 | 70 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 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.utils; | |
17 | + | |
18 | +import org.apache.commons.lang3.math.NumberUtils; | |
19 | +import org.thingsboard.server.common.data.kv.DataType; | |
20 | + | |
21 | +import java.math.BigDecimal; | |
22 | +import java.util.Map; | |
23 | + | |
24 | +public class TypeCastUtil { | |
25 | + | |
26 | + private TypeCastUtil() {} | |
27 | + | |
28 | + public static Map.Entry<DataType, Object> castValue(String value) { | |
29 | + if (isNumber(value)) { | |
30 | + String formattedValue = value.replace(',', '.'); | |
31 | + try { | |
32 | + BigDecimal bd = new BigDecimal(formattedValue); | |
33 | + if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) { | |
34 | + if (bd.scale() <= 16) { | |
35 | + return Map.entry(DataType.DOUBLE, bd.doubleValue()); | |
36 | + } | |
37 | + } else { | |
38 | + return Map.entry(DataType.LONG, bd.longValueExact()); | |
39 | + } | |
40 | + } catch (RuntimeException ignored) {} | |
41 | + } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { | |
42 | + return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value)); | |
43 | + } | |
44 | + return Map.entry(DataType.STRING, value); | |
45 | + } | |
46 | + | |
47 | + private static boolean isNumber(String value) { | |
48 | + return NumberUtils.isParsable(value.replace(',', '.')); | |
49 | + } | |
50 | + | |
51 | + private static boolean isSimpleDouble(String valueAsString) { | |
52 | + return valueAsString.contains(".") && !valueAsString.contains("E") && !valueAsString.contains("e"); | |
53 | + } | |
54 | + | |
55 | +} | ... | ... |
... | ... | @@ -76,7 +76,7 @@ public class JsonConverter { |
76 | 76 | |
77 | 77 | public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonElement, long ts) throws JsonSyntaxException { |
78 | 78 | PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder(); |
79 | - convertToTelemetry(jsonElement, ts, null, builder, isTypeCastEnabled); | |
79 | + convertToTelemetry(jsonElement, ts, null, builder); | |
80 | 80 | return builder.build(); |
81 | 81 | } |
82 | 82 | |
... | ... | @@ -84,13 +84,13 @@ public class JsonConverter { |
84 | 84 | return convertToTelemetryProto(jsonElement, System.currentTimeMillis()); |
85 | 85 | } |
86 | 86 | |
87 | - private static void convertToTelemetry(JsonElement jsonElement, long systemTs, Map<Long, List<KvEntry>> result, PostTelemetryMsg.Builder builder, boolean typeCastEnabled) { | |
87 | + private static void convertToTelemetry(JsonElement jsonElement, long systemTs, Map<Long, List<KvEntry>> result, PostTelemetryMsg.Builder builder) { | |
88 | 88 | if (jsonElement.isJsonObject()) { |
89 | - parseObject(systemTs, result, builder, jsonElement.getAsJsonObject(), typeCastEnabled); | |
89 | + parseObject(systemTs, result, builder, jsonElement.getAsJsonObject()); | |
90 | 90 | } else if (jsonElement.isJsonArray()) { |
91 | 91 | jsonElement.getAsJsonArray().forEach(je -> { |
92 | 92 | if (je.isJsonObject()) { |
93 | - parseObject(systemTs, result, builder, je.getAsJsonObject(), typeCastEnabled); | |
93 | + parseObject(systemTs, result, builder, je.getAsJsonObject()); | |
94 | 94 | } else { |
95 | 95 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je); |
96 | 96 | } |
... | ... | @@ -100,11 +100,11 @@ public class JsonConverter { |
100 | 100 | } |
101 | 101 | } |
102 | 102 | |
103 | - private static void parseObject(long systemTs, Map<Long, List<KvEntry>> result, PostTelemetryMsg.Builder builder, JsonObject jo, boolean typeCastEnabled) { | |
103 | + private static void parseObject(long systemTs, Map<Long, List<KvEntry>> result, PostTelemetryMsg.Builder builder, JsonObject jo) { | |
104 | 104 | if (result != null) { |
105 | - parseObject(result, systemTs, jo, typeCastEnabled); | |
105 | + parseObject(result, systemTs, jo); | |
106 | 106 | } else { |
107 | - parseObject(builder, systemTs, jo, typeCastEnabled); | |
107 | + parseObject(builder, systemTs, jo); | |
108 | 108 | } |
109 | 109 | } |
110 | 110 | |
... | ... | @@ -146,7 +146,7 @@ public class JsonConverter { |
146 | 146 | public static PostAttributeMsg convertToAttributesProto(JsonElement jsonObject) throws JsonSyntaxException { |
147 | 147 | if (jsonObject.isJsonObject()) { |
148 | 148 | PostAttributeMsg.Builder result = PostAttributeMsg.newBuilder(); |
149 | - List<KeyValueProto> keyValueList = parseProtoValues(jsonObject.getAsJsonObject(), isTypeCastEnabled); | |
149 | + List<KeyValueProto> keyValueList = parseProtoValues(jsonObject.getAsJsonObject()); | |
150 | 150 | result.addAllKv(keyValueList); |
151 | 151 | return result.build(); |
152 | 152 | } else { |
... | ... | @@ -164,29 +164,29 @@ public class JsonConverter { |
164 | 164 | return result; |
165 | 165 | } |
166 | 166 | |
167 | - private static void parseObject(PostTelemetryMsg.Builder builder, long systemTs, JsonObject jo, boolean typeCastEnabled) { | |
167 | + private static void parseObject(PostTelemetryMsg.Builder builder, long systemTs, JsonObject jo) { | |
168 | 168 | if (jo.has("ts") && jo.has("values")) { |
169 | - parseWithTs(builder, jo, typeCastEnabled); | |
169 | + parseWithTs(builder, jo); | |
170 | 170 | } else { |
171 | - parseWithoutTs(builder, systemTs, jo, typeCastEnabled); | |
171 | + parseWithoutTs(builder, systemTs, jo); | |
172 | 172 | } |
173 | 173 | } |
174 | 174 | |
175 | - private static void parseWithoutTs(PostTelemetryMsg.Builder request, long systemTs, JsonObject jo, boolean typeCastEnabled) { | |
175 | + private static void parseWithoutTs(PostTelemetryMsg.Builder request, long systemTs, JsonObject jo) { | |
176 | 176 | TsKvListProto.Builder builder = TsKvListProto.newBuilder(); |
177 | 177 | builder.setTs(systemTs); |
178 | - builder.addAllKv(parseProtoValues(jo, typeCastEnabled)); | |
178 | + builder.addAllKv(parseProtoValues(jo)); | |
179 | 179 | request.addTsKvList(builder.build()); |
180 | 180 | } |
181 | 181 | |
182 | - private static void parseWithTs(PostTelemetryMsg.Builder request, JsonObject jo, boolean typeCastEnabled) { | |
182 | + private static void parseWithTs(PostTelemetryMsg.Builder request, JsonObject jo) { | |
183 | 183 | TsKvListProto.Builder builder = TsKvListProto.newBuilder(); |
184 | 184 | builder.setTs(jo.get("ts").getAsLong()); |
185 | - builder.addAllKv(parseProtoValues(jo.get("values").getAsJsonObject(), typeCastEnabled)); | |
185 | + builder.addAllKv(parseProtoValues(jo.get("values").getAsJsonObject())); | |
186 | 186 | request.addTsKvList(builder.build()); |
187 | 187 | } |
188 | 188 | |
189 | - private static List<KeyValueProto> parseProtoValues(JsonObject valuesObject, boolean typeCastEnabled) { | |
189 | + private static List<KeyValueProto> parseProtoValues(JsonObject valuesObject) { | |
190 | 190 | List<KeyValueProto> result = new ArrayList<>(); |
191 | 191 | for (Entry<String, JsonElement> valueEntry : valuesObject.entrySet()) { |
192 | 192 | JsonElement element = valueEntry.getValue(); |
... | ... | @@ -197,9 +197,9 @@ public class JsonConverter { |
197 | 197 | String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", value.getAsString().length(), valueEntry.getKey(), maxStringValueLength); |
198 | 198 | throw new JsonSyntaxException(message); |
199 | 199 | } |
200 | - if (typeCastEnabled && isNumber(value)) { | |
200 | + if (isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { | |
201 | 201 | try { |
202 | - result.add(buildNumericKeyValueProto(value, valueEntry.getKey(), typeCastEnabled)); | |
202 | + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); | |
203 | 203 | } catch (RuntimeException th) { |
204 | 204 | result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) |
205 | 205 | .setStringV(value.getAsString()).build()); |
... | ... | @@ -212,7 +212,7 @@ public class JsonConverter { |
212 | 212 | result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) |
213 | 213 | .setBoolV(value.getAsBoolean()).build()); |
214 | 214 | } else if (value.isNumber()) { |
215 | - result.add(buildNumericKeyValueProto(value, valueEntry.getKey(), typeCastEnabled)); | |
215 | + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); | |
216 | 216 | } else if (!value.isJsonNull()) { |
217 | 217 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); |
218 | 218 | } |
... | ... | @@ -225,15 +225,15 @@ public class JsonConverter { |
225 | 225 | return result; |
226 | 226 | } |
227 | 227 | |
228 | - private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key, boolean typeCastEnabled) { | |
229 | - String valueAsString = value.getAsString().replace(',', '.'); | |
228 | + private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { | |
229 | + String valueAsString = value.getAsString(); | |
230 | 230 | KeyValueProto.Builder builder = KeyValueProto.newBuilder().setKey(key); |
231 | 231 | var bd = new BigDecimal(valueAsString); |
232 | 232 | if (bd.stripTrailingZeros().scale() <= 0 && !isSimpleDouble(valueAsString)) { |
233 | 233 | try { |
234 | 234 | return builder.setType(KeyValueType.LONG_V).setLongV(bd.longValueExact()).build(); |
235 | 235 | } catch (ArithmeticException e) { |
236 | - if (typeCastEnabled) { | |
236 | + if (isTypeCastEnabled) { | |
237 | 237 | return builder.setType(KeyValueType.STRING_V).setStringV(bd.toPlainString()).build(); |
238 | 238 | } else { |
239 | 239 | throw new JsonSyntaxException("Big integer values are not supported!"); |
... | ... | @@ -242,7 +242,7 @@ public class JsonConverter { |
242 | 242 | } else { |
243 | 243 | if (bd.scale() <= 16) { |
244 | 244 | return builder.setType(KeyValueType.DOUBLE_V).setDoubleV(bd.doubleValue()).build(); |
245 | - } else if (typeCastEnabled) { | |
245 | + } else if (isTypeCastEnabled) { | |
246 | 246 | return builder.setType(KeyValueType.STRING_V).setStringV(bd.toPlainString()).build(); |
247 | 247 | } else { |
248 | 248 | throw new JsonSyntaxException("Big integer values are not supported!"); |
... | ... | @@ -260,15 +260,15 @@ public class JsonConverter { |
260 | 260 | return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build(); |
261 | 261 | } |
262 | 262 | |
263 | - private static void parseNumericValue(List<KvEntry> result, Entry<String, JsonElement> valueEntry, JsonPrimitive value, boolean typeCastEnabled) { | |
264 | - String valueAsString = value.getAsString().replace(',', '.'); | |
263 | + private static void parseNumericValue(List<KvEntry> result, Entry<String, JsonElement> valueEntry, JsonPrimitive value) { | |
264 | + String valueAsString = value.getAsString(); | |
265 | 265 | String key = valueEntry.getKey(); |
266 | 266 | var bd = new BigDecimal(valueAsString); |
267 | 267 | if (bd.stripTrailingZeros().scale() <= 0 && !isSimpleDouble(valueAsString)) { |
268 | 268 | try { |
269 | 269 | result.add(new LongDataEntry(key, bd.longValueExact())); |
270 | 270 | } catch (ArithmeticException e) { |
271 | - if (typeCastEnabled) { | |
271 | + if (isTypeCastEnabled) { | |
272 | 272 | result.add(new StringDataEntry(key, bd.toPlainString())); |
273 | 273 | } else { |
274 | 274 | throw new JsonSyntaxException("Big integer values are not supported!"); |
... | ... | @@ -277,7 +277,7 @@ public class JsonConverter { |
277 | 277 | } else { |
278 | 278 | if (bd.scale() <= 16) { |
279 | 279 | result.add(new DoubleDataEntry(key, bd.doubleValue())); |
280 | - } else if (typeCastEnabled) { | |
280 | + } else if (isTypeCastEnabled) { | |
281 | 281 | result.add(new StringDataEntry(key, bd.toPlainString())); |
282 | 282 | } else { |
283 | 283 | throw new JsonSyntaxException("Big integer values are not supported!"); |
... | ... | @@ -487,17 +487,13 @@ public class JsonConverter { |
487 | 487 | } |
488 | 488 | |
489 | 489 | public static Set<AttributeKvEntry> convertToAttributes(JsonElement element) { |
490 | - return convertToAttributes(element, isTypeCastEnabled); | |
491 | - } | |
492 | - | |
493 | - public static Set<AttributeKvEntry> convertToAttributes(JsonElement element, boolean typeCastEnabled) { | |
494 | 490 | Set<AttributeKvEntry> result = new HashSet<>(); |
495 | 491 | long ts = System.currentTimeMillis(); |
496 | - result.addAll(parseValues(element.getAsJsonObject(), typeCastEnabled).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); | |
492 | + result.addAll(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); | |
497 | 493 | return result; |
498 | 494 | } |
499 | 495 | |
500 | - private static List<KvEntry> parseValues(JsonObject valuesObject, boolean typeCastEnabled) { | |
496 | + private static List<KvEntry> parseValues(JsonObject valuesObject) { | |
501 | 497 | List<KvEntry> result = new ArrayList<>(); |
502 | 498 | for (Entry<String, JsonElement> valueEntry : valuesObject.entrySet()) { |
503 | 499 | JsonElement element = valueEntry.getValue(); |
... | ... | @@ -508,9 +504,9 @@ public class JsonConverter { |
508 | 504 | String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", value.getAsString().length(), valueEntry.getKey(), maxStringValueLength); |
509 | 505 | throw new JsonSyntaxException(message); |
510 | 506 | } |
511 | - if (typeCastEnabled && isNumber(value)) { | |
507 | + if (isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { | |
512 | 508 | try { |
513 | - parseNumericValue(result, valueEntry, value, typeCastEnabled); | |
509 | + parseNumericValue(result, valueEntry, value); | |
514 | 510 | } catch (RuntimeException th) { |
515 | 511 | result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); |
516 | 512 | } |
... | ... | @@ -520,7 +516,7 @@ public class JsonConverter { |
520 | 516 | } else if (value.isBoolean()) { |
521 | 517 | result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); |
522 | 518 | } else if (value.isNumber()) { |
523 | - parseNumericValue(result, valueEntry, value, typeCastEnabled); | |
519 | + parseNumericValue(result, valueEntry, value); | |
524 | 520 | } else { |
525 | 521 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); |
526 | 522 | } |
... | ... | @@ -545,35 +541,30 @@ public class JsonConverter { |
545 | 541 | |
546 | 542 | public static Map<Long, List<KvEntry>> convertToTelemetry(JsonElement jsonElement, long systemTs, boolean sorted) throws |
547 | 543 | JsonSyntaxException { |
548 | - return convertToTelemetry(jsonElement, systemTs, sorted, isTypeCastEnabled); | |
549 | - } | |
550 | - | |
551 | - public static Map<Long, List<KvEntry>> convertToTelemetry(JsonElement jsonElement, long systemTs, boolean sorted, boolean typeCastEnabled) throws | |
552 | - JsonSyntaxException { | |
553 | 544 | Map<Long, List<KvEntry>> result = sorted ? new TreeMap<>() : new HashMap<>(); |
554 | - convertToTelemetry(jsonElement, systemTs, result, null, typeCastEnabled); | |
545 | + convertToTelemetry(jsonElement, systemTs, result, null); | |
555 | 546 | return result; |
556 | 547 | } |
557 | 548 | |
558 | 549 | |
559 | - private static void parseObject(Map<Long, List<KvEntry>> result, long systemTs, JsonObject jo, boolean typeCastEnabled) { | |
550 | + private static void parseObject(Map<Long, List<KvEntry>> result, long systemTs, JsonObject jo) { | |
560 | 551 | if (jo.has("ts") && jo.has("values")) { |
561 | - parseWithTs(result, jo, typeCastEnabled); | |
552 | + parseWithTs(result, jo); | |
562 | 553 | } else { |
563 | - parseWithoutTs(result, systemTs, jo, typeCastEnabled); | |
554 | + parseWithoutTs(result, systemTs, jo); | |
564 | 555 | } |
565 | 556 | } |
566 | 557 | |
567 | - private static void parseWithoutTs(Map<Long, List<KvEntry>> result, long systemTs, JsonObject jo, boolean typeCastEnabled) { | |
568 | - for (KvEntry entry : parseValues(jo, typeCastEnabled)) { | |
558 | + private static void parseWithoutTs(Map<Long, List<KvEntry>> result, long systemTs, JsonObject jo) { | |
559 | + for (KvEntry entry : parseValues(jo)) { | |
569 | 560 | result.computeIfAbsent(systemTs, tmp -> new ArrayList<>()).add(entry); |
570 | 561 | } |
571 | 562 | } |
572 | 563 | |
573 | - public static void parseWithTs(Map<Long, List<KvEntry>> result, JsonObject jo, boolean typeCastEnabled) { | |
564 | + public static void parseWithTs(Map<Long, List<KvEntry>> result, JsonObject jo) { | |
574 | 565 | long ts = jo.get("ts").getAsLong(); |
575 | 566 | JsonObject valuesObject = jo.get("values").getAsJsonObject(); |
576 | - for (KvEntry entry : parseValues(valuesObject, typeCastEnabled)) { | |
567 | + for (KvEntry entry : parseValues(valuesObject)) { | |
577 | 568 | result.computeIfAbsent(ts, tmp -> new ArrayList<>()).add(entry); |
578 | 569 | } |
579 | 570 | } |
... | ... | @@ -643,10 +634,6 @@ public class JsonConverter { |
643 | 634 | } |
644 | 635 | |
645 | 636 | |
646 | - private static boolean isNumber(JsonPrimitive value) { | |
647 | - return NumberUtils.isParsable(value.getAsString().replace(',', '.')); | |
648 | - } | |
649 | - | |
650 | 637 | private static String getStrValue(JsonObject jo, String field, boolean requiredField) { |
651 | 638 | if (jo.has(field)) { |
652 | 639 | return jo.get(field).getAsString(); | ... | ... |
... | ... | @@ -66,12 +66,6 @@ public class JsonConverterTest { |
66 | 66 | } |
67 | 67 | |
68 | 68 | @Test |
69 | - public void testParseAsDoubleWithCommaDecimalSeparatorAndTypeCast() { | |
70 | - var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": \"-1,1\"}"), 0L); | |
71 | - Assert.assertEquals(-1.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); | |
72 | - } | |
73 | - | |
74 | - @Test | |
75 | 69 | public void testParseAsLong() { |
76 | 70 | var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 11}"), 0L); |
77 | 71 | Assert.assertEquals(11L, result.get(0L).get(0).getLongValue().get().longValue()); | ... | ... |