Skip to content

Commit 3d4f40c

Browse files
authored
Merge pull request #24 from oracle-devrel/dbextractreplay
Dbextractreplay
2 parents c5f9f5e + 00a1354 commit 3d4f40c

14 files changed

Lines changed: 1100 additions & 25 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.oracle.demo.timg.iot.iotdbjdbc.dataread.filereader;
2+
3+
public enum FileDataInputMode {
4+
REAL_TIME, HIGH_SPEED
5+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.oracle.demo.timg.iot.iotdbjdbc.dataread.filereader;
2+
3+
public class FileReaderProperties {
4+
public final static String NORMALIZED_DATA_FILE_INPUT = "iotdatacache.filereader.normalizeddata";
5+
public final static String NORMALIZED_DATA_FILE_INPUT_ENABLED = NORMALIZED_DATA_FILE_INPUT + ".enabled";
6+
public final static String NORMALIZED_DATA_FILE_INPUT_ORDER = NORMALIZED_DATA_FILE_INPUT + ".order";
7+
public final static String NORMALIZED_DATA_FILE_INPUT_REPLAY = NORMALIZED_DATA_FILE_INPUT + ".replay";
8+
public final static String NORMALIZED_DATA_FILE_INPUT_REPLAY_DURATION = NORMALIZED_DATA_FILE_INPUT_REPLAY
9+
+ ".duration";
10+
public static final String NORMALIZED_DATA_FILE_INPUT_REPLAY_START_OFFSET = NORMALIZED_DATA_FILE_INPUT_REPLAY
11+
+ ".startoffset";
12+
public final static String NORMALIZED_DATA_FILE_INPUT_REPLAY_OUTPUT_END_TIME_OBSERVED = NORMALIZED_DATA_FILE_INPUT_REPLAY
13+
+ ".endtimeobserved";
14+
public final static String NORMALIZED_DATA_FILE_INPUT_REPLAY_HIGH_SPEED_PLAYBACK_DELAY = NORMALIZED_DATA_FILE_INPUT_REPLAY
15+
+ ".highspeeddelay";
16+
public final static String NORMALIZED_DATA_FILE_INPUT_MODE = NORMALIZED_DATA_FILE_INPUT + ".mode";
17+
public final static String NORMALIZED_DATA_FILE_INPUT_SOURCE_FILE = NORMALIZED_DATA_FILE_INPUT + ".sourcefile";
18+
19+
public final static String RAW_DATA_FILE_INPUT = "iotdatacache.filereader.rawdata";
20+
public final static String RAW_DATA_FILE_INPUT_ENABLED = RAW_DATA_FILE_INPUT + ".enabled";
21+
public final static String RAW_DATA_FILE_INPUT_ORDER = RAW_DATA_FILE_INPUT + ".order";
22+
public final static String RAW_DATA_FILE_INPUT_REPLAY = RAW_DATA_FILE_INPUT + ".replay";
23+
public final static String RAW_DATA_FILE_INPUT_REPLAY_DURATION = RAW_DATA_FILE_INPUT_REPLAY + ".duration";
24+
public static final String RAW_DATA_FILE_INPUT_REPLAY_START_OFFSET = RAW_DATA_FILE_INPUT_REPLAY + ".startoffset";
25+
public final static String RAW_DATA_FILE_INPUT_REPLAY_OUTPUT_END_TIME_OBSERVED = RAW_DATA_FILE_INPUT_REPLAY
26+
+ ".endtimeobserved";
27+
public final static String RAW_DATA_FILE_INPUT_REPLAY_HIGH_SPEED_PLAYBACK_DELAY = RAW_DATA_FILE_INPUT_REPLAY
28+
+ ".highspeeddelay";
29+
public final static String RAW_DATA_FILE_INPUT_MODE = RAW_DATA_FILE_INPUT + ".mode";
30+
public final static String RAW_DATA_FILE_INPUT_SOURCE_FILE = RAW_DATA_FILE_INPUT + ".sourcefile";
31+
}

IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/dataread/filereader/NormalizedDataFileInput.java

Lines changed: 424 additions & 0 deletions
Large diffs are not rendered by default.

IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/filters/rawdata/RawDataDeviceModelMessageFilter.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on
3636
*/
3737
package com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.filters.rawdata;
3838

39-
import java.sql.PreparedStatement;
40-
4139
import com.oracle.demo.timg.iot.iotdbjdbc.aqdata.RawData;
4240
import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.RawDataMessageHandler;
4341
import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.filters.common.DeviceModelMessageFilterCoreOrig;
@@ -58,9 +56,6 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on
5856
@Requires(property = "iotdatacache.schemaname")
5957
@Log
6058
public class RawDataDeviceModelMessageFilter extends DeviceModelMessageFilterCoreOrig implements RawDataMessageHandler {
61-
62-
private PreparedStatement selectModelIdByInstanceIdPS;
63-
6459
@Inject
6560
public RawDataDeviceModelMessageFilter(DBConnectionSupplier dbConnectionSupplier,
6661
@Property(name = "iotdatacache.schemaname") String schemaName,

IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/iotdbutils/DeviceModelInstancesCache.java

Lines changed: 133 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on
5151

5252
import io.micronaut.context.annotation.Property;
5353
import io.micronaut.context.event.ShutdownEvent;
54-
import io.micronaut.context.event.StartupEvent;
5554
import io.micronaut.runtime.event.annotation.EventListener;
55+
import jakarta.annotation.PostConstruct;
5656
import jakarta.inject.Inject;
5757
import jakarta.inject.Singleton;
5858
import jakarta.validation.constraints.NotEmpty;
@@ -75,25 +75,29 @@ public class DeviceModelInstancesCache {
7575
// we we don't know about this then we will try an individual load
7676
public final static String SELECT_MODEL_NAME_BY_MODEL_ID = "SELECT JSON_VALUE(dtm.data, '$.displayName' ) AS modelname FROM digital_twin_models dtm WHERE JSON_VALUE (dtm.data, '$._id' ) = ? ";
7777
public final static String SELECT_MODEL_ID_BY_MODEL_NAME = "SELECT JSON_VALUE (dtm.data, '$._id' ) AS modelid FROM digital_twin_models dtm WHERE JSON_VALUE(dtm.data, '$.displayName' ) = ? ";
78-
public final static String SELECT_MODEL_ID_DISPLAY_NAME_AND_EXTERNAL_KEY_BY_INSTANCE_ID = "SELECT JSON_VALUE (dti.data, '$.digitalTwinModelId' ) AS modelid, JSON_VALUE (dti.data, '$.externalKey' ) AS externalkey, JSON_VALUE(dti.data, '$.displayName' ) FROM digital_twin_instances dti WHERE JSON_VALUE(dti.data, '$._id' ) = ?";
78+
public final static String SELECT_MODEL_ID_DISPLAY_NAME_AND_EXTERNAL_KEY_BY_INSTANCE_ID = "SELECT JSON_VALUE (dti.data, '$.digitalTwinModelId' ) AS modelid, JSON_VALUE (dti.data, '$.externalKey' ) AS externalkey, JSON_VALUE(dti.data, '$.displayName' )AS displayname FROM digital_twin_instances dti WHERE JSON_VALUE(dti.data, '$._id' ) = ?";
79+
public final static String SELECT_MODEL_ID_INSTANCE_ID_AND_EXTERNAL_KEY_BY_INSTANCE_DISPLAY_NAME = "SELECT JSON_VALUE (dti.data, '$.digitalTwinModelId' ) AS modelid, JSON_VALUE (dti.data, '$.externalKey' ) AS externalkey, JSON_VALUE (dti.data, '$._id' ) AS instanceid FROM digital_twin_instances dti WHERE JSON_VALUE(dti.data, '$.displayName' ) = ?";
7980

8081
private final String schemaName;
8182
private final DBConnectionSupplier dbConnectionSupplier;
8283
private Connection connection;
8384

8485
private final Map<String, String> instanceIdToModelId = Collections.synchronizedMap(new HashMap<>());
8586
private final Map<String, String> instanceIdToExternalKey = Collections.synchronizedMap(new HashMap<>());
86-
private final Map<String, String> instanceIdToInstanceName = Collections.synchronizedMap(new HashMap<>());
87+
private final Map<String, String> instanceIdToInstanceDisplayName = Collections.synchronizedMap(new HashMap<>());
88+
private final Map<String, String> instanceDisplayNameToInstanceId = Collections.synchronizedMap(new HashMap<>());
8789
private final Map<String, String> instanceIdToModelName = Collections.synchronizedMap(new HashMap<>());
8890
private final Map<String, String> externalKeyToInstanceId = Collections.synchronizedMap(new HashMap<>());
8991
private final Map<String, String> modelIdToModelName = Collections.synchronizedMap(new HashMap<>());
9092
private final Map<String, String> modelNameToModelId = Collections.synchronizedMap(new HashMap<>());
9193
private final Set<String> foundMissingModelIds = Collections.synchronizedSet(new HashSet<>());
9294
private final Set<String> foundMissingModelNames = Collections.synchronizedSet(new HashSet<>());
9395
private final Set<String> foundMissingInstanceIds = Collections.synchronizedSet(new HashSet<>());
96+
private final Set<String> foundMissingInstanceDisplayNames = Collections.synchronizedSet(new HashSet<>());
9497
private final Set<String> foundMissingExternalKeys = Collections.synchronizedSet(new HashSet<>());
9598

96-
private PreparedStatement selectModelIdByInstanceIdPS;
99+
private PreparedStatement selectInstanceDetailsByInstanceIdPS;
100+
private PreparedStatement selectInstanceDetailsByInstanceDisplayNamePS;
97101
private PreparedStatement selectModelNameByModelIdPS;
98102
private PreparedStatement selectModelIdByModelNamePS;
99103
private final boolean preloadExistingModels;
@@ -113,9 +117,9 @@ public DeviceModelInstancesCache(DBConnectionSupplier dbConnectionSupplier,
113117
this.preloadExistingInstances = preloadExistingInstances;
114118
}
115119

116-
@EventListener
117-
public void onStartup(StartupEvent event) {
118-
log.info("Startup event received for DeviceModelInstancesCache");
120+
@PostConstruct
121+
public void postConstruct() {
122+
log.info("Post Construct event received for DeviceModelInstancesCache");
119123
try {
120124
configure();
121125
} catch (Exception e) {
@@ -149,8 +153,10 @@ public void configure() throws Exception {
149153
// set this up so we can re-use it later if we need to query for an instance we
150154
// didn't know about
151155
log.fine("Creating prepared statements");
152-
selectModelIdByInstanceIdPS = connection
156+
selectInstanceDetailsByInstanceIdPS = connection
153157
.prepareStatement(SELECT_MODEL_ID_DISPLAY_NAME_AND_EXTERNAL_KEY_BY_INSTANCE_ID);
158+
selectInstanceDetailsByInstanceDisplayNamePS = connection
159+
.prepareStatement(SELECT_MODEL_ID_INSTANCE_ID_AND_EXTERNAL_KEY_BY_INSTANCE_DISPLAY_NAME);
154160
selectModelNameByModelIdPS = connection.prepareStatement(SELECT_MODEL_NAME_BY_MODEL_ID);
155161
selectModelIdByModelNamePS = connection.prepareStatement(SELECT_MODEL_ID_BY_MODEL_NAME);
156162
log.fine("Prepared statements created");
@@ -360,7 +366,16 @@ private void preloadExistingInstances() throws SQLException {
360366
String instanceDisplayName = rs.getString(INSTANCE_ID_COLUMN_DISPLAY_NAME);
361367
String modelName = modelIdToModelName.get(modelIdExistingInstance);
362368
instanceIdToModelId.put(instanceIdExistingInstance, modelIdExistingInstance);
363-
instanceIdToInstanceName.put(instanceIdExistingInstance, instanceDisplayName);
369+
instanceIdToInstanceDisplayName.put(instanceIdExistingInstance, instanceDisplayName);
370+
if (instanceDisplayNameToInstanceId.containsKey(instanceDisplayName)) {
371+
log.warning("Instance display name cache already contains key " + instanceDisplayName
372+
+ " connected to instance id " + instanceDisplayNameToInstanceId.get(instanceDisplayName)
373+
+ ", duplicates are not added");
374+
} else {
375+
instanceDisplayNameToInstanceId.put(instanceDisplayName, instanceIdExistingInstance);
376+
log.info("Added instance name " + instanceDisplayName + " to instanceId "
377+
+ instanceIdExistingInstance + " mapping");
378+
}
364379
log.info("Added instance id " + instanceIdExistingInstance + " named " + instanceDisplayName
365380
+ " to modelId " + modelIdExistingInstance + " mapping");
366381
instanceIdToModelName.put(instanceIdExistingInstance, modelName);
@@ -451,10 +466,10 @@ public String getInstanceDisplayNameByInstanceId(@NotNull @NotEmpty String insta
451466
}
452467
// do we already have the info ? note that empty string and null are valid
453468
// responses here.
454-
if (instanceIdToInstanceName.containsKey(instanceId)) {
469+
if (instanceIdToInstanceDisplayName.containsKey(instanceId)) {
455470
// we have the key, the model could be a string, null blank etc if one hasn't
456471
// been set, but that's still valid.
457-
return instanceIdToInstanceName.get(instanceId);
472+
return instanceIdToInstanceDisplayName.get(instanceId);
458473
}
459474
// we don't have a cached version
460475
// let's try and locate it
@@ -474,6 +489,54 @@ public String getInstanceDisplayNameByInstanceId(@NotNull @NotEmpty String insta
474489
}
475490
}
476491

492+
/**
493+
* try to get the modelId from the cache or if there is no cachedata
494+
*
495+
* @param instanceId the instance to locate
496+
* @param cacheMissingResults if true and we already have looked but not found
497+
* this then don't look again
498+
* @return the instance display name is there is one
499+
* @throws MissingInstanceException throws an exception if we can't locate the
500+
* instance (either in the known missing cache
501+
* if cacheMissingResults is true, or in the
502+
* IoT service otherwise)
503+
* @throws SQLException if there was a problem querying the iot
504+
* service
505+
*/
506+
public String getInstanceIdByInstanceDisplayName(@NotNull @NotEmpty String instanceDisplayName,
507+
boolean cacheMissingResults) throws MissingInstanceException, SQLException {
508+
boolean knownMissing = foundMissingInstanceDisplayNames.contains(instanceDisplayName);
509+
// are we looking at the cache ?
510+
if (cacheMissingResults && knownMissing) {
511+
// we know it's missing, and we are not checking an other time
512+
throw new MissingInstanceException(
513+
"No instance found in cache and not checking again for instanceDisplayName" + instanceDisplayName);
514+
}
515+
// do we already have the info ? note that empty string and null are valid
516+
// responses here.
517+
if (instanceDisplayNameToInstanceId.containsKey(instanceDisplayName)) {
518+
// we have the key, the model could be a string, null blank etc if one hasn't
519+
// been set, but that's still valid.
520+
return instanceDisplayNameToInstanceId.get(instanceDisplayName);
521+
}
522+
// we don't have a cached version
523+
// let's try and locate it
524+
try {
525+
InstanceKeyInfo ike = loadInstanceByInstanceDisplayName(instanceDisplayName);
526+
// OK we have something, was it previously tagged as knownMissing ? if so remove
527+
// the id
528+
if (knownMissing) {
529+
foundMissingInstanceDisplayNames.remove(instanceDisplayName);
530+
}
531+
return ike.getInstanceId();
532+
} catch (MissingInstanceException e) {
533+
// cache the missing result for later use
534+
foundMissingInstanceDisplayNames.add(instanceDisplayName);
535+
// then throw the exception
536+
throw e;
537+
}
538+
}
539+
477540
/**
478541
* try to get the external key from the cache or if there is no cachedata
479542
*
@@ -568,10 +631,10 @@ public String getModelNameByInstanceId(@NotNull @NotEmpty String instanceId, boo
568631
*/
569632
private InstanceKeyInfo loadInstanceByInstanceId(@NotNull @NotEmpty String instanceId)
570633
throws SQLException, MissingInstanceException {
571-
synchronized (selectModelIdByInstanceIdPS) {
572-
selectModelIdByInstanceIdPS.setString(1, instanceId);
634+
synchronized (selectInstanceDetailsByInstanceIdPS) {
635+
selectInstanceDetailsByInstanceIdPS.setString(1, instanceId);
573636
// get all of the results
574-
try (ResultSet rs = selectModelIdByInstanceIdPS.executeQuery()) {
637+
try (ResultSet rs = selectInstanceDetailsByInstanceIdPS.executeQuery()) {
575638
if (rs.next()) {
576639
String modelId = rs.getString(MODEL_ID_COLUMN_NAME);
577640
String externalKey = rs.getString(EXTERNAL_KEY_COLUMN_NAME);
@@ -580,7 +643,15 @@ private InstanceKeyInfo loadInstanceByInstanceId(@NotNull @NotEmpty String insta
580643
instanceIdToModelId.put(instanceId, modelId);
581644
instanceIdToModelName.put(instanceId, modelName);
582645
instanceIdToExternalKey.put(instanceId, externalKey);
583-
instanceIdToInstanceName.put(instanceId, instanceDisplayName);
646+
instanceIdToInstanceDisplayName.put(instanceId, instanceDisplayName);
647+
if (instanceDisplayNameToInstanceId.containsKey(instanceDisplayName)) {
648+
log.warning("Instance display name cache already contains key " + instanceDisplayName
649+
+ " connected to instance id "
650+
+ instanceDisplayNameToInstanceId.get(instanceDisplayName)
651+
+ ", duplicates are not added");
652+
} else {
653+
instanceDisplayNameToInstanceId.put(instanceDisplayName, instanceId);
654+
}
584655
externalKeyToInstanceId.put(externalKey, instanceId);
585656
return new InstanceKeyInfo(instanceId, modelId, externalKey, instanceDisplayName);
586657
} else {
@@ -593,6 +664,51 @@ private InstanceKeyInfo loadInstanceByInstanceId(@NotNull @NotEmpty String insta
593664
}
594665
}
595666

667+
/**
668+
* on demand load an entry in the mode details cache
669+
*
670+
* @param instanceId
671+
* @throws SQLException
672+
* @throws MissingInstanceException
673+
* @return the located modelId, null for instances with no model
674+
*/
675+
private InstanceKeyInfo loadInstanceByInstanceDisplayName(@NotNull @NotEmpty String instanceDisplayName)
676+
throws SQLException, MissingInstanceException {
677+
synchronized (selectInstanceDetailsByInstanceDisplayNamePS) {
678+
selectInstanceDetailsByInstanceDisplayNamePS.setString(1, instanceDisplayName);
679+
// get all of the results
680+
try (ResultSet rs = selectInstanceDetailsByInstanceDisplayNamePS.executeQuery()) {
681+
if (rs.next()) {
682+
String modelId = rs.getString(MODEL_ID_COLUMN_NAME);
683+
String externalKey = rs.getString(EXTERNAL_KEY_COLUMN_NAME);
684+
String instanceId = rs.getString(INSTANCE_ID_COLUMN_NAME);
685+
String modelName = modelIdToModelName.get(modelId);
686+
instanceIdToModelId.put(instanceId, modelId);
687+
instanceIdToModelName.put(instanceId, modelName);
688+
instanceIdToExternalKey.put(instanceId, externalKey);
689+
instanceIdToInstanceDisplayName.put(instanceId, instanceDisplayName);
690+
if (instanceDisplayNameToInstanceId.containsKey(instanceDisplayName)) {
691+
log.warning("Instance display name cache already contains key " + instanceDisplayName
692+
+ " connected to instance id "
693+
+ instanceDisplayNameToInstanceId.get(instanceDisplayName)
694+
+ ", duplicates are not added");
695+
} else {
696+
instanceDisplayNameToInstanceId.put(instanceDisplayName, instanceId);
697+
}
698+
externalKeyToInstanceId.put(externalKey, instanceId);
699+
return new InstanceKeyInfo(instanceId, modelId, externalKey, instanceDisplayName);
700+
} else {
701+
throw new MissingInstanceException("No instance found for instance id " + instanceDisplayName);
702+
}
703+
} catch (SQLException e) {
704+
log.severe(
705+
"SQLException getting existing model / instance mappings in loadInstanceByInstanceDisplayName, "
706+
+ e.getLocalizedMessage());
707+
throw e;
708+
}
709+
}
710+
}
711+
596712
@Data
597713
@AllArgsConstructor
598714
private class InstanceKeyInfo {
@@ -623,6 +739,7 @@ public void unconfigure() throws Exception {
623739
foundMissingModelIds.clear();
624740
foundMissingModelNames.clear();
625741
foundMissingInstanceIds.clear();
742+
foundMissingInstanceDisplayNames.clear();
626743
foundMissingExternalKeys.clear();
627744
configured = false;
628745
}
@@ -639,6 +756,7 @@ public String getConfig() {
639756
+ modelIdToModelName + ")" + " model ids, " + modelNameToModelId.size() + " ( " + modelNameToModelId
640757
+ ")" + " model names, " + foundMissingModelIds.size() + " found missing model ids"
641758
+ foundMissingModelNames.size() + " found missing model names, " + foundMissingInstanceIds.size()
759+
+ " found missing instance display names, " + foundMissingInstanceDisplayNames.size()
642760
+ " found missing instance ids. preloadExistingModels=" + preloadExistingModels
643761
+ ", preloadExistingInstances=" + preloadExistingInstances;
644762
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.filewriter;
2+
3+
public class FileWriterProperties {
4+
public final static String NORMALIZED_DATA_FILE_OUTPUT = "messagehandler.output.normalizeddata.fileoutput";
5+
public final static String NORMALIZED_DATA_FILE_OUTPUT_ENABLED = NORMALIZED_DATA_FILE_OUTPUT + ".enabled";
6+
public final static String NORMALIZED_DATA_FILE_OUTPUT_ORDER = NORMALIZED_DATA_FILE_OUTPUT + ".order";
7+
public final static String NORMALIZED_DATA_FILE_OUTPUT_DURATION = NORMALIZED_DATA_FILE_OUTPUT + ".duration";
8+
public final static String NORMALIZED_DATA_FILE_OUTPUT_TARGET_FILE = NORMALIZED_DATA_FILE_OUTPUT + ".targetfile";
9+
10+
public final static String RAW_DATA_FILE_OUTPUT = "messagehandler.output.rawdata.fileoutput";
11+
public final static String RAW_DATA_FILE_OUTPUT_ENABLED = RAW_DATA_FILE_OUTPUT + ".enabled";
12+
public final static String RAW_DATA_FILE_OUTPUT_ORDER = RAW_DATA_FILE_OUTPUT + ".order";
13+
public final static String RAW_DATA_FILE_OUTPUT_DURATION = RAW_DATA_FILE_OUTPUT + ".duration";
14+
public final static String RAW_DATA_FILE_OUTPUT_TARGET_FILE = RAW_DATA_FILE_OUTPUT + ".targetfile";
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.filewriter;
2+
3+
import io.micronaut.serde.annotation.Serdeable;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
import lombok.experimental.SuperBuilder;
7+
import lombok.extern.java.Log;
8+
9+
@Log
10+
@Data
11+
@NoArgsConstructor
12+
@SuperBuilder
13+
@Serdeable
14+
public abstract class IoTDataCoreFileVersion {
15+
private String digitalTwinInstanceDisplayName;
16+
}

0 commit comments

Comments
 (0)