04075f296e570bf8926b6702c1dd603ba8b1f147
[aaf/authz.git] / cadi / client / src / main / java / org / onap / aaf / cadi / client / Rcli.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.client;
23
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.io.PrintStream;
27 import java.net.URI;
28 import java.util.Enumeration;
29
30 import javax.servlet.ServletInputStream;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33
34 import org.onap.aaf.cadi.CadiException;
35 import org.onap.aaf.cadi.SecuritySetter;
36 import org.onap.aaf.cadi.client.EClient.Transfer;
37 import org.onap.aaf.misc.env.APIException;
38 import org.onap.aaf.misc.env.Data.TYPE;
39 import org.onap.aaf.misc.env.util.Pool;
40 import org.onap.aaf.misc.env.util.Pool.Pooled;
41 import org.onap.aaf.misc.rosetta.env.RosettaDF;
42
43 public abstract class Rcli<CT> {
44         public static final String FORM_ENCODED = "application/x-www-form-urlencoded";
45         public static final String APPL_JSON = "application/json";
46         public static final String APPL_XML = "application/xml";
47         public static final String BLANK = "";
48         public static final String CONTENT_TYPE = "Content-Type";
49         public static final String ACCEPT = "Accept";
50
51         protected static final String POST = "POST";
52         protected static final String GET = "GET";
53         protected static final String PUT = "PUT";
54         protected static final String DELETE = "DELETE";
55         protected TYPE type;
56         protected String apiVersion;
57         protected int readTimeout = 5000;
58         protected int connectionTimeout = 3000;
59         protected URI uri;
60         private String oneCallQueryParams;
61         public static Pool<byte[]> buffPool = new Pool<byte[]>(new Pool.Creator<byte[]>() {
62                 @Override
63                 public byte[] create() throws APIException {
64                         return new byte[1024];
65                 }
66
67                 @Override
68                 public void destroy(byte[] t) {
69                 }
70
71                 @Override
72                 public boolean isValid(byte[] t) {
73                         return true;
74                 }
75
76                 @Override
77                 public void reuse(byte[] t) {
78                 }
79         });
80
81
82         public Rcli() {
83                 super();
84         }
85
86         public abstract void setSecuritySetter(SecuritySetter<CT> ss);
87         public abstract SecuritySetter<CT> getSecuritySetter();
88
89
90         public Rcli<CT> forUser(SecuritySetter<CT> ss) {
91                 Rcli<CT> rv = clone(uri==null?this.uri:uri,ss);
92                 setSecuritySetter(ss);
93                 rv.type = type;
94                 rv.apiVersion = apiVersion;
95                 return rv;
96         }
97         
98         protected abstract Rcli<CT> clone(URI uri, SecuritySetter<CT> ss);
99         
100         public abstract void invalidate() throws CadiException;
101
102         public Rcli<CT> readTimeout(int millis) {
103                 readTimeout = millis;
104                 return this;
105         }
106
107         public Rcli<CT> connectionTimeout(int millis) {
108                 connectionTimeout = millis;
109                 return this;
110         }
111
112         public Rcli<CT> type(TYPE type) {
113                 this.type=type;
114                 return this;
115         }
116
117         public Rcli<CT> apiVersion(String apiVersion) {
118                 this.apiVersion = apiVersion;
119                 return this;
120         }
121         
122         public boolean isApiVersion(String prospective) {
123                 return apiVersion.equals(prospective);
124         }
125
126
127         public String typeString(Class<?> cls) {
128                 return "application/"+cls.getSimpleName()+"+"+type.name().toLowerCase()+
129                                 (apiVersion==null?BLANK:";version="+apiVersion);
130         }
131
132         protected abstract EClient<CT> client() throws CadiException;
133
134
135         public<T> Future<T> create(final String pathinfo, final String contentType, final RosettaDF<T> df, final T t) throws APIException, CadiException {
136                 final ParsePath pp = new ParsePath(pathinfo);
137
138                 EClient<CT> client = client();
139                 client.setMethod(POST);
140                 client.addHeader(CONTENT_TYPE,contentType);
141                 client.setPathInfo(pp.path());
142                 client.setQueryParams(pp.query());
143                 client.setFragment(pp.frag());
144                 client.setPayload(new EClient.Transfer() {
145                         @Override
146                         public void transfer(OutputStream os) throws IOException, APIException {
147                                 df.newData().out(type).direct(t,os);
148                         }
149                 });
150                 client.send();
151                 return client.futureCreate(df.getTypeClass());
152         }
153
154         public<T> Future<T> create(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException {
155                 final ParsePath pp = new ParsePath(pathinfo);
156
157                 EClient<CT> client = client();
158                 client.setMethod(POST);
159                 client.addHeader(CONTENT_TYPE,typeString(df.getTypeClass()));
160                 client.setPathInfo(pp.path());
161                 client.setQueryParams(pp.query());
162                 client.setFragment(pp.frag());
163                 client.setPayload(new EClient.Transfer() {
164                         @Override
165                         public void transfer(OutputStream os) throws IOException, APIException {
166                                 df.newData().out(type).direct(t,os);
167                         }
168                 });
169                 client.send();
170                 return client.futureCreate(df.getTypeClass());
171         }
172
173         public<T> Future<T> create(String pathinfo, Class<?> cls, final RosettaDF<T> df, final T t) throws APIException, CadiException {
174                 final ParsePath pp = new ParsePath(pathinfo);
175
176                 EClient<CT> client = client();
177                 client.setMethod(POST);
178                 client.addHeader(CONTENT_TYPE,typeString(cls));
179                 client.setPathInfo(pp.path());
180                 client.setQueryParams(pp.query());
181                 client.setFragment(pp.frag());
182                 client.setPayload(new EClient.Transfer() {
183                         @Override
184                         public void transfer(OutputStream os) throws IOException, APIException {
185                                 df.newData().out(type).direct(t,os);
186                         }
187                 });
188                 client.send();
189                 return client.futureCreate(df.getTypeClass());
190         }
191
192         public<T> Future<T> create(String pathinfo, Class<T> cls) throws APIException, CadiException {
193                 final ParsePath pp = new ParsePath(pathinfo);
194
195                 EClient<CT> client = client();
196                 client.setMethod(POST);
197                 client.addHeader(CONTENT_TYPE,typeString(cls));
198                 client.setPathInfo(pp.path());
199                 client.setQueryParams(pp.query());
200                 client.setFragment(pp.frag());
201                 client.setPayload(null);
202                 client.send();
203                 return client.futureCreate(cls);
204         }
205
206         public Future<Void> create(String pathinfo, String contentType) throws APIException, CadiException {
207                 final ParsePath pp = new ParsePath(pathinfo);
208
209                 EClient<CT> client = client();
210                 client.setMethod(POST);
211                 client.addHeader(CONTENT_TYPE,contentType);
212                 client.setPathInfo(pp.path());
213                 client.setQueryParams(pp.query());
214                 client.setFragment(pp.frag());
215                 client.setPayload(null);
216                 client.send();
217                 return client.futureCreate(Void.class);
218         }
219
220         public Future<Void> create(String pathinfo, String contentType, EClient.Transfer content) throws APIException, CadiException {
221                 final ParsePath pp = new ParsePath(pathinfo);
222
223                 EClient<CT> client = client();
224                 client.setMethod(POST);
225                 client.addHeader(CONTENT_TYPE,contentType);
226                 client.setPathInfo(pp.path());
227                 client.setQueryParams(pp.query());
228                 client.setFragment(pp.frag());
229                 client.setPayload(content);
230                 client.send();
231                 return client.futureCreate(Void.class);
232         }
233
234
235         /**
236          * Post Data in WWW expected format, with the format tag1=value1&tag2=value2, etc
237          * Note Shortcut:
238          *   Because typically, you will want to have a variable as value, you can type, as long as tag ends with "="
239          *   postForm(..., "tag1=value1","tag2=",var2);
240          * @param pathinfo
241          * @param df
242          * @param cls
243          * @param formParam
244          * @return
245          * @throws APIException
246          * @throws CadiException
247          */
248         public <T> Future<T> postForm(String pathinfo, final RosettaDF<T> df, final String ... formParam) throws APIException, CadiException {
249                 final ParsePath pp = new ParsePath(pathinfo);
250
251                 EClient<CT> client = client();
252                 client.setMethod(POST);
253                 client.addHeader(CONTENT_TYPE,FORM_ENCODED);
254                 switch(type) {
255                         case JSON:
256                                 client.addHeader(ACCEPT, APPL_JSON);
257                                 break;
258                         case XML:
259                                 client.addHeader(ACCEPT, APPL_XML);
260                                 break;
261                         default:
262                                 break;
263                 }
264                 client.setPathInfo(pp.path());
265                 client.setQueryParams(pp.query());
266                 client.setFragment(pp.frag());
267                 client.setPayload(new Transfer() {
268                         @Override
269                         public void transfer(OutputStream os) throws IOException, APIException {
270                                 PrintStream ps;
271                                 if(os instanceof PrintStream) {
272                                         ps = (PrintStream)os;
273                                 } else {
274                                         ps = new PrintStream(os);
275                                 }
276                                 boolean first = true;
277                                 for(String fp : formParam) {
278                                         if(fp!=null) {
279                                                 if(first) {
280                                                         first = false;
281                                                 } else {
282                                                         ps.print('&');
283                                                 }
284                                                 if(fp.endsWith("=")) {
285                                                         first = true;
286                                                 }
287                                                 ps.print(fp);
288                                         }
289                                 }
290                         }});
291                 client.send();
292                 return client.futureRead(df,TYPE.JSON);
293         }
294
295         /**
296          * Read String, using POST for keyInfo
297          * 
298          * @param pathinfo
299          * @param df
300          * @param t
301          * @param resp
302          * @return
303          * @throws APIException
304          * @throws CadiException
305          */
306         public<T> Future<String> readPost(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException {
307                 final ParsePath pp = new ParsePath(pathinfo);
308
309                 EClient<CT> client = client();
310                 client.setMethod(POST);
311                 client.addHeader(CONTENT_TYPE,typeString(df.getTypeClass()));
312                 client.setPathInfo(pp.path());
313                 client.setQueryParams(pp.query());
314                 client.setFragment(pp.frag());
315                 client.setPayload(new EClient.Transfer() {
316                         @Override
317                         public void transfer(OutputStream os) throws IOException, APIException {
318                                 df.newData().out(type).direct(t,os);
319                         }
320                 });
321                 client.send();
322                 return client.futureReadString();
323         }
324
325         /**
326          * Read using POST for keyInfo, responding with marshaled Objects
327          *
328          * @param pathinfo
329          * @param df
330          * @param t
331          * @param resp
332          * @return
333          * @throws APIException
334          * @throws CadiException
335          */
336         public<T,R> Future<R> readPost(String pathinfo, final RosettaDF<T> df, final T t, final RosettaDF<R> resp) throws APIException, CadiException {
337                 final ParsePath pp = new ParsePath(pathinfo);
338                 
339                 EClient<CT> client = client();
340                 client.setMethod(POST);
341                 client.addHeader(CONTENT_TYPE,typeString(df.getTypeClass()));
342                 client.setPathInfo(pp.path());
343                 client.setQueryParams(pp.query());
344                 client.setFragment(pp.frag());
345                 client.setPayload(new EClient.Transfer() {
346                         @Override
347                         public void transfer(OutputStream os) throws IOException, APIException {
348                                 df.newData().out(type).direct(t,os);
349                         }
350                 });
351                 client.send();
352                 return client.futureRead(resp,resp.getOutType());
353         }
354
355         public Future<String> readPost(String pathinfo, String contentType, String ... headers) throws CadiException, APIException {
356                 final ParsePath pp = new ParsePath(pathinfo);
357
358                 EClient<CT> client = client();
359                 client.setMethod(POST);
360                 client.addHeader(CONTENT_TYPE,contentType);
361                 client.setPathInfo(pp.path());
362                 client.setQueryParams(pp.query());
363                 client.setFragment(pp.frag());
364                 client.setPayload(new EClient.Transfer() {
365                         @Override
366                         public void transfer(OutputStream os) throws IOException, APIException {
367                         }});
368                 client.send();
369                 return client.futureReadString();
370         }
371
372         public Future<String> read(String pathinfo, String accept, String ... headers) throws APIException, CadiException {
373                 final ParsePath pp = new ParsePath(pathinfo);
374         
375                 EClient<CT> client = client();
376                 client.setMethod(GET);
377                 client.addHeader(ACCEPT, accept);
378                 
379                 for(int i=1;i<headers.length;i=i+2) {
380                         client.addHeader(headers[i-1],headers[i]);
381                 }
382                 client.setPathInfo(pp.path());
383                 client.setQueryParams(pp.query());
384                 client.setFragment(pp.frag());
385                 client.setPayload(null);
386                 client.send();
387                 return client.futureReadString();
388         }
389
390         public<T> Future<T> read(String pathinfo, String accept, RosettaDF<T> df, String ... headers) throws APIException, CadiException {
391                 final ParsePath pp = new ParsePath(pathinfo);
392
393                 EClient<CT> client = client();
394                 client.setMethod(GET);
395                 client.addHeader(ACCEPT, accept);
396                 for(int i=1;i<headers.length;i=i+2) {
397                         client.addHeader(headers[i-1],headers[i]);
398                 }
399                 client.setPathInfo(pp.path());
400                 client.setQueryParams(pp.query());
401                 client.setFragment(pp.frag());
402                 client.setPayload(null);
403                 client.send();
404                 return client.futureRead(df,type);
405         }
406
407         public<T> Future<T> read(String pathinfo, RosettaDF<T> df,String ... headers) throws APIException, CadiException {
408                 final ParsePath pp = new ParsePath(pathinfo);
409
410                 EClient<CT> client = client();
411                 client.setMethod(GET);
412                 client.addHeader(ACCEPT, typeString(df.getTypeClass()));
413                 for(int i=1;i<headers.length;i=i+2) {
414                         client.addHeader(headers[i-1],headers[i]);
415                 }
416                 client.setPathInfo(pp.path());
417                 client.setQueryParams(pp.query());
418                 client.setFragment(pp.frag());
419                 
420                 client.setPayload(null);
421                 client.send();
422                 return client.futureRead(df,type);
423         }
424
425         public<T> Future<T> read(String pathinfo, Class<?> cls, RosettaDF<T> df) throws APIException, CadiException {
426                 final ParsePath pp = new ParsePath(pathinfo);
427
428                 EClient<CT> client = client();
429                 client.setMethod(GET);
430                 client.addHeader(ACCEPT, typeString(cls));
431                 client.setPathInfo(pp.path());
432                 client.setQueryParams(pp.query());
433                 client.setFragment(pp.frag());          
434
435                 client.setPayload(null);
436                 client.send();
437                 return client.futureRead(df,type);
438         }
439
440         public<T> Future<T> update(String pathinfo, String contentType, final RosettaDF<T> df, final T t) throws APIException, CadiException {
441                 final ParsePath pp = new ParsePath(pathinfo);
442
443                 EClient<CT> client = client();
444                 client.setMethod(PUT);
445                 client.addHeader(CONTENT_TYPE,contentType);
446                 client.setPathInfo(pp.path());
447                 client.setQueryParams(pp.query());
448                 client.setFragment(pp.frag());          
449                 client.setPayload(new EClient.Transfer() {
450                         @Override
451                         public void transfer(OutputStream os) throws IOException, APIException {
452                                 df.newData().out(type).direct(t,os);
453                         }
454                 });
455                 client.send();
456                 return client.future(t);
457         }
458         
459         public<T> Future<String> updateRespondString(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException {
460                 final ParsePath pp = new ParsePath(pathinfo);
461                 
462                 EClient<CT> client = client();
463                 client.setMethod(PUT);
464                 client.addHeader(CONTENT_TYPE, typeString(df.getTypeClass()));
465                 client.setPathInfo(pp.path());
466                 client.setQueryParams(pp.query());
467                 client.setFragment(pp.frag());          
468
469                 client.setPayload(new EClient.Transfer() {
470                         @Override
471                         public void transfer(OutputStream os) throws IOException, APIException {
472                                 //String s = df.newData().out(type).load(t).asString();
473                                 df.newData().out(type).direct(t,os);
474                         }
475                 });
476                 client.send();
477                 return client.futureReadString();
478         }
479         
480         public Future<String> update(String pathinfo, String contentType, EClient.Transfer content) throws APIException, CadiException {
481                 final ParsePath pp = new ParsePath(pathinfo);
482
483                 EClient<CT> client = client();
484                 client.setMethod(PUT);
485                 client.addHeader(CONTENT_TYPE,contentType);
486                 client.setPathInfo(pp.path());
487                 client.setQueryParams(pp.query());
488                 client.setFragment(pp.frag());
489                 client.setPayload(content);
490                 client.send();
491                 return client.futureReadString();
492         }
493
494
495
496
497         public<T> Future<T> update(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException {
498                 final ParsePath pp = new ParsePath(pathinfo);
499
500                 EClient<CT> client = client();
501                 client.setMethod(PUT);
502                 client.addHeader(CONTENT_TYPE, typeString(df.getTypeClass()));
503                 client.setPathInfo(pp.path());
504                 client.setQueryParams(pp.query());
505                 client.setFragment(pp.frag());  
506                 
507                 client.setPayload(new EClient.Transfer() {
508                         @Override
509                         public void transfer(OutputStream os) throws IOException, APIException {
510                                 df.newData().out(type).direct(t,os);
511                         }
512                 });
513                 client.send();
514                 return client.future(t);
515         }
516         
517         public<T> Future<T> update(String pathinfo, Class<?> cls, final RosettaDF<T> df, final T t) throws APIException, CadiException {
518                 final ParsePath pp = new ParsePath(pathinfo);
519                 
520                 EClient<CT> client = client();
521                 client.setMethod(PUT);
522                 client.addHeader(CONTENT_TYPE, typeString(cls));
523                 client.setPathInfo(pp.path());
524                 client.setQueryParams(pp.query());
525                 client.setFragment(pp.frag());  
526
527                 client.setPayload(new EClient.Transfer() {
528                         @Override
529                         public void transfer(OutputStream os) throws IOException, APIException {
530                                 df.newData().out(type).direct(t,os);
531                         }
532                 });
533                 client.send();
534                 return client.future(t);
535         }
536         
537
538
539         /**
540          * A method to update with a VOID
541          * @param pathinfo
542          * @param resp
543          * @param expected
544          * @return
545          * @throws APIException
546          * @throws CadiException
547          */
548         public<T> Future<Void> update(String pathinfo) throws APIException, CadiException {
549                 final ParsePath pp = new ParsePath(pathinfo);
550
551                 EClient<CT> client = client();
552                 client.setMethod(PUT);
553                 client.addHeader(CONTENT_TYPE, typeString(Void.class));
554                 client.setPathInfo(pp.path());
555                 client.setQueryParams(pp.query());
556                 client.setFragment(pp.frag());  
557
558 //              client.setPayload(new EClient.Transfer() {
559 //                      @Override
560 //                      public void transfer(OutputStream os) throws IOException, APIException {
561 //                      }
562 //              });
563                 client.send();
564                 return client.future(null);
565         }
566         
567
568         public<T> Future<T> delete(String pathinfo, String contentType, final RosettaDF<T> df, final T t) throws APIException, CadiException {
569                 final ParsePath pp = new ParsePath(pathinfo);
570
571                 EClient<CT> client = client();
572                 client.setMethod(DELETE);
573                 client.addHeader(CONTENT_TYPE, contentType);
574                 client.setPathInfo(pp.path());
575                 client.setQueryParams(pp.query());
576                 client.setFragment(pp.frag());  
577
578                 client.setPayload(new EClient.Transfer() {
579                         @Override
580                         public void transfer(OutputStream os) throws IOException, APIException {
581                                 df.newData().out(type).direct(t,os);
582                         }
583                 });
584                 client.send();
585                 return client.future(t);
586         }
587
588         public<T> Future<T> delete(String pathinfo, Class<?> cls, final RosettaDF<T> df, final T t) throws APIException, CadiException {
589                 final ParsePath pp = new ParsePath(pathinfo);
590
591                 EClient<CT> client = client();
592                 client.setMethod(DELETE);
593                 client.addHeader(CONTENT_TYPE, typeString(cls));
594                 client.setPathInfo(pp.path());
595                 client.setQueryParams(pp.query());
596                 client.setFragment(pp.frag());  
597                 client.setPayload(new EClient.Transfer() {
598                         @Override
599                         public void transfer(OutputStream os) throws IOException, APIException {
600                                 df.newData().out(type).direct(t,os);
601                         }
602                 });
603                 client.send();
604                 return client.future(t);
605         }
606
607         public<T> Future<T> delete(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException {
608                 final ParsePath pp = new ParsePath(pathinfo);
609
610                 EClient<CT> client = client();
611                 client.setMethod(DELETE);
612                 client.addHeader(CONTENT_TYPE, typeString(df.getTypeClass()));
613                 client.setPathInfo(pp.path());
614                 client.setQueryParams(pp.query());
615                 client.setFragment(pp.frag());  
616                 client.setPayload(new EClient.Transfer() {
617                         @Override
618                         public void transfer(OutputStream os) throws IOException, APIException {
619                                 df.newData().out(type).direct(t,os);
620                         }
621                 });
622
623                 client.send();
624                 return client.future(t);
625         }
626
627
628         public<T> Future<T> delete(String pathinfo, Class<T> cls) throws APIException, CadiException {
629                 final ParsePath pp = new ParsePath(pathinfo);
630
631                 EClient<CT> client = client();
632                 client.setMethod(DELETE);
633                 client.addHeader(CONTENT_TYPE, typeString(cls));
634                 client.setPathInfo(pp.path());
635                 client.setQueryParams(pp.query());
636                 client.setFragment(pp.frag());  
637
638                 client.setPayload(null);
639                 client.send();
640                 return client.future((T)null);
641         }
642
643         public Future<Void> delete(String pathinfo, String contentType) throws APIException, CadiException {
644                 final ParsePath pp = new ParsePath(pathinfo);
645
646                 EClient<CT> client = client();
647                 client.setMethod(DELETE);
648                 client.addHeader(CONTENT_TYPE, contentType);
649                 client.setPathInfo(pp.path());
650                 client.setQueryParams(pp.query());
651                 client.setFragment(pp.frag());  
652
653                 client.setPayload(null);
654                 client.send();
655                 return client.future(null);
656         }
657
658         public Future<Void> transfer(final HttpServletRequest req, final HttpServletResponse resp, final String pathParam, final int expected) throws CadiException, APIException {
659                 EClient<CT> client = client();
660                 URI uri;
661                 try {
662                         uri = new URI(req.getRequestURI());
663                 } catch (Exception e) {
664                         throw new CadiException("Invalid incoming URI",e);
665                 }
666                 String name;
667                 for(Enumeration<String> en = req.getHeaderNames();en.hasMoreElements();) {
668                         name = en.nextElement();
669                         client.addHeader(name,req.getHeader(name));
670                 }
671                 client.setQueryParams(req.getQueryString());
672                 client.setFragment(uri.getFragment());
673                 client.setPathInfo(pathParam);
674                 String meth = req.getMethod();
675                 client.setMethod(meth);
676                 if(!"GET".equals(meth)) {
677                         client.setPayload(new EClient.Transfer() {
678                                 @Override
679                                 public void transfer(OutputStream os) throws IOException, APIException {
680                                         final ServletInputStream is = req.getInputStream();
681                                         int read;
682                                         // reuse Buffers
683                                         Pooled<byte[]> pbuff = buffPool.get();
684                                         try { 
685                                                 while((read=is.read(pbuff.content))>=0) {
686                                                         os.write(pbuff.content,0,read);
687                                                 }
688                                         } finally {
689                                                 pbuff.done();
690                                         }
691                                 }
692                         });
693                 }
694                 client.send();
695                 return client.future(resp, expected);
696         }
697
698         private class ParsePath {
699                 private final String path;
700                 private final int query;
701                 private final int queryEnd;
702                 private final int pound;
703                 private final String queryParams;
704
705                 public ParsePath(final String origPath) {
706                         path = origPath;
707                         if(origPath==null) {
708                                 query=queryEnd=pound=-1;
709                                 queryParams=null;
710                         } else {
711                                 query = origPath.indexOf('?');
712                                 pound = origPath.indexOf('#');
713                                 queryEnd = pound>=0?pound:path.length();
714                                 if(oneCallQueryParams==null) {
715                                         if(query>=0) {
716                                                 queryParams = path.substring(query+1,queryEnd); 
717                                         } else {
718                                                 queryParams=null;
719                                         }
720                                 } else {
721                                         if(query>=0) {
722                                                 queryParams = oneCallQueryParams + '&' + path.substring(query+1,queryEnd); 
723                                         } else {
724                                                 queryParams = oneCallQueryParams;
725                                         }
726                                         oneCallQueryParams = null;
727                                 }
728                         }
729                 }
730                 
731                 public String path() {
732                         if(query>=0) {
733                                 if(pound>=0) {
734                                         return path.substring(pound+1);
735                                 }
736                                 return path.substring(0,query);
737                         } else if(pound>=0) {
738                                 return path.substring(0,pound);
739                         } else {
740                                 return path;
741                         }
742                 }
743                 
744                 public String query() {
745                         return queryParams;
746                 }
747                 
748                 public String frag() {
749                         if(pound>=0) {
750                                 return path.substring(pound+1);
751                         } else {
752                                 return null;
753                         }
754                 }
755         }
756
757         public String toString() {
758                 return uri.toString();
759         }
760
761         public URI getURI() {
762                 return uri;
763         }
764
765         public void setQueryParams(final String queryParams) {
766                 oneCallQueryParams=queryParams;
767         }
768
769 }