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.FileInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.Serializable;
23  import java.util.ArrayList;
24  import java.util.Date;
25  import java.util.List;
26  import java.util.Properties;
27  import java.util.concurrent.TimeUnit;
28  import org.apache.commons.io.FileUtils;
29  import org.apache.commons.io.IOUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
32  import org.rundeck.api.RundeckApiException.RundeckApiTokenException;
33  import org.rundeck.api.domain.RundeckAbort;
34  import org.rundeck.api.domain.RundeckExecution;
35  import org.rundeck.api.domain.RundeckHistory;
36  import org.rundeck.api.domain.RundeckJob;
37  import org.rundeck.api.domain.RundeckJobsImportMethod;
38  import org.rundeck.api.domain.RundeckJobsImportResult;
39  import org.rundeck.api.domain.RundeckNode;
40  import org.rundeck.api.domain.RundeckProject;
41  import org.rundeck.api.domain.RundeckSystemInfo;
42  import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
43  import org.rundeck.api.parser.AbortParser;
44  import org.rundeck.api.parser.ExecutionParser;
45  import org.rundeck.api.parser.HistoryParser;
46  import org.rundeck.api.parser.JobParser;
47  import org.rundeck.api.parser.JobsImportResultParser;
48  import org.rundeck.api.parser.ListParser;
49  import org.rundeck.api.parser.NodeParser;
50  import org.rundeck.api.parser.ProjectParser;
51  import org.rundeck.api.parser.StringParser;
52  import org.rundeck.api.parser.SystemInfoParser;
53  import org.rundeck.api.util.AssertUtil;
54  import org.rundeck.api.util.ParametersUtil;
55  
56  /**
57   * Main entry point to talk to a RunDeck instance.<br>
58   * You have 2 methods for authentication : login-based or token-based. If you want to use the first, you need to provide
59   * both a "login" and a "password". Otherwise, just provide a "token" (also called "auth-token"). See the RunDeck
60   * documentation for generating such a token.<br>
61   * <br>
62   * Usage : <br>
63   * <code>
64   * <pre>
65   * // using login-based authentication :
66   * RundeckClient rundeck = new RundeckClient("http://localhost:4440", "admin", "admin");
67   * // or for a token-based authentication :
68   * RundeckClient rundeck = new RundeckClient("http://localhost:4440", "PDDNKo5VE29kpk4prOUDr2rsKdRkEvsD");
69   * 
70   * List&lt;RundeckProject&gt; projects = rundeck.getProjects();
71   * 
72   * RundeckJob job = rundeck.findJob("my-project", "main-group/sub-group", "job-name");
73   * RundeckExecution execution = rundeck.triggerJob(job.getId(),
74   *                                                 new OptionsBuilder().addOption("version", "1.2.0").toProperties());
75   * 
76   * List&lt;RundeckExecution&gt; runningExecutions = rundeck.getRunningExecutions("my-project");
77   * 
78   * rundeck.exportJobsToFile("/tmp/jobs.xml", FileType.XML, "my-project");
79   * rundeck.importJobs("/tmp/jobs.xml", FileType.XML);
80   * </pre>
81   * </code>
82   * 
83   * @author Vincent Behar
84   */
85  public class RundeckClient implements Serializable {
86  
87      private static final long serialVersionUID = 1L;
88  
89      /** Version of the API supported */
90      public static final transient int API_VERSION = 2;
91  
92      /** End-point of the API */
93      public static final transient String API_ENDPOINT = "/api/" + API_VERSION;
94  
95      /** Default value for the "pooling interval" used when running jobs/commands/scripts */
96      private static final transient long DEFAULT_POOLING_INTERVAL = 5;
97  
98      /** Default unit of the "pooling interval" used when running jobs/commands/scripts */
99      private static final transient TimeUnit DEFAULT_POOLING_UNIT = TimeUnit.SECONDS;
100 
101     /** URL of the RunDeck instance ("http://localhost:4440" target="alexandria_uri">http://localhost:4440", "http://rundeck.your-compagny.com/", etc) */
102     private final String url;
103 
104     /** Auth-token for authentication (if not using login-based auth) */
105     private final String token;
106 
107     /** Login to use for authentication on the RunDeck instance (if not using token-based auth) */
108     private final String login;
109 
110     /** Password to use for authentication on the RunDeck instance (if not using token-based auth) */
111     private final String password;
112 
113     /**
114      * Instantiate a new {@link RundeckClient} for the RunDeck instance at the given url, using login-based
115      * authentication.
116      * 
117      * @param url of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc)
118      * @param login to use for authentication on the RunDeck instance
119      * @param password to use for authentication on the RunDeck instance
120      * @throws IllegalArgumentException if the url, login or password is blank (null, empty or whitespace)
121      */
122     public RundeckClient(String url, String login, String password) throws IllegalArgumentException {
123         super();
124         AssertUtil.notBlank(url, "The RunDeck URL is mandatory !");
125         AssertUtil.notBlank(login, "The RunDeck login is mandatory !");
126         AssertUtil.notBlank(password, "The RunDeck password is mandatory !");
127         this.url = url;
128         this.login = login;
129         this.password = password;
130         this.token = null;
131     }
132 
133     /**
134      * Instantiate a new {@link RundeckClient} for the RunDeck instance at the given url, using token-based
135      * authentication.
136      * 
137      * @param url of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc)
138      * @param token to use for authentication on the RunDeck instance
139      * @throws IllegalArgumentException if the url or token is blank (null, empty or whitespace)
140      */
141     public RundeckClient(String url, String token) throws IllegalArgumentException {
142         super();
143         AssertUtil.notBlank(url, "The RunDeck URL is mandatory !");
144         AssertUtil.notBlank(token, "The RunDeck auth-token is mandatory !");
145         this.url = url;
146         this.token = token;
147         this.login = null;
148         this.password = null;
149     }
150 
151     /**
152      * Try to "ping" the RunDeck instance to see if it is alive
153      * 
154      * @throws RundeckApiException if the ping fails
155      */
156     public void ping() throws RundeckApiException {
157         new ApiCall(this).ping();
158     }
159 
160     /**
161      * Test the authentication on the RunDeck instance.
162      * 
163      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
164      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
165      */
166     public void testAuth() throws RundeckApiLoginException, RundeckApiTokenException {
167         new ApiCall(this).testAuth();
168     }
169 
170     /**
171      * @deprecated Use {@link #testAuth()}
172      * @see #testAuth()
173      */
174     @Deprecated
175     public void testCredentials() throws RundeckApiLoginException, RundeckApiTokenException {
176         testAuth();
177     }
178 
179     /*
180      * Projects
181      */
182 
183     /**
184      * List all projects
185      * 
186      * @return a {@link List} of {@link RundeckProject} : might be empty, but won't be null
187      * @throws RundeckApiException in case of error when calling the API
188      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
189      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
190      */
191     public List<RundeckProject> getProjects() throws RundeckApiException, RundeckApiLoginException,
192             RundeckApiTokenException {
193         return new ApiCall(this).get(new ApiPathBuilder("/projects"),
194                                      new ListParser<RundeckProject>(new ProjectParser(), "result/projects/project"));
195     }
196 
197     /**
198      * Get the definition of a single project, identified by the given name
199      * 
200      * @param projectName name of the project - mandatory
201      * @return a {@link RundeckProject} instance - won't be null
202      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
203      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
204      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
205      * @throws IllegalArgumentException if the projectName is blank (null, empty or whitespace)
206      */
207     public RundeckProject getProject(String projectName) throws RundeckApiException, RundeckApiLoginException,
208             RundeckApiTokenException, IllegalArgumentException {
209         AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !");
210         return new ApiCall(this).get(new ApiPathBuilder("/project/", projectName),
211                                      new ProjectParser("result/projects/project"));
212     }
213 
214     /*
215      * Jobs
216      */
217 
218     /**
219      * List all jobs (for all projects)
220      * 
221      * @return a {@link List} of {@link RundeckJob} : might be empty, but won't be null
222      * @throws RundeckApiException in case of error when calling the API
223      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
224      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
225      */
226     public List<RundeckJob> getJobs() throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException {
227         List<RundeckJob> jobs = new ArrayList<RundeckJob>();
228         for (RundeckProject project : getProjects()) {
229             jobs.addAll(getJobs(project.getName()));
230         }
231         return jobs;
232     }
233 
234     /**
235      * List all jobs that belongs to the given project
236      * 
237      * @param project name of the project - mandatory
238      * @return a {@link List} of {@link RundeckJob} : might be empty, but won't be null
239      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
240      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
241      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
242      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
243      * @see #getJobs(String, String, String, String...)
244      */
245     public List<RundeckJob> getJobs(String project) throws RundeckApiException, RundeckApiLoginException,
246             RundeckApiTokenException, IllegalArgumentException {
247         return getJobs(project, null, null, new String[0]);
248     }
249 
250     /**
251      * List the jobs that belongs to the given project, and matches the given criteria (jobFilter, groupPath and jobIds)
252      * 
253      * @param project name of the project - mandatory
254      * @param jobFilter a filter for the job Name - optional
255      * @param groupPath a group or partial group path to include all jobs within that group path - optional
256      * @param jobIds a list of Job IDs to include - optional
257      * @return a {@link List} of {@link RundeckJob} : might be empty, but won't be null
258      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
259      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
260      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
261      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
262      * @see #getJobs(String)
263      */
264     public List<RundeckJob> getJobs(String project, String jobFilter, String groupPath, String... jobIds)
265             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
266         AssertUtil.notBlank(project, "project is mandatory to get all jobs !");
267         return new ApiCall(this).get(new ApiPathBuilder("/jobs").param("project", project)
268                                                                 .param("jobFilter", jobFilter)
269                                                                 .param("groupPath", groupPath)
270                                                                 .param("idlist", StringUtils.join(jobIds, ",")),
271                                      new ListParser<RundeckJob>(new JobParser(), "result/jobs/job"));
272     }
273 
274     /**
275      * Export the definitions of all jobs that belongs to the given project
276      * 
277      * @param filename path of the file where the content should be saved - mandatory
278      * @param format of the export. See {@link FileType} - mandatory
279      * @param project name of the project - mandatory
280      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
281      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
282      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
283      * @throws IllegalArgumentException if the format or project is blank (null, empty or whitespace), or the format is
284      *             invalid
285      * @throws IOException if we failed to write to the file
286      * @see #exportJobsToFile(String, FileType, String, String, String, String...)
287      * @see #exportJobs(String, String)
288      */
289     public void exportJobsToFile(String filename, String format, String project) throws RundeckApiException,
290             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
291         AssertUtil.notBlank(format, "format is mandatory to export jobs !");
292         exportJobsToFile(filename, FileType.valueOf(StringUtils.upperCase(format)), project);
293     }
294 
295     /**
296      * Export the definitions of all jobs that belongs to the given project
297      * 
298      * @param filename path of the file where the content should be saved - mandatory
299      * @param format of the export. See {@link FileType} - mandatory
300      * @param project name of the project - mandatory
301      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
302      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
303      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
304      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the format is null
305      * @throws IOException if we failed to write to the file
306      * @see #exportJobsToFile(String, FileType, String, String, String, String...)
307      * @see #exportJobs(FileType, String)
308      */
309     public void exportJobsToFile(String filename, FileType format, String project) throws RundeckApiException,
310             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
311         exportJobsToFile(filename, format, project, null, null, new String[0]);
312     }
313 
314     /**
315      * Export the definitions of the jobs that belongs to the given project, and matches the given criteria (jobFilter,
316      * groupPath and jobIds)
317      * 
318      * @param filename path of the file where the content should be saved - mandatory
319      * @param format of the export. See {@link FileType} - mandatory
320      * @param project name of the project - mandatory
321      * @param jobFilter a filter for the job Name - optional
322      * @param groupPath a group or partial group path to include all jobs within that group path - optional
323      * @param jobIds a list of Job IDs to include - optional
324      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
325      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
326      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
327      * @throws IllegalArgumentException if the filename, format or project is blank (null, empty or whitespace), or the
328      *             format is invalid
329      * @throws IOException if we failed to write to the file
330      * @see #exportJobsToFile(String, FileType, String, String, String, String...)
331      * @see #exportJobs(FileType, String, String, String, String...)
332      */
333     public void exportJobsToFile(String filename, String format, String project, String jobFilter, String groupPath,
334             String... jobIds) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
335             IllegalArgumentException, IOException {
336         AssertUtil.notBlank(format, "format is mandatory to export jobs !");
337         exportJobsToFile(filename,
338                          FileType.valueOf(StringUtils.upperCase(format)),
339                          project,
340                          jobFilter,
341                          groupPath,
342                          jobIds);
343     }
344 
345     /**
346      * Export the definitions of the jobs that belongs to the given project, and matches the given criteria (jobFilter,
347      * groupPath and jobIds)
348      * 
349      * @param filename path of the file where the content should be saved - mandatory
350      * @param format of the export. See {@link FileType} - mandatory
351      * @param project name of the project - mandatory
352      * @param jobFilter a filter for the job Name - optional
353      * @param groupPath a group or partial group path to include all jobs within that group path - optional
354      * @param jobIds a list of Job IDs to include - optional
355      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
356      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
357      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
358      * @throws IllegalArgumentException if the filename or project is blank (null, empty or whitespace), or the format
359      *             is null
360      * @throws IOException if we failed to write to the file
361      * @see #exportJobs(FileType, String, String, String, String...)
362      */
363     public void exportJobsToFile(String filename, FileType format, String project, String jobFilter, String groupPath,
364             String... jobIds) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
365             IllegalArgumentException, IOException {
366         AssertUtil.notBlank(filename, "filename is mandatory to export a job !");
367         InputStream inputStream = exportJobs(format, project, jobFilter, groupPath, jobIds);
368         FileUtils.writeByteArrayToFile(new File(filename), IOUtils.toByteArray(inputStream));
369     }
370 
371     /**
372      * Export the definitions of all jobs that belongs to the given project
373      * 
374      * @param format of the export. See {@link FileType} - mandatory
375      * @param project name of the project - mandatory
376      * @return an {@link InputStream} instance, not linked to any network resources - won't be null
377      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
378      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
379      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
380      * @throws IllegalArgumentException if the format or project is blank (null, empty or whitespace), or the format is
381      *             invalid
382      * @see #exportJobs(FileType, String, String, String, String...)
383      * @see #exportJobsToFile(String, String, String)
384      */
385     public InputStream exportJobs(String format, String project) throws RundeckApiException, RundeckApiLoginException,
386             RundeckApiTokenException, IllegalArgumentException {
387         AssertUtil.notBlank(format, "format is mandatory to export jobs !");
388         return exportJobs(FileType.valueOf(StringUtils.upperCase(format)), project);
389     }
390 
391     /**
392      * Export the definitions of all jobs that belongs to the given project
393      * 
394      * @param format of the export. See {@link FileType} - mandatory
395      * @param project name of the project - mandatory
396      * @return an {@link InputStream} instance, not linked to any network resources - won't be null
397      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
398      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
399      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
400      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the format is null
401      * @see #exportJobs(FileType, String, String, String, String...)
402      * @see #exportJobsToFile(String, FileType, String)
403      */
404     public InputStream exportJobs(FileType format, String project) throws RundeckApiException,
405             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
406         return exportJobs(format, project, null, null, new String[0]);
407     }
408 
409     /**
410      * Export the definitions of the jobs that belongs to the given project, and matches the given criteria (jobFilter,
411      * groupPath and jobIds)
412      * 
413      * @param format of the export. See {@link FileType} - mandatory
414      * @param project name of the project - mandatory
415      * @param jobFilter a filter for the job Name - optional
416      * @param groupPath a group or partial group path to include all jobs within that group path - optional
417      * @param jobIds a list of Job IDs to include - optional
418      * @return an {@link InputStream} instance, not linked to any network resources - won't be null
419      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
420      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
421      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
422      * @throws IllegalArgumentException if the format or project is blank (null, empty or whitespace), or the format is
423      *             invalid
424      * @see #exportJobs(FileType, String, String, String, String...)
425      * @see #exportJobsToFile(String, String, String, String, String, String...)
426      */
427     public InputStream exportJobs(String format, String project, String jobFilter, String groupPath, String... jobIds)
428             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
429         AssertUtil.notBlank(format, "format is mandatory to export jobs !");
430         return exportJobs(FileType.valueOf(StringUtils.upperCase(format)), project, jobFilter, groupPath, jobIds);
431     }
432 
433     /**
434      * Export the definitions of the jobs that belongs to the given project, and matches the given criteria (jobFilter,
435      * groupPath and jobIds)
436      * 
437      * @param format of the export. See {@link FileType} - mandatory
438      * @param project name of the project - mandatory
439      * @param jobFilter a filter for the job Name - optional
440      * @param groupPath a group or partial group path to include all jobs within that group path - optional
441      * @param jobIds a list of Job IDs to include - optional
442      * @return an {@link InputStream} instance, not linked to any network resources - won't be null
443      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
444      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
445      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
446      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the format is null
447      * @see #exportJobsToFile(String, FileType, String, String, String, String...)
448      */
449     public InputStream exportJobs(FileType format, String project, String jobFilter, String groupPath, String... jobIds)
450             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
451         AssertUtil.notNull(format, "format is mandatory to export jobs !");
452         AssertUtil.notBlank(project, "project is mandatory to export jobs !");
453         return new ApiCall(this).get(new ApiPathBuilder("/jobs/export").param("format", format)
454                                                                        .param("project", project)
455                                                                        .param("jobFilter", jobFilter)
456                                                                        .param("groupPath", groupPath)
457                                                                        .param("idlist", StringUtils.join(jobIds, ",")));
458     }
459 
460     /**
461      * Export the definition of a single job (identified by the given ID)
462      * 
463      * @param filename path of the file where the content should be saved - mandatory
464      * @param format of the export. See {@link FileType} - mandatory
465      * @param jobId identifier of the job - mandatory
466      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
467      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
468      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
469      * @throws IllegalArgumentException if the filename, format or jobId is blank (null, empty or whitespace), or the
470      *             format is invalid
471      * @throws IOException if we failed to write to the file
472      * @see #exportJobToFile(String, FileType, String)
473      * @see #exportJob(String, String)
474      * @see #getJob(String)
475      */
476     public void exportJobToFile(String filename, String format, String jobId) throws RundeckApiException,
477             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
478         AssertUtil.notBlank(format, "format is mandatory to export a job !");
479         exportJobToFile(filename, FileType.valueOf(StringUtils.upperCase(format)), jobId);
480     }
481 
482     /**
483      * Export the definition of a single job (identified by the given ID)
484      * 
485      * @param filename path of the file where the content should be saved - mandatory
486      * @param format of the export. See {@link FileType} - mandatory
487      * @param jobId identifier of the job - mandatory
488      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
489      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
490      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
491      * @throws IllegalArgumentException if the filename or jobId is blank (null, empty or whitespace), or the format is
492      *             null
493      * @throws IOException if we failed to write to the file
494      * @see #exportJob(FileType, String)
495      * @see #getJob(String)
496      */
497     public void exportJobToFile(String filename, FileType format, String jobId) throws RundeckApiException,
498             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
499         AssertUtil.notBlank(filename, "filename is mandatory to export a job !");
500         InputStream inputStream = exportJob(format, jobId);
501         FileUtils.writeByteArrayToFile(new File(filename), IOUtils.toByteArray(inputStream));
502     }
503 
504     /**
505      * Export the definition of a single job, identified by the given ID
506      * 
507      * @param format of the export. See {@link FileType} - mandatory
508      * @param jobId identifier of the job - mandatory
509      * @return an {@link InputStream} instance, not linked to any network resources - won't be null
510      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
511      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
512      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
513      * @throws IllegalArgumentException if the format or jobId is blank (null, empty or whitespace), or the format is
514      *             invalid
515      * @see #exportJobToFile(String, String, String)
516      * @see #getJob(String)
517      */
518     public InputStream exportJob(String format, String jobId) throws RundeckApiException, RundeckApiLoginException,
519             RundeckApiTokenException, IllegalArgumentException {
520         AssertUtil.notBlank(format, "format is mandatory to export a job !");
521         return exportJob(FileType.valueOf(StringUtils.upperCase(format)), jobId);
522     }
523 
524     /**
525      * Export the definition of a single job, identified by the given ID
526      * 
527      * @param format of the export. See {@link FileType} - mandatory
528      * @param jobId identifier of the job - mandatory
529      * @return an {@link InputStream} instance, not linked to any network resources - won't be null
530      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
531      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
532      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
533      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace), or the format is null
534      * @see #exportJobToFile(String, FileType, String)
535      * @see #getJob(String)
536      */
537     public InputStream exportJob(FileType format, String jobId) throws RundeckApiException, RundeckApiLoginException,
538             RundeckApiTokenException, IllegalArgumentException {
539         AssertUtil.notNull(format, "format is mandatory to export a job !");
540         AssertUtil.notBlank(jobId, "jobId is mandatory to export a job !");
541         return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId).param("format", format));
542     }
543 
544     /**
545      * Import the definitions of jobs, from the given file
546      * 
547      * @param filename of the file containing the jobs definitions - mandatory
548      * @param fileType type of the file. See {@link FileType} - mandatory
549      * @return a {@link RundeckJobsImportResult} instance - won't be null
550      * @throws RundeckApiException in case of error when calling the API
551      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
552      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
553      * @throws IllegalArgumentException if the filename or fileType is blank (null, empty or whitespace), or the
554      *             fileType is invalid
555      * @throws IOException if we failed to read the file
556      * @see #importJobs(InputStream, String)
557      * @see #importJobs(String, FileType, RundeckJobsImportMethod)
558      */
559     public RundeckJobsImportResult importJobs(String filename, String fileType) throws RundeckApiException,
560             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
561         AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
562         return importJobs(filename, FileType.valueOf(StringUtils.upperCase(fileType)));
563     }
564 
565     /**
566      * Import the definitions of jobs, from the given file
567      * 
568      * @param filename of the file containing the jobs definitions - mandatory
569      * @param fileType type of the file. See {@link FileType} - mandatory
570      * @return a {@link RundeckJobsImportResult} instance - won't be null
571      * @throws RundeckApiException in case of error when calling the API
572      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
573      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
574      * @throws IllegalArgumentException if the filename is blank (null, empty or whitespace), or the fileType is null
575      * @throws IOException if we failed to read the file
576      * @see #importJobs(InputStream, FileType)
577      * @see #importJobs(String, FileType, RundeckJobsImportMethod)
578      */
579     public RundeckJobsImportResult importJobs(String filename, FileType fileType) throws RundeckApiException,
580             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
581         return importJobs(filename, fileType, (RundeckJobsImportMethod) null);
582     }
583 
584     /**
585      * Import the definitions of jobs, from the given file, using the given behavior
586      * 
587      * @param filename of the file containing the jobs definitions - mandatory
588      * @param fileType type of the file. See {@link FileType} - mandatory
589      * @param importBehavior see {@link RundeckJobsImportMethod}
590      * @return a {@link RundeckJobsImportResult} instance - won't be null
591      * @throws RundeckApiException in case of error when calling the API
592      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
593      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
594      * @throws IllegalArgumentException if the filename or fileType is blank (null, empty or whitespace), or the
595      *             fileType or behavior is not valid
596      * @throws IOException if we failed to read the file
597      * @see #importJobs(InputStream, String, String)
598      * @see #importJobs(String, FileType, RundeckJobsImportMethod)
599      */
600     public RundeckJobsImportResult importJobs(String filename, String fileType, String importBehavior)
601             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException,
602             IOException {
603         AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
604         return importJobs(filename,
605                           FileType.valueOf(StringUtils.upperCase(fileType)),
606                           RundeckJobsImportMethod.valueOf(StringUtils.upperCase(importBehavior)));
607     }
608 
609     /**
610      * Import the definitions of jobs, from the given file, using the given behavior
611      * 
612      * @param filename of the file containing the jobs definitions - mandatory
613      * @param fileType type of the file. See {@link FileType} - mandatory
614      * @param importBehavior see {@link RundeckJobsImportMethod}
615      * @return a {@link RundeckJobsImportResult} instance - won't be null
616      * @throws RundeckApiException in case of error when calling the API
617      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
618      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
619      * @throws IllegalArgumentException if the filename is blank (null, empty or whitespace), or the fileType is null
620      * @throws IOException if we failed to read the file
621      * @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
622      */
623     public RundeckJobsImportResult importJobs(String filename, FileType fileType, RundeckJobsImportMethod importBehavior)
624             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException,
625             IOException {
626         AssertUtil.notBlank(filename, "filename (of jobs file) is mandatory to import jobs !");
627         FileInputStream stream = null;
628         try {
629             stream = FileUtils.openInputStream(new File(filename));
630             return importJobs(stream, fileType, importBehavior);
631         } finally {
632             IOUtils.closeQuietly(stream);
633         }
634     }
635 
636     /**
637      * Import the definitions of jobs, from the given input stream
638      * 
639      * @param stream inputStream for reading the definitions - mandatory
640      * @param fileType type of the file. See {@link FileType} - mandatory
641      * @return a {@link RundeckJobsImportResult} instance - won't be null
642      * @throws RundeckApiException in case of error when calling the API
643      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
644      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
645      * @throws IllegalArgumentException if the stream is null, or the fileType is blank (null, empty or whitespace) or
646      *             invalid
647      * @see #importJobs(String, String)
648      * @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
649      */
650     public RundeckJobsImportResult importJobs(InputStream stream, String fileType) throws RundeckApiException,
651             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
652         AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
653         return importJobs(stream, FileType.valueOf(StringUtils.upperCase(fileType)));
654     }
655 
656     /**
657      * Import the definitions of jobs, from the given input stream
658      * 
659      * @param stream inputStream for reading the definitions - mandatory
660      * @param fileType type of the file. See {@link FileType} - mandatory
661      * @return a {@link RundeckJobsImportResult} instance - won't be null
662      * @throws RundeckApiException in case of error when calling the API
663      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
664      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
665      * @throws IllegalArgumentException if the stream or fileType is null
666      * @see #importJobs(String, FileType)
667      * @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
668      */
669     public RundeckJobsImportResult importJobs(InputStream stream, FileType fileType) throws RundeckApiException,
670             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
671         return importJobs(stream, fileType, (RundeckJobsImportMethod) null);
672     }
673 
674     /**
675      * Import the definitions of jobs, from the given input stream, using the given behavior
676      * 
677      * @param stream inputStream for reading the definitions - mandatory
678      * @param fileType type of the file. See {@link FileType} - mandatory
679      * @param importBehavior see {@link RundeckJobsImportMethod}
680      * @return a {@link RundeckJobsImportResult} instance - won't be null
681      * @throws RundeckApiException in case of error when calling the API
682      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
683      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
684      * @throws IllegalArgumentException if the stream is null, or the fileType is blank (null, empty or whitespace), or
685      *             the fileType or behavior is not valid
686      * @see #importJobs(String, String, String)
687      * @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
688      */
689     public RundeckJobsImportResult importJobs(InputStream stream, String fileType, String importBehavior)
690             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
691         AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
692         return importJobs(stream,
693                           FileType.valueOf(StringUtils.upperCase(fileType)),
694                           RundeckJobsImportMethod.valueOf(StringUtils.upperCase(importBehavior)));
695     }
696 
697     /**
698      * Import the definitions of jobs, from the given input stream, using the given behavior
699      * 
700      * @param stream inputStream for reading the definitions - mandatory
701      * @param fileType type of the file. See {@link FileType} - mandatory
702      * @param importBehavior see {@link RundeckJobsImportMethod}
703      * @return a {@link RundeckJobsImportResult} instance - won't be null
704      * @throws RundeckApiException in case of error when calling the API
705      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
706      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
707      * @throws IllegalArgumentException if the stream or fileType is null
708      * @see #importJobs(String, FileType, RundeckJobsImportMethod)
709      */
710     public RundeckJobsImportResult importJobs(InputStream stream, FileType fileType,
711             RundeckJobsImportMethod importBehavior) throws RundeckApiException, RundeckApiLoginException,
712             RundeckApiTokenException, IllegalArgumentException {
713         AssertUtil.notNull(stream, "inputStream of jobs is mandatory to import jobs !");
714         AssertUtil.notNull(fileType, "fileType is mandatory to import jobs !");
715         return new ApiCall(this).post(new ApiPathBuilder("/jobs/import").param("format", fileType)
716                                                                         .param("dupeOption", importBehavior)
717                                                                         .attach("xmlBatch", stream),
718                                       new JobsImportResultParser("result"));
719     }
720 
721     /**
722      * Find a job, identified by its project, group and name. Note that the groupPath is optional, as a job does not
723      * need to belong to a group (either pass null, or an empty string).
724      * 
725      * @param project name of the project - mandatory
726      * @param groupPath group to which the job belongs (if it belongs to a group) - optional
727      * @param name of the job to find - mandatory
728      * @return a {@link RundeckJob} instance - null if not found
729      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
730      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
731      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
732      * @throws IllegalArgumentException if the project or the name is blank (null, empty or whitespace)
733      * @see #getJob(String)
734      */
735     public RundeckJob findJob(String project, String groupPath, String name) throws RundeckApiException,
736             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
737         AssertUtil.notBlank(project, "project is mandatory to find a job !");
738         AssertUtil.notBlank(name, "job name is mandatory to find a job !");
739         List<RundeckJob> jobs = getJobs(project, name, groupPath, new String[0]);
740         return jobs.isEmpty() ? null : jobs.get(0);
741     }
742 
743     /**
744      * Get the definition of a single job, identified by the given ID
745      * 
746      * @param jobId identifier of the job - mandatory
747      * @return a {@link RundeckJob} instance - won't be null
748      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
749      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
750      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
751      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
752      * @see #findJob(String, String, String)
753      * @see #exportJob(String, String)
754      */
755     public RundeckJob getJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
756             RundeckApiTokenException, IllegalArgumentException {
757         AssertUtil.notBlank(jobId, "jobId is mandatory to get the details of a job !");
758         return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId), new JobParser("joblist/job"));
759     }
760 
761     /**
762      * Delete a single job, identified by the given ID
763      * 
764      * @param jobId identifier of the job - mandatory
765      * @return the success message (note that in case of error, you'll get an exception)
766      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
767      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
768      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
769      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
770      */
771     public String deleteJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
772             RundeckApiTokenException, IllegalArgumentException {
773         AssertUtil.notBlank(jobId, "jobId is mandatory to delete a job !");
774         return new ApiCall(this).delete(new ApiPathBuilder("/job/", jobId), new StringParser("result/success/message"));
775     }
776 
777     /**
778      * Trigger the execution of a RunDeck job (identified by the given ID), and return immediately (without waiting the
779      * end of the job execution)
780      * 
781      * @param jobId identifier of the job - mandatory
782      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
783      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
784      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
785      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
786      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
787      * @see #triggerJob(String, Properties, Properties)
788      * @see #runJob(String)
789      */
790     public RundeckExecution triggerJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
791             RundeckApiTokenException, IllegalArgumentException {
792         return triggerJob(jobId, null);
793     }
794 
795     /**
796      * Trigger the execution of a RunDeck job (identified by the given ID), and return immediately (without waiting the
797      * end of the job execution)
798      * 
799      * @param jobId identifier of the job - mandatory
800      * @param options of the job - optional. See {@link OptionsBuilder}.
801      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
802      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
803      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
804      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
805      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
806      * @see #triggerJob(String, Properties, Properties)
807      * @see #runJob(String, Properties)
808      */
809     public RundeckExecution triggerJob(String jobId, Properties options) throws RundeckApiException,
810             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
811         return triggerJob(jobId, options, null);
812     }
813 
814     /**
815      * Trigger the execution of a RunDeck job (identified by the given ID), and return immediately (without waiting the
816      * end of the job execution)
817      * 
818      * @param jobId identifier of the job - mandatory
819      * @param options of the job - optional. See {@link OptionsBuilder}.
820      * @param nodeFilters for overriding the nodes on which the job will be executed - optional. See
821      *            {@link NodeFiltersBuilder}
822      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
823      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
824      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
825      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
826      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
827      * @see #triggerJob(String)
828      * @see #runJob(String, Properties, Properties)
829      */
830     public RundeckExecution triggerJob(String jobId, Properties options, Properties nodeFilters)
831             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
832         AssertUtil.notBlank(jobId, "jobId is mandatory to trigger a job !");
833         return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/run").param("argString",
834                                                                                       ParametersUtil.generateArgString(options))
835                                                                                .nodeFilters(nodeFilters),
836                                      new ExecutionParser("result/executions/execution"));
837     }
838 
839     /**
840      * Run a RunDeck job (identified by the given ID), and wait until its execution is finished (or aborted) to return.
841      * We will poll the RunDeck server at regular interval (every 5 seconds) to know if the execution is finished (or
842      * aborted) or is still running.
843      * 
844      * @param jobId identifier of the job - mandatory
845      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
846      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
847      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
848      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
849      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
850      * @see #triggerJob(String)
851      * @see #runJob(String, Properties, Properties, long, TimeUnit)
852      */
853     public RundeckExecution runJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
854             RundeckApiTokenException, IllegalArgumentException {
855         return runJob(jobId, null);
856     }
857 
858     /**
859      * Run a RunDeck job (identified by the given ID), and wait until its execution is finished (or aborted) to return.
860      * We will poll the RunDeck server at regular interval (every 5 seconds) to know if the execution is finished (or
861      * aborted) or is still running.
862      * 
863      * @param jobId identifier of the job - mandatory
864      * @param options of the job - optional. See {@link OptionsBuilder}.
865      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
866      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
867      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
868      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
869      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
870      * @see #triggerJob(String, Properties)
871      * @see #runJob(String, Properties, Properties, long, TimeUnit)
872      */
873     public RundeckExecution runJob(String jobId, Properties options) throws RundeckApiException,
874             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
875         return runJob(jobId, options, null);
876     }
877 
878     /**
879      * Run a RunDeck job (identified by the given ID), and wait until its execution is finished (or aborted) to return.
880      * We will poll the RunDeck server at regular interval (every 5 seconds) to know if the execution is finished (or
881      * aborted) or is still running.
882      * 
883      * @param jobId identifier of the job - mandatory
884      * @param options of the job - optional. See {@link OptionsBuilder}.
885      * @param nodeFilters for overriding the nodes on which the job will be executed - optional. See
886      *            {@link NodeFiltersBuilder}
887      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
888      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
889      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
890      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
891      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
892      * @see #triggerJob(String, Properties, Properties)
893      * @see #runJob(String, Properties, Properties, long, TimeUnit)
894      */
895     public RundeckExecution runJob(String jobId, Properties options, Properties nodeFilters)
896             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
897         return runJob(jobId, options, nodeFilters, DEFAULT_POOLING_INTERVAL, DEFAULT_POOLING_UNIT);
898     }
899 
900     /**
901      * Run a RunDeck job (identified by the given ID), and wait until its execution is finished (or aborted) to return.
902      * We will poll the RunDeck server at regular interval (configured by the poolingInterval/poolingUnit couple) to
903      * know if the execution is finished (or aborted) or is still running.
904      * 
905      * @param jobId identifier of the job - mandatory
906      * @param options of the job - optional. See {@link OptionsBuilder}.
907      * @param poolingInterval for checking the status of the execution. Must be > 0.
908      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
909      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
910      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
911      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
912      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
913      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
914      * @see #triggerJob(String, Properties)
915      * @see #runJob(String, Properties, Properties, long, TimeUnit)
916      */
917     public RundeckExecution runJob(String jobId, Properties options, long poolingInterval, TimeUnit poolingUnit)
918             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
919         return runJob(jobId, options, null, poolingInterval, poolingUnit);
920     }
921 
922     /**
923      * Run a RunDeck job (identified by the given ID), and wait until its execution is finished (or aborted) to return.
924      * We will poll the RunDeck server at regular interval (configured by the poolingInterval/poolingUnit couple) to
925      * know if the execution is finished (or aborted) or is still running.
926      * 
927      * @param jobId identifier of the job - mandatory
928      * @param options of the job - optional. See {@link OptionsBuilder}.
929      * @param nodeFilters for overriding the nodes on which the job will be executed - optional. See
930      *            {@link NodeFiltersBuilder}
931      * @param poolingInterval for checking the status of the execution. Must be > 0.
932      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
933      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
934      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
935      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
936      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
937      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
938      * @see #triggerJob(String, Properties)
939      * @see #runJob(String, Properties, Properties, long, TimeUnit)
940      */
941     public RundeckExecution runJob(String jobId, Properties options, Properties nodeFilters, long poolingInterval,
942             TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
943             IllegalArgumentException {
944         if (poolingInterval <= 0) {
945             poolingInterval = DEFAULT_POOLING_INTERVAL;
946             poolingUnit = DEFAULT_POOLING_UNIT;
947         }
948         if (poolingUnit == null) {
949             poolingUnit = DEFAULT_POOLING_UNIT;
950         }
951 
952         RundeckExecution execution = triggerJob(jobId, options, nodeFilters);
953         while (ExecutionStatus.RUNNING.equals(execution.getStatus())) {
954             try {
955                 Thread.sleep(poolingUnit.toMillis(poolingInterval));
956             } catch (InterruptedException e) {
957                 break;
958             }
959             execution = getExecution(execution.getId());
960         }
961         return execution;
962     }
963 
964     /*
965      * Ad-hoc commands
966      */
967 
968     /**
969      * Trigger the execution of an ad-hoc command, and return immediately (without waiting the end of the execution).
970      * The command will not be dispatched to nodes, but be executed on the RunDeck server.
971      * 
972      * @param project name of the project - mandatory
973      * @param command to be executed - mandatory
974      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
975      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
976      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
977      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
978      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
979      * @see #triggerAdhocCommand(String, String, Properties, Integer, Boolean)
980      * @see #runAdhocCommand(String, String)
981      */
982     public RundeckExecution triggerAdhocCommand(String project, String command) throws RundeckApiException,
983             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
984         return triggerAdhocCommand(project, command, null);
985     }
986 
987     /**
988      * Trigger the execution of an ad-hoc command, and return immediately (without waiting the end of the execution).
989      * The command will be dispatched to nodes, accordingly to the nodeFilters parameter.
990      * 
991      * @param project name of the project - mandatory
992      * @param command to be executed - mandatory
993      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
994      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
995      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
996      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
997      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
998      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
999      * @see #triggerAdhocCommand(String, String, Properties, Integer, Boolean)
1000      * @see #runAdhocCommand(String, String, Properties)
1001      */
1002     public RundeckExecution triggerAdhocCommand(String project, String command, Properties nodeFilters)
1003             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1004         return triggerAdhocCommand(project, command, nodeFilters, null, null);
1005     }
1006 
1007     /**
1008      * Trigger the execution of an ad-hoc command, and return immediately (without waiting the end of the execution).
1009      * The command will be dispatched to nodes, accordingly to the nodeFilters parameter.
1010      * 
1011      * @param project name of the project - mandatory
1012      * @param command to be executed - mandatory
1013      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1014      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1015      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1016      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1017      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1018      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1019      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1020      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1021      * @see #triggerAdhocCommand(String, String)
1022      * @see #runAdhocCommand(String, String, Properties)
1023      */
1024     public RundeckExecution triggerAdhocCommand(String project, String command, Properties nodeFilters,
1025             Integer nodeThreadcount, Boolean nodeKeepgoing) throws RundeckApiException, RundeckApiLoginException,
1026             RundeckApiTokenException, IllegalArgumentException {
1027         AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc command !");
1028         AssertUtil.notBlank(command, "command is mandatory to trigger an ad-hoc command !");
1029         RundeckExecution execution = new ApiCall(this).get(new ApiPathBuilder("/run/command").param("project", project)
1030                                                                                              .param("exec", command)
1031                                                                                              .param("nodeThreadcount",
1032                                                                                                     nodeThreadcount)
1033                                                                                              .param("nodeKeepgoing",
1034                                                                                                     nodeKeepgoing)
1035                                                                                              .nodeFilters(nodeFilters),
1036                                                            new ExecutionParser("result/execution"));
1037         // the first call just returns the ID of the execution, so we need another call to get a "real" execution
1038         return getExecution(execution.getId());
1039     }
1040 
1041     /**
1042      * Run an ad-hoc command, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1043      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1044      * running. The command will not be dispatched to nodes, but be executed on the RunDeck server.
1045      * 
1046      * @param project name of the project - mandatory
1047      * @param command to be executed - mandatory
1048      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1049      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1050      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1051      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1052      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1053      * @see #runAdhocCommand(String, String, Properties, Integer, Boolean, long, TimeUnit)
1054      * @see #triggerAdhocCommand(String, String)
1055      */
1056     public RundeckExecution runAdhocCommand(String project, String command) throws RundeckApiException,
1057             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1058         return runAdhocCommand(project, command, null);
1059     }
1060 
1061     /**
1062      * Run an ad-hoc command, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1063      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1064      * finished (or aborted) or is still running. The command will not be dispatched to nodes, but be executed on the
1065      * RunDeck server.
1066      * 
1067      * @param project name of the project - mandatory
1068      * @param command to be executed - mandatory
1069      * @param poolingInterval for checking the status of the execution. Must be > 0.
1070      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1071      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1072      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1073      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1074      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1075      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1076      * @see #runAdhocCommand(String, String, Properties, Integer, Boolean, long, TimeUnit)
1077      * @see #triggerAdhocCommand(String, String)
1078      */
1079     public RundeckExecution runAdhocCommand(String project, String command, long poolingInterval, TimeUnit poolingUnit)
1080             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1081         return runAdhocCommand(project, command, null, poolingInterval, poolingUnit);
1082     }
1083 
1084     /**
1085      * Run an ad-hoc command, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1086      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1087      * running. The command will be dispatched to nodes, accordingly to the nodeFilters parameter.
1088      * 
1089      * @param project name of the project - mandatory
1090      * @param command to be executed - mandatory
1091      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1092      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1093      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1094      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1095      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1096      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1097      * @see #runAdhocCommand(String, String, Properties, Integer, Boolean, long, TimeUnit)
1098      * @see #triggerAdhocCommand(String, String, Properties)
1099      */
1100     public RundeckExecution runAdhocCommand(String project, String command, Properties nodeFilters)
1101             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1102         return runAdhocCommand(project, command, nodeFilters, null, null);
1103     }
1104 
1105     /**
1106      * Run an ad-hoc command, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1107      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1108      * finished (or aborted) or is still running. The command will be dispatched to nodes, accordingly to the
1109      * nodeFilters parameter.
1110      * 
1111      * @param project name of the project - mandatory
1112      * @param command to be executed - mandatory
1113      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1114      * @param poolingInterval for checking the status of the execution. Must be > 0.
1115      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1116      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1117      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1118      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1119      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1120      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1121      * @see #runAdhocCommand(String, String, Properties, Integer, Boolean, long, TimeUnit)
1122      * @see #triggerAdhocCommand(String, String, Properties)
1123      */
1124     public RundeckExecution runAdhocCommand(String project, String command, Properties nodeFilters,
1125             long poolingInterval, TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException,
1126             RundeckApiTokenException, IllegalArgumentException {
1127         return runAdhocCommand(project, command, nodeFilters, null, null, poolingInterval, poolingUnit);
1128     }
1129 
1130     /**
1131      * Run an ad-hoc command, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1132      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1133      * running. The command will be dispatched to nodes, accordingly to the nodeFilters parameter.
1134      * 
1135      * @param project name of the project - mandatory
1136      * @param command to be executed - mandatory
1137      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1138      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1139      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1140      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1141      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1142      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1143      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1144      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1145      * @see #runAdhocCommand(String, String, Properties, Integer, Boolean, long, TimeUnit)
1146      * @see #triggerAdhocCommand(String, String, Properties, Integer, Boolean)
1147      */
1148     public RundeckExecution runAdhocCommand(String project, String command, Properties nodeFilters,
1149             Integer nodeThreadcount, Boolean nodeKeepgoing) throws RundeckApiException, RundeckApiLoginException,
1150             RundeckApiTokenException, IllegalArgumentException {
1151         return runAdhocCommand(project,
1152                                command,
1153                                nodeFilters,
1154                                nodeThreadcount,
1155                                nodeKeepgoing,
1156                                DEFAULT_POOLING_INTERVAL,
1157                                DEFAULT_POOLING_UNIT);
1158     }
1159 
1160     /**
1161      * Run an ad-hoc command, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1162      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1163      * finished (or aborted) or is still running. The command will be dispatched to nodes, accordingly to the
1164      * nodeFilters parameter.
1165      * 
1166      * @param project name of the project - mandatory
1167      * @param command to be executed - mandatory
1168      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1169      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1170      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1171      * @param poolingInterval for checking the status of the execution. Must be > 0.
1172      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1173      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1174      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1175      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1176      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1177      * @throws IllegalArgumentException if the project or command is blank (null, empty or whitespace)
1178      * @see #triggerAdhocCommand(String, String, Properties, Integer, Boolean)
1179      */
1180     public RundeckExecution runAdhocCommand(String project, String command, Properties nodeFilters,
1181             Integer nodeThreadcount, Boolean nodeKeepgoing, long poolingInterval, TimeUnit poolingUnit)
1182             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1183         if (poolingInterval <= 0) {
1184             poolingInterval = DEFAULT_POOLING_INTERVAL;
1185             poolingUnit = DEFAULT_POOLING_UNIT;
1186         }
1187         if (poolingUnit == null) {
1188             poolingUnit = DEFAULT_POOLING_UNIT;
1189         }
1190 
1191         RundeckExecution execution = triggerAdhocCommand(project, command, nodeFilters, nodeThreadcount, nodeKeepgoing);
1192         while (ExecutionStatus.RUNNING.equals(execution.getStatus())) {
1193             try {
1194                 Thread.sleep(poolingUnit.toMillis(poolingInterval));
1195             } catch (InterruptedException e) {
1196                 break;
1197             }
1198             execution = getExecution(execution.getId());
1199         }
1200         return execution;
1201     }
1202 
1203     /*
1204      * Ad-hoc scripts
1205      */
1206 
1207     /**
1208      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1209      * script will not be dispatched to nodes, but be executed on the RunDeck server.
1210      * 
1211      * @param project name of the project - mandatory
1212      * @param scriptFilename filename of the script to be executed - mandatory
1213      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1214      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1215      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1216      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1217      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1218      * @throws IOException if we failed to read the file
1219      * @see #triggerAdhocScript(String, String, Properties, Properties, Integer, Boolean)
1220      * @see #runAdhocScript(String, String)
1221      */
1222     public RundeckExecution triggerAdhocScript(String project, String scriptFilename) throws RundeckApiException,
1223             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1224         return triggerAdhocScript(project, scriptFilename, null);
1225     }
1226 
1227     /**
1228      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1229      * script will not be dispatched to nodes, but be executed on the RunDeck server.
1230      * 
1231      * @param project name of the project - mandatory
1232      * @param scriptFilename filename of the script to be executed - mandatory
1233      * @param options of the script - optional. See {@link OptionsBuilder}.
1234      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1235      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1236      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1237      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1238      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1239      * @throws IOException if we failed to read the file
1240      * @see #triggerAdhocScript(String, String, Properties, Properties, Integer, Boolean)
1241      * @see #runAdhocScript(String, String, Properties)
1242      */
1243     public RundeckExecution triggerAdhocScript(String project, String scriptFilename, Properties options)
1244             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException,
1245             IOException {
1246         return triggerAdhocScript(project, scriptFilename, options, null);
1247     }
1248 
1249     /**
1250      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1251      * script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1252      * 
1253      * @param project name of the project - mandatory
1254      * @param scriptFilename filename of the script to be executed - mandatory
1255      * @param options of the script - optional. See {@link OptionsBuilder}.
1256      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1257      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1258      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1259      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1260      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1261      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1262      * @throws IOException if we failed to read the file
1263      * @see #triggerAdhocScript(String, String, Properties, Properties, Integer, Boolean)
1264      * @see #runAdhocScript(String, String, Properties, Properties)
1265      */
1266     public RundeckExecution triggerAdhocScript(String project, String scriptFilename, Properties options,
1267             Properties nodeFilters) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1268             IllegalArgumentException, IOException {
1269         return triggerAdhocScript(project, scriptFilename, options, nodeFilters, null, null);
1270     }
1271 
1272     /**
1273      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1274      * script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1275      * 
1276      * @param project name of the project - mandatory
1277      * @param scriptFilename filename of the script to be executed - mandatory
1278      * @param options of the script - optional. See {@link OptionsBuilder}.
1279      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1280      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1281      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1282      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1283      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1284      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1285      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1286      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1287      * @throws IOException if we failed to read the file
1288      * @see #triggerAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean)
1289      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1290      */
1291     public RundeckExecution triggerAdhocScript(String project, String scriptFilename, Properties options,
1292             Properties nodeFilters, Integer nodeThreadcount, Boolean nodeKeepgoing) throws RundeckApiException,
1293             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1294         AssertUtil.notBlank(scriptFilename, "scriptFilename is mandatory to trigger an ad-hoc script !");
1295         FileInputStream stream = null;
1296         try {
1297             stream = FileUtils.openInputStream(new File(scriptFilename));
1298             return triggerAdhocScript(project, stream, options, nodeFilters, nodeThreadcount, nodeKeepgoing);
1299         } finally {
1300             IOUtils.closeQuietly(stream);
1301         }
1302     }
1303 
1304     /**
1305      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1306      * script will not be dispatched to nodes, but be executed on the RunDeck server.
1307      * 
1308      * @param project name of the project - mandatory
1309      * @param script inputStream for reading the script to be executed - mandatory
1310      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1311      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1312      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1313      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1314      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1315      * @see #triggerAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean)
1316      * @see #runAdhocScript(String, InputStream)
1317      */
1318     public RundeckExecution triggerAdhocScript(String project, InputStream script) throws RundeckApiException,
1319             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1320         return triggerAdhocScript(project, script, null);
1321     }
1322 
1323     /**
1324      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1325      * script will not be dispatched to nodes, but be executed on the RunDeck server.
1326      * 
1327      * @param project name of the project - mandatory
1328      * @param script inputStream for reading the script to be executed - mandatory
1329      * @param options of the script - optional. See {@link OptionsBuilder}.
1330      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1331      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1332      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1333      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1334      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1335      * @see #triggerAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean)
1336      * @see #runAdhocScript(String, InputStream, Properties)
1337      */
1338     public RundeckExecution triggerAdhocScript(String project, InputStream script, Properties options)
1339             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1340         return triggerAdhocScript(project, script, options, null);
1341     }
1342 
1343     /**
1344      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1345      * script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1346      * 
1347      * @param project name of the project - mandatory
1348      * @param script inputStream for reading the script to be executed - mandatory
1349      * @param options of the script - optional. See {@link OptionsBuilder}.
1350      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1351      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1352      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1353      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1354      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1355      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1356      * @see #triggerAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean)
1357      * @see #runAdhocScript(String, InputStream, Properties, Properties)
1358      */
1359     public RundeckExecution triggerAdhocScript(String project, InputStream script, Properties options,
1360             Properties nodeFilters) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1361             IllegalArgumentException {
1362         return triggerAdhocScript(project, script, options, nodeFilters, null, null);
1363     }
1364 
1365     /**
1366      * Trigger the execution of an ad-hoc script, and return immediately (without waiting the end of the execution). The
1367      * script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1368      * 
1369      * @param project name of the project - mandatory
1370      * @param script inputStream for reading the script to be executed - mandatory
1371      * @param options of the script - optional. See {@link OptionsBuilder}.
1372      * @param nodeFilters for selecting nodes on which the command will be executed. See {@link NodeFiltersBuilder}
1373      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1374      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1375      * @return a {@link RundeckExecution} instance for the newly created (and running) execution - won't be null
1376      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1377      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1378      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1379      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1380      * @see #triggerAdhocScript(String, String, Properties, Properties, Integer, Boolean)
1381      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1382      */
1383     public RundeckExecution triggerAdhocScript(String project, InputStream script, Properties options,
1384             Properties nodeFilters, Integer nodeThreadcount, Boolean nodeKeepgoing) throws RundeckApiException,
1385             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1386         AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc script !");
1387         AssertUtil.notNull(script, "script is mandatory to trigger an ad-hoc script !");
1388         RundeckExecution execution = new ApiCall(this).post(new ApiPathBuilder("/run/script").param("project", project)
1389                                                                                              .attach("scriptFile",
1390                                                                                                      script)
1391                                                                                              .param("argString",
1392                                                                                                     ParametersUtil.generateArgString(options))
1393                                                                                              .param("nodeThreadcount",
1394                                                                                                     nodeThreadcount)
1395                                                                                              .param("nodeKeepgoing",
1396                                                                                                     nodeKeepgoing)
1397                                                                                              .nodeFilters(nodeFilters),
1398                                                             new ExecutionParser("result/execution"));
1399         // the first call just returns the ID of the execution, so we need another call to get a "real" execution
1400         return getExecution(execution.getId());
1401     }
1402 
1403     /**
1404      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1405      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1406      * running. The script will not be dispatched to nodes, but be executed on the RunDeck server.
1407      * 
1408      * @param project name of the project - mandatory
1409      * @param scriptFilename filename of the script to be executed - mandatory
1410      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1411      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1412      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1413      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1414      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1415      * @throws IOException if we failed to read the file
1416      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1417      * @see #triggerAdhocScript(String, String)
1418      */
1419     public RundeckExecution runAdhocScript(String project, String scriptFilename) throws RundeckApiException,
1420             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1421         return runAdhocScript(project, scriptFilename, null);
1422     }
1423 
1424     /**
1425      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1426      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1427      * finished (or aborted) or is still running. The script will not be dispatched to nodes, but be executed on the
1428      * RunDeck server.
1429      * 
1430      * @param project name of the project - mandatory
1431      * @param scriptFilename filename of the script to be executed - mandatory
1432      * @param poolingInterval for checking the status of the execution. Must be > 0.
1433      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1434      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1435      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1436      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1437      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1438      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1439      * @throws IOException if we failed to read the file
1440      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1441      * @see #triggerAdhocScript(String, String)
1442      */
1443     public RundeckExecution runAdhocScript(String project, String scriptFilename, long poolingInterval,
1444             TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1445             IllegalArgumentException, IOException {
1446         return runAdhocScript(project, scriptFilename, null, poolingInterval, poolingUnit);
1447     }
1448 
1449     /**
1450      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1451      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1452      * running. The script will not be dispatched to nodes, but be executed on the RunDeck server.
1453      * 
1454      * @param project name of the project - mandatory
1455      * @param scriptFilename filename of the script to be executed - mandatory
1456      * @param options of the script - optional. See {@link OptionsBuilder}.
1457      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1458      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1459      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1460      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1461      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1462      * @throws IOException if we failed to read the file
1463      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1464      * @see #triggerAdhocScript(String, String, Properties)
1465      */
1466     public RundeckExecution runAdhocScript(String project, String scriptFilename, Properties options)
1467             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException,
1468             IOException {
1469         return runAdhocScript(project, scriptFilename, options, null);
1470     }
1471 
1472     /**
1473      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1474      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1475      * finished (or aborted) or is still running. The script will not be dispatched to nodes, but be executed on the
1476      * RunDeck server.
1477      * 
1478      * @param project name of the project - mandatory
1479      * @param scriptFilename filename of the script to be executed - mandatory
1480      * @param options of the script - optional. See {@link OptionsBuilder}.
1481      * @param poolingInterval for checking the status of the execution. Must be > 0.
1482      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1483      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1484      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1485      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1486      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1487      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1488      * @throws IOException if we failed to read the file
1489      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1490      * @see #triggerAdhocScript(String, String, Properties)
1491      */
1492     public RundeckExecution runAdhocScript(String project, String scriptFilename, Properties options,
1493             long poolingInterval, TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException,
1494             RundeckApiTokenException, IllegalArgumentException, IOException {
1495         return runAdhocScript(project, scriptFilename, options, null, poolingInterval, poolingUnit);
1496     }
1497 
1498     /**
1499      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1500      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1501      * running. The script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1502      * 
1503      * @param project name of the project - mandatory
1504      * @param scriptFilename filename of the script to be executed - mandatory
1505      * @param options of the script - optional. See {@link OptionsBuilder}.
1506      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1507      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1508      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1509      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1510      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1511      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1512      * @throws IOException if we failed to read the file
1513      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1514      * @see #triggerAdhocScript(String, String, Properties, Properties)
1515      */
1516     public RundeckExecution runAdhocScript(String project, String scriptFilename, Properties options,
1517             Properties nodeFilters) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1518             IllegalArgumentException, IOException {
1519         return runAdhocScript(project, scriptFilename, options, nodeFilters, null, null);
1520     }
1521 
1522     /**
1523      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1524      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1525      * finished (or aborted) or is still running. The script will be dispatched to nodes, accordingly to the nodeFilters
1526      * parameter.
1527      * 
1528      * @param project name of the project - mandatory
1529      * @param scriptFilename filename of the script to be executed - mandatory
1530      * @param options of the script - optional. See {@link OptionsBuilder}.
1531      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1532      * @param poolingInterval for checking the status of the execution. Must be > 0.
1533      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1534      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1535      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1536      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1537      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1538      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1539      * @throws IOException if we failed to read the file
1540      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1541      * @see #triggerAdhocScript(String, String, Properties, Properties)
1542      */
1543     public RundeckExecution runAdhocScript(String project, String scriptFilename, Properties options,
1544             Properties nodeFilters, long poolingInterval, TimeUnit poolingUnit) throws RundeckApiException,
1545             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1546         return runAdhocScript(project, scriptFilename, options, nodeFilters, null, null, poolingInterval, poolingUnit);
1547     }
1548 
1549     /**
1550      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1551      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1552      * running. The script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1553      * 
1554      * @param project name of the project - mandatory
1555      * @param scriptFilename filename of the script to be executed - mandatory
1556      * @param options of the script - optional. See {@link OptionsBuilder}.
1557      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1558      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1559      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1560      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1561      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1562      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1563      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1564      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1565      * @throws IOException if we failed to read the file
1566      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1567      * @see #triggerAdhocScript(String, String, Properties, Properties, Integer, Boolean)
1568      */
1569     public RundeckExecution runAdhocScript(String project, String scriptFilename, Properties options,
1570             Properties nodeFilters, Integer nodeThreadcount, Boolean nodeKeepgoing) throws RundeckApiException,
1571             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1572         return runAdhocScript(project,
1573                               scriptFilename,
1574                               options,
1575                               nodeFilters,
1576                               nodeThreadcount,
1577                               nodeKeepgoing,
1578                               DEFAULT_POOLING_INTERVAL,
1579                               DEFAULT_POOLING_UNIT);
1580     }
1581 
1582     /**
1583      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1584      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1585      * finished (or aborted) or is still running. The script will be dispatched to nodes, accordingly to the nodeFilters
1586      * parameter.
1587      * 
1588      * @param project name of the project - mandatory
1589      * @param scriptFilename filename of the script to be executed - mandatory
1590      * @param options of the script - optional. See {@link OptionsBuilder}.
1591      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1592      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1593      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1594      * @param poolingInterval for checking the status of the execution. Must be > 0.
1595      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1596      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1597      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1598      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1599      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1600      * @throws IllegalArgumentException if the project or scriptFilename is blank (null, empty or whitespace)
1601      * @throws IOException if we failed to read the file
1602      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1603      * @see #triggerAdhocScript(String, String, Properties, Properties, Integer, Boolean)
1604      */
1605     public RundeckExecution runAdhocScript(String project, String scriptFilename, Properties options,
1606             Properties nodeFilters, Integer nodeThreadcount, Boolean nodeKeepgoing, long poolingInterval,
1607             TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1608             IllegalArgumentException, IOException {
1609         AssertUtil.notBlank(scriptFilename, "scriptFilename is mandatory to run an ad-hoc script !");
1610         FileInputStream stream = null;
1611         try {
1612             stream = FileUtils.openInputStream(new File(scriptFilename));
1613             return runAdhocScript(project,
1614                                   stream,
1615                                   options,
1616                                   nodeFilters,
1617                                   nodeThreadcount,
1618                                   nodeKeepgoing,
1619                                   poolingInterval,
1620                                   poolingUnit);
1621         } finally {
1622             IOUtils.closeQuietly(stream);
1623         }
1624     }
1625 
1626     /**
1627      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1628      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1629      * running. The script will not be dispatched to nodes, but be executed on the RunDeck server.
1630      * 
1631      * @param project name of the project - mandatory
1632      * @param script inputStream for reading the script to be executed - mandatory
1633      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1634      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1635      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1636      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1637      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1638      * @throws IOException if we failed to read the file
1639      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1640      * @see #triggerAdhocScript(String, InputStream)
1641      */
1642     public RundeckExecution runAdhocScript(String project, InputStream script) throws RundeckApiException,
1643             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1644         return runAdhocScript(project, script, null);
1645     }
1646 
1647     /**
1648      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1649      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1650      * finished (or aborted) or is still running. The script will not be dispatched to nodes, but be executed on the
1651      * RunDeck server.
1652      * 
1653      * @param project name of the project - mandatory
1654      * @param script inputStream for reading the script to be executed - mandatory
1655      * @param poolingInterval for checking the status of the execution. Must be > 0.
1656      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1657      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1658      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1659      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1660      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1661      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1662      * @throws IOException if we failed to read the file
1663      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1664      * @see #triggerAdhocScript(String, InputStream)
1665      */
1666     public RundeckExecution runAdhocScript(String project, InputStream script, long poolingInterval,
1667             TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1668             IllegalArgumentException, IOException {
1669         return runAdhocScript(project, script, null, poolingInterval, poolingUnit);
1670     }
1671 
1672     /**
1673      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1674      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1675      * running. The script will not be dispatched to nodes, but be executed on the RunDeck server.
1676      * 
1677      * @param project name of the project - mandatory
1678      * @param script inputStream for reading the script to be executed - mandatory
1679      * @param options of the script - optional. See {@link OptionsBuilder}.
1680      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1681      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1682      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1683      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1684      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1685      * @throws IOException if we failed to read the file
1686      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1687      * @see #triggerAdhocScript(String, InputStream, Properties)
1688      */
1689     public RundeckExecution runAdhocScript(String project, InputStream script, Properties options)
1690             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException,
1691             IOException {
1692         return runAdhocScript(project, script, options, null);
1693     }
1694 
1695     /**
1696      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1697      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1698      * finished (or aborted) or is still running. The script will not be dispatched to nodes, but be executed on the
1699      * RunDeck server.
1700      * 
1701      * @param project name of the project - mandatory
1702      * @param script inputStream for reading the script to be executed - mandatory
1703      * @param options of the script - optional. See {@link OptionsBuilder}.
1704      * @param poolingInterval for checking the status of the execution. Must be > 0.
1705      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1706      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1707      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1708      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1709      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1710      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1711      * @throws IOException if we failed to read the file
1712      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1713      * @see #triggerAdhocScript(String, InputStream, Properties)
1714      */
1715     public RundeckExecution runAdhocScript(String project, InputStream script, Properties options,
1716             long poolingInterval, TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException,
1717             RundeckApiTokenException, IllegalArgumentException, IOException {
1718         return runAdhocScript(project, script, options, null, poolingInterval, poolingUnit);
1719     }
1720 
1721     /**
1722      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1723      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1724      * running. The script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1725      * 
1726      * @param project name of the project - mandatory
1727      * @param script inputStream for reading the script to be executed - mandatory
1728      * @param options of the script - optional. See {@link OptionsBuilder}.
1729      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1730      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1731      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1732      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1733      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1734      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1735      * @throws IOException if we failed to read the file
1736      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1737      * @see #triggerAdhocScript(String, InputStream, Properties, Properties)
1738      */
1739     public RundeckExecution runAdhocScript(String project, InputStream script, Properties options,
1740             Properties nodeFilters) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1741             IllegalArgumentException, IOException {
1742         return runAdhocScript(project, script, options, nodeFilters, null, null);
1743     }
1744 
1745     /**
1746      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1747      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1748      * finished (or aborted) or is still running. The script will be dispatched to nodes, accordingly to the nodeFilters
1749      * parameter.
1750      * 
1751      * @param project name of the project - mandatory
1752      * @param script inputStream for reading the script to be executed - mandatory
1753      * @param options of the script - optional. See {@link OptionsBuilder}.
1754      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1755      * @param poolingInterval for checking the status of the execution. Must be > 0.
1756      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1757      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1758      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1759      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1760      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1761      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1762      * @throws IOException if we failed to read the file
1763      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1764      * @see #triggerAdhocScript(String, InputStream, Properties, Properties)
1765      */
1766     public RundeckExecution runAdhocScript(String project, InputStream script, Properties options,
1767             Properties nodeFilters, long poolingInterval, TimeUnit poolingUnit) throws RundeckApiException,
1768             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1769         return runAdhocScript(project, script, options, nodeFilters, null, null, poolingInterval, poolingUnit);
1770     }
1771 
1772     /**
1773      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1774      * server at regular interval (every 5 seconds) to know if the execution is finished (or aborted) or is still
1775      * running. The script will be dispatched to nodes, accordingly to the nodeFilters parameter.
1776      * 
1777      * @param project name of the project - mandatory
1778      * @param script inputStream for reading the script to be executed - mandatory
1779      * @param options of the script - optional. See {@link OptionsBuilder}.
1780      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1781      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1782      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1783      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1784      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1785      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1786      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1787      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1788      * @throws IOException if we failed to read the file
1789      * @see #runAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean, long, TimeUnit)
1790      * @see #triggerAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean)
1791      */
1792     public RundeckExecution runAdhocScript(String project, InputStream script, Properties options,
1793             Properties nodeFilters, Integer nodeThreadcount, Boolean nodeKeepgoing) throws RundeckApiException,
1794             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException, IOException {
1795         return runAdhocScript(project,
1796                               script,
1797                               options,
1798                               nodeFilters,
1799                               nodeThreadcount,
1800                               nodeKeepgoing,
1801                               DEFAULT_POOLING_INTERVAL,
1802                               DEFAULT_POOLING_UNIT);
1803     }
1804 
1805     /**
1806      * Run an ad-hoc script, and wait until its execution is finished (or aborted) to return. We will poll the RunDeck
1807      * server at regular interval (configured by the poolingInterval/poolingUnit couple) to know if the execution is
1808      * finished (or aborted) or is still running. The script will be dispatched to nodes, accordingly to the nodeFilters
1809      * parameter.
1810      * 
1811      * @param project name of the project - mandatory
1812      * @param script inputStream for reading the script to be executed - mandatory
1813      * @param options of the script - optional. See {@link OptionsBuilder}.
1814      * @param nodeFilters for selecting nodes on which the script will be executed. See {@link NodeFiltersBuilder}
1815      * @param nodeThreadcount thread count to use (for parallelizing when running on multiple nodes) - optional
1816      * @param nodeKeepgoing if true, continue executing on other nodes even if some fail - optional
1817      * @param poolingInterval for checking the status of the execution. Must be > 0.
1818      * @param poolingUnit unit (seconds, milli-seconds, ...) of the interval. Default to seconds.
1819      * @return a {@link RundeckExecution} instance for the (finished/aborted) execution - won't be null
1820      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1821      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1822      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1823      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace) or the script is null
1824      * @throws IOException if we failed to read the file
1825      * @see #runAdhocScript(String, String, Properties, Properties, Integer, Boolean, long, TimeUnit)
1826      * @see #triggerAdhocScript(String, InputStream, Properties, Properties, Integer, Boolean)
1827      */
1828     public RundeckExecution runAdhocScript(String project, InputStream script, Properties options,
1829             Properties nodeFilters, Integer nodeThreadcount, Boolean nodeKeepgoing, long poolingInterval,
1830             TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
1831             IllegalArgumentException {
1832         if (poolingInterval <= 0) {
1833             poolingInterval = DEFAULT_POOLING_INTERVAL;
1834             poolingUnit = DEFAULT_POOLING_UNIT;
1835         }
1836         if (poolingUnit == null) {
1837             poolingUnit = DEFAULT_POOLING_UNIT;
1838         }
1839 
1840         RundeckExecution execution = triggerAdhocScript(project,
1841                                                         script,
1842                                                         options,
1843                                                         nodeFilters,
1844                                                         nodeThreadcount,
1845                                                         nodeKeepgoing);
1846         while (ExecutionStatus.RUNNING.equals(execution.getStatus())) {
1847             try {
1848                 Thread.sleep(poolingUnit.toMillis(poolingInterval));
1849             } catch (InterruptedException e) {
1850                 break;
1851             }
1852             execution = getExecution(execution.getId());
1853         }
1854         return execution;
1855     }
1856 
1857     /*
1858      * Executions
1859      */
1860 
1861     /**
1862      * Get all running executions (for all projects)
1863      * 
1864      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1865      * @throws RundeckApiException in case of error when calling the API
1866      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1867      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1868      * @see #getRunningExecutions(String)
1869      */
1870     public List<RundeckExecution> getRunningExecutions() throws RundeckApiException, RundeckApiLoginException,
1871             RundeckApiTokenException {
1872         List<RundeckExecution> executions = new ArrayList<RundeckExecution>();
1873         for (RundeckProject project : getProjects()) {
1874             executions.addAll(getRunningExecutions(project.getName()));
1875         }
1876         return executions;
1877     }
1878 
1879     /**
1880      * Get the running executions for the given project
1881      * 
1882      * @param project name of the project - mandatory
1883      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1884      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
1885      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1886      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1887      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
1888      * @see #getRunningExecutions()
1889      */
1890     public List<RundeckExecution> getRunningExecutions(String project) throws RundeckApiException,
1891             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1892         AssertUtil.notBlank(project, "project is mandatory get all running executions !");
1893         return new ApiCall(this).get(new ApiPathBuilder("/executions/running").param("project", project),
1894                                      new ListParser<RundeckExecution>(new ExecutionParser(),
1895                                                                       "result/executions/execution"));
1896     }
1897 
1898     /**
1899      * Get the executions of the given job
1900      * 
1901      * @param jobId identifier of the job - mandatory
1902      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1903      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
1904      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1905      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1906      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
1907      * @see #getJobExecutions(String, RundeckExecution.ExecutionStatus, Long, Long)
1908      */
1909     public List<RundeckExecution> getJobExecutions(String jobId) throws RundeckApiException, RundeckApiLoginException,
1910             RundeckApiTokenException, IllegalArgumentException {
1911         return getJobExecutions(jobId, (ExecutionStatus) null);
1912     }
1913 
1914     /**
1915      * Get the executions of the given job
1916      * 
1917      * @param jobId identifier of the job - mandatory
1918      * @param status of the executions, see {@link ExecutionStatus} - optional (null for all)
1919      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1920      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
1921      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1922      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1923      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace), or the executionStatus is
1924      *             invalid
1925      * @see #getJobExecutions(String, String, Long, Long)
1926      */
1927     public List<RundeckExecution> getJobExecutions(String jobId, String status) throws RundeckApiException,
1928             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1929         return getJobExecutions(jobId,
1930                                 StringUtils.isBlank(status) ? null : ExecutionStatus.valueOf(StringUtils.upperCase(status)));
1931     }
1932 
1933     /**
1934      * Get the executions of the given job
1935      * 
1936      * @param jobId identifier of the job - mandatory
1937      * @param status of the executions, see {@link ExecutionStatus} - optional (null for all)
1938      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1939      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
1940      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1941      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1942      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
1943      * @see #getJobExecutions(String, RundeckExecution.ExecutionStatus, Long, Long)
1944      */
1945     public List<RundeckExecution> getJobExecutions(String jobId, ExecutionStatus status) throws RundeckApiException,
1946             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1947         return getJobExecutions(jobId, status, null, null);
1948     }
1949 
1950     /**
1951      * Get the executions of the given job
1952      * 
1953      * @param jobId identifier of the job - mandatory
1954      * @param status of the executions, see {@link ExecutionStatus} - optional (null for all)
1955      * @param max number of results to return - optional (null for all)
1956      * @param offset the 0-indexed offset for the first result to return - optional
1957      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1958      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
1959      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1960      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1961      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace), or the executionStatus is
1962      *             invalid
1963      * @see #getJobExecutions(String, RundeckExecution.ExecutionStatus, Long, Long)
1964      */
1965     public List<RundeckExecution> getJobExecutions(String jobId, String status, Long max, Long offset)
1966             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1967         return getJobExecutions(jobId,
1968                                 StringUtils.isBlank(status) ? null : ExecutionStatus.valueOf(StringUtils.upperCase(status)),
1969                                 max,
1970                                 offset);
1971     }
1972 
1973     /**
1974      * Get the executions of the given job
1975      * 
1976      * @param jobId identifier of the job - mandatory
1977      * @param status of the executions, see {@link ExecutionStatus} - optional (null for all)
1978      * @param max number of results to return - optional (null for all)
1979      * @param offset the 0-indexed offset for the first result to return - optional
1980      * @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
1981      * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID)
1982      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
1983      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
1984      * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
1985      */
1986     public List<RundeckExecution> getJobExecutions(String jobId, ExecutionStatus status, Long max, Long offset)
1987             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
1988         AssertUtil.notBlank(jobId, "jobId is mandatory to get the executions of a job !");
1989         return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/executions").param("status", status)
1990                                                                                       .param("max", max)
1991                                                                                       .param("offset", offset),
1992                                      new ListParser<RundeckExecution>(new ExecutionParser(),
1993                                                                       "result/executions/execution"));
1994     }
1995 
1996     /**
1997      * Get a single execution, identified by the given ID
1998      * 
1999      * @param executionId identifier of the execution - mandatory
2000      * @return a {@link RundeckExecution} instance - won't be null
2001      * @throws RundeckApiException in case of error when calling the API (non-existent execution with this ID)
2002      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2003      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2004      * @throws IllegalArgumentException if the executionId is null
2005      */
2006     public RundeckExecution getExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException,
2007             RundeckApiTokenException, IllegalArgumentException {
2008         AssertUtil.notNull(executionId, "executionId is mandatory to get the details of an execution !");
2009         return new ApiCall(this).get(new ApiPathBuilder("/execution/", executionId.toString()),
2010                                      new ExecutionParser("result/executions/execution"));
2011     }
2012 
2013     /**
2014      * Abort an execution (identified by the given ID). The execution should be running...
2015      * 
2016      * @param executionId identifier of the execution - mandatory
2017      * @return a {@link RundeckAbort} instance - won't be null
2018      * @throws RundeckApiException in case of error when calling the API (non-existent execution with this ID)
2019      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2020      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2021      * @throws IllegalArgumentException if the executionId is null
2022      */
2023     public RundeckAbort abortExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException,
2024             RundeckApiTokenException, IllegalArgumentException {
2025         AssertUtil.notNull(executionId, "executionId is mandatory to abort an execution !");
2026         return new ApiCall(this).get(new ApiPathBuilder("/execution/", executionId.toString(), "/abort"),
2027                                      new AbortParser("result/abort"));
2028     }
2029 
2030     /*
2031      * History
2032      */
2033 
2034     /**
2035      * Get the (events) history for the given project
2036      * 
2037      * @param project name of the project - mandatory
2038      * @return a {@link RundeckHistory} instance - won't be null
2039      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2040      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2041      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2042      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2043      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2044      */
2045     public RundeckHistory getHistory(String project) throws RundeckApiException, RundeckApiLoginException,
2046             RundeckApiTokenException, IllegalArgumentException {
2047         return getHistory(project, null, null, null, null, null, null, null, null);
2048     }
2049 
2050     /**
2051      * Get the (events) history for the given project
2052      * 
2053      * @param project name of the project - mandatory
2054      * @param max number of results to return - optional (default to 20)
2055      * @param offset the 0-indexed offset for the first result to return - optional (default to O)
2056      * @return a {@link RundeckHistory} instance - won't be null
2057      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2058      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2059      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2060      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2061      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2062      */
2063     public RundeckHistory getHistory(String project, Long max, Long offset) throws RundeckApiException,
2064             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2065         return getHistory(project, null, null, null, null, null, null, max, offset);
2066     }
2067 
2068     /**
2069      * Get the (events) history for the given project
2070      * 
2071      * @param project name of the project - mandatory
2072      * @param jobId include only events matching the given job ID - optional
2073      * @param reportId include only events matching the given report ID - optional
2074      * @param user include only events created by the given user - optional
2075      * @return a {@link RundeckHistory} instance - won't be null
2076      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2077      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2078      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2079      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2080      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2081      */
2082     public RundeckHistory getHistory(String project, String jobId, String reportId, String user)
2083             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2084         return getHistory(project, jobId, reportId, user, null, null, null, null, null);
2085     }
2086 
2087     /**
2088      * Get the (events) history for the given project
2089      * 
2090      * @param project name of the project - mandatory
2091      * @param jobId include only events matching the given job ID - optional
2092      * @param reportId include only events matching the given report ID - optional
2093      * @param user include only events created by the given user - optional
2094      * @param max number of results to return - optional (default to 20)
2095      * @param offset the 0-indexed offset for the first result to return - optional (default to O)
2096      * @return a {@link RundeckHistory} instance - won't be null
2097      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2098      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2099      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2100      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2101      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2102      */
2103     public RundeckHistory getHistory(String project, String jobId, String reportId, String user, Long max, Long offset)
2104             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2105         return getHistory(project, jobId, reportId, user, null, null, null, max, offset);
2106     }
2107 
2108     /**
2109      * Get the (events) history for the given project
2110      * 
2111      * @param project name of the project - mandatory
2112      * @param recent include only events matching the given period of time. Format : "XY", where X is an integer, and Y
2113      *            is one of : "h" (hour), "d" (day), "w" (week), "m" (month), "y" (year). Example : "2w" (= last 2
2114      *            weeks), "5d" (= last 5 days), etc. Optional.
2115      * @return a {@link RundeckHistory} instance - won't be null
2116      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2117      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2118      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2119      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2120      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2121      */
2122     public RundeckHistory getHistory(String project, String recent) throws RundeckApiException,
2123             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2124         return getHistory(project, null, null, null, recent, null, null, null, null);
2125     }
2126 
2127     /**
2128      * Get the (events) history for the given project
2129      * 
2130      * @param project name of the project - mandatory
2131      * @param recent include only events matching the given period of time. Format : "XY", where X is an integer, and Y
2132      *            is one of : "h" (hour), "d" (day), "w" (week), "m" (month), "y" (year). Example : "2w" (= last 2
2133      *            weeks), "5d" (= last 5 days), etc. Optional.
2134      * @param max number of results to return - optional (default to 20)
2135      * @param offset the 0-indexed offset for the first result to return - optional (default to O)
2136      * @return a {@link RundeckHistory} instance - won't be null
2137      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2138      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2139      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2140      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2141      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2142      */
2143     public RundeckHistory getHistory(String project, String recent, Long max, Long offset) throws RundeckApiException,
2144             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2145         return getHistory(project, null, null, null, recent, null, null, max, offset);
2146     }
2147 
2148     /**
2149      * Get the (events) history for the given project
2150      * 
2151      * @param project name of the project - mandatory
2152      * @param begin date for the earlier events to retrieve - optional
2153      * @param end date for the latest events to retrieve - optional
2154      * @return a {@link RundeckHistory} instance - won't be null
2155      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2156      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2157      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2158      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2159      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2160      */
2161     public RundeckHistory getHistory(String project, Date begin, Date end) throws RundeckApiException,
2162             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2163         return getHistory(project, null, null, null, null, begin, end, null, null);
2164     }
2165 
2166     /**
2167      * Get the (events) history for the given project
2168      * 
2169      * @param project name of the project - mandatory
2170      * @param begin date for the earlier events to retrieve - optional
2171      * @param end date for the latest events to retrieve - optional
2172      * @param max number of results to return - optional (default to 20)
2173      * @param offset the 0-indexed offset for the first result to return - optional (default to O)
2174      * @return a {@link RundeckHistory} instance - won't be null
2175      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2176      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2177      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2178      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2179      * @see #getHistory(String, String, String, String, String, Date, Date, Long, Long)
2180      */
2181     public RundeckHistory getHistory(String project, Date begin, Date end, Long max, Long offset)
2182             throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2183         return getHistory(project, null, null, null, null, begin, end, max, offset);
2184     }
2185 
2186     /**
2187      * Get the (events) history for the given project
2188      * 
2189      * @param project name of the project - mandatory
2190      * @param jobId include only events matching the given job ID - optional
2191      * @param reportId include only events matching the given report ID - optional
2192      * @param user include only events created by the given user - optional
2193      * @param recent include only events matching the given period of time. Format : "XY", where X is an integer, and Y
2194      *            is one of : "h" (hour), "d" (day), "w" (week), "m" (month), "y" (year). Example : "2w" (= last 2
2195      *            weeks), "5d" (= last 5 days), etc. Optional.
2196      * @param begin date for the earlier events to retrieve - optional
2197      * @param end date for the latest events to retrieve - optional
2198      * @param max number of results to return - optional (default to 20)
2199      * @param offset the 0-indexed offset for the first result to return - optional (default to O)
2200      * @return a {@link RundeckHistory} instance - won't be null
2201      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2202      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2203      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2204      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2205      */
2206     public RundeckHistory getHistory(String project, String jobId, String reportId, String user, String recent,
2207             Date begin, Date end, Long max, Long offset) throws RundeckApiException, RundeckApiLoginException,
2208             RundeckApiTokenException, IllegalArgumentException {
2209         AssertUtil.notBlank(project, "project is mandatory to get the history !");
2210         return new ApiCall(this).get(new ApiPathBuilder("/history").param("project", project)
2211                                                                    .param("jobIdFilter", jobId)
2212                                                                    .param("reportIdFilter", reportId)
2213                                                                    .param("userFilter", user)
2214                                                                    .param("recentFilter", recent)
2215                                                                    .param("begin", begin)
2216                                                                    .param("end", end)
2217                                                                    .param("max", max)
2218                                                                    .param("offset", offset),
2219                                      new HistoryParser("result/events"));
2220     }
2221 
2222     /*
2223      * Nodes
2224      */
2225 
2226     /**
2227      * List all nodes (for all projects)
2228      * 
2229      * @return a {@link List} of {@link RundeckNode} : might be empty, but won't be null
2230      * @throws RundeckApiException in case of error when calling the API
2231      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2232      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2233      */
2234     public List<RundeckNode> getNodes() throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException {
2235         List<RundeckNode> nodes = new ArrayList<RundeckNode>();
2236         for (RundeckProject project : getProjects()) {
2237             nodes.addAll(getNodes(project.getName()));
2238         }
2239         return nodes;
2240     }
2241 
2242     /**
2243      * List all nodes that belongs to the given project
2244      * 
2245      * @param project name of the project - mandatory
2246      * @return a {@link List} of {@link RundeckNode} : might be empty, but won't be null
2247      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2248      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2249      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2250      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2251      * @see #getNodes(String, Properties)
2252      */
2253     public List<RundeckNode> getNodes(String project) throws RundeckApiException, RundeckApiLoginException,
2254             RundeckApiTokenException, IllegalArgumentException {
2255         return getNodes(project, null);
2256     }
2257 
2258     /**
2259      * List nodes that belongs to the given project
2260      * 
2261      * @param project name of the project - mandatory
2262      * @param nodeFilters for filtering the nodes - optional. See {@link NodeFiltersBuilder}
2263      * @return a {@link List} of {@link RundeckNode} : might be empty, but won't be null
2264      * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
2265      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2266      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2267      * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
2268      */
2269     public List<RundeckNode> getNodes(String project, Properties nodeFilters) throws RundeckApiException,
2270             RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
2271         AssertUtil.notBlank(project, "project is mandatory to get all nodes !");
2272         return new ApiCall(this).get(new ApiPathBuilder("/resources").param("project", project)
2273                                                                      .nodeFilters(nodeFilters),
2274                                      new ListParser<RundeckNode>(new NodeParser(), "project/node"));
2275     }
2276 
2277     /**
2278      * Get the definition of a single node
2279      * 
2280      * @param name of the node - mandatory
2281      * @param project name of the project - mandatory
2282      * @return a {@link RundeckNode} instance - won't be null
2283      * @throws RundeckApiException in case of error when calling the API (non-existent name or project with this name)
2284      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2285      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2286      * @throws IllegalArgumentException if the name or project is blank (null, empty or whitespace)
2287      */
2288     public RundeckNode getNode(String name, String project) throws RundeckApiException, RundeckApiLoginException,
2289             RundeckApiTokenException, IllegalArgumentException {
2290         AssertUtil.notBlank(name, "the name of the node is mandatory to get a node !");
2291         AssertUtil.notBlank(project, "project is mandatory to get a node !");
2292         return new ApiCall(this).get(new ApiPathBuilder("/resource/", name).param("project", project),
2293                                      new NodeParser("project/node"));
2294     }
2295 
2296     /*
2297      * System Info
2298      */
2299 
2300     /**
2301      * Get system informations about the RunDeck server
2302      * 
2303      * @return a {@link RundeckSystemInfo} instance - won't be null
2304      * @throws RundeckApiException in case of error when calling the API
2305      * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
2306      * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
2307      */
2308     public RundeckSystemInfo getSystemInfo() throws RundeckApiException, RundeckApiLoginException,
2309             RundeckApiTokenException {
2310         return new ApiCall(this).get(new ApiPathBuilder("/system/info"), new SystemInfoParser("result/system"));
2311     }
2312 
2313     /**
2314      * @return the URL of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc)
2315      */
2316     public String getUrl() {
2317         return url;
2318     }
2319 
2320     /**
2321      * @return the auth-token used for authentication on the RunDeck instance (null if using login-based auth)
2322      */
2323     public String getToken() {
2324         return token;
2325     }
2326 
2327     /**
2328      * @return the login used for authentication on the RunDeck instance (null if using token-based auth)
2329      */
2330     public String getLogin() {
2331         return login;
2332     }
2333 
2334     /**
2335      * @return the password used for authentication on the RunDeck instance (null if using token-based auth)
2336      */
2337     public String getPassword() {
2338         return password;
2339     }
2340 
2341     @Override
2342     public String toString() {
2343         StringBuilder str = new StringBuilder();
2344         str.append("RundeckClient ").append(API_VERSION);
2345         str.append(" [").append(url).append("] ");
2346         if (token != null) {
2347             str.append("(token=").append(token).append(")");
2348         } else {
2349             str.append("(credentials=").append(login).append("|").append(password).append(")");
2350         }
2351         return str.toString();
2352     }
2353 
2354     @Override
2355     public int hashCode() {
2356         final int prime = 31;
2357         int result = 1;
2358         result = prime * result + ((login == null) ? 0 : login.hashCode());
2359         result = prime * result + ((password == null) ? 0 : password.hashCode());
2360         result = prime * result + ((token == null) ? 0 : token.hashCode());
2361         result = prime * result + ((url == null) ? 0 : url.hashCode());
2362         return result;
2363     }
2364 
2365     @Override
2366     public boolean equals(Object obj) {
2367         if (this == obj)
2368             return true;
2369         if (obj == null)
2370             return false;
2371         if (getClass() != obj.getClass())
2372             return false;
2373         RundeckClient other = (RundeckClient) obj;
2374         if (login == null) {
2375             if (other.login != null)
2376                 return false;
2377         } else if (!login.equals(other.login))
2378             return false;
2379         if (password == null) {
2380             if (other.password != null)
2381                 return false;
2382         } else if (!password.equals(other.password))
2383             return false;
2384         if (token == null) {
2385             if (other.token != null)
2386                 return false;
2387         } else if (!token.equals(other.token))
2388             return false;
2389         if (url == null) {
2390             if (other.url != null)
2391                 return false;
2392         } else if (!url.equals(other.url))
2393             return false;
2394         return true;
2395     }
2396 
2397 }