View Javadoc
1   package fr.tiogars.template.client;
2   
3   import com.fasterxml.jackson.core.type.TypeReference;
4   import com.fasterxml.jackson.databind.ObjectMapper;
5   import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
6   
7   import java.io.IOException;
8   import java.net.URI;
9   import java.net.http.HttpClient;
10  import java.net.http.HttpRequest;
11  import java.net.http.HttpResponse;
12  import java.util.List;
13  
14  /**
15   * Provides HTTP access to the DomainTemplate API.
16   *
17   * @since 0.1.0
18   */
19  public class DomainTemplateClient {
20  
21      private static final String API_PATH = "/api/v1/domaintemplate";
22      private static final String APPLICATION_JSON = "application/json";
23      private static final String TEXT_CSV = "text/csv";
24      private static final TypeReference<List<DomainTemplate>> TEMPLATE_LIST = new TypeReference<>() {
25      };
26  
27      private final URI baseUri;
28      private final HttpClient httpClient;
29      private final ObjectMapper objectMapper;
30  
31      /**
32       * Creates a client backed by the default Java HTTP client.
33       *
34       * @param baseUri base URI of the server
35       */
36      public DomainTemplateClient(URI baseUri) {
37          this(baseUri, HttpClient.newHttpClient(), new ObjectMapper().registerModule(new JavaTimeModule()));
38      }
39  
40      DomainTemplateClient(URI baseUri, HttpClient httpClient, ObjectMapper objectMapper) {
41          this.baseUri = baseUri;
42          this.httpClient = httpClient;
43          this.objectMapper = objectMapper;
44      }
45  
46      /**
47       * Creates a DomainTemplate.
48       *
49       * @param request template data to create
50       * @return template created by the server
51       * @throws IOException if request serialization or response deserialization fails
52       * @throws InterruptedException if the thread is interrupted while awaiting the response
53       * @throws DomainTemplateException if the server returns an unexpected HTTP status
54       */
55      public DomainTemplate create(CreateDomainTemplateRequest request) throws IOException, InterruptedException {
56          return sendJson(jsonRequest(uri(""), "POST", request), DomainTemplate.class, 201);
57      }
58  
59      /**
60       * Lists all DomainTemplates.
61       *
62       * @return templates returned by the server
63       * @throws IOException if response deserialization fails
64       * @throws InterruptedException if the thread is interrupted while awaiting the response
65       * @throws DomainTemplateException if the server returns an unexpected HTTP status
66       */
67      public List<DomainTemplate> findAll() throws IOException, InterruptedException {
68          return sendJson(getRequest(uri(""), APPLICATION_JSON), TEMPLATE_LIST, 200);
69      }
70  
71      /**
72       * Finds a DomainTemplate by identifier.
73       *
74       * @param id persistent template identifier
75       * @return matching template
76       * @throws IOException if response deserialization fails
77       * @throws InterruptedException if the thread is interrupted while awaiting the response
78       * @throws DomainTemplateException if the server returns an unexpected HTTP status
79       */
80      public DomainTemplate findById(long id) throws IOException, InterruptedException {
81          return sendJson(getRequest(uri("/" + id), APPLICATION_JSON), DomainTemplate.class, 200);
82      }
83  
84      /**
85       * Updates a DomainTemplate.
86       *
87       * @param id persistent template identifier
88       * @param request replacement label and description
89       * @return template updated by the server
90       * @throws IOException if request serialization or response deserialization fails
91       * @throws InterruptedException if the thread is interrupted while awaiting the response
92       * @throws DomainTemplateException if the server returns an unexpected HTTP status
93       */
94      public DomainTemplate update(long id, UpdateDomainTemplateRequest request) throws IOException, InterruptedException {
95          return sendJson(jsonRequest(uri("/" + id), "PUT", request), DomainTemplate.class, 200);
96      }
97  
98      /**
99       * Deletes a DomainTemplate.
100      *
101      * @param id persistent template identifier
102      * @throws IOException if the request fails
103      * @throws InterruptedException if the thread is interrupted while awaiting the response
104      * @throws DomainTemplateException if the server returns an unexpected HTTP status
105      */
106     public void delete(long id) throws IOException, InterruptedException {
107         HttpRequest request = HttpRequest.newBuilder(uri("/" + id)).DELETE().build();
108         send(request, 204);
109     }
110 
111     /**
112      * Seeds DomainTemplates from structured requests.
113      *
114      * @param requests templates to seed
115      * @return templates stored by the server
116      * @throws IOException if request serialization or response deserialization fails
117      * @throws InterruptedException if the thread is interrupted while awaiting the response
118      * @throws DomainTemplateException if the server returns an unexpected HTTP status
119      */
120     public List<DomainTemplate> seed(List<CreateDomainTemplateRequest> requests) throws IOException, InterruptedException {
121         return sendJson(jsonRequest(uri("/seed"), "POST", requests), TEMPLATE_LIST, 201);
122     }
123 
124     /**
125      * Imports DomainTemplates from JSON-compatible requests.
126      *
127      * @param requests templates to import
128      * @return templates imported by the server
129      * @throws IOException if request serialization or response deserialization fails
130      * @throws InterruptedException if the thread is interrupted while awaiting the response
131      * @throws DomainTemplateException if the server returns an unexpected HTTP status
132      */
133     public List<DomainTemplate> importJson(List<CreateDomainTemplateRequest> requests)
134             throws IOException, InterruptedException {
135         return sendJson(jsonRequest(uri("/import.json"), "POST", requests), TEMPLATE_LIST, 201);
136     }
137 
138     /**
139      * Imports DomainTemplates from a CSV document.
140      *
141      * @param csv CSV document to import
142      * @return templates imported by the server
143      * @throws IOException if the request or response deserialization fails
144      * @throws InterruptedException if the thread is interrupted while awaiting the response
145      * @throws DomainTemplateException if the server returns an unexpected HTTP status
146      */
147     public List<DomainTemplate> importCsv(String csv) throws IOException, InterruptedException {
148         HttpRequest request = HttpRequest.newBuilder(uri("/import.csv"))
149                 .header("Content-Type", TEXT_CSV)
150                 .header("Accept", APPLICATION_JSON)
151                 .POST(HttpRequest.BodyPublishers.ofString(csv))
152                 .build();
153         return sendJson(request, TEMPLATE_LIST, 201);
154     }
155 
156     /**
157      * Exports all DomainTemplates as JSON-compatible objects.
158      *
159      * @return templates exported by the server
160      * @throws IOException if response deserialization fails
161      * @throws InterruptedException if the thread is interrupted while awaiting the response
162      * @throws DomainTemplateException if the server returns an unexpected HTTP status
163      */
164     public List<DomainTemplate> exportJson() throws IOException, InterruptedException {
165         return sendJson(getRequest(uri("/export.json"), APPLICATION_JSON), TEMPLATE_LIST, 200);
166     }
167 
168     /**
169      * Exports all DomainTemplates as CSV.
170      *
171      * @return CSV document returned by the server
172      * @throws IOException if the request fails
173      * @throws InterruptedException if the thread is interrupted while awaiting the response
174      * @throws DomainTemplateException if the server returns an unexpected HTTP status
175      */
176     public String exportCsv() throws IOException, InterruptedException {
177         return send(getRequest(uri("/export.csv"), TEXT_CSV), 200).body();
178     }
179 
180     private HttpRequest jsonRequest(URI uri, String method, Object body) throws IOException {
181         return HttpRequest.newBuilder(uri)
182                 .header("Content-Type", APPLICATION_JSON)
183                 .header("Accept", APPLICATION_JSON)
184                 .method(method, HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))
185                 .build();
186     }
187 
188     private HttpRequest getRequest(URI uri, String accept) {
189         return HttpRequest.newBuilder(uri).header("Accept", accept).GET().build();
190     }
191 
192     private <T> T sendJson(HttpRequest request, Class<T> responseType, int expectedStatus)
193             throws IOException, InterruptedException {
194         return objectMapper.readValue(send(request, expectedStatus).body(), responseType);
195     }
196 
197     private <T> T sendJson(HttpRequest request, TypeReference<T> responseType, int expectedStatus)
198             throws IOException, InterruptedException {
199         return objectMapper.readValue(send(request, expectedStatus).body(), responseType);
200     }
201 
202     private HttpResponse<String> send(HttpRequest request, int expectedStatus) throws IOException, InterruptedException {
203         HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
204         if (response.statusCode() != expectedStatus) {
205             throw new DomainTemplateException(
206                     "Expected HTTP " + expectedStatus + " but received " + response.statusCode());
207         }
208         return response;
209     }
210 
211     private URI uri(String suffix) {
212         return baseUri.resolve(API_PATH + suffix);
213     }
214 }