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