Remove Tabs, per Jococo
[aaf/authz.git] / cadi / client / src / main / java / org / onap / aaf / cadi / http / HClient.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.cadi.http;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.Reader;
29 import java.net.HttpURLConnection;
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.net.URL;
33 import java.util.ArrayList;
34
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.onap.aaf.cadi.CadiException;
38 import org.onap.aaf.cadi.LocatorException;
39 import org.onap.aaf.cadi.SecuritySetter;
40 import org.onap.aaf.cadi.client.EClient;
41 import org.onap.aaf.cadi.client.Future;
42 import org.onap.aaf.cadi.client.Rcli;
43 import org.onap.aaf.misc.env.APIException;
44 import org.onap.aaf.misc.env.Data;
45 import org.onap.aaf.misc.env.Data.TYPE;
46 import org.onap.aaf.misc.env.util.Pool.Pooled;
47 import org.onap.aaf.misc.rosetta.env.RosettaDF;
48
49 /**
50  * Low Level Http Client Mechanism. Chances are, you want the high level "HRcli"
51  * for Rosetta Object Translation
52  * 
53  * @author Jonathan
54  *
55  */
56 public class HClient implements EClient<HttpURLConnection> {
57     private URI uri;
58     private ArrayList<Header> headers;
59     private String meth;
60     private String pathinfo;
61     private String query;
62     private String fragment;
63     private Transfer transfer;
64     private SecuritySetter<HttpURLConnection> ss;
65     private HttpURLConnection huc;
66     private int connectTimeout;
67
68     public HClient(SecuritySetter<HttpURLConnection> ss, URI uri,int connectTimeout) throws LocatorException {
69         if (uri == null) {
70             throw new LocatorException("No Service available to call");
71         }
72         this.uri = uri;
73         this.ss = ss;
74         this.connectTimeout = connectTimeout;
75         pathinfo = query = fragment = null; 
76     }
77
78     @Override
79     public void setMethod(String meth) {
80         this.meth = meth;
81     }
82
83     @Override
84     public void setPathInfo(String pathinfo) {
85         this.pathinfo = pathinfo;
86     }
87
88     @Override
89     public void setPayload(Transfer transfer) {
90         this.transfer = transfer;
91     }
92     
93     @Override
94     public void addHeader(String tag, String value) {
95         if (headers == null)
96             headers = new ArrayList<>();
97         headers.add(new Header(tag, value));
98     }
99
100     @Override
101     public void setQueryParams(String q) {
102         query = q;
103     }
104
105     @Override
106     public void setFragment(String f) {
107         fragment = f;
108     }
109
110     @Override
111     public void send() throws APIException {
112         // Build URL from given URI plus current Settings
113         if (uri.getPath()==null) {
114             throw new APIException("Invalid URL entered for HClient");
115         }
116         StringBuilder pi=null;
117         if (pathinfo!=null) { // additional pathinfo
118             pi = new StringBuilder(uri.getPath());
119             if (!pathinfo.startsWith("/")) {
120                 pi.append('/');
121             }
122             pi.append(pathinfo);
123         }
124            URI sendURI = null;
125         try {
126             sendURI = new URI(
127                     uri.getScheme(),
128                     uri.getAuthority(),
129                     pi==null?uri.getPath():pi.toString(),
130                     query==null?uri.getQuery():query,
131                     fragment==null?uri.getFragment():fragment
132                     );
133             huc = getConnection(sendURI, pi);
134             huc.setRequestMethod(meth);
135             if (ss!=null) {
136                 ss.setSecurity(huc); 
137             }
138             if (headers != null)
139                 for (Header d : headers) {                    
140                     huc.addRequestProperty(d.tag, d.value);
141                 }
142             huc.setDoInput(true);
143             huc.setDoOutput(true);
144             huc.setUseCaches(false);
145             huc.setConnectTimeout(connectTimeout);
146             huc.connect();
147             if (transfer != null) {
148                 transfer.transfer(huc.getOutputStream());
149             }
150             // TODO other settings? There's a bunch here.
151         } catch (APIException e) {
152             throw e;
153         } catch (Exception e) {
154             if(sendURI==null) {
155                 throw new APIException("Cannot connect to Root URI: '" + uri.toString() + '\'',e);
156             } else {
157                 throw new APIException("Cannot connect to '" + sendURI.toString() + "' (Root URI: '" + uri.toString() + "')",e);
158             }
159         } finally { // ensure all these are reset after sends
160             meth=pathinfo=null;
161             if (headers!=null) {
162                 headers.clear();
163             }
164             pathinfo = query = fragment = "";
165         }
166     }
167     
168     public URI getURI() {
169         return uri;
170     }
171     
172     public void setURI(URI uri) {
173         this.uri = uri;
174     }
175
176     public int timeout() {
177         return connectTimeout;
178     }
179     
180     protected HttpURLConnection getConnection(URI uri, StringBuilder pi) throws IOException, URISyntaxException {
181         URL url = new URI(
182                 uri.getScheme(), 
183                 uri.getAuthority(),
184                 pi==null?uri.getPath():pi.toString(), 
185                 query,
186                 fragment).toURL();
187         return (HttpURLConnection) url.openConnection();
188     }
189     
190      public abstract class HFuture<T> extends Future<T> {
191         protected HttpURLConnection huc;
192         protected int respCode;
193         protected IOException exception;
194         protected StringBuilder errContent;
195     
196         public HFuture(final HttpURLConnection huc) {
197             this.huc = huc;
198         }
199     
200         protected boolean evalInfo(HttpURLConnection huc) throws APIException, IOException{
201             return respCode == 200;
202         };
203     
204         @Override
205         public final boolean get(int timeout) throws CadiException {
206             try {
207                 huc.setReadTimeout(timeout);
208                 respCode = huc.getResponseCode();
209                 ss.setLastResponse(respCode);
210                 if (evalInfo(huc)) {
211                     return true;
212                 } else {
213                     extractError();
214                     return false;
215                 }
216             } catch (IOException | APIException e) {
217                 throw new CadiException(e);
218             } finally {
219                 close();
220             }
221         }
222     
223         private void extractError() {
224             InputStream is = huc.getErrorStream();
225             try {
226                 if (is==null) {
227                     is = huc.getInputStream();
228                 }
229                 if (is!=null) {
230                 errContent = new StringBuilder();
231                 int c;
232                     while ((c=is.read())>=0) {
233                         errContent.append((char)c);
234                     }
235                 }
236             } catch (IOException e) {
237                 exception = e;
238             }
239         }
240     
241         // Typically only used by Read
242         public StringBuilder inputStreamToString(InputStream is) {
243             // Avoids Carriage returns, and is reasonably efficient, given
244             // the buffer reads.
245             try {
246                 StringBuilder sb = new StringBuilder();
247                 Reader rdr = new InputStreamReader(is);
248                 try {
249                     char[] buf = new char[256];
250                     int read;
251                     while ((read = rdr.read(buf)) >= 0) {
252                         sb.append(buf, 0, read);
253                     }
254                 } finally {
255                     rdr.close();
256                 }
257                 return sb;
258             } catch (IOException e) {
259                 exception = e;
260                 return null;
261             }
262         }
263     
264     
265         @Override
266         public int code() {
267             return respCode;
268         }
269     
270         public HttpURLConnection huc() {
271             return huc;
272         }
273     
274         public IOException exception() {
275             return exception;
276         }
277     
278         @Override
279         public String header(String tag) {
280             return huc.getHeaderField(tag);
281         }
282     
283         public void close() {
284             if (huc!=null) {
285                 huc.disconnect();
286             }
287         }
288     }
289
290     @Override
291     public <T> Future<T> futureCreate(Class<T> t) {
292         return new HFuture<T>(huc) {
293             public boolean evalInfo(HttpURLConnection huc) {
294                 return respCode==201;
295             }
296
297             @Override
298             public String body() {
299                 if (errContent != null) {
300                     return errContent.toString();
301                 }
302                 return "";
303             }
304         };
305     }
306
307     @Override
308     public Future<String> futureReadString() {
309         return new HFuture<String>(huc) {
310             public boolean evalInfo(HttpURLConnection huc) throws IOException {
311                 if (respCode == 200) {
312                     StringBuilder sb = inputStreamToString(huc.getInputStream());
313                     if (sb != null) {
314                         value = sb.toString();
315                     }
316                     return true;
317                 }
318                 return false;
319             }
320
321             @Override
322             public String body() {
323                 if (value != null) {
324                     return value;
325                 } else if (errContent != null) {
326                     return errContent.toString();
327                 }
328                 return "";
329             }
330
331         };
332     }
333
334     @Override
335     public <T> Future<T> futureRead(final RosettaDF<T> df, final TYPE type) {
336         return new HFuture<T>(huc) {
337             private Data<T> data;
338
339             public boolean evalInfo(HttpURLConnection huc) throws APIException, IOException {
340                 if (respCode == 200) {
341                     data = df.newData().in(type).load(huc.getInputStream());
342                     value = data.asObject();
343                     return true;
344                 }
345                 return false;
346             }
347
348             @Override
349             public String body() {
350                 if (data != null) {
351                     try {
352                         return data.asString();
353                     } catch (APIException e) {
354                     }
355                 } else if (errContent != null) {
356                     return errContent.toString();
357                 }
358                 return "";
359             }
360         };
361     }
362
363     @Override
364     public <T> Future<T> future(final T t) {
365         return new HFuture<T>(huc) {
366             public boolean evalInfo(HttpURLConnection huc) {
367                 if (respCode == 200) {
368                     value = t;
369                     return true;
370                 }
371                 return false;
372             }
373
374             @Override
375             public String body() {
376                 if (errContent != null) {
377                     return errContent.toString();
378                 }
379                 return Integer.toString(respCode);
380             }
381         };
382     }
383
384     @Override
385     public Future<Void> future(final HttpServletResponse resp, final int expected) throws APIException {
386         return new HFuture<Void>(huc) {
387             public boolean evalInfo(HttpURLConnection huc) throws IOException, APIException {
388                 resp.setStatus(respCode);
389                 int read;
390                 InputStream is;
391                 OutputStream os = resp.getOutputStream();
392                 if (respCode==expected) {
393                     is = huc.getInputStream();
394                     // reuse Buffers
395                     Pooled<byte[]> pbuff = Rcli.buffPool.get();
396                     try { 
397                         while ((read=is.read(pbuff.content))>=0) {
398                             os.write(pbuff.content,0,read);
399                         }
400                     } finally {
401                         pbuff.done();
402                     }
403                     return true;
404                 } else {
405                     is = huc.getErrorStream();
406                     if (is==null) {
407                         is = huc.getInputStream();
408                     }
409                     if (is!=null) {
410                         errContent = new StringBuilder();
411                         Pooled<byte[]> pbuff = Rcli.buffPool.get();
412                         try { 
413                             while ((read=is.read(pbuff.content))>=0) {
414                                 os.write(pbuff.content,0,read);
415                             }
416                         } finally {
417                             pbuff.done();
418                         }
419                     }
420                 }
421                 return false;
422             }
423
424             @Override
425             public String body() {
426                 return errContent==null?null:errContent.toString();
427             }
428         };
429     }
430
431     private static class Header {
432         public final String tag;
433         public final String value;
434
435         public Header(String t, String v) {
436             this.tag = t;
437             this.value = v;
438         }
439         
440         public String toString() {
441             return tag + '=' + value;
442         }
443     }
444     
445     public String toString() {
446         return "HttpURLConnection Client configured to " + uri.toString();
447     }
448 }