Skip to content

Commit 83da0d4

Browse files
committed
Fixing datatype mismatch issue when value type is display
1 parent 5b53e3b commit 83da0d4

18 files changed

+232
-62
lines changed

src/main/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImpl.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,14 @@
4848
import org.slf4j.LoggerFactory;
4949

5050
import java.io.IOException;
51-
import java.io.UnsupportedEncodingException;
5251
import java.lang.reflect.Type;
5352
import java.util.ArrayList;
5453
import java.util.List;
5554
import java.util.Map;
55+
import java.util.Objects;
5656
import java.util.concurrent.Callable;
5757
import java.util.concurrent.ExecutionException;
5858
import java.util.concurrent.TimeUnit;
59-
import java.util.function.Predicate;
6059
import javax.annotation.Nullable;
6160

6261
/**
@@ -247,6 +246,7 @@ public List<Map<String, String>> fetchTableRecordsRetryableMode(String tableName
247246
}
248247

249248
/**
249+
* Fetch schema for actual value type
250250
* @param tableName ServiceNow table name for which schema is getting fetched
251251
* @param collector FailureCollector
252252
* @return schema for given ServiceNow table
@@ -255,7 +255,7 @@ public List<Map<String, String>> fetchTableRecordsRetryableMode(String tableName
255255
public Schema fetchTableSchema(String tableName, FailureCollector collector) {
256256
Schema schema = null;
257257
try {
258-
schema = fetchTableSchema(tableName);
258+
schema = fetchTableSchema(tableName, SourceValueType.SHOW_ACTUAL_VALUE);
259259
} catch (Exception e) {
260260
LOG.error("Failed to fetch schema on table {}", tableName, e);
261261
collector.addFailure(String.format("Connection failed. Unable to fetch schema for table: %s. Cause: %s",
@@ -276,35 +276,42 @@ public SchemaResponse parseSchemaResponse(String responseBody) {
276276
* @return schema for given ServiceNow table
277277
* @throws ServiceNowAPIException
278278
*/
279-
public Schema fetchTableSchema(String tableName)
279+
public Schema fetchTableSchema(String tableName, SourceValueType valueType)
280280
throws ServiceNowAPIException {
281-
return fetchTableSchema(tableName, getAccessToken());
281+
return fetchTableSchema(tableName, getAccessToken(), valueType);
282282
}
283283

284284
/**
285285
* Fetches the table schema from ServiceNow
286286
*
287287
* @param tableName ServiceNow table name for which schema is getting fetched
288288
* @param accessToken Access Token to use
289+
* @param valueType Type of value (Actual/Display)
289290
* @return schema for given ServiceNow table
290291
*/
291-
public Schema fetchTableSchema(String tableName, String accessToken)
292+
public Schema fetchTableSchema(String tableName, String accessToken, SourceValueType valueType)
292293
throws ServiceNowAPIException {
293294
ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder(
294295
this.conf.getRestApiEndpoint(), tableName, true)
295296
.setExcludeReferenceLink(true);
296297

297-
RestAPIResponse apiResponse;
298+
RestAPIResponse restAPIResponse;
298299
requestBuilder.setAuthHeader(accessToken);
299-
apiResponse = executeGetWithRetries(requestBuilder.build());
300-
SchemaResponse response = parseSchemaResponse(apiResponse.getResponseBody());
300+
restAPIResponse = executeGetWithRetries(requestBuilder.build());
301+
SchemaResponse schemaResponse = parseSchemaResponse(restAPIResponse.getResponseBody());
301302
List<ServiceNowColumn> columns = new ArrayList<>();
302303

303-
if (response.getResult() == null && response.getResult().isEmpty()) {
304+
if (schemaResponse.getResult() == null && schemaResponse.getResult().getColumns().isEmpty()) {
304305
throw new RuntimeException("Error - Schema Response does not contain any result");
305306
}
306-
for (ServiceNowSchemaField field : response.getResult()) {
307-
columns.add(new ServiceNowColumn(field.getName(), field.getInternalType()));
307+
308+
for (ServiceNowSchemaField field : schemaResponse.getResult().getColumns().values()) {
309+
if (valueType.equals(SourceValueType.SHOW_DISPLAY_VALUE) &&
310+
!Objects.equals(field.getType(), field.getInternalType())) {
311+
columns.add(new ServiceNowColumn(field.getName(), field.getType()));
312+
} else {
313+
columns.add(new ServiceNowColumn(field.getName(), field.getInternalType()));
314+
}
308315
}
309316
return SchemaBuilder.constructSchema(tableName, columns);
310317
}

src/main/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIRequestBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,14 @@ public class ServiceNowTableAPIRequestBuilder extends RestAPIRequest.Builder {
4343
*/
4444
private static final String SCHEMA_API_URL_TEMPLATE = "%s/api/now/doc/table/schema/%s";
4545

46+
/**
47+
* ServiceNow API URL to fetch column metadata
48+
*/
49+
private static final String METADATA_API_URL_TEMPLATE = "%s/api/now/ui/meta/%s";
50+
4651
public ServiceNowTableAPIRequestBuilder(String instanceBaseUrl, String tableName, boolean isSchemaRequired) {
4752
if (isSchemaRequired) {
48-
this.setUrl(String.format(SCHEMA_API_URL_TEMPLATE, instanceBaseUrl, tableName));
53+
this.setUrl(String.format(METADATA_API_URL_TEMPLATE, instanceBaseUrl, tableName));
4954
} else {
5055
this.setUrl(String.format(TABLE_API_URL_TEMPLATE, instanceBaseUrl, tableName));
5156
}

src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowConnector.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package io.cdap.plugin.servicenow.connector;
1717

1818
import com.google.gson.Gson;
19-
import com.google.gson.JsonObject;
2019
import io.cdap.cdap.api.annotation.Description;
2120
import io.cdap.cdap.api.annotation.Name;
2221
import io.cdap.cdap.api.annotation.Plugin;
@@ -206,8 +205,14 @@ private List<StructuredRecord> getTableData(String tableName, int limit)
206205
@Nullable
207206
private Schema getSchema(String tableName) {
208207
SourceQueryMode mode = SourceQueryMode.TABLE;
209-
List<ServiceNowTableInfo> tableInfo = ServiceNowInputFormat.fetchTableInfo(mode, config, tableName,
210-
null);
208+
// Use display type schema as connector shows a limited number of values
209+
// and display value type provides easy to read values
210+
List<ServiceNowTableInfo> tableInfo = ServiceNowInputFormat.fetchTableInfo(
211+
mode,
212+
config,
213+
tableName,
214+
null,
215+
SourceValueType.SHOW_DISPLAY_VALUE);
211216
Schema schema = tableInfo.stream().findFirst().isPresent() ? tableInfo.stream().findFirst().get().getSchema() :
212217
null;
213218
return schema;

src/main/java/io/cdap/plugin/servicenow/sink/model/SchemaResponse.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,18 @@
1616

1717
package io.cdap.plugin.servicenow.sink.model;
1818

19-
import java.util.List;
20-
2119
/**
2220
* Model class for Schema Response from Schema API
2321
*/
2422
public class SchemaResponse {
2523

26-
private final List<ServiceNowSchemaField> result;
24+
private final ServiceNowSchemaResult result;
2725

28-
public SchemaResponse(List<ServiceNowSchemaField> result) {
26+
public SchemaResponse(ServiceNowSchemaResult result) {
2927
this.result = result;
3028
}
3129

32-
public List<ServiceNowSchemaField> getResult() {
30+
public ServiceNowSchemaResult getResult() {
3331
return result;
3432
}
3533

src/main/java/io/cdap/plugin/servicenow/sink/model/ServiceNowSchemaField.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,38 @@
1616

1717
package io.cdap.plugin.servicenow.sink.model;
1818

19+
import com.google.gson.annotations.SerializedName;
20+
1921
/**
2022
* Model class for Schema Field from Schema API
2123
*/
2224
public class ServiceNowSchemaField {
2325
private final String label;
24-
private final String exampleValue;
26+
@SerializedName("internal_type")
2527
private final String internalType;
2628
private final String name;
29+
private final String type;
2730

28-
public ServiceNowSchemaField(String label, String exampleValue, String internalType, String name) {
31+
public ServiceNowSchemaField(String label, String internalType, String name, String type) {
2932
this.label = label;
30-
this.exampleValue = exampleValue;
3133
this.internalType = internalType;
3234
this.name = name;
35+
this.type = type;
3336
}
3437

3538
public String getLabel() {
3639
return label;
3740
}
3841

39-
public String getExampleValue() {
40-
return exampleValue;
41-
}
42-
4342
public String getInternalType() {
4443
return internalType;
4544
}
4645

4746
public String getName() {
4847
return name;
4948
}
49+
50+
public String getType() {
51+
return type;
52+
}
5053
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright © 2025 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* 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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package io.cdap.plugin.servicenow.sink.model;
18+
19+
import java.util.Map;
20+
21+
/**
22+
* Model class for Schema Result returned by the ServiceNow Column Schema API.
23+
*
24+
* <p>The {@code columns} map contains metadata for each column in the ServiceNow table.
25+
* The key of the map is the column's internal name (as used in the table schema),
26+
* and the value is a {@link ServiceNowSchemaField} object containing the details for that column.
27+
*
28+
* <p>Example JSON from ServiceNow:
29+
* <pre>
30+
* {
31+
* "result": {
32+
* "columns": {
33+
* "state": {
34+
* "label": "State",
35+
* "type": "string",
36+
* "internal_type": "integer",
37+
* "name": "state"
38+
* },
39+
* "active": {
40+
* "label": "Active",
41+
* "type": "boolean",
42+
* "internal_type": "boolean",
43+
* "name": "active"
44+
* }
45+
* }
46+
* }
47+
* }
48+
* </pre>
49+
*
50+
* In this example, the map will contain keys like {@code "state"} and {@code "active"},
51+
* each pointing to a {@code ServiceNowSchemaField} instance with metadata about that field.
52+
*/
53+
public class ServiceNowSchemaResult {
54+
private final Map<String, ServiceNowSchemaField> columns;
55+
56+
public ServiceNowSchemaResult(Map<String, ServiceNowSchemaField> columns) {
57+
this.columns = columns;
58+
}
59+
60+
public Map<String, ServiceNowSchemaField> getColumns() {
61+
return columns;
62+
}
63+
}

src/main/java/io/cdap/plugin/servicenow/source/ServiceNowInputFormat.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,17 @@
2121
import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException;
2222
import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl;
2323
import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig;
24-
import io.cdap.plugin.servicenow.util.ServiceNowConstants;
2524
import io.cdap.plugin.servicenow.util.ServiceNowTableInfo;
2625
import io.cdap.plugin.servicenow.util.SourceApplication;
2726
import io.cdap.plugin.servicenow.util.SourceQueryMode;
27+
import io.cdap.plugin.servicenow.util.SourceValueType;
2828
import org.apache.hadoop.conf.Configuration;
2929
import org.apache.hadoop.io.NullWritable;
3030
import org.apache.hadoop.mapreduce.InputFormat;
3131
import org.apache.hadoop.mapreduce.InputSplit;
3232
import org.apache.hadoop.mapreduce.JobContext;
3333
import org.apache.hadoop.mapreduce.RecordReader;
3434
import org.apache.hadoop.mapreduce.TaskAttemptContext;
35-
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
36-
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
3735
import org.slf4j.Logger;
3836
import org.slf4j.LoggerFactory;
3937

@@ -65,18 +63,19 @@ public static List<ServiceNowTableInfo> setInput(Configuration jobConfig, Source
6563
// Depending on conf value fetch the list of fields for each table and create schema object
6664
// return the schema object for each table as ServiceNowTableInfo
6765
List<ServiceNowTableInfo> tableInfos = fetchTableInfo(mode, conf.getConnection(), conf.getTableName(),
68-
conf.getApplicationName());
66+
conf.getApplicationName(), conf.getValueType());
6967
jobConf.setTableInfos(tableInfos);
7068

7169
return tableInfos;
7270
}
7371

7472
public static List<ServiceNowTableInfo> fetchTableInfo(SourceQueryMode mode, ServiceNowConnectorConfig conf,
7573
@Nullable String tableName,
76-
@Nullable SourceApplication application) {
74+
@Nullable SourceApplication application,
75+
@Nullable SourceValueType valueType) {
7776
// When mode = Table, fetch details from the table name provided in plugin config
7877
if (mode == SourceQueryMode.TABLE) {
79-
ServiceNowTableInfo tableInfo = getTableMetaData(tableName, conf);
78+
ServiceNowTableInfo tableInfo = getTableMetaData(tableName, conf, valueType);
8079
return (tableInfo == null) ? Collections.emptyList() : Collections.singletonList(tableInfo);
8180
}
8281

@@ -86,7 +85,7 @@ public static List<ServiceNowTableInfo> fetchTableInfo(SourceQueryMode mode, Ser
8685

8786
List<String> tableNames = application.getTableNames();
8887
for (String table : tableNames) {
89-
ServiceNowTableInfo tableInfo = getTableMetaData(table, conf);
88+
ServiceNowTableInfo tableInfo = getTableMetaData(table, conf, valueType);
9089
if (tableInfo == null) {
9190
continue;
9291
}
@@ -96,14 +95,16 @@ public static List<ServiceNowTableInfo> fetchTableInfo(SourceQueryMode mode, Ser
9695
return tableInfos;
9796
}
9897

99-
private static ServiceNowTableInfo getTableMetaData(String tableName, ServiceNowConnectorConfig conf) {
98+
private static ServiceNowTableInfo getTableMetaData(String tableName,
99+
ServiceNowConnectorConfig conf,
100+
SourceValueType valueType) {
100101
// Call API to fetch first record from the table
101102
ServiceNowTableAPIClientImpl restApi = new ServiceNowTableAPIClientImpl(conf);
102103

103104
Schema schema = null;
104105
int recordCount = 0;
105106
try {
106-
schema = restApi.fetchTableSchema(tableName);
107+
schema = restApi.fetchTableSchema(tableName, valueType);
107108
recordCount = restApi.getTableRecordCount(tableName);
108109
} catch (ServiceNowAPIException e) {
109110
throw new RuntimeException(String.format("Error in fetching table metadata due to reason: %s", e.getMessage()),

src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiInputFormat.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig;
2525
import io.cdap.plugin.servicenow.util.ServiceNowConstants;
2626
import io.cdap.plugin.servicenow.util.ServiceNowTableInfo;
27+
import io.cdap.plugin.servicenow.util.SourceValueType;
2728
import org.apache.hadoop.conf.Configuration;
2829
import org.apache.hadoop.io.NullWritable;
2930
import org.apache.hadoop.mapreduce.InputFormat;
@@ -96,7 +97,10 @@ private static ServiceNowTableInfo getTableMetaData(String tableName, ServiceNow
9697
Schema schema;
9798
int recordCount;
9899
try {
99-
schema = restApi.fetchTableSchema(tableName);
100+
// Use actual value type as connector config does not have an option to select value type
101+
// This is used for ServiceNowMultiSource and provides structure of the table and being dependent on
102+
// connector config, makes it a read only function
103+
schema = restApi.fetchTableSchema(tableName, SourceValueType.SHOW_ACTUAL_VALUE);
100104
recordCount = restApi.getTableRecordCount(tableName);
101105
} catch (ServiceNowAPIException e) {
102106
throw new RuntimeException(e);

src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ void fetchData() throws ServiceNowAPIException {
108108
private void fetchSchema(ServiceNowTableAPIClientImpl restApi) {
109109
// Fetch the schema
110110
try {
111-
Schema tempSchema = restApi.fetchTableSchema(tableName);
111+
Schema tempSchema = restApi.fetchTableSchema(tableName, multiSourcePluginConf.getValueType());
112112
tableFields = tempSchema.getFields();
113113
List<Schema.Field> schemaFields = new ArrayList<>(tableFields);
114114
schemaFields.add(Schema.Field.of(tableNameField, Schema.of(Schema.Type.STRING)));

src/main/java/io/cdap/plugin/servicenow/source/ServiceNowSource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ public void configurePipeline(PipelineConfigurer pipelineConfigurer) {
8282
List<ServiceNowTableInfo> tableInfo = ServiceNowInputFormat.fetchTableInfo(conf.getQueryMode(collector),
8383
conf.getConnection(),
8484
conf.getTableName(),
85-
conf.getApplicationName());
85+
conf.getApplicationName(),
86+
conf.getValueType());
8687
stageConfigurer.setOutputSchema(tableInfo.stream().findFirst().get().getSchema());
8788
} else if (conf.getQueryMode() == SourceQueryMode.REPORTING) {
8889
stageConfigurer.setOutputSchema(null);

0 commit comments

Comments
 (0)