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