From bccd56e9cc895adec120aa5948c192eceadd0891 Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:21:07 +0100 Subject: [PATCH 1/7] seems that there has been a change somewhere in the service and the oauth it't behaving as expected, adding some test stuff --- .../timeseriesdb/TimeSeriesDBOutputOTLP.java | 37 +++++++++++++++++-- .../timeseriesdb/TimeSeriesDBProperties.java | 4 ++ .../oauth/TimeSeriesDBOAuthClient.java | 2 +- .../TimeSeriesDBOAuthTokenRequestFilter.java | 2 + .../TimeSeriesDBOAuthTokenRetriever.java | 29 +++++++++------ .../src/main/resources/logging.properties | 3 ++ 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java index 8d11373..ee0d3c7 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java @@ -37,6 +37,7 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on package com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb; import java.sql.SQLException; +import java.time.LocalDateTime; import com.oracle.demo.timg.iot.iotdbjdbc.aqdata.NormalizedData; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.NormalizedDataMessageHandler; @@ -45,11 +46,14 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.iotdbutils.MissingModelException; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.endpoints.TimeSeriesEndpointsQueryParams; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.endpoints.TimeSeriesEndpointsRetriever; +import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.oauth.OAuthTokenRetrievalException; +import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.oauth.TimeSeriesDBOAuthTokenRetriever; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.otlp.MetricsData; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.otlp.NormalizedDataMetricsDataBuilder; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.otlp.OtlpMetricsClient; import com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.otlp.OtlpProperties; +import io.micronaut.context.BeanProvider; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Requires; import io.micronaut.context.event.StartupEvent; @@ -81,11 +85,18 @@ public class TimeSeriesDBOutputOTLP implements NormalizedDataMessageHandler { @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_METRICS_PATH, defaultValue = "/tel/v1/metrics") private String metricsPath; + // for debugging if we're not uploading we can still trigger a oauth check, that + // way any oauth debugging will happen + @Inject + private BeanProvider tokenRetriever; + private final boolean noUpload; + @Inject public TimeSeriesDBOutputOTLP(DeviceModelInstancesCache deviceModelInstancesCache, OtlpMetricsClient metricsClient, TimeSeriesEndpointsRetriever timeSeriesEndpointsRetriever, ObjectMapper mapper, @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_ORDER) int order, - @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_SENT_DATA_IS_COMPLETED, defaultValue = "true") boolean sentDataIsCompleted) { + @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_SENT_DATA_IS_COMPLETED, defaultValue = "true") boolean sentDataIsCompleted, + @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_DEBUG_NO_UPLOAD, defaultValue = "false") boolean noUpload) { this.deviceModelInstancesCache = deviceModelInstancesCache; this.metricsClient = metricsClient; TimeSeriesEndpointsQueryParams queryParams = timeSeriesEndpointsRetriever.getQueryParams(); @@ -95,6 +106,7 @@ public TimeSeriesDBOutputOTLP(DeviceModelInstancesCache deviceModelInstancesCach this.mapper = mapper; this.order = order; this.sentDataIsCompleted = sentDataIsCompleted; + this.noUpload = noUpload; } @Override @@ -129,10 +141,14 @@ public NormalizedData[] processNormalizedData(NormalizedData normalizedData) thr log.info(() -> "About to upload to time series db " + metricsDataString); log.info(() -> "Uploading url=" + metricsClientUrl + ", path=" + metricsPath + ", queryX=" + queryX + ", queryY=" + queryY); + if (noUpload) { + log.info("noUpload is true, skipping upload"); - HttpResponse resp = metricsClient.uploadMetrics(queryX, queryY, metricsDataString); - log.info("Upload to time series DB response is " + resp.getStatus().getCode() + "(" - + resp.getStatus().toString() + ") with body " + resp.getBody().orElse("No response data")); + } else { + HttpResponse resp = metricsClient.uploadMetrics(queryX, queryY, metricsDataString); + log.info("Upload to time series DB response is " + resp.getStatus().getCode() + "(" + + resp.getStatus().toString() + ") with body " + resp.getBody().orElse("No response data")); + } return sentDataIsCompleted ? new NormalizedData[0] : new NormalizedData[] { normalizedData }; } @@ -168,5 +184,18 @@ private void addResourceAttributes(NormalizedDataMetricsDataBuilder builder, Nor public void onStartup(StartupEvent event) { log.info("Startup event received for TimeSeriesDBOutputOTLP, queryX=" + queryX + ", queryY=" + queryY + ", Uploading to " + metricsClientUrl + " with path " + metricsPath); + if (noUpload) { + // trigger the oauth provider to do it's stuff + try { + log.info("noUpload set, forcing an OAuth operation"); + String token = tokenRetriever.get().getToken(); + String tokenType = tokenRetriever.get().getTokenType(); + LocalDateTime tokenExpire = tokenRetriever.get().getCurrentTokenRenewTime(); + log.info("Token type=" + tokenType + ", token expiry=" + tokenExpire + ", token=" + token); + } catch (OAuthTokenRetrievalException e) { + // TODO Auto-generated catch block + log.severe("noUpload set, Problem getting the OAuth token " + e.getLocalizedMessage()); + } + } } } diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBProperties.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBProperties.java index 2070829..89baceb 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBProperties.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBProperties.java @@ -64,4 +64,8 @@ public class TimeSeriesDBProperties { public static final String TIME_SERIES_PROPERTY_METRICS_QUERY_PARAMS_Y = TIME_SERIES_PROPERTY_METRICS_QUERY_PARAMS + ".y"; public static final String TIME_SERIES_PROPERTY_METRICS_PATH = TIME_SERIES_PROPERTY_METRICS + ".path"; + + public static final String TIME_SERIES_PROPERTY_DEBUG = TIME_SERIES_PROPERTY_PREFIX + ".debug"; + public static final String TIME_SERIES_PROPERTY_DEBUG_OAUTH = TIME_SERIES_PROPERTY_DEBUG + ".oauth"; + public static final String TIME_SERIES_PROPERTY_DEBUG_NO_UPLOAD = TIME_SERIES_PROPERTY_DEBUG + ".noupload"; } diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthClient.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthClient.java index 2675fba..be6d0c1 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthClient.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthClient.java @@ -58,7 +58,7 @@ public interface TimeSeriesDBOAuthClient { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Post("${" + TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_PATH + ":/tel/token}") - public OAuthTokenResponse getOAuthToken(@QueryValue("x") String queryParamX, @QueryValue("y") String queryParamY, + public String getOAuthToken(@QueryValue("x") String queryParamX, @QueryValue("y") String queryParamY, @Body String timeSeriesDBCredentials) throws HttpClientException; } diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRequestFilter.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRequestFilter.java index 0d7081a..d1ec10b 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRequestFilter.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRequestFilter.java @@ -48,6 +48,7 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on import io.micronaut.http.annotation.RequestFilter; import io.micronaut.runtime.event.annotation.EventListener; import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.extern.java.Log; /* @@ -56,6 +57,7 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on // @ClientFilter(patterns = { "${" + TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_PATH + "}" }) @Requires(property = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_ENABLED, value = "true", defaultValue = "false") @ClientFilter(patterns = "${" + TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_PATH + ":/tel/token}") +@Singleton @Log public class TimeSeriesDBOAuthTokenRequestFilter { public final static String HEADER_REQUEST_ID = "Request-Id"; diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java index 77b4548..7344bb8 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java @@ -58,21 +58,14 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on @Singleton @Requires(property = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_ENABLED, value = "true", defaultValue = "false") -//@Requires(property = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_QUERY_PARAMS_X) -//@Requires(property = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_QUERY_PARAMS_Y) @Log public class TimeSeriesDBOAuthTokenRetriever { @Inject private ObjectMapper mapper; - @Inject - private TimeSeriesDBCredentials tsDBuserCredentials; + private final TimeSeriesDBCredentials tsDBuserCredentials; @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_RENEWAL_PREEMPT, defaultValue = "PT60S") private Duration renewalPreempt; - // @Property(name = - // TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_QUERY_PARAMS_X) private final String queryX; - // @Property(name = - // TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH_QUERY_PARAMS_Y) private final String queryY; @Getter private LocalDateTime currentTokenRenewTime = null; @@ -80,12 +73,20 @@ public class TimeSeriesDBOAuthTokenRetriever { private String currentToken = null; @Getter private String tokenType; + @Property(name = TimeSeriesDBProperties.TIME_SERIES_PROPERTY_DEBUG_OAUTH, defaultValue = "false") + boolean debugOauth = false; @Inject - public TimeSeriesDBOAuthTokenRetriever(TimeSeriesEndpointsRetriever endpointsRetriever) { + public TimeSeriesDBOAuthTokenRetriever(TimeSeriesEndpointsRetriever endpointsRetriever, + TimeSeriesDBCredentials tsDBuserCredentials) { TimeSeriesEndpointsQueryParams endpointsQueryParams = endpointsRetriever.getQueryParams(); this.queryX = endpointsQueryParams.getOauthQueryX(); this.queryY = endpointsQueryParams.getOauthQueryY(); + log.info("Constructor params for OAUTH queryX=" + queryX + ", queryY=" + queryY); + this.tsDBuserCredentials = tsDBuserCredentials; + if (debugOauth) { + log.info("Oauth retrieve credentials are " + tsDBuserCredentials); + } } @Inject @@ -116,13 +117,17 @@ public synchronized String getToken() throws OAuthTokenRetrievalException { log.info("Retrieveing oauth token from time series DB"); try { String credentials = mapper.writeValueAsString(tsDBuserCredentials); - log.fine("Setting body to " + credentials); - atr = authClient.getOAuthToken(queryX, queryY, credentials); + log.fine(() -> "Setting body to " + credentials); + String oauthRespStr = authClient.getOAuthToken(queryX, queryY, credentials); + log.fine(() -> "OAuth response is " + oauthRespStr); + atr = mapper.readValue(oauthRespStr, OAuthTokenResponse.class); } catch (HttpClientException e) { + log.warning("Problem getting the OAuth token " + e.getLocalizedMessage()); throw new OAuthTokenRetrievalException("Problem getting the OAuth token " + e.getLocalizedMessage(), e); } catch (IOException e) { + log.warning("IOException in mapping, this should not happen " + e.getLocalizedMessage()); throw new OAuthTokenRetrievalException( - "IOException building mapping, this should not happen " + e.getLocalizedMessage(), e); + "IOException in mapping, this should not happen " + e.getLocalizedMessage(), e); } this.currentToken = atr.getAccessToken(); this.tokenType = atr.getTokenType(); diff --git a/IoTDBJDBC/src/main/resources/logging.properties b/IoTDBJDBC/src/main/resources/logging.properties index a100208..532e5d9 100644 --- a/IoTDBJDBC/src/main/resources/logging.properties +++ b/IoTDBJDBC/src/main/resources/logging.properties @@ -1,2 +1,5 @@ // register SLF4JBridgeHandler as handler for the j.u.l. root logger handlers = org.slf4j.bridge.SLF4JBridgeHandler + +com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.oauth.TimeSeriesDBOAuthTokenRequestFilter.level = FINER +com.oracle.demo.timg.iot.iotdbjdbc.messagehandler.outputs.http.normalizeddata.timeseriesdb.TimeSeriesDBOutputOTLP = FINER \ No newline at end of file From fce7bcc08bbbb10310af939186a9517ffdccecbb Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:18:10 +0100 Subject: [PATCH 2/7] update versions in a hope to get consistency --- IoTDBJDBC/.factorypath | 22 +++++++++++----------- IoTDBJDBC/pom.xml | 12 ++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/IoTDBJDBC/.factorypath b/IoTDBJDBC/.factorypath index 4e17d8c..56a794f 100644 --- a/IoTDBJDBC/.factorypath +++ b/IoTDBJDBC/.factorypath @@ -1,9 +1,9 @@ - + - - + + @@ -13,17 +13,17 @@ - + - - - - + + + + - - - + + + diff --git a/IoTDBJDBC/pom.xml b/IoTDBJDBC/pom.xml index 002ced6..0eb9c22 100644 --- a/IoTDBJDBC/pom.xml +++ b/IoTDBJDBC/pom.xml @@ -46,18 +46,18 @@ SOFTWARE. --> io.micronaut.platform micronaut-parent - 4.10.14 + 4.10.17 jar 21 21 - 3.91.0 + 3.92.0 1.18.46 2.0.7 - 23.8.0.25.04 + 23.26.2.0.0 3.85.0 netty @@ -118,12 +118,12 @@ SOFTWARE. --> the standard Java logging utilities. By making the dependency declaration below Maven will bring in the core SLF4J jar files, and also the SLJ4J to java.util.logging binding --> - + io.micronaut.jsonschema micronaut-json-schema-validation @@ -177,7 +177,7 @@ SOFTWARE. --> com.oracle.nosql.sdk nosqldriver - 5.3.7 + 5.4.23 From 7fd16c1d09a03b82fe50c4e0a9f09de0245c9ed8 Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:51:24 +0100 Subject: [PATCH 3/7] try to fix version problems with oci bmc --- IoTDBJDBC/pom.xml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/IoTDBJDBC/pom.xml b/IoTDBJDBC/pom.xml index 0eb9c22..522a284 100644 --- a/IoTDBJDBC/pom.xml +++ b/IoTDBJDBC/pom.xml @@ -58,7 +58,9 @@ SOFTWARE. --> 2.0.7 23.26.2.0.0 - 3.85.0 + 21.21.0.0 + + 5.4.21 netty false @@ -109,7 +111,7 @@ SOFTWARE. --> org.projectlombok lombok - ${version.lombok} + compile com.oracle.oci.sdk oci-java-sdk-common - ${version.ocisdk} + compile com.oracle.oci.sdk oci-java-sdk-common-httpclient-jersey3 - ${version.ocisdk} + com.oracle.oci.sdk oci-java-sdk-identitydataplane - ${version.ocisdk} + @@ -165,20 +167,20 @@ SOFTWARE. --> com.oracle.database.security osdt_core - 21.21.0.0 + ${version.oracledbsecurity} com.oracle.database.security osdt_cert - 21.21.0.0 + ${version.oracledbsecurity} com.oracle.nosql.sdk nosqldriver - 5.4.23 - + + ${version.nosql} From 704bd1a09cc99db8b4395b999727238b7f8e7b16 Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:26:39 +0100 Subject: [PATCH 4/7] updates to cache and output the instance display name in otlp output --- .../iotdbutils/DeviceModelInstancesCache.java | 70 +++++++++++++++++-- .../timeseriesdb/TimeSeriesDBOutputOTLP.java | 7 ++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/iotdbutils/DeviceModelInstancesCache.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/iotdbutils/DeviceModelInstancesCache.java index 005c8ff..8c687e7 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/iotdbutils/DeviceModelInstancesCache.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/iotdbutils/DeviceModelInstancesCache.java @@ -65,16 +65,17 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on @Singleton public class DeviceModelInstancesCache { private static final String INSTANCE_ID_COLUMN_NAME = "instanceid"; + private static final String INSTANCE_ID_COLUMN_DISPLAY_NAME = "displayname"; private static final String EXTERNAL_KEY_COLUMN_NAME = "externalkey"; private static final String MODEL_ID_COLUMN_NAME = "modelid"; private static final String MODEL_NAME_COLUMN_NAME = "modelname"; // these are used for bulk pre-loading of entries public final static String SELECT_MODEL_IDS_AND_MODEL_NAMES = "SELECT JSON_VALUE (dtm.data, '$._id' ) AS modelid, JSON_VALUE(dtm.data, '$.displayName' ) AS modelname FROM digital_twin_models dtm"; - public final static String SELECT_MODEL_ID_EXTERNAL_KEY_AND_INSTANCE_ID = "SELECT JSON_VALUE (dti.data, '$._id' ) AS instanceid, JSON_VALUE (dti.data, '$.digitalTwinModelId' ) AS modelid, JSON_VALUE (dti.data, '$.externalKey' ) AS externalkey FROM digital_twin_instances dti"; + public final static String SELECT_MODEL_ID_EXTERNAL_KEY_DISPLAY_NAME_AND_INSTANCE_ID = "SELECT JSON_VALUE (dti.data, '$._id' ) AS instanceid, 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"; // we we don't know about this then we will try an individual load 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' ) = ? "; 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' ) = ? "; - public final static String SELECT_MODEL_ID_AND_EXTERNAL_KEY_BY_INSTANCE_ID = "SELECT JSON_VALUE (dti.data, '$.digitalTwinModelId' ) AS modelid, JSON_VALUE (dti.data, '$.externalKey' ) AS externalkey FROM digital_twin_instances dti WHERE JSON_VALUE(dti.data, '$._id' ) = ?"; + 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' ) = ?"; private final String schemaName; private final DBConnectionSupplier dbConnectionSupplier; @@ -82,6 +83,7 @@ public class DeviceModelInstancesCache { private final Map instanceIdToModelId = Collections.synchronizedMap(new HashMap<>()); private final Map instanceIdToExternalKey = Collections.synchronizedMap(new HashMap<>()); + private final Map instanceIdToInstanceName = Collections.synchronizedMap(new HashMap<>()); private final Map instanceIdToModelName = Collections.synchronizedMap(new HashMap<>()); private final Map externalKeyToInstanceId = Collections.synchronizedMap(new HashMap<>()); private final Map modelIdToModelName = Collections.synchronizedMap(new HashMap<>()); @@ -147,7 +149,8 @@ public void configure() throws Exception { // set this up so we can re-use it later if we need to query for an instance we // didn't know about log.fine("Creating prepared statements"); - selectModelIdByInstanceIdPS = connection.prepareStatement(SELECT_MODEL_ID_AND_EXTERNAL_KEY_BY_INSTANCE_ID); + selectModelIdByInstanceIdPS = connection + .prepareStatement(SELECT_MODEL_ID_DISPLAY_NAME_AND_EXTERNAL_KEY_BY_INSTANCE_ID); selectModelNameByModelIdPS = connection.prepareStatement(SELECT_MODEL_NAME_BY_MODEL_ID); selectModelIdByModelNamePS = connection.prepareStatement(SELECT_MODEL_ID_BY_MODEL_NAME); log.fine("Prepared statements created"); @@ -349,15 +352,17 @@ private String loadModelByModelName(@NotNull @NotEmpty String modelName) private void preloadExistingInstances() throws SQLException { // get all of the results try (Statement s = connection.createStatement(); - ResultSet rs = s.executeQuery(SELECT_MODEL_ID_EXTERNAL_KEY_AND_INSTANCE_ID)) { + ResultSet rs = s.executeQuery(SELECT_MODEL_ID_EXTERNAL_KEY_DISPLAY_NAME_AND_INSTANCE_ID)) { while (rs.next()) { String modelIdExistingInstance = rs.getString(MODEL_ID_COLUMN_NAME); String instanceIdExistingInstance = rs.getString(INSTANCE_ID_COLUMN_NAME); String externalKeyExistingInstance = rs.getString(EXTERNAL_KEY_COLUMN_NAME); + String instanceDisplayName = rs.getString(INSTANCE_ID_COLUMN_DISPLAY_NAME); String modelName = modelIdToModelName.get(modelIdExistingInstance); instanceIdToModelId.put(instanceIdExistingInstance, modelIdExistingInstance); - log.info("Added instance id " + instanceIdExistingInstance + " to modelId " + modelIdExistingInstance - + " mapping"); + instanceIdToInstanceName.put(instanceIdExistingInstance, instanceDisplayName); + log.info("Added instance id " + instanceIdExistingInstance + " named " + instanceDisplayName + + " to modelId " + modelIdExistingInstance + " mapping"); instanceIdToModelName.put(instanceIdExistingInstance, modelName); log.info("Added instance id " + instanceIdExistingInstance + " to modelName " + modelName + " mapping"); instanceIdToExternalKey.put(instanceIdExistingInstance, externalKeyExistingInstance); @@ -421,6 +426,54 @@ public String getModelIdByInstanceId(@NotNull @NotEmpty String instanceId, boole } } + /** + * try to get the modelId from the cache or if there is no cachedata + * + * @param instanceId the instance to locate + * @param cacheMissingResults if true and we already have looked but not found + * this then don't look again + * @return the instance display name is there is one + * @throws MissingInstanceException throws an exception if we can't locate the + * instance (either in the known missing cache + * if cacheMissingResults is true, or in the + * IoT service otherwise) + * @throws SQLException if there was a problem querying the iot + * service + */ + public String getInstanceDisplayNameByInstanceId(@NotNull @NotEmpty String instanceId, boolean cacheMissingResults) + throws MissingInstanceException, SQLException { + boolean knownMissing = foundMissingInstanceIds.contains(instanceId); + // are we looking at the cache ? + if (cacheMissingResults && knownMissing) { + // we know it's missing, and we are not checking an other time + throw new MissingInstanceException( + "No instance found in cache and not checking again for instanceid" + instanceId); + } + // do we already have the info ? note that empty string and null are valid + // responses here. + if (instanceIdToExternalKey.containsKey(instanceId)) { + // we have the key, the model could be a string, null blank etc if one hasn't + // been set, but that's still valid. + return instanceIdToExternalKey.get(instanceId); + } + // we don't have a cached version + // let's try and locate it + try { + InstanceKeyInfo ike = loadInstanceByInstanceId(instanceId); + // OK we have something, was it previously tagged as knownMissing ? if so remove + // the id + if (knownMissing) { + foundMissingInstanceIds.remove(instanceId); + } + return ike.getInstanceDisplayName(); + } catch (MissingInstanceException e) { + // cache the missing result for later use + foundMissingInstanceIds.add(instanceId); + // then throw the exception + throw e; + } + } + /** * try to get the external key from the cache or if there is no cachedata * @@ -522,12 +575,14 @@ private InstanceKeyInfo loadInstanceByInstanceId(@NotNull @NotEmpty String insta if (rs.next()) { String modelId = rs.getString(MODEL_ID_COLUMN_NAME); String externalKey = rs.getString(EXTERNAL_KEY_COLUMN_NAME); + String instanceDisplayName = rs.getString(INSTANCE_ID_COLUMN_DISPLAY_NAME); String modelName = modelIdToModelName.get(modelId); instanceIdToModelId.put(instanceId, modelId); instanceIdToModelName.put(instanceId, modelName); instanceIdToExternalKey.put(instanceId, externalKey); + instanceIdToInstanceName.put(instanceId, instanceDisplayName); externalKeyToInstanceId.put(externalKey, instanceId); - return new InstanceKeyInfo(instanceId, modelId, externalKey); + return new InstanceKeyInfo(instanceId, modelId, externalKey, instanceDisplayName); } else { throw new MissingInstanceException("No instance found for instance id " + instanceId); } @@ -544,6 +599,7 @@ private class InstanceKeyInfo { String instanceId; String modelId; String externalKey; + String instanceDisplayName; } public void unconfigure() throws Exception { diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java index ee0d3c7..81688b4 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java @@ -163,6 +163,13 @@ private void addResourceAttributes(NormalizedDataMetricsDataBuilder builder, Nor String modelId = deviceModelInstancesCache.getModelIdByInstanceId(instanceId, true); builder.resourceAttribute("iot.digital_twin.model_id", modelId); log.info("Added model ID resource attribute of " + modelId); + String instanceDisplayName = deviceModelInstancesCache.getInstanceDisplayNameByInstanceId(instanceId, true); + if (instanceDisplayName != null) { + builder.resourceAttribute("iot.digital_twin.display_name", instanceDisplayName); + log.info("Added instance display name of " + instanceDisplayName); + } else { + log.info("Can't locate instance display name for instance " + instanceId); + } String modelName; try { modelName = deviceModelInstancesCache.getModelNameByModelId(modelId, true); From 850fd8699715589f70d8d7ef46b05314bc8fca1a Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:38:22 +0100 Subject: [PATCH 5/7] more ts db oauth debug --- .../normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java index 81688b4..4592ea4 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java @@ -143,7 +143,6 @@ public NormalizedData[] processNormalizedData(NormalizedData normalizedData) thr + ", queryY=" + queryY); if (noUpload) { log.info("noUpload is true, skipping upload"); - } else { HttpResponse resp = metricsClient.uploadMetrics(queryX, queryY, metricsDataString); log.info("Upload to time series DB response is " + resp.getStatus().getCode() + "(" @@ -190,7 +189,7 @@ private void addResourceAttributes(NormalizedDataMetricsDataBuilder builder, Nor @EventListener public void onStartup(StartupEvent event) { log.info("Startup event received for TimeSeriesDBOutputOTLP, queryX=" + queryX + ", queryY=" + queryY - + ", Uploading to " + metricsClientUrl + " with path " + metricsPath); + + ", Uploading to " + metricsClientUrl + " with path " + metricsPath + ", noUpload=" + noUpload); if (noUpload) { // trigger the oauth provider to do it's stuff try { From 52532ae53c7d6db7827ab0cf7ae356f72925ede2 Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:46:55 +0100 Subject: [PATCH 6/7] added some validation checks to the tsdb oauth --- .../timeseriesdb/oauth/OAuthTokenResponse.java | 6 +++++- .../oauth/TimeSeriesDBOAuthTokenRetriever.java | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/OAuthTokenResponse.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/OAuthTokenResponse.java index f540508..e1a5283 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/OAuthTokenResponse.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/OAuthTokenResponse.java @@ -49,7 +49,11 @@ public class OAuthTokenResponse { @JsonProperty(value = "refresh_token") private String refreshToken; @JsonProperty(value = "expires_in") - private int expiresIn; + private Integer expiresIn; @JsonProperty(value = "token_type") private String tokenType; + + public boolean checkInvalid() { + return ((accessToken == null) || (expiresIn == null) || (tokenType == null)); + } } diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java index 7344bb8..3dd45e8 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java @@ -129,6 +129,12 @@ public synchronized String getToken() throws OAuthTokenRetrievalException { throw new OAuthTokenRetrievalException( "IOException in mapping, this should not happen " + e.getLocalizedMessage(), e); } + // make sure that the fields we need have been set + if (atr.checkInvalid()) { + throw new OAuthTokenRetrievalException("Returned token details have null or missing values " + atr + + ", maybe an authentication issue. Check the properties username, password, tenancyocid and databasename under " + + TimeSeriesDBProperties.TIME_SERIES_PROPERTY_OAUTH); + } this.currentToken = atr.getAccessToken(); this.tokenType = atr.getTokenType(); // get a new renewal time, allow 60 seconds for processing the renewal if we @@ -148,5 +154,8 @@ public synchronized String getToken() throws OAuthTokenRetrievalException { public void onStartup(StartupEvent event) { log.info("Startup event received for TimeSeriesDBOAuthTokenRetriever tsDBuserCredentials=" + tsDBuserCredentials.safeToString() + ", queryX=" + queryX + ", queryY=" + queryY); + if (debugOauth) { + log.info("Full credentials are " + tsDBuserCredentials.toString()); + } } } \ No newline at end of file From 68ace10c469b18fb4c7026bca5e1782d91217c02 Mon Sep 17 00:00:00 2001 From: tim_graves <28924492+atimgraves@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:20:34 +0100 Subject: [PATCH 7/7] tidy up log levels and make a number of inlined strings constants --- .../timeseriesdb/TimeSeriesDBOutputOTLP.java | 35 ++++++++++++------- .../TimeSeriesDBOAuthTokenRetriever.java | 4 +-- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java index 4592ea4..02d02db 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/TimeSeriesDBOutputOTLP.java @@ -70,7 +70,15 @@ Software and the Larger Work(s), and to sublicense the foregoing rights on @Log public class TimeSeriesDBOutputOTLP implements NormalizedDataMessageHandler { - private static final long NANOS_PER_SECOND = 1_000_000_000L; + private static final String OTLP_SERVICE_NAME = "IoTDBJDBC"; + private static final String OTLP_SCOPE_NAME = "com.oracle.demo.timg.iot.iotdbjdbc"; + private static final String OTLP_SCOPE_VERSION = "1.0.0"; + private static final String OTLP_METRIC_DESCRIPTION = "IoT normalized data value"; + private static final String OTLP_METRIC_UNIT = "1"; + private static final String OTLP_METRIC_NAME = "iot.normalized"; + private static final String TS_RESOURCE_ATTR_IOT_DIGITAL_TWIN_MODEL_NAME_NAME = "iot.digital_twin.model_name"; + private static final String TS_RESOURCE_ATTR_IOT_DIGITAL_TWIN_INSTANCE_DISPLAY_NAME_NAME = "iot.digital_twin.instance_display_name"; + private static final String TS_RESOURCE_ATTR_IOT_DIGITAL_TWIN_MODEL_ID_NAME = "iot.digital_twin.model_id"; private final int order; private final boolean sentDataIsCompleted; private final DeviceModelInstancesCache deviceModelInstancesCache; @@ -126,9 +134,9 @@ public String getConfig() { @Override public NormalizedData[] processNormalizedData(NormalizedData normalizedData) throws Exception { - NormalizedDataMetricsDataBuilder builder = new NormalizedDataMetricsDataBuilder().serviceName("IoTDBJDBC") - .scope("com.oracle.demo.timg.iot.iotdbjdbc", "1.0.0") - .metric("iot.normalized", "1", "IoT normalized data value").gaugeMetric(normalizedData); + NormalizedDataMetricsDataBuilder builder = new NormalizedDataMetricsDataBuilder().serviceName(OTLP_SERVICE_NAME) + .scope(OTLP_SCOPE_NAME, OTLP_SCOPE_VERSION) + .metric(OTLP_METRIC_NAME, OTLP_METRIC_UNIT, OTLP_METRIC_DESCRIPTION).gaugeMetric(normalizedData); // we want to have a few "standard" attributes (modelId and name) added beyond // it's default setup @@ -139,13 +147,13 @@ public NormalizedData[] processNormalizedData(NormalizedData normalizedData) thr String metricsDataString = mapper.writeValueAsString(metricsData); log.info(() -> "About to upload to time series db " + metricsDataString); - log.info(() -> "Uploading url=" + metricsClientUrl + ", path=" + metricsPath + ", queryX=" + queryX + log.fine(() -> "Uploading url=" + metricsClientUrl + ", path=" + metricsPath + ", queryX=" + queryX + ", queryY=" + queryY); if (noUpload) { log.info("noUpload is true, skipping upload"); } else { HttpResponse resp = metricsClient.uploadMetrics(queryX, queryY, metricsDataString); - log.info("Upload to time series DB response is " + resp.getStatus().getCode() + "(" + log.fine("Upload to time series DB response is " + resp.getStatus().getCode() + "(" + resp.getStatus().toString() + ") with body " + resp.getBody().orElse("No response data")); } return sentDataIsCompleted ? new NormalizedData[0] : new NormalizedData[] { normalizedData }; @@ -160,20 +168,21 @@ private void addResourceAttributes(NormalizedDataMetricsDataBuilder builder, Nor // if we have them add the model id and model name try { String modelId = deviceModelInstancesCache.getModelIdByInstanceId(instanceId, true); - builder.resourceAttribute("iot.digital_twin.model_id", modelId); - log.info("Added model ID resource attribute of " + modelId); + builder.resourceAttribute(TS_RESOURCE_ATTR_IOT_DIGITAL_TWIN_MODEL_ID_NAME, modelId); + log.finer("Added model ID resource attribute of " + modelId); String instanceDisplayName = deviceModelInstancesCache.getInstanceDisplayNameByInstanceId(instanceId, true); if (instanceDisplayName != null) { - builder.resourceAttribute("iot.digital_twin.display_name", instanceDisplayName); - log.info("Added instance display name of " + instanceDisplayName); + builder.resourceAttribute(TS_RESOURCE_ATTR_IOT_DIGITAL_TWIN_INSTANCE_DISPLAY_NAME_NAME, + instanceDisplayName); + log.finer(() -> "Added instance display name of " + instanceDisplayName); } else { - log.info("Can't locate instance display name for instance " + instanceId); + log.info(() -> "Can't locate instance display name for instance " + instanceId); } String modelName; try { modelName = deviceModelInstancesCache.getModelNameByModelId(modelId, true); - builder.resourceAttribute("iot.digital_twin.model_name", modelName); - log.info("Added model name resource attribute of " + modelName); + builder.resourceAttribute(TS_RESOURCE_ATTR_IOT_DIGITAL_TWIN_MODEL_NAME_NAME, modelName); + log.finer(() -> "Added model name resource attribute of " + modelName); } catch (MissingModelException e) { log.severe("No model name found for modelid " + modelId); } catch (SQLException e) { diff --git a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java index 3dd45e8..ee358de 100644 --- a/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java +++ b/IoTDBJDBC/src/main/java/com/oracle/demo/timg/iot/iotdbjdbc/messagehandler/outputs/http/normalizeddata/timeseriesdb/oauth/TimeSeriesDBOAuthTokenRetriever.java @@ -114,7 +114,7 @@ public synchronized String getToken() throws OAuthTokenRetrievalException { if ((currentToken == null) || (currentTokenRenewTime == null) || LocalDateTime.now().isAfter(currentTokenRenewTime)) { OAuthTokenResponse atr; - log.info("Retrieveing oauth token from time series DB"); + log.info("Retrieveing new oauth token from time series DB"); try { String credentials = mapper.writeValueAsString(tsDBuserCredentials); log.fine(() -> "Setting body to " + credentials); @@ -144,7 +144,7 @@ public synchronized String getToken() throws OAuthTokenRetrievalException { log.info("Got token details with type " + atr.getTokenType() + " and expiring in " + atr.getExpiresIn() + " seconds"); } else { - log.info("Using existing token"); + log.finer("Using existing token"); } // we have a current token and it is still valid return currentToken;