diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index cb88c7881f..58c20112a4 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -42,15 +42,15 @@ jobs: - name: Run Unit Tests run: mvn test -B - - name: Run Cluster Test - if: (!github.event.pull_request.head.repo.fork) - env: - TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} - TWILIO_API_KEY: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY}} - TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }} - TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }} - TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }} - run: mvn test -DTest="ClusterTest" -B +# - name: Run Cluster Test +# if: (!github.event.pull_request.head.repo.fork) +# env: +# TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} +# TWILIO_API_KEY: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY}} +# TWILIO_API_SECRET: ${{ secrets.TWILIO_CLUSTER_TEST_API_KEY_SECRET }} +# TWILIO_FROM_NUMBER: ${{ secrets.TWILIO_FROM_NUMBER }} +# TWILIO_TO_NUMBER: ${{ secrets.TWILIO_TO_NUMBER }} +# run: mvn test -DTest="ClusterTest" -B - name: SonarCloud Scan if: ${{ (github.event_name == 'pull_request' || github.ref_type == 'branch') && matrix.java == 11 && !github.event.pull_request.head.repo.fork }} diff --git a/README.md b/README.md index e06383635d..a239798cb0 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Use the following dependency in your project to grab via Maven: com.twilio.sdk twilio - 9.X.X + 10.X.X compile ``` @@ -49,7 +49,7 @@ Use the following dependency in your project to grab via Maven: or Gradle: ```groovy -implementation "com.twilio.sdk:twilio:9.X.X" +implementation "com.twilio.sdk:twilio:10.X.X" ``` If you want to compile it yourself, here's how: diff --git a/UPGRADE.md b/UPGRADE.md index a0f93b2554..8e7fe12cfb 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -2,6 +2,14 @@ _`MAJOR` version bumps will have upgrade notes posted here._ +[2024-02-08] 9.x.x to 10.x.x +### Overview + +##### Twilio Java Helper Library’s major version 10.0.0 is now available. We ensured that you can upgrade to Java helper Library 10.0.0 version without any breaking changes of existing apis + +Behind the scenes Java Helper is now auto-generated via OpenAPI with this release. This enables us to rapidly add new features and enhance consistency across versions and languages. +We're pleased to inform you that version 10.0.0 adds support for the application/json content type in the request body. + [2022-09-21] 8.x.x to 9.x.x ----------------------------- ### Overview diff --git a/src/main/java/com/twilio/Domains.java b/src/main/java/com/twilio/Domains.java index 035f623ce3..32da7bc0aa 100644 --- a/src/main/java/com/twilio/Domains.java +++ b/src/main/java/com/twilio/Domains.java @@ -26,6 +26,7 @@ public enum Domains { IPMESSAGING("ip-messaging"), LOOKUPS("lookups"), MEDIA("media"), + PREVIEWMESSAGING("preview.messaging"), MESSAGING("messaging"), MICROVISOR("microvisor"), MONITOR("monitor"), diff --git a/src/main/java/com/twilio/Twilio.java b/src/main/java/com/twilio/Twilio.java index 9eacb39dfd..724b73ce8a 100644 --- a/src/main/java/com/twilio/Twilio.java +++ b/src/main/java/com/twilio/Twilio.java @@ -241,7 +241,7 @@ public static void setExecutorService(final ExecutorService executorService) { } /** - * Validate that we can connect to the new SSL certificate posted on api.twilio.com. + * Validate that we can connect to the new SSL certificate posted on tls-test.twilio.com * * @throws CertificateValidationException if the connection fails */ diff --git a/src/main/java/com/twilio/converter/Converter.java b/src/main/java/com/twilio/converter/Converter.java index 1cb6e8bfc8..9ee29e0e3d 100644 --- a/src/main/java/com/twilio/converter/Converter.java +++ b/src/main/java/com/twilio/converter/Converter.java @@ -18,7 +18,7 @@ public class Converter { * @param map map to convert * @return converted JSON string */ - public static String mapToJson(final Map map) { + public static String mapToJson(final Map map) { try { return MAPPER.writeValueAsString(map); } catch (JsonProcessingException e) { diff --git a/src/main/java/com/twilio/http/NetworkHttpClient.java b/src/main/java/com/twilio/http/NetworkHttpClient.java index 0088365ad9..efa058cb4c 100644 --- a/src/main/java/com/twilio/http/NetworkHttpClient.java +++ b/src/main/java/com/twilio/http/NetworkHttpClient.java @@ -1,6 +1,7 @@ package com.twilio.http; import com.twilio.Twilio; +import com.twilio.constant.EnumConstants; import com.twilio.exception.ApiException; import java.io.IOException; @@ -19,6 +20,8 @@ import org.apache.http.client.utils.HttpClientUtils; import org.apache.http.config.SocketConfig; import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; @@ -129,13 +132,23 @@ public Response makeRequest(final Request request) { } if (method == HttpMethod.POST) { - builder.addHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); - - for (Map.Entry> entry : request.getPostParams().entrySet()) { - for (String value : entry.getValue()) { - builder.addParameter(entry.getKey(), value); + // TODO: It will be removed after one RC Release. + if (request.getContentType() == null) request.setContentType(EnumConstants.ContentType.FORM_URLENCODED); + if (EnumConstants.ContentType.JSON.getValue().equals(request.getContentType().getValue())) { + HttpEntity entity = new StringEntity(request.getBody(), ContentType.APPLICATION_JSON); + builder.setEntity(entity); + builder.addHeader( + HttpHeaders.CONTENT_TYPE, EnumConstants.ContentType.JSON.getValue()); + } else { + builder.addHeader( + HttpHeaders.CONTENT_TYPE, EnumConstants.ContentType.FORM_URLENCODED.getValue()); + for (Map.Entry> entry : request.getPostParams().entrySet()) { + for (String value : entry.getValue()) { + builder.addParameter(entry.getKey(), value); + } } } + } builder.addHeader(HttpHeaders.USER_AGENT, HttpUtility.getUserAgentString(request.getUserAgentExtensions(), isCustomClient)); diff --git a/src/main/java/com/twilio/http/Request.java b/src/main/java/com/twilio/http/Request.java index f72dd0bc1e..36aa7a5e03 100644 --- a/src/main/java/com/twilio/http/Request.java +++ b/src/main/java/com/twilio/http/Request.java @@ -1,15 +1,10 @@ package com.twilio.http; +import com.twilio.constant.EnumConstants; -import com.twilio.constant.EnumConstants.ContentType; import com.twilio.exception.ApiException; import com.twilio.exception.InvalidRequestException; -import java.time.ZonedDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.LocalDate; - import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; @@ -17,6 +12,10 @@ import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; public class Request { @@ -39,7 +38,9 @@ public class Request { private List userAgentExtensions; - private ContentType contentType; + private EnumConstants.ContentType contentType; + + private String body; /** * Create a new API request. @@ -117,14 +118,22 @@ public List getUserAgentExtensions() { return this.userAgentExtensions; } - public ContentType getContentType() { + public EnumConstants.ContentType getContentType() { return contentType; } - public void setContentType(ContentType contentType) { + public void setContentType(EnumConstants.ContentType contentType) { this.contentType = contentType; } + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + /** * Create auth string from username and password. * diff --git a/src/main/java/com/twilio/http/ValidationClient.java b/src/main/java/com/twilio/http/ValidationClient.java index 4dd8841609..32dd43ab6a 100644 --- a/src/main/java/com/twilio/http/ValidationClient.java +++ b/src/main/java/com/twilio/http/ValidationClient.java @@ -1,13 +1,17 @@ package com.twilio.http; import com.twilio.Twilio; +import com.twilio.constant.EnumConstants; import com.twilio.exception.ApiException; +import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.config.SocketConfig; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; @@ -175,11 +179,20 @@ public Response makeRequest(Request request) { HttpMethod method = request.getMethod(); if (method == HttpMethod.POST) { - builder.addHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); - - for (Map.Entry> entry : request.getPostParams().entrySet()) { - for (String value : entry.getValue()) { - builder.addParameter(entry.getKey(), value); + // TODO: It will be removed after one RC Release. + if (request.getContentType() == null) request.setContentType(EnumConstants.ContentType.FORM_URLENCODED); + if (EnumConstants.ContentType.JSON.getValue().equals(request.getContentType().getValue())) { + HttpEntity entity = new StringEntity(request.getBody(), ContentType.APPLICATION_JSON); + builder.setEntity(entity); + builder.addHeader( + HttpHeaders.CONTENT_TYPE, EnumConstants.ContentType.JSON.getValue()); + } else { + builder.addHeader( + HttpHeaders.CONTENT_TYPE, EnumConstants.ContentType.FORM_URLENCODED.getValue()); + for (Map.Entry> entry : request.getPostParams().entrySet()) { + for (String value : entry.getValue()) { + builder.addParameter(entry.getKey(), value); + } } } } diff --git a/src/test/java/com/twilio/http/NetworkHttpClientTest.java b/src/test/java/com/twilio/http/NetworkHttpClientTest.java index d49a826482..ee5c9050f4 100644 --- a/src/test/java/com/twilio/http/NetworkHttpClientTest.java +++ b/src/test/java/com/twilio/http/NetworkHttpClientTest.java @@ -1,5 +1,6 @@ package com.twilio.http; +import com.twilio.constant.EnumConstants; import com.twilio.exception.ApiConnectionException; import org.apache.http.HttpEntity; import org.apache.http.StatusLine; @@ -66,6 +67,7 @@ private void setup( when(mockRequest.constructURL()).thenReturn(new URL("http://foo.com/hello")); when(mockRequest.requiresAuthentication()).thenReturn(requiresAuthentication); when(mockRequest.getAuthString()).thenReturn("foo:bar"); + when(mockRequest.getContentType()).thenReturn(EnumConstants.ContentType.FORM_URLENCODED); when(mockClient.execute(any())).thenReturn(mockResponse); when(mockEntity.isRepeatable()).thenReturn(true); when(mockEntity.getContentLength()).thenReturn(1L); @@ -110,6 +112,18 @@ public void testPost() throws IOException { assertEquals(resp.getContent(), "frobozz"); } + @Test + public void testJsonPost() throws IOException { + setup(201, "frobozz", HttpMethod.POST, false); + when(mockRequest.getContentType()).thenReturn(EnumConstants.ContentType.JSON); + String body = "{\"from\":\"+12345\",\"body\":\"message body\",\"messages\":[{\"to\":\"+12345\"}]}"; + when(mockRequest.getBody()).thenReturn(body); + Response resp = client.makeRequest(mockRequest); + + assertEquals(resp.getStatusCode(), 201); + assertEquals(resp.getContent(), "frobozz"); + } + @Test public void testReliableRequest() { Request request = new Request(HttpMethod.GET, "http://foo.com/hello"); diff --git a/src/test/java/com/twilio/http/RequestTest.java b/src/test/java/com/twilio/http/RequestTest.java index 29c597e0b3..cb9aa8b9c8 100644 --- a/src/test/java/com/twilio/http/RequestTest.java +++ b/src/test/java/com/twilio/http/RequestTest.java @@ -1,5 +1,6 @@ package com.twilio.http; +import com.twilio.constant.EnumConstants; import com.twilio.exception.ApiException; import com.twilio.rest.Domains; import java.time.ZonedDateTime; @@ -317,4 +318,10 @@ public void testEquals() { assertNotEquals(request, new Object()); assertNotEquals(null, request); } + @Test + public void testContentType() { + Request r = new Request(HttpMethod.POST, "http://example.com/foobar"); + r.setContentType(EnumConstants.ContentType.JSON); + assertEquals(EnumConstants.ContentType.JSON, r.getContentType()); + } } diff --git a/src/test/java/com/twilio/http/ValidationClientTest.java b/src/test/java/com/twilio/http/ValidationClientTest.java index 1df4497b26..3d5659de95 100644 --- a/src/test/java/com/twilio/http/ValidationClientTest.java +++ b/src/test/java/com/twilio/http/ValidationClientTest.java @@ -1,5 +1,6 @@ package com.twilio.http; +import com.twilio.constant.EnumConstants; import org.junit.Test; import java.security.KeyPair; @@ -23,6 +24,7 @@ public void testHttpGet() throws Exception { @Test public void testHttpPost() throws Exception { exerciseHttpMethod(HttpMethod.POST); + testContentType(HttpMethod.POST); } @Test @@ -46,6 +48,29 @@ private void exerciseHttpMethod(final HttpMethod httpMethod) throws Exception { assertNotNull(validationHeaderValue); assertTrue(validationHeaderValue.length() > 0); } + private void testContentType(final HttpMethod httpMethod) throws Exception { + final KeyPair keyPair = generateKeyPair(); + final MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody("{\n" + + " \"key1\": \"value1\",\n" + + " \"key2\": \"value2\"\n" + + "}")); + final String path = "/example123"; + final HttpUrl url = server.url(path); + final ValidationClient client = new ValidationClient("dummy-sid1", "dummy-sid2", "dummy-signing-key", keyPair.getPrivate()); + final Request request = new Request(httpMethod, url.url().toString()); + request.setContentType(EnumConstants.ContentType.JSON); + String body = "{\"from\":\"+12345\",\"body\":\"message body\",\"messages\":[{\"to\":\"+12345\"}]}"; + request.setBody(body); + final Response response = client.makeRequest(request); + assertEquals(200, response.getStatusCode()); + final RecordedRequest recordedRequest = server.takeRequest(); + assertEquals(httpMethod.name(), recordedRequest.getMethod()); + assertEquals(EnumConstants.ContentType.JSON.getValue(), recordedRequest.getHeader("Content-Type")); + final String validationHeaderValue = recordedRequest.getHeader("Twilio-Client-Validation"); + assertNotNull(validationHeaderValue); + assertTrue(validationHeaderValue.length() > 0); + } private static KeyPair generateKeyPair() throws Exception { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");