Skip to content

Commit 10c3c08

Browse files
authored
Merge pull request #129 from cloudsufi/cherry-pick/GA_fixes
[🍒] [PLUGIN-1927] Map fields ending with `_stc` to string type [PLUGIN-1928] Set connect & read timeout to http client and retry ConnectTimeoutException & SocketException
2 parents 7db6971 + df06588 commit 10c3c08

File tree

6 files changed

+123
-5
lines changed

6 files changed

+123
-5
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import org.apache.http.HttpResponse;
66
import org.apache.http.HttpStatus;
7+
import org.apache.http.conn.ConnectTimeoutException;
78
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
89

10+
import java.net.SocketException;
911
import java.util.Arrays;
1012
import java.util.HashSet;
1113
import java.util.Set;
@@ -65,6 +67,8 @@ public boolean isErrorRetryable() {
6567
}
6668
Throwable t = this.getCause();
6769
return t instanceof OAuthSystemException
70+
|| t instanceof ConnectTimeoutException
71+
|| t instanceof SocketException
6872
|| (this.getMessage() != null
6973
&& this.getMessage().contains(ServiceNowConstants.MAXIMUM_EXECUTION_TIME_EXCEEDED))
7074
|| RETRYABLE_CODES.contains(getStatusCode());

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
import java.util.concurrent.TimeUnit;
6666
import javax.annotation.Nullable;
6767

68+
import static io.cdap.plugin.servicenow.util.ServiceNowConstants.STC_FIELD_SUFFIX;
69+
6870
/**
6971
* Implementation class for ServiceNow Table API.
7072
*/
@@ -393,9 +395,14 @@ private Schema prepareSchemaWithMetadataAPI(RestAPIResponse restAPIResponse, Lis
393395
}
394396

395397
for (MetadataAPISchemaField field : metadataAPISchemaResponse.getResult().getColumns().values()) {
396-
if (valueType.equals(SourceValueType.SHOW_DISPLAY_VALUE) &&
397-
!Objects.equals(field.getType(), field.getInternalType())) {
398-
columns.add(new ServiceNowColumn(field.getName(), field.getType()));
398+
if (valueType.equals(SourceValueType.SHOW_DISPLAY_VALUE)) {
399+
if (!Objects.equals(field.getType(), field.getInternalType())) {
400+
columns.add(new ServiceNowColumn(field.getName(), field.getType()));
401+
} else if (field.getName().endsWith(STC_FIELD_SUFFIX) && field.getType().equals("integer")) {
402+
columns.add(new ServiceNowColumn(field.getName(), Schema.Type.STRING.name()));
403+
} else {
404+
columns.add(new ServiceNowColumn(field.getName(), field.getInternalType()));
405+
}
399406
} else if (valueType.equals(SourceValueType.SHOW_ACTUAL_VALUE) &&
400407
GLIDE_TIME_DATATYPE.equalsIgnoreCase(field.getInternalType())) {
401408
columns.add(new ServiceNowColumn(field.getName(), GLIDE_DATE_TIME_DATATYPE));

src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIClient.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import com.github.rholder.retry.WaitStrategies;
2525
import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException;
2626
import io.cdap.plugin.servicenow.util.ServiceNowConstants;
27+
import org.apache.http.client.config.RequestConfig;
2728
import org.apache.http.client.methods.CloseableHttpResponse;
2829
import org.apache.http.client.methods.HttpGet;
2930
import org.apache.http.client.methods.HttpPost;
31+
import org.apache.http.conn.ConnectTimeoutException;
3032
import org.apache.http.impl.client.CloseableHttpClient;
3133
import org.apache.http.impl.client.HttpClientBuilder;
3234
import org.apache.oltu.oauth2.client.OAuthClient;
@@ -41,6 +43,8 @@
4143
import org.slf4j.LoggerFactory;
4244

4345
import java.io.IOException;
46+
import java.net.SocketException;
47+
import java.util.Collections;
4448
import java.util.concurrent.Callable;
4549
import java.util.concurrent.ExecutionException;
4650
import java.util.concurrent.TimeUnit;
@@ -50,6 +54,17 @@
5054
*/
5155
public abstract class RestAPIClient {
5256
private static final Logger LOG = LoggerFactory.getLogger(RestAPIClient.class);
57+
58+
/* Connect Timout in ms for establishing the conenction with the server */
59+
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 120000;
60+
61+
/* Read Timeout in ms for waiting for data after the connection is established */
62+
private static final int DEFAULT_READ_TIMEOUT_MS = 300000;
63+
64+
private static final RequestConfig requestConfig = RequestConfig.custom()
65+
.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MS)
66+
.setSocketTimeout(DEFAULT_READ_TIMEOUT_MS)
67+
.build();
5368

5469
/**
5570
* Executes the Rest API request and returns the response.
@@ -61,10 +76,13 @@ public RestAPIResponse executeGet(RestAPIRequest request) throws IOException {
6176
HttpGet httpGet = new HttpGet(request.getUrl());
6277
request.getHeaders().entrySet().forEach(e -> httpGet.addHeader(e.getKey(), e.getValue()));
6378

64-
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
79+
try (CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build()) {
6580
try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
6681
return RestAPIResponse.parse(httpResponse, request.getResponseHeaders());
6782
}
83+
} catch (ConnectTimeoutException | SocketException e) {
84+
ServiceNowAPIException exception = new ServiceNowAPIException(e, null);
85+
return new RestAPIResponse(Collections.emptyMap(), null, exception);
6886
}
6987
}
7088

@@ -136,7 +154,7 @@ public RestAPIResponse executePost(RestAPIRequest request) throws IOException {
136154
// We're retrying all transport exceptions while executing the HTTP POST method and the generic transport
137155
// exceptions in HttpClient are represented by the standard java.io.IOException class
138156
// https://hc.apache.org/httpclient-legacy/exception-handling.html
139-
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
157+
try (CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build()) {
140158
try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) {
141159
return RestAPIResponse.parse(httpResponse, request.getResponseHeaders());
142160
}

src/main/java/io/cdap/plugin/servicenow/util/ServiceNowConstants.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,9 @@ public interface ServiceNowConstants {
281281
* Default Precision supported by ServiceNow Rest API
282282
*/
283283
int DEFAULT_PRECISION = 20;
284+
285+
/**
286+
* Suffix for ServiceNow fields that store elapsed time in seconds (e.g., calendar_stc, business_stc).
287+
*/
288+
String STC_FIELD_SUFFIX = "_stc";
284289
}

src/test/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImplTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,41 @@ public void testFetchTableSchema_DisplayValueType() throws Exception {
187187
Assert.assertEquals(Schema.Type.STRING,
188188
schema.getField("user_name").getSchema().getUnionSchemas().get(0).getType());
189189
}
190+
191+
@Test
192+
public void testFetchTableSchema_StcFieldsWithDisplayValueType_ParseAsString() throws Exception {
193+
194+
ServiceNowConnectorConfig mockConfig = Mockito.mock(ServiceNowConnectorConfig.class);
195+
ServiceNowTableAPIClientImpl impl = new ServiceNowTableAPIClientImpl(mockConfig, true);
196+
ServiceNowTableAPIClientImpl implSpy = Mockito.spy(impl);
197+
String jsonResponse = "{\n" +
198+
" \"result\": {\n" +
199+
" \"columns\": {\n" +
200+
" \"calendar_stc\": {\n" +
201+
" \"label\": \"Resolve time\",\n" +
202+
" \"type\": \"integer\",\n" +
203+
" \"name\": \"calendar_stc\",\n" +
204+
" \"internal_type\": \"integer\"\n" +
205+
" },\n" +
206+
" \"business_stc\": {\n" +
207+
" \"label\": \"Business resolve time\",\n" +
208+
" \"type\": \"integer\",\n" +
209+
" \"name\": \"business_stc\",\n" +
210+
" \"internal_type\": \"integer\"\n" +
211+
" }\n" +
212+
" }\n" +
213+
" }\n" +
214+
"}";
215+
RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), jsonResponse, null);
216+
Mockito.doReturn(mockResponse).when(implSpy).executeGetWithRetries(Mockito.any());
217+
Schema schema = implSpy.fetchTableSchema("incident", "dummy-access-token",
218+
SourceValueType.SHOW_DISPLAY_VALUE, SchemaType.METADATA_API_BASED);
219+
Assert.assertNotNull(schema);
220+
Assert.assertEquals("record", schema.getDisplayName());
221+
Assert.assertEquals(2, schema.getFields().size());
222+
Assert.assertEquals(Schema.Type.STRING,
223+
schema.getField("business_stc").getSchema().getUnionSchemas().get(0).getType());
224+
Assert.assertEquals(Schema.Type.STRING,
225+
schema.getField("calendar_stc").getSchema().getUnionSchemas().get(0).getType());
226+
}
190227
}

src/test/java/io/cdap/plugin/servicenow/restapi/RestAPIClientTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.apache.http.HttpStatus;
77
import org.apache.http.StatusLine;
88
import org.apache.http.client.methods.CloseableHttpResponse;
9+
import org.apache.http.conn.ConnectTimeoutException;
910
import org.apache.http.impl.client.CloseableHttpClient;
1011
import org.apache.http.impl.client.HttpClientBuilder;
1112
import org.apache.http.message.BasicStatusLine;
@@ -19,6 +20,7 @@
1920
import org.powermock.modules.junit4.PowerMockRunner;
2021

2122
import java.io.IOException;
23+
import java.net.SocketException;
2224

2325
@RunWith(PowerMockRunner.class)
2426
@PrepareForTest({
@@ -102,4 +104,49 @@ public void testExecuteGet_StatusOk() throws IOException {
102104
ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true);
103105
client.executeGet(request);
104106
}
107+
108+
@Test
109+
public void testExecuteGet_throwConnectTimeoutException_markAsRetryable() throws IOException {
110+
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
111+
HttpClientBuilder httpClientBuilder = Mockito.mock(HttpClientBuilder.class);
112+
PowerMockito.mockStatic(HttpClientBuilder.class);
113+
PowerMockito.when(HttpClientBuilder.create()).thenReturn(httpClientBuilder);
114+
Mockito.when(httpClientBuilder.build()).thenReturn(httpClient);
115+
Mockito.when(httpClient.execute(Mockito.any()))
116+
.thenThrow(new ConnectTimeoutException("Connection timed out"));
117+
118+
ServiceNowTableAPIRequestBuilder builder = new ServiceNowTableAPIRequestBuilder("url");
119+
RestAPIRequest request = builder.build();
120+
121+
ServiceNowConnectorConfig config = Mockito.mock(ServiceNowConnectorConfig.class);
122+
ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true);
123+
RestAPIResponse actualResponse = client.executeGet(request);
124+
Assert.assertNotNull(actualResponse.getException());
125+
Assert.assertTrue(actualResponse.getException().isErrorRetryable());
126+
Throwable ex = actualResponse.getException().getCause();
127+
Assert.assertTrue("Expected ConnectTimeoutException or similar, got: " + ex,
128+
ex instanceof ConnectTimeoutException || ex.getMessage().toLowerCase().contains("Connection timed out"));
129+
}
130+
131+
@Test
132+
public void testExecuteGet_throwSocketException_markAsRetryable() throws IOException {
133+
CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class);
134+
HttpClientBuilder httpClientBuilder = Mockito.mock(HttpClientBuilder.class);
135+
PowerMockito.mockStatic(HttpClientBuilder.class);
136+
PowerMockito.when(HttpClientBuilder.create()).thenReturn(httpClientBuilder);
137+
Mockito.when(httpClientBuilder.build()).thenReturn(httpClient);
138+
Mockito.when(httpClient.execute(Mockito.any()))
139+
.thenThrow(new SocketException());
140+
141+
ServiceNowTableAPIRequestBuilder builder = new ServiceNowTableAPIRequestBuilder("url");
142+
RestAPIRequest request = builder.build();
143+
144+
ServiceNowConnectorConfig config = Mockito.mock(ServiceNowConnectorConfig.class);
145+
ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true);
146+
RestAPIResponse actualResponse = client.executeGet(request);
147+
Assert.assertNotNull(actualResponse.getException());
148+
Assert.assertTrue(actualResponse.getException().isErrorRetryable());
149+
Throwable ex = actualResponse.getException().getCause();
150+
Assert.assertTrue("Expected SocketException or similar, got: " + ex, ex instanceof SocketException);
151+
}
105152
}

0 commit comments

Comments
 (0)