4f825e08053f25e526afd7a60e82225a629ede6a
[msb/discovery.git] / sdclient / discovery-service / src / main / java / org / onap / msb / sdclient / wrapper / consul / util / ClientUtil.java
1 /**
2  * Copyright 2016-2017 ZTE, Inc. and others.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package org.onap.msb.sdclient.wrapper.consul.util;
15
16 import java.math.BigInteger;
17 import java.util.List;
18 import java.util.Map;
19
20 import javax.ws.rs.ServerErrorException;
21 import javax.ws.rs.WebApplicationException;
22 import javax.ws.rs.client.InvocationCallback;
23 import javax.ws.rs.client.WebTarget;
24 import javax.ws.rs.core.GenericType;
25 import javax.ws.rs.core.MediaType;
26 import javax.ws.rs.core.Response;
27
28 import org.onap.msb.sdclient.wrapper.consul.ConsulException;
29 import org.onap.msb.sdclient.wrapper.consul.async.ConsulResponseCallback;
30 import org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse;
31 import org.onap.msb.sdclient.wrapper.consul.option.CatalogOptions;
32 import org.onap.msb.sdclient.wrapper.consul.option.ParamAdder;
33 import org.onap.msb.sdclient.wrapper.consul.option.QueryOptions;
34
35 import com.google.common.base.Optional;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.collect.ImmutableMap;
38
39 /**
40  * A collection of stateless utility methods for use in constructing requests and responses to the
41  * Consul HTTP API.
42  */
43 public class ClientUtil {
44
45     /**
46      * Applies all key/values from the params map to query string parameters.
47      *
48      * @param webTarget The JAX-RS target to apply the query parameters.
49      * @param params Map of parameters.
50      * @return The new target with the parameters applied.
51      */
52     public static WebTarget queryParams(WebTarget webTarget, Map<String, String> params) {
53         WebTarget target = webTarget;
54
55         if (params != null) {
56             for (Map.Entry<String, String> entry : params.entrySet()) {
57                 target = target.queryParam(entry.getKey(), entry.getValue());
58             }
59         }
60
61         return target;
62     }
63
64     /**
65      * Given a {@link org.onap.msb.sdclient.wrapper.consul.option.ParamAdder} object, adds the
66      * appropriate query string parameters to the request being built.
67      *
68      * @param webTarget The base {@link javax.ws.rs.client.WebTarget}.
69      * @param paramAdder will add specific params to the target.
70      * @return A {@link javax.ws.rs.client.WebTarget} with all appropriate query string parameters.
71      */
72     public static WebTarget addParams(WebTarget webTarget, ParamAdder paramAdder) {
73         return paramAdder == null ? webTarget : paramAdder.apply(webTarget);
74     }
75
76     /**
77      * Generates a {@link org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse} for a specific
78      * datacenter, set of {@link org.onap.msb.sdclient.wrapper.consul.option.QueryOptions}, and a
79      * result type.
80      *
81      * @param target The base {@link javax.ws.rs.client.WebTarget}.
82      * @param catalogOptions Catalog specific options to use.
83      * @param queryOptions The Query Options to use.
84      * @param type The generic type to marshall the resulting data to.
85      * @param <T> The result type.
86      * @return A {@link org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse}.
87      */
88     public static <T> ConsulResponse<T> response(WebTarget target, CatalogOptions catalogOptions,
89                     QueryOptions queryOptions, GenericType<T> type) {
90         target = addParams(target, catalogOptions);
91         target = addParams(target, queryOptions);
92
93         return response(target, type);
94     }
95
96     /**
97      * Generates a {@link org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse} for a specific
98      * datacenter, set of {@link org.onap.msb.sdclient.wrapper.consul.option.QueryOptions}, and a
99      * result type.
100      *
101      * @param target The base {@link javax.ws.rs.client.WebTarget}.
102      * @param catalogOptions Catalog specific options to use.
103      * @param queryOptions The Query Options to use.
104      * @param type The generic type to marshall the resulting data to.
105      * @param <T> The result type.
106      */
107     public static <T> void response(WebTarget target, CatalogOptions catalogOptions, QueryOptions queryOptions,
108                     GenericType<T> type, ConsulResponseCallback<T> callback) {
109
110         target = addParams(target, catalogOptions);
111         target = addParams(target, queryOptions);
112
113         response(target, type, callback);
114     }
115
116     /**
117      * Given a {@link javax.ws.rs.client.WebTarget} object and a type to marshall the result JSON
118      * into, complete the HTTP GET request.
119      *
120      * @param webTarget The JAX-RS target.
121      * @param responseType The class to marshall the JSON into.
122      * @param <T> The class to marshall the JSON into.
123      * @return A {@link org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse} containing the
124      *         result.
125      */
126     public static <T> ConsulResponse<T> response(WebTarget webTarget, GenericType<T> responseType) {
127         Response response = webTarget.request().accept(MediaType.APPLICATION_JSON_TYPE).get();
128
129         return consulResponse(responseType, response);
130     }
131
132     /**
133      * Given a {@link javax.ws.rs.client.WebTarget} object and a type to marshall the result JSON
134      * into, complete the HTTP GET request.
135      *
136      * @param webTarget The JAX-RS target.
137      * @param responseType The class to marshall the JSON into.
138      * @param callback The callback object to handle the result on a different thread.
139      * @param <T> The class to marshall the JSON into.
140      */
141     public static <T> void response(WebTarget webTarget, final GenericType<T> responseType,
142                     final ConsulResponseCallback<T> callback) {
143         webTarget.request().accept(MediaType.APPLICATION_JSON_TYPE).async().get(new InvocationCallback<Response>() {
144
145             @Override
146             public void completed(Response response) {
147                 try {
148                     callback.onComplete(consulResponse(responseType, response));
149                 } catch (Exception ex) {
150                     callback.onFailure(ex);
151                 }
152             }
153
154             @Override
155             public void failed(Throwable throwable) {
156                 callback.onFailure(throwable);
157             }
158         });
159     }
160
161     /**
162      * Extracts Consul specific headers and adds them to a
163      * {@link org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse} object, which also contains
164      * the returned JSON entity.
165      *
166      * @param responseType The class to marshall the JSON to.
167      * @param response The HTTP response.
168      * @param <T> The class to marshall the JSON to.
169      * @return A {@link org.onap.msb.sdclient.wrapper.consul.model.ConsulResponse} object.
170      */
171     private static <T> ConsulResponse<T> consulResponse(GenericType<T> responseType, Response response) {
172         handleErrors(response);
173
174         String indexHeaderValue = response.getHeaderString("X-Consul-Index");
175         String lastContactHeaderValue = response.getHeaderString("X-Consul-Lastcontact");
176         String knownLeaderHeaderValue = response.getHeaderString("X-Consul-Knownleader");
177
178         BigInteger index = new BigInteger(indexHeaderValue);
179         long lastContact = lastContactHeaderValue == null ? -1 : Long.parseLong(lastContactHeaderValue);
180         boolean knownLeader = knownLeaderHeaderValue == null ? false : Boolean.valueOf(knownLeaderHeaderValue);
181
182         ConsulResponse<T> consulResponse =
183                         new ConsulResponse<T>(readResponse(response, responseType), lastContact, knownLeader, index);
184
185         response.close();
186
187         return consulResponse;
188     }
189
190     /**
191      * Converts a {@link Response} object to the generic type provided, or an empty representation
192      * if appropriate
193      *
194      * @param response response
195      * @param responseType response type
196      * @param <T>
197      * @return the re
198      */
199     @SuppressWarnings("unchecked")
200     private static <T> T readResponse(Response response, GenericType<T> responseType) {
201         if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
202             // would be nice I knew a better way to do this
203             if (responseType.getRawType() == List.class) {
204                 return (T) ImmutableList.of();
205             } else if (responseType.getRawType() == Optional.class) {
206                 return (T) Optional.absent();
207             } else if (responseType.getRawType() == Map.class) {
208                 return (T) ImmutableMap.of();
209             } else {
210                 // Not sure if this case will be reached, but if it is it'll be nice to know
211                 throw new IllegalStateException(
212                                 "Cannot determine empty representation for " + responseType.getRawType());
213             }
214         }
215         return response.readEntity(responseType);
216     }
217
218     /**
219      * Since Consul returns plain text when an error occurs, check for unsuccessful HTTP status
220      * code, and throw an exception with the text from Consul as the message.
221      *
222      * @param response The HTTP response.
223      */
224     public static void handleErrors(Response response) {
225
226         if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL
227                         || response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
228             // not an error
229             return;
230         }
231
232         try {
233             final String message = response.hasEntity() ? response.readEntity(String.class) : null;
234             if (response.getStatusInfo().getFamily() == Response.Status.Family.SERVER_ERROR) {
235                 throw new ServerErrorException(message, response);
236             } else {
237                 throw new WebApplicationException(message, response);
238             }
239         } catch (Exception e) {
240             throw new ConsulException(e.getLocalizedMessage(), e);
241         } finally {
242             response.close();
243         }
244     }
245 }