3434import io .cdap .plugin .servicenow .restapi .RestAPIResponse ;
3535import io .cdap .plugin .servicenow .sink .model .APIResponse ;
3636import io .cdap .plugin .servicenow .sink .model .CreateRecordAPIResponse ;
37+ import io .cdap .plugin .servicenow .sink .model .MetadataAPISchemaResponse ;
38+ import io .cdap .plugin .servicenow .sink .model .SchemaField ;
3739import io .cdap .plugin .servicenow .sink .model .SchemaResponse ;
3840import io .cdap .plugin .servicenow .sink .model .ServiceNowSchemaField ;
3941import io .cdap .plugin .servicenow .util .SchemaBuilder ;
42+ import io .cdap .plugin .servicenow .util .SchemaType ;
4043import io .cdap .plugin .servicenow .util .ServiceNowColumn ;
4144import io .cdap .plugin .servicenow .util .ServiceNowConstants ;
4245import io .cdap .plugin .servicenow .util .SourceValueType ;
@@ -72,10 +75,12 @@ public class ServiceNowTableAPIClientImpl extends RestAPIClient {
7275 private static final String GLIDE_DATE_TIME_DATATYPE = "glide_date_time" ;
7376 private static final Gson GSON = new Gson ();
7477 private final ServiceNowConnectorConfig conf ;
78+ public final Boolean useConnection ;
7579 public static JsonArray serviceNowJsonResultArray ;
7680
77- public ServiceNowTableAPIClientImpl (ServiceNowConnectorConfig conf ) {
81+ public ServiceNowTableAPIClientImpl (ServiceNowConnectorConfig conf , Boolean useConnection ) {
7882 this .conf = conf ;
83+ this .useConnection = useConnection ;
7984 }
8085
8186 public String getAccessToken () throws ServiceNowAPIException {
@@ -129,7 +134,7 @@ public List<Map<String, String>> fetchTableRecords(
129134 int limit )
130135 throws ServiceNowAPIException {
131136 ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder (
132- this .conf .getRestApiEndpoint (), tableName , false )
137+ this .conf .getRestApiEndpoint (), tableName , false , getSchemaTypeBasedOnUseConnection ( useConnection ) )
133138 .setExcludeReferenceLink (true )
134139 .setDisplayValue (valueType )
135140 .setLimit (limit );
@@ -178,7 +183,6 @@ private int getRecordCountFromHeader(RestAPIResponse apiResponse) {
178183
179184 public List <Map <String , String >> parseResponseToResultListOfMap (String responseBody ) {
180185
181-
182186 JsonObject jo = GSON .fromJson (responseBody , JsonObject .class );
183187 JsonArray ja = jo .getAsJsonArray (ServiceNowConstants .RESULT );
184188
@@ -257,7 +261,7 @@ public List<Map<String, String>> fetchTableRecordsRetryableMode(String tableName
257261 public Schema fetchTableSchema (String tableName , FailureCollector collector ) {
258262 Schema schema = null ;
259263 try {
260- schema = fetchTableSchema (tableName , SourceValueType .SHOW_ACTUAL_VALUE );
264+ schema = fetchTableSchema (tableName , SourceValueType .SHOW_ACTUAL_VALUE , useConnection );
261265 } catch (Exception e ) {
262266 LOG .error ("Failed to fetch schema on table {}" , tableName , e );
263267 collector .addFailure (String .format ("Connection failed. Unable to fetch schema for table: %s. Cause: %s" ,
@@ -267,20 +271,37 @@ public Schema fetchTableSchema(String tableName, FailureCollector collector) {
267271 }
268272
269273 @ VisibleForTesting
270- public SchemaResponse parseSchemaResponse (String responseBody ) {
274+ public MetadataAPISchemaResponse parseSchemaResponse (String responseBody ) {
275+ return GSON .fromJson (responseBody , MetadataAPISchemaResponse .class );
276+ }
277+
278+ public SchemaResponse parseSchemaResponseWithoutMetadata (String responseBody ) {
271279 return GSON .fromJson (responseBody , SchemaResponse .class );
272280 }
273281
274282 /**
275283 * Fetches the table schema from ServiceNow
276284 *
277285 * @param tableName ServiceNow table name for which schema is getting fetched
286+ * @param valueType Whether to fetch schema for actual value or display value
287+ * @param useConnection This flag is added to backward compatibility with 1.1 release,
288+ * in case of null for useConnection, schema will be string based.
278289 * @return schema for given ServiceNow table
279290 * @throws ServiceNowAPIException
280291 */
281- public Schema fetchTableSchema (String tableName , SourceValueType valueType )
292+ public Schema fetchTableSchema (String tableName , SourceValueType valueType , Boolean useConnection )
282293 throws ServiceNowAPIException {
283- return fetchTableSchema (tableName , getAccessToken (), valueType );
294+ SchemaType schemaType = getSchemaTypeBasedOnUseConnection (useConnection );
295+ return fetchTableSchema (tableName , getAccessToken (), valueType , schemaType );
296+ }
297+
298+ private SchemaType getSchemaTypeBasedOnUseConnection (Boolean useConnection ) {
299+ // use connection was added in release/1.2, so it will be null for users who are upgrading from release/1.1
300+ // This is added to support backward compatibility for 1.1 users.
301+ if (useConnection == null ) {
302+ return SchemaType .STRING_BASED ;
303+ }
304+ return SchemaType .SCHEMA_API_BASED ;
284305 }
285306
286307 /**
@@ -289,25 +310,88 @@ public Schema fetchTableSchema(String tableName, SourceValueType valueType)
289310 * @param tableName ServiceNow table name for which schema is getting fetched
290311 * @param accessToken Access Token to use
291312 * @param valueType Type of value (Actual/Display)
313+ * @param schemaType Enum to determine which approach to take to fetch schema.
292314 * @return schema for given ServiceNow table
293315 */
294- public Schema fetchTableSchema (String tableName , String accessToken , SourceValueType valueType )
316+ public Schema fetchTableSchema (String tableName , String accessToken , SourceValueType valueType ,
317+ SchemaType schemaType )
295318 throws ServiceNowAPIException {
296319 ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder (
297- this .conf .getRestApiEndpoint (), tableName , true )
320+ this .conf .getRestApiEndpoint (), tableName , true , schemaType )
298321 .setExcludeReferenceLink (true );
299322
300323 RestAPIResponse restAPIResponse ;
301324 requestBuilder .setAuthHeader (accessToken );
302325 restAPIResponse = executeGetWithRetries (requestBuilder .build ());
303- SchemaResponse schemaResponse = parseSchemaResponse (restAPIResponse .getResponseBody ());
304326 List <ServiceNowColumn > columns = new ArrayList <>();
305327
306- if (schemaResponse .getResult () == null && schemaResponse .getResult ().getColumns ().isEmpty ()) {
328+ if (schemaType == SchemaType .METADATA_API_BASED ) {
329+ return prepareSchemaWithMetadataAPI (restAPIResponse , columns , tableName , valueType );
330+ } else if (schemaType == SchemaType .SCHEMA_API_BASED ) {
331+ return prepareSchemaWithSchemaAPI (restAPIResponse , columns , tableName );
332+ } else {
333+ return prepareStringBasedSchema (restAPIResponse , columns , tableName );
334+ }
335+ }
336+
337+ /**
338+ * Processes a schema response obtained from the ServiceNow Table API (without using Metadata API)
339+ * and constructs a {@link Schema} object based on the parsed column definitions.
340+ *
341+ * <p>This method parses the raw JSON response body into a list of schema fields,
342+ * extracts the internal column types, and appends them to the provided column list.
343+ * The final schema is constructed using the {@link SchemaBuilder} utility.</p>
344+ *
345+ * @param restAPIResponse The raw API response received from the ServiceNow Table API.
346+ * @param columns A list to which parsed {@link ServiceNowColumn} objects will be added.
347+ * @param tableName The name of the table for which the schema is being constructed.
348+ *
349+ * @return A {@link Schema} object representing the table structure as interpreted from the Schema API.
350+ *
351+ * @throws RuntimeException if the schema response is null or contains no result.
352+ */
353+ private Schema prepareSchemaWithSchemaAPI (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
354+ String tableName ) {
355+ SchemaResponse schemaResponse =
356+ parseSchemaResponseWithoutMetadata (restAPIResponse .getResponseBody ());
357+
358+ if (schemaResponse .getResult () == null && schemaResponse .getResult ().isEmpty ()) {
307359 throw new RuntimeException ("Error - Schema Response does not contain any result" );
308360 }
309361
310- for (ServiceNowSchemaField field : schemaResponse .getResult ().getColumns ().values ()) {
362+ for (SchemaField field : schemaResponse .getResult ()) {
363+ columns .add (new ServiceNowColumn (field .getName (), field .getInternalType ()));
364+ }
365+ return SchemaBuilder .constructSchema (tableName , columns );
366+ }
367+
368+ /**
369+ * Parses a ServiceNow schema response obtained via the Metadata API and constructs a
370+ * {@link Schema} object using the extracted field information and value type preferences.
371+ *
372+ * <p>This method reads the JSON response, extracts the column metadata including field names
373+ * and data types, and adds each as a {@link ServiceNowColumn} to the provided list. The choice
374+ * between internal values and display values is based on the {@code valueType} parameter.</p>
375+ *
376+ * @param restAPIResponse The response returned from the ServiceNow Metadata API.
377+ * @param columns A list to which parsed {@link ServiceNowColumn} definitions will be added.
378+ * @param tableName The name of the ServiceNow table for which the schema is being generated.
379+ * @param valueType The value type preference (e.g., {@code SHOW_DISPLAY_VALUE} or {@code USE_INTERNAL_VALUE}).
380+ * Determines whether to use display types or internal types in the resulting schema.
381+ *
382+ * @return A {@link Schema} object representing the table structure as interpreted from the Metadata API.
383+ *
384+ * @throws RuntimeException if the response does not contain valid column information.
385+ */
386+ private Schema prepareSchemaWithMetadataAPI (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
387+ String tableName , SourceValueType valueType ) {
388+ MetadataAPISchemaResponse metadataAPISchemaResponse = parseSchemaResponse (restAPIResponse .getResponseBody ());
389+
390+ if (metadataAPISchemaResponse .getResult () == null && metadataAPISchemaResponse .getResult ().getColumns ().isEmpty ()) {
391+ throw new RuntimeException ("Error - Schema Response does not contain any result" );
392+ }
393+
394+ for (ServiceNowSchemaField field : metadataAPISchemaResponse .getResult ().getColumns ().values ()) {
311395 if (valueType .equals (SourceValueType .SHOW_DISPLAY_VALUE ) &&
312396 !Objects .equals (field .getType (), field .getInternalType ())) {
313397 columns .add (new ServiceNowColumn (field .getName (), field .getType ()));
@@ -343,7 +427,7 @@ public int getTableRecordCount(String tableName)
343427 */
344428 public int getTableRecordCount (String tableName , String accessToken ) throws ServiceNowAPIException {
345429 ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder (
346- this .conf .getRestApiEndpoint (), tableName , false )
430+ this .conf .getRestApiEndpoint (), tableName , false , getSchemaTypeBasedOnUseConnection ( useConnection ) )
347431 .setExcludeReferenceLink (true )
348432 .setDisplayValue (SourceValueType .SHOW_DISPLAY_VALUE )
349433 .setLimit (1 );
@@ -363,7 +447,7 @@ public int getTableRecordCount(String tableName, String accessToken) throws Serv
363447 */
364448 public String createRecord (String tableName , HttpEntity entity ) throws IOException , ServiceNowAPIException {
365449 ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder (
366- this .conf .getRestApiEndpoint (), tableName , false );
450+ this .conf .getRestApiEndpoint (), tableName , false , getSchemaTypeBasedOnUseConnection ( useConnection ) );
367451 String systemID ;
368452 RestAPIResponse apiResponse = null ;
369453 try {
@@ -398,7 +482,7 @@ public Map<String, String> getRecordFromServiceNowTable(String tableName, String
398482 throws ServiceNowAPIException {
399483
400484 ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder (
401- this .conf .getRestApiEndpoint (), tableName , false )
485+ this .conf .getRestApiEndpoint (), tableName , false , getSchemaTypeBasedOnUseConnection ( useConnection ) )
402486 .setQuery (query );
403487
404488 RestAPIResponse restAPIResponse ;
@@ -409,4 +493,29 @@ public Map<String, String> getRecordFromServiceNowTable(String tableName, String
409493 APIResponse apiResponse = GSON .fromJson (restAPIResponse .getResponseBody (), APIResponse .class );
410494 return apiResponse .getResult ().get (0 );
411495 }
496+
497+ /**
498+ * Processes the response obtained from the ServiceNow Table API
499+ * and constructs a {@link Schema} object based on the first record.
500+ *
501+ * @param restAPIResponse The raw API response received from the ServiceNow Table API.
502+ * @param columns A list to which parsed {@link ServiceNowColumn} objects will be added.
503+ * @param tableName The name of the table for which the schema is being constructed.
504+ *
505+ * @return A {@link Schema} object representing the table structure as interpreted from the Schema API.
506+ *
507+ * @throws RuntimeException if the schema response is null or contains no result.
508+ */
509+ private Schema prepareStringBasedSchema (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
510+ String tableName ) {
511+ List <Map <String , String >> result = parseResponseToResultListOfMap (restAPIResponse .getResponseBody ());
512+ if (result != null && !result .isEmpty ()) {
513+ Map <String , String > firstRecord = result .get (0 );
514+ for (String key : firstRecord .keySet ()) {
515+ columns .add (new ServiceNowColumn (key , "string" ));
516+ }
517+ }
518+ return SchemaBuilder .constructSchema (tableName , columns );
519+ }
520+
412521}
0 commit comments