2727import com .google .gson .JsonArray ;
2828import com .google .gson .JsonObject ;
2929import com .google .gson .reflect .TypeToken ;
30- import com .sun . org . apache . xpath . internal . operations . Bool ;
30+ import com .google . gson . stream . JsonReader ;
3131import io .cdap .cdap .api .data .schema .Schema ;
3232import io .cdap .cdap .etl .api .FailureCollector ;
3333import io .cdap .plugin .servicenow .connector .ServiceNowConnectorConfig ;
5656import org .slf4j .LoggerFactory ;
5757
5858import java .io .IOException ;
59+ import java .io .InputStream ;
60+ import java .io .InputStreamReader ;
5961import java .lang .reflect .Type ;
62+ import java .nio .charset .StandardCharsets ;
6063import java .util .ArrayList ;
6164import java .util .List ;
6265import java .util .Map ;
@@ -86,6 +89,7 @@ public class ServiceNowTableAPIClientImpl extends RestAPIClient {
8689 public static JsonArray serviceNowJsonResultArray ;
8790
8891 public ServiceNowTableAPIClientImpl (ServiceNowConnectorConfig conf , Boolean useConnection ) {
92+ super ();
8993 this .conf = conf ;
9094 this .schemaType = getSchemaTypeBasedOnUseConnection (useConnection );
9195 }
@@ -135,7 +139,7 @@ public String getAccessTokenRetryableMode() throws ExecutionException, RetryExce
135139 * @return A list of maps, where each map represents a record from the table.
136140 * @throws ServiceNowAPIException If an error occurs while fetching the records.
137141 */
138- public List < Map < String , String >> fetchTableRecords (
142+ public RestAPIResponse fetchTableRecords (
139143 String tableName ,
140144 SourceValueType valueType ,
141145 String filterQuery ,
@@ -159,7 +163,7 @@ public List<Map<String, String>> fetchTableRecords(
159163 String accessToken = getAccessToken ();
160164 requestBuilder .setAuthHeader (accessToken );
161165 RestAPIResponse apiResponse = executeGetWithRetries (requestBuilder .build ());
162- return parseResponseToResultListOfMap ( apiResponse . getResponseBody ()) ;
166+ return apiResponse ;
163167 }
164168
165169 private int getRecordCountFromHeader (RestAPIResponse apiResponse ) {
@@ -209,30 +213,24 @@ private String getErrorMessage(String responseBody) {
209213 * @param limit The number of records to be fetched
210214 * @return The list of Map; each Map representing a table row
211215 */
212- public List < Map < String , String >> fetchTableRecordsRetryableMode (String tableName , SourceValueType valueType ,
216+ public RestAPIResponse fetchTableRecordsRetryableMode (String tableName , SourceValueType valueType ,
213217 String filterQuery , int offset ,
214218 int limit ) throws ServiceNowAPIException {
215- final List <Map <String , String >> results = new ArrayList <>();
216- Callable <Boolean > fetchRecords = () -> {
217- results .addAll (fetchTableRecords (tableName , valueType , filterQuery , offset , limit ));
218- return true ;
219- };
219+ Callable <RestAPIResponse > fetchRecords = () -> fetchTableRecords (tableName , valueType , filterQuery , offset , limit );
220220
221- Retryer <Boolean > retryer = RetryerBuilder .<Boolean >newBuilder ()
221+ Retryer <RestAPIResponse > retryer = RetryerBuilder .<RestAPIResponse >newBuilder ()
222222 .retryIfException (this ::isExceptionRetryable )
223223 .withWaitStrategy (WaitStrategies .exponentialWait (ServiceNowConstants .WAIT_TIME , TimeUnit .MILLISECONDS ))
224224 .withStopStrategy (StopStrategies .stopAfterAttempt (ServiceNowConstants .MAX_NUMBER_OF_RETRY_ATTEMPTS ))
225225 .build ();
226226
227227 try {
228- retryer .call (fetchRecords );
228+ return retryer .call (fetchRecords );
229229 } catch (RetryException | ExecutionException e ) {
230230 throw new ServiceNowAPIException (
231231 String .format ("Data Recovery failed for batch %s to %s." , offset , (offset + limit )),
232232 e , null , false );
233233 }
234-
235- return results ;
236234 }
237235
238236 /**
@@ -255,8 +253,8 @@ public Schema fetchTableSchema(String tableName, FailureCollector collector) {
255253 }
256254
257255 @ VisibleForTesting
258- public MetadataAPISchemaResponse parseSchemaResponse (String responseBody ) {
259- return GSON .fromJson (responseBody , MetadataAPISchemaResponse .class );
256+ public MetadataAPISchemaResponse parseSchemaResponse (InputStream responseStream ) {
257+ return GSON .fromJson (createJsonReader ( responseStream ) , MetadataAPISchemaResponse .class );
260258 }
261259
262260 /**
@@ -331,7 +329,7 @@ public Schema fetchTableSchema(String tableName, String accessToken, SourceValue
331329 private Schema prepareSchemaWithSchemaAPI (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
332330 String tableName ) throws ServiceNowAPIException {
333331 SchemaAPISchemaResponse schemaAPISchemaResponse =
334- GSON .fromJson (restAPIResponse .getResponseBody ( ), SchemaAPISchemaResponse .class );
332+ GSON .fromJson (createJsonReader ( restAPIResponse .getResponseStream () ), SchemaAPISchemaResponse .class );
335333
336334 if (schemaAPISchemaResponse .getResult () == null || schemaAPISchemaResponse .getResult ().isEmpty ()) {
337335 throw new ServiceNowAPIException (
@@ -364,7 +362,7 @@ private Schema prepareSchemaWithSchemaAPI(RestAPIResponse restAPIResponse, List<
364362 */
365363 private Schema prepareSchemaWithMetadataAPI (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
366364 String tableName , SourceValueType valueType , Boolean legacyMapping ) throws ServiceNowAPIException {
367- MetadataAPISchemaResponse metadataAPISchemaResponse = parseSchemaResponse (restAPIResponse .getResponseBody ());
365+ MetadataAPISchemaResponse metadataAPISchemaResponse = parseSchemaResponse (restAPIResponse .getResponseStream ());
368366
369367 if (metadataAPISchemaResponse .getResult () == null || metadataAPISchemaResponse .getResult ().getColumns () == null ||
370368 metadataAPISchemaResponse .getResult ().getColumns ().isEmpty ()) {
@@ -391,6 +389,11 @@ private Schema prepareSchemaWithMetadataAPI(RestAPIResponse restAPIResponse, Lis
391389 return SchemaBuilder .constructSchema (tableName , columns , legacyMapping );
392390 }
393391
392+ public JsonReader createJsonReader (InputStream inputStream ) {
393+ Objects .requireNonNull (inputStream , "InputStream must not be null" );
394+ return new JsonReader (new InputStreamReader (inputStream , StandardCharsets .UTF_8 ));
395+ }
396+
394397 /**
395398 * Gets the total number of records in the table based on the provided date range.
396399 *
@@ -451,8 +454,8 @@ public String createRecord(String tableName, HttpEntity entity) throws IOExcepti
451454 requestBuilder .setContentTypeHeader ("application/json" );
452455 requestBuilder .setEntity (entity );
453456 apiResponse = executePost (requestBuilder .build ());
454-
455457 systemID = String .valueOf (getSystemId (apiResponse ));
458+ apiResponse .close ();
456459 } catch (IOException e ) {
457460 throw new ServiceNowAPIException ("Error in creating a new record" , e , null , false );
458461 }
@@ -482,15 +485,17 @@ public String createRecordInDisplayMode(String tableName, HttpEntity entity) thr
482485 apiResponse = executePost (requestBuilder .build ());
483486
484487 systemID = String .valueOf (getSystemId (apiResponse ));
488+ apiResponse .close ();
485489 } catch (IOException e ) {
486490 throw new ServiceNowAPIException ("Error in creating a new record" , e , null , false );
487491 }
488492 return systemID ;
489493 }
490494
491495 private String getSystemId (RestAPIResponse restAPIResponse ) {
492- CreateRecordAPIResponse apiResponse = GSON .fromJson (restAPIResponse .getResponseBody (),
493- CreateRecordAPIResponse .class );
496+ CreateRecordAPIResponse apiResponse = GSON .fromJson (
497+ new InputStreamReader (restAPIResponse .getResponseStream (), StandardCharsets .UTF_8 ),
498+ CreateRecordAPIResponse .class );
494499 return apiResponse .getResult ().get (ServiceNowConstants .SYSTEM_ID ).toString ();
495500 }
496501
@@ -501,7 +506,7 @@ private String getSystemId(RestAPIResponse restAPIResponse) {
501506 * @param tableName The ServiceNow table name
502507 * @param query The query
503508 */
504- public Map < String , String > getRecordFromServiceNowTable (String tableName , String query )
509+ public JsonObject getRecordFromServiceNowTable (String tableName , String query )
505510 throws ServiceNowAPIException {
506511
507512 ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder (
@@ -513,7 +518,8 @@ public Map<String, String> getRecordFromServiceNowTable(String tableName, String
513518 requestBuilder .setAuthHeader (accessToken );
514519 restAPIResponse = executeGetWithRetries (requestBuilder .build ());
515520
516- APIResponse apiResponse = GSON .fromJson (restAPIResponse .getResponseBody (), APIResponse .class );
521+ APIResponse apiResponse = GSON .fromJson (
522+ new InputStreamReader (restAPIResponse .getResponseStream (), StandardCharsets .UTF_8 ), APIResponse .class );
517523 return apiResponse .getResult ().get (0 );
518524 }
519525
@@ -530,19 +536,45 @@ public Map<String, String> getRecordFromServiceNowTable(String tableName, String
530536 *
531537 * @throws RuntimeException if the schema response is null or contains no result.
532538 */
533- private Schema prepareStringBasedSchema (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
539+ private Schema prepareStringBasedSchema (RestAPIResponse restAPIResponse , List <ServiceNowColumn > columns ,
534540 String tableName ) {
535- List <Map <String , String >> result = parseResponseToResultListOfMap (restAPIResponse .getResponseBody ());
536- if (result != null && !result .isEmpty ()) {
537- Map <String , String > firstRecord = result .get (0 );
538- for (String key : firstRecord .keySet ()) {
539- columns .add (new ServiceNowColumn (key , "string" ));
541+ InputStream in = restAPIResponse .getResponseStream ();
542+ JsonReader reader = new JsonReader (new InputStreamReader (in , StandardCharsets .UTF_8 ));
543+ JsonObject firstRecord ;
544+ try {
545+ firstRecord = getFirstRecord (reader );
546+ restAPIResponse .close ();
547+ } catch (IOException e ) {
548+ throw new RuntimeException ("Failed to parse schema for table: " + tableName , e );
540549 }
550+ if (firstRecord != null ) {
551+ firstRecord .entrySet ().forEach (entry ->
552+ columns .add (new ServiceNowColumn (entry .getKey (), "string" ))
553+ );
541554 return SchemaBuilder .constructSchema (tableName , columns , false );
542555 }
543556 return null ;
544557 }
545558
559+ public JsonObject getFirstRecord (JsonReader reader ) throws IOException {
560+ reader .beginObject ();
561+ while (reader .hasNext ()) {
562+ String name = reader .nextName ();
563+ if (name .equals (ServiceNowConstants .RESULT )) {
564+ reader .beginArray ();
565+ if (reader .hasNext ()) {
566+ // ONLY parse the first object into memory
567+ return GSON .fromJson (reader , JsonObject .class );
568+ }
569+ reader .endArray ();
570+ } else {
571+ reader .skipValue ();
572+ }
573+ }
574+ reader .endObject ();
575+ return null ;
576+ }
577+
546578
547579 /**
548580 * This function is being used in end-to-end (e2e) tests to delete a record from
0 commit comments