View Javadoc

1   /*
2    * Copyright 2011 Vincent Behar
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.rundeck.api;
17  
18  import java.io.File;
19  import java.io.InputStream;
20  import java.nio.charset.Charset;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Properties;
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.http.NameValuePair;
30  import org.apache.http.message.BasicNameValuePair;
31  import org.dom4j.Document;
32  import org.rundeck.api.generator.XmlDocumentGenerator;
33  import org.rundeck.api.util.ParametersUtil;
34  
35  /**
36   * Builder for API paths
37   *
38   * @author Vincent Behar
39   */
40  class ApiPathBuilder {
41  
42      /** Internally, we store everything in a {@link StringBuilder} */
43      private final StringBuilder apiPath;
44  
45      private String accept="text/xml";
46  
47      /** When POSTing, we can add attachments */
48      private final Map<String, InputStream> attachments;
49      private final List<NameValuePair> form = new ArrayList<NameValuePair>();
50      private Document xmlDocument;
51      private InputStream contentStream;
52      private byte[] contents;
53      private File contentFile;
54      private String contentType;
55      private String requiredContentType;
56      private boolean emptyContent = false;
57  
58      /** Marker for using the right separator between parameters ("?" or "&") */
59      private boolean firstParamDone = false;
60  
61      /**
62       * Build a new instance, for the given "path" (the "path" is the part before the parameters. The path and the
63       * parameters are separated by a "?")
64       *
65       * @param paths elements of the path
66       */
67      public ApiPathBuilder(String... paths) {
68          apiPath = new StringBuilder();
69          attachments = new HashMap<String, InputStream>();
70          paths(paths);
71      }
72  
73      public ApiPathBuilder paths(String... paths) {
74          if (paths != null) {
75              for (String path : paths) {
76                  if (StringUtils.isNotBlank(path)) {
77                      append(path);
78                  }
79              }
80          }
81          return this;
82      }
83  
84      /**
85       * Set the accept header
86       */
87      public ApiPathBuilder accept(String mimeTypes) {
88          accept=mimeTypes;
89          return this;
90      }
91      /**
92       * Visit a {@link BuildsParameters} and add the parameters
93       */
94      public ApiPathBuilder param(BuildsParameters params) {
95          params.buildParameters(this, false);
96          return this;
97      }
98      /**
99       * Visit a {@link BuildsParameters} and add the parameters
100      */
101     public ApiPathBuilder field(BuildsParameters params) {
102         params.buildParameters(this, true);
103         return this;
104     }
105     /**
106      * Append the given parameter (key and value). This will only append the parameter if it is not blank (null, empty
107      * or whitespace), and make sure to add the right separator ("?" or "&") before. The key and value will be separated
108      * by the "=" character. Also, the value will be url-encoded.
109      *
110      * @param key of the parameter. Must not be null or empty
111      * @param value of the parameter. May be null/empty/blank. Will be url-encoded.
112      * @return this, for method chaining
113      */
114     public ApiPathBuilder param(String key, String value) {
115         if (StringUtils.isNotBlank(value)) {
116             appendSeparator();
117             append(key);
118             append("=");
119             append(ParametersUtil.urlEncode(value));
120         }
121         return this;
122     }
123 
124     /**
125      * Append the given parameter (key and value). This will only append the parameter if it is not blank (null, empty
126      * or whitespace), and make sure to add the right separator ("?" or "&") before. The key and value will be separated
127      * by the "=" character. Also, the value will be url-encoded.
128      *
129      * @param key of the parameter. Must not be null or empty
130      * @param value of the parameter. May be null/empty/blank. Will be url-encoded.
131      * @return this, for method chaining
132      */
133     public ApiPathBuilder param(final String key, final Collection<String> values) {
134         for(final String value: values){
135             if (StringUtils.isNotBlank(value)) {
136                 appendSeparator();
137                 append(key);
138                 append("=");
139                 append(ParametersUtil.urlEncode(value));
140             }
141         }
142         return this;
143     }
144 
145     /**
146      * Append multiple values for the given Form field. This will be appended if it is not blank (null, empty
147      * or whitespace).  The form field values will only be used for a "post" request
148      *
149      * @param key of the field name. Must not be null or empty
150      * @param values of the field. May be null/empty/blank. Will be url-encoded.
151      * @return this, for method chaining
152      */
153     public ApiPathBuilder field(final String key, final Collection<String> values) {
154         if (null!=values) {
155             for(final String value: values){
156                 if (StringUtils.isNotBlank(value)) {
157                     form.add(new BasicNameValuePair(key, value));
158                 }
159             }
160         }
161         return this;
162     }
163 
164     /**
165      * Append a single value for the given Form field. This will be appended if it is not blank (null, empty
166      * or whitespace).  The form field values will only be used for a "post" request
167      *
168      * @param key of the field name. Must not be null or empty
169      * @param value of the field. May be null/empty/blank. Will be url-encoded.
170      * @return this, for method chaining
171      */
172     public ApiPathBuilder field(final String key, final String value) {
173         if (StringUtils.isNotBlank(value)) {
174             form.add(new BasicNameValuePair(key, value));
175         }
176         return this;
177     }
178 
179     /**
180      * Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
181      * to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character. Also,
182      * the value will be converted to lower-case.
183      *
184      * @param key of the parameter. Must not be null or empty
185      * @param value of the parameter. May be null
186      * @return this, for method chaining
187      */
188     public ApiPathBuilder param(String key, Enum<?> value) {
189         if (value != null) {
190             param(key, StringUtils.lowerCase(value.toString()));
191         }
192         return this;
193     }
194 
195     /**
196      * Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
197      * to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character.
198      *
199      * @param key of the parameter. Must not be null or empty
200      * @param value of the parameter. May be null
201      * @return this, for method chaining
202      */
203     public ApiPathBuilder param(String key, Date value) {
204         if (value != null) {
205             param(key, value.getTime());
206         }
207         return this;
208     }
209 
210     /**
211      * Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
212      * to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character.
213      *
214      * @param key of the parameter. Must not be null or empty
215      * @param value of the parameter. May be null
216      * @return this, for method chaining
217      */
218     public ApiPathBuilder param(String key, Long value) {
219         if (value != null) {
220             param(key, value.toString());
221         }
222         return this;
223     }
224 
225     /**
226      * Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
227      * to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character.
228      *
229      * @param key of the parameter. Must not be null or empty
230      * @param value of the parameter. May be null
231      * @return this, for method chaining
232      */
233     public ApiPathBuilder param(String key, Integer value) {
234         if (value != null) {
235             param(key, value.toString());
236         }
237         return this;
238     }
239 
240     /**
241      * Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
242      * to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character.
243      *
244      * @param key of the parameter. Must not be null or empty
245      * @param value of the parameter. May be null
246      * @return this, for method chaining
247      */
248     public ApiPathBuilder param(String key, Boolean value) {
249         if (value != null) {
250             param(key, value.toString());
251         }
252         return this;
253     }
254 
255     /**
256      * Append the given node filters, only if it is not null/empty
257      *
258      * @param nodeFilters may be null/empty
259      * @return this, for method chaining
260      * @see ParametersUtil#generateNodeFiltersString(Properties)
261      */
262     public ApiPathBuilder nodeFilters(Properties nodeFilters) {
263         String filters = ParametersUtil.generateNodeFiltersString(nodeFilters);
264         if (StringUtils.isNotBlank(filters)) {
265             appendSeparator();
266             append(filters);
267         }
268         return this;
269     }
270 
271     /**
272      * When POSTing a request, add the given {@link InputStream} as an attachment to the content of the request. This
273      * will only add the stream if it is not null.
274      *
275      * @param name of the attachment. Must not be null or empty
276      * @param stream. May be null
277      * @return this, for method chaining
278      */
279     public ApiPathBuilder attach(String name, InputStream stream) {
280         if (stream != null) {
281             attachments.put(name, stream);
282         }
283         return this;
284     }
285     /**
286      * When POSTing a request, use the given {@link InputStream} as the content of the request. This
287      * will only add the stream if it is not null.
288      *
289      * @param contentType MIME content type ofr hte request
290      * @param stream content stream
291      * @return this, for method chaining
292      */
293     public ApiPathBuilder content(final String contentType, final InputStream stream) {
294         if (stream != null && contentType != null) {
295             this.contentStream=stream;
296             this.contentType=contentType;
297         }
298         return this;
299     }
300     /**
301      * When POSTing a request, use the given {@link File} as the content of the request. This
302      * will only add the stream if it is not null.
303      *
304      * @param contentType MIME content type ofr hte request
305      * @param file content from a file
306      * @return this, for method chaining
307      */
308     public ApiPathBuilder content(final String contentType, final File file) {
309         if (file != null && contentType != null) {
310             this.contentFile=file;
311             this.contentType=contentType;
312         }
313         return this;
314     }
315     /**
316      * When POSTing a request, use the given data as the content of the request. This
317      * will only add the stream if it is not null.
318      *
319      * @param contentType MIME content type ofr hte request
320      * @param contents content
321      * @return this, for method chaining
322      */
323     public ApiPathBuilder content(final String contentType, final byte[] contents) {
324         if (contents != null && contentType != null) {
325             this.contents=contents;
326             this.contentType=contentType;
327         }
328         return this;
329     }
330     /**
331      * When POSTing a request, use the given string as the content of the request. This
332      * will only add the stream if it is not null.
333      *
334      * @param contentType MIME content type ofr hte request
335      * @param contents content
336      * @return this, for method chaining
337      */
338     public ApiPathBuilder content(final String contentType, final String contents) {
339         return content(contentType, contents.getBytes(Charset.forName("UTF-8")));
340     }
341     /**
342      * When POSTing a request, send an empty request.
343      *
344      * @return this, for method chaining
345      */
346     public ApiPathBuilder emptyContent() {
347         this.emptyContent=true;
348         return this;
349     }
350     /**
351      * When POSTing a request, add the given XMl Document as the content of the request.
352      *
353      * @param document XMl document to send
354      * @return this, for method chaining
355      */
356     public ApiPathBuilder xml(final Document document) {
357         if (document != null) {
358             xmlDocument = document;
359         }
360         return this;
361     }
362     /**
363      * When POSTing a request, add the given XMl Document as the content of the request.
364      *
365      * @param document XMl document to send
366      * @return this, for method chaining
367      */
368     public ApiPathBuilder xml(final XmlDocumentGenerator document) {
369         if (document != null) {
370             xmlDocument = document.generateXmlDocument();
371         }
372         return this;
373     }
374 
375     /**
376      * @return all attachments to be POSTed, with their names
377      */
378     public Map<String, InputStream> getAttachments() {
379         return attachments;
380     }
381 
382     @Override
383     public String toString() {
384         return apiPath.toString();
385     }
386 
387     /**
388      * Append the given string
389      *
390      * @param str to append
391      */
392     private void append(String str) {
393         apiPath.append(str);
394     }
395 
396     /**
397      * Append the right separator "?" or "&" between 2 parameters
398      */
399     private void appendSeparator() {
400         if (firstParamDone) {
401             append("&");
402         } else {
403             append("?");
404             firstParamDone = true;
405         }
406     }
407 
408     /**
409      * Form fields for POST request
410      */
411     public List<NameValuePair> getForm() {
412         return form;
413     }
414 
415     /**
416      * Return true if there are any Attachments or Form data for a POST request.
417      */
418     public boolean hasPostContent() {
419         return getAttachments().size() > 0 || getForm().size() > 0 || null != xmlDocument;
420     }
421 
422     /**
423      * Accept header value, default "text/xml"
424      */
425     public String getAccept() {
426         return accept;
427     }
428 
429     public Document getXmlDocument() {
430         return xmlDocument;
431     }
432 
433     public InputStream getContentStream() {
434         return contentStream;
435     }
436 
437     public String getContentType() {
438         return contentType;
439     }
440 
441     public File getContentFile() {
442         return contentFile;
443     }
444 
445     public boolean isEmptyContent() {
446         return emptyContent;
447     }
448 
449     public ApiPathBuilder requireContentType(String contentType) {
450         this.requiredContentType=contentType;
451         return this;
452     }
453 
454     public String getRequiredContentType() {
455         return requiredContentType;
456     }
457 
458     public byte[] getContents() {
459         return contents;
460     }
461 
462     /**
463      * BuildsParameters can add URL or POST parameters to an {@link ApiPathBuilder}
464      *
465      * @author Greg Schueler <a href="mailto:greg@dtosolutions.com">greg@dtosolutions.com</a>
466      */
467     public static interface BuildsParameters {
468         /**
469          * Add the parameters or form fields to the ApiPathBuilder
470          *
471          * @param builder the builder
472          * @param doPost  if true, use form fields, otherwise use query parameters
473          */
474         public boolean buildParameters(ApiPathBuilder builder, boolean doPost);
475     }
476 }