Repository: sendgrid/sendgrid-java
Pull request #743 (2023-11-01 17:05:38)

feat: Add data residency for eu and global regions
Related ticket: [DII-1228](https://issues.corp.twilio.com/browse/DII-1228)
[Reference doc](https://docs.google.com/document/d/1gDPJA09zKMaBcObwQByA7H6HTOd3WL_J9rgmk3ap7GE/edit)
<!--
We appreciate the effort for this pull request but before that please make sure you read the contribution guidelines, then fill out the blanks below.

Please format the PR title appropriately based on the type of change:
  <type>[!]: <description>
Where <type> is one of: docs, chore, feat, fix, test, misc.
Add a '!' after the type for breaking changes (e.g. feat!: new breaking feature).

**All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.**

Please enter each Issue number you are resolving in your PR after one of the following words [Fixes, Closes, Resolves]. This will auto-link these issues and close them when this PR is merged!
e.g.
Fixes #1
Closes #2
-->

# Fixes #

A short description of what this PR does.

### Checklist
- [x] I acknowledge that all my contributions will be made under the project's license
- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar)
- [ ] I have read the [Contribution Guidelines](https://github.com/sendgrid/sendgrid-java/blob/main/CONTRIBUTING.md) and my PR follows them
- [ ] I have titled the PR appropriately
- [ ] I have updated my branch with the main branch
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added the necessary documentation about the functionality in the appropriate .md file
- [ ] I have added inline documentation to the code I modified

If you have questions, please file a [support ticket](https://support.sendgrid.com).


gladysmae08: I'd recommend adding some info in the README or Example Use cases, unless you think this is adequately documented in the Sendgrid API Documentation
shrutiburman: > I'd recommend adding some info in the README or Example Use cases, unless you think this is adequately documented in the Sendgrid API Documentation

I have added an example folder for now. The documentation change is planned but not in place yet. 

Patch:
diff --git a/examples/dataresidency/setregion.java b/examples/dataresidency/setregion.java
new file mode 100644
index 0000000..5dc4215
--- /dev/null
+++ b/examples/dataresidency/setregion.java
@@ -0,0 +1,77 @@
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.sendgrid.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+//////////////////////////////////////////////////////////////////
+// Set data residency to navigate to a region/edge.
+// Currently supported: "global", "eu"
+
+
+public class Example {
+  public static void main(String[] args) throws IOException {
+    try {
+
+      final Mail helloWorld = buildHelloEmail();
+
+      Request request = new Request();
+      request.setEndpoint("mail/send");
+      request.setBody(helloWorld.build());
+      request.setMethod(Method.POST);
+
+      // sending to global data residency
+      Sendgrid sg = buildSendgridObj("global");
+      Response response = sg.api(request);
+      System.out.println("Sending to hostname: " + sg.getHost());
+      System.out.println(response.getStatusCode());
+      System.out.println(response.getBody());
+      System.out.println(response.getHeaders());
+
+      // sending to EU data residency
+      Sendgrid sg = buildSendgridObj("eu");
+      Response response = sg.api(request);
+      System.out.println("Sending to hostname: " + sg.getHost());
+      System.out.println(response.getStatusCode());
+      System.out.println(response.getBody());
+      System.out.println(response.getHeaders());
+
+      // not configuring any region defaults to global
+      Sendgrid sg = new SendGrid(System.getenv("SENDGRID_API_KEY"));
+      Response response = sg.api(request);
+      System.out.println("Sending to hostname: " + sg.getHost());
+      System.out.println(response.getStatusCode());
+      System.out.println(response.getBody());
+      System.out.println(response.getHeaders());
+
+    } catch (IOException ex) {
+      throw ex;
+    }
+  }
+
+  public static Mail buildHelloEmail() {
+    Email from = new Email("test@example.com");
+    String subject = "Hello World from the Twilio SendGrid Java Library";
+    Email to = new Email("test@example.com");
+    Content content = new Content("text/plain", "some text here");
+    // Note that when you use this constructor an initial personalization object
+    // is created for you. It can be accessed via
+    // mail.personalization.get(0) as it is a List object
+    Mail mail = new Mail(from, subject, to, content);
+    Email email = new Email("test2@example.com");
+    mail.personalization.get(0).addTo(email);
+
+    return mail;
+  }
+
+  public static Sendgrid buildSendgridObj(String region){
+    SendGrid sg = new SendGrid(System.getenv("SENDGRID_API_KEY"));
+    sg.setDataResidency(region);
+    return sg;
+
+  }
+}
+
diff --git a/src/main/java/com/sendgrid/BaseInterface.java b/src/main/java/com/sendgrid/BaseInterface.java
index 0a812c1..bef1814 100644
--- a/src/main/java/com/sendgrid/BaseInterface.java
+++ b/src/main/java/com/sendgrid/BaseInterface.java
@@ -17,6 +17,13 @@ public abstract class BaseInterface implements SendGridAPI {
   private static final int RATE_LIMIT_RESPONSE_CODE = 429;
   private static final int THREAD_POOL_SIZE = 8;
 
+  private static final Map<String, String> allowedRegionsHostMap;
+  static {
+    //todo: change this to Map.of() when we've moved on from Java 8
+    allowedRegionsHostMap = new HashMap<>();
+    allowedRegionsHostMap.put("eu", "api.eu.sendgrid.com");
+    allowedRegionsHostMap.put("global", "api.sendgrid.com");
+  }
   private ExecutorService pool;
 
   /**
@@ -336,4 +343,22 @@ public abstract class BaseInterface implements SendGridAPI {
       }
     });
   }
+
+  /*
+  * Client libraries contain setters for specifying region/edge.
+  * This allows support global and eu regions only. This set will likely expand in the future.
+  * Global should be the default
+  * Global region means the message should be sent through:
+  * HTTP: api.sendgrid.com
+  * EU region means the message should be sent through:
+  * HTTP: api.eu.sendgrid.com
+*/
+  public void setDataResidency(String region){
+      if (allowedRegionsHostMap.containsKey(region)){
+        this.host = allowedRegionsHostMap.get(region);
+      }
+      else{
+        throw new IllegalArgumentException("region can only be \"eu\" or \"global\"");
+      }
+    }
 }


Test patch:
diff --git a/src/test/java/com/sendgrid/SendGridTest.java b/src/test/java/com/sendgrid/SendGridTest.java
index 2ac11fd..00f6679 100644
--- a/src/test/java/com/sendgrid/SendGridTest.java
+++ b/src/test/java/com/sendgrid/SendGridTest.java
@@ -3242,4 +3242,50 @@ public class SendGridTest {
     sg.api(request);
     verify(client).api(argThat((Request req) -> req.getHeaders().get("set-on-both").equals("456")));
   }
+
+  @Test
+  public void testSetResidency_happy_path_eu() {
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    sg.setDataResidency("eu");
+    Assert.assertEquals(sg.getHost(), "api.eu.sendgrid.com");
+  }
+  @Test
+  public void testSetResidency_happy_path_global() {
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    sg.setDataResidency("global");
+    Assert.assertEquals(sg.getHost(), "api.sendgrid.com");
+  }
+
+
+  @Test
+  public void testSetResidency_override_host() {
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    sg.setHost("api.new.com");
+    sg.setDataResidency("eu");
+    Assert.assertEquals(sg.getHost(), "api.eu.sendgrid.com");
+  }
+
+  @Test
+  public void testsetResidency_override_data_residency() {
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    sg.setDataResidency("eu");
+    sg.setHost("api.new.com");
+    Assert.assertEquals(sg.getHost(), "api.new.com");
+  }
+
+  @Test (expected = IllegalArgumentException.class)
+  public void testsetResidency_incorrect_region() {
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    sg.setDataResidency("foo");
+  }
+  @Test (expected = IllegalArgumentException.class)
+  public void testsetResidency_null_region(){
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    sg.setDataResidency("");
+  }
+  @Test
+  public void testsetResidency_default_region() {
+    SendGrid sg = new SendGrid(SENDGRID_API_KEY);
+    Assert.assertEquals(sg.getHost(), "api.sendgrid.com");
+  }
 }