001package io.konik.sdk.http;
002
003import com.fasterxml.jackson.databind.ObjectMapper;
004import com.google.api.client.http.*;
005import com.google.api.client.http.javanet.NetHttpTransport;
006import com.google.api.client.json.JsonObjectParser;
007import com.google.api.client.json.jackson2.JacksonFactory;
008import com.google.common.io.ByteStreams;
009import io.konik.sdk.ZinvoiceApiConfig;
010import org.apache.tika.detect.DefaultDetector;
011import org.apache.tika.detect.Detector;
012import org.apache.tika.metadata.Metadata;
013import org.apache.tika.mime.MediaType;
014
015import java.io.ByteArrayInputStream;
016import java.io.IOException;
017import java.io.InputStream;
018import java.util.Map;
019
020/**
021 * HTTP client for Zinvoice services.
022 *
023 * It allows to call Zinvoice endpoints with no worries about internal connection details.
024 */
025public class ZinvoiceHttpClient {
026
027        public static final HttpRequestFactory DEFAULT_HTTP_REQUEST_FACTORY = new NetHttpTransport().createRequestFactory(new HttpRequestInitializer() {
028                @Override
029                public void initialize(HttpRequest httpRequest) throws IOException {
030                        httpRequest.setParser(new JsonObjectParser(new JacksonFactory()));
031                }
032        });
033
034        private final ZinvoiceApiConfig apiConfig;
035
036        private final HttpRequestFactory httpRequestFactory;
037
038        private final ObjectMapper objectMapper;
039
040        public ZinvoiceHttpClient(ZinvoiceApiConfig apiConfig, HttpRequestFactory httpRequestFactory, ObjectMapper objectMapper) {
041                this.apiConfig = apiConfig;
042                this.httpRequestFactory = httpRequestFactory;
043                this.objectMapper = objectMapper;
044        }
045
046        public ZinvoiceHttpClient(ZinvoiceApiConfig apiConfig, ObjectMapper objectMapper) {
047                this.apiConfig = apiConfig;
048                this.objectMapper = objectMapper;
049                this.httpRequestFactory = DEFAULT_HTTP_REQUEST_FACTORY;
050        }
051
052        public ZinvoiceHttpClient(ZinvoiceApiConfig apiConfig) {
053                this.apiConfig = apiConfig;
054                this.objectMapper = apiConfig.getDefaulObjectMapper();
055                this.httpRequestFactory = DEFAULT_HTTP_REQUEST_FACTORY;
056        }
057
058        /**
059         * Runs GET request.
060         *
061         * @param endpoint
062         * @param responseTypeClass
063         * @return
064         */
065        public <T> T get(String endpoint, Class<T> responseTypeClass) {
066                T result;
067
068                try {
069                        HttpRequest request = httpRequestFactory.buildGetRequest(createEndpoint(endpoint));
070                        request.setHeaders(new HttpHeaders().set("API-KEY", apiConfig.getApiKey()));
071
072                        HttpResponse response = request.execute();
073                        result = objectMapper.readValue(response.parseAsString(), responseTypeClass);
074                } catch (IOException e) {
075                        throw new RuntimeException(e);
076                }
077
078                return result;
079        }
080
081        /**
082         * Downloads content as {@see InputStream}.
083         *
084         * @param endpoint
085         * @return
086         */
087        public InputStream download(String endpoint) {
088                try {
089                        HttpRequest request = httpRequestFactory.buildGetRequest(createEndpoint(endpoint));
090                        request.setHeaders(new HttpHeaders()
091                                        .set("API-KEY", apiConfig.getApiKey())
092                                        .setAccept("application/json"));
093                        return request.execute().getContent();
094                } catch (IOException e) {
095                        throw new RuntimeException(e);
096                }
097        }
098
099        /**
100         * Uploads {@see InputStream} to given endpoint.
101         *
102         * @param endpoint
103         * @param files map where key is field name and value is a content of uploading file
104         * @param responseTypeClass 
105         * @return 
106         */
107        public <T> T upload(String endpoint, Map<String, InputStream> files, Class<T> responseTypeClass) {
108                try {
109                        MultipartContent content = new MultipartContent();
110                        content.setMediaType(new HttpMediaType("multipart/form-data")
111                                        .setParameter("boundary", "__END_OF_PART__"));
112
113                        Detector detector = new DefaultDetector();
114
115                        for (String key : files.keySet()) {
116                                InputStream file = new ByteArrayInputStream(ByteStreams.toByteArray(files.get(key)));
117                                MediaType mediaType = detector.detect(file, new Metadata());
118                                MultipartContent.Part part = new MultipartContent.Part(new InputStreamContent(mediaType.toString(), file));
119                                part.setHeaders(new HttpHeaders().set("Content-Disposition", String.format("form-data; name=\"%s\"; filename=\"%s\"", key, key)));
120                                content.addPart(part);
121                        }
122
123                        HttpRequest request = httpRequestFactory.buildPostRequest(createEndpoint(endpoint), content);
124                        request.setHeaders(new HttpHeaders()
125                                        .set("API-KEY", apiConfig.getApiKey())
126                                        .setAccept("application/json"));
127                        HttpResponse response = request.execute();
128
129                        return objectMapper.readValue(response.parseAsString(), responseTypeClass);
130
131                } catch (HttpResponseException e) {
132                        if (e.getStatusCode() == 400) {
133                                throw new BadRequestException(getErrorResponse(e));
134                        }
135                        throw new RuntimeException(e);
136                } catch (IOException e) {
137                        throw new RuntimeException(e);
138                }
139        }
140
141        /**
142         * Runs POST request.
143         *
144         * @param endpoint
145         * @param body 
146         * @param contentType 
147         * @param responseTypeClass
148         * @return
149         */
150        public <T> T post(String endpoint, byte[] body, String contentType, Class<T> responseTypeClass) {
151                T result;
152
153                try {
154                        HttpContent content = new InputStreamContent(contentType, new ByteArrayInputStream(body));
155                        HttpRequest request = httpRequestFactory.buildPostRequest(createEndpoint(endpoint), content);
156                        request.setHeaders(new HttpHeaders().set("API-KEY", apiConfig.getApiKey()));
157
158                        HttpResponse response = request.execute();
159                        result = objectMapper.readValue(response.parseAsString(), responseTypeClass);
160                } catch (HttpResponseException e) {
161                        if (e.getStatusCode() == 400) {
162                                throw new BadRequestException(getErrorResponse(e));
163                        }
164                        throw new RuntimeException(e);
165                } catch (IOException e) {
166                        throw new RuntimeException(e);
167                }
168
169                return result;
170        }
171
172        /**
173         * Runs PUT request.
174         *
175         * @param endpoint
176         * @param body 
177         * @param contentType 
178         * @param responseTypeClass
179         * @return
180         */
181        public <T> T put(String endpoint, byte[] body, String contentType, Class<T> responseTypeClass) {
182                T result;
183
184                try {
185                        HttpContent content = new InputStreamContent(contentType, new ByteArrayInputStream(body));
186                        HttpRequest request = httpRequestFactory.buildPutRequest(createEndpoint(endpoint), content);
187                        request.setHeaders(new HttpHeaders().set("API-KEY", apiConfig.getApiKey()));
188
189                        HttpResponse response = request.execute();
190                        result = objectMapper.readValue(response.parseAsString(), responseTypeClass);
191                } catch (HttpResponseException e) {
192                        if (e.getStatusCode() == 400) {
193                                throw new BadRequestException(getErrorResponse(e));
194                        }
195                        throw new RuntimeException(e);
196                } catch (IOException e) {
197                        throw new RuntimeException(e);
198                }
199
200                return result;
201        }
202
203        /**
204         * Runs DELETE request.
205         *
206         * @param endpoint
207         * @return
208         */
209        public void delete(String endpoint) {
210                try {
211                        HttpRequest request = httpRequestFactory.buildDeleteRequest(createEndpoint(endpoint));
212                        request.setHeaders(new HttpHeaders().set("API-KEY", apiConfig.getApiKey()));
213                        request.execute();
214                } catch (IOException e) {
215                        throw new RuntimeException(e);
216                }
217        }
218
219        private ErrorResponse getErrorResponse(HttpResponseException e) {
220                ErrorResponse errorResponse;
221                try {
222                         errorResponse = objectMapper.readValue(e.getContent(), ErrorResponse.class);
223                } catch (IOException e1) {
224                        throw new RuntimeException(e1);
225                }
226                return errorResponse;
227        }
228
229        private GenericUrl createEndpoint(String endpoint) {
230                return new GenericUrl(apiConfig.getDestinationUrl() + endpoint);
231        }
232}