diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 16e0a38..197c5a9 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -104,13 +104,13 @@ class ApiCall { * Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the * API call. At the end, the given parser will be used to convert the response to a more useful result object. * - * @param apiPath on which we will make the HTTP request + * @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder} * @param parser used to parse the response * @return the result of the call, as formatted by the parser * @throws RundeckApiException in case of error when calling the API * @throws RundeckApiLoginException if the login fails */ - public T get(String apiPath, NodeParser parser) throws RundeckApiException, RundeckApiLoginException { + public T get(ApiPathBuilder apiPath, NodeParser parser) throws RundeckApiException, RundeckApiLoginException { String apiUrl = client.getUrl() + RundeckClient.API_ENDPOINT + apiPath; HttpClient httpClient = instantiateHttpClient(); diff --git a/src/main/java/org/rundeck/api/ApiPathBuilder.java b/src/main/java/org/rundeck/api/ApiPathBuilder.java new file mode 100644 index 0000000..b311976 --- /dev/null +++ b/src/main/java/org/rundeck/api/ApiPathBuilder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2011 Vincent Behar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rundeck.api; + +import java.util.Properties; +import org.apache.commons.lang.StringUtils; +import org.rundeck.api.util.ParametersUtil; + +/** + * Builder for API paths + * + * @author Vincent Behar + */ +class ApiPathBuilder { + + /** Internally, we store everything in a {@link StringBuilder} */ + private final StringBuilder apiPath; + + /** Maker for using the right separator between parameters ("?" or "&") */ + private boolean firstParamDone = false; + + /** + * Build a new instance, for the given "path" (the "path" is the part before the parameters. The path and the + * parameters are separated by a "?") + * + * @param paths elements of the path + */ + public ApiPathBuilder(String... paths) { + apiPath = new StringBuilder(); + if (paths != null) { + for (String path : paths) { + if (StringUtils.isNotBlank(path)) { + append(path); + } + } + } + } + + /** + * Append the given parameter (key and value). This will only append the parameter if it is not blank (null, empty + * or whitespace), and make sure to add the right separator ("?" or "&") before. The key and value will be separated + * by the "=" character. Also, the value will be url-encoded. + * + * @param key of the parameter. Must not be null or empty + * @param value of the parameter. May be null/empty/blank. Will be url-encoded. + * @return this, for method chaining + */ + public ApiPathBuilder param(String key, String value) { + if (StringUtils.isNotBlank(value)) { + appendSeparator(); + append(key); + append("="); + append(ParametersUtil.urlEncode(value)); + } + return this; + } + + /** + * Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure + * to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character. + * + * @param key of the parameter. Must not be null or empty + * @param value of the parameter. May be null + * @return this, for method chaining + */ + public ApiPathBuilder param(String key, Long value) { + if (value != null) { + param(key, value.toString()); + } + return this; + } + + /** + * Append the given node filters, only if it is not null/empty + * + * @param nodeFilters may be null/empty + * @return this, for method chaining + * @see ParametersUtil#generateNodeFiltersString(Properties) + */ + public ApiPathBuilder nodeFilters(Properties nodeFilters) { + String filters = ParametersUtil.generateNodeFiltersString(nodeFilters); + if (StringUtils.isNotBlank(filters)) { + appendSeparator(); + append(filters); + } + return this; + } + + @Override + public String toString() { + return apiPath.toString(); + } + + /** + * Append the given string + * + * @param str to append + */ + private void append(String str) { + apiPath.append(str); + } + + /** + * Append the right separator "?" or "&" between 2 parameters + */ + private void appendSeparator() { + if (firstParamDone) { + append("&"); + } else { + append("?"); + firstParamDone = true; + } + } + +} diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index c380b7b..4c24565 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -115,7 +115,7 @@ public class RundeckClient implements Serializable { * @throws RundeckApiLoginException if the login failed */ public List getProjects() throws RundeckApiException, RundeckApiLoginException { - return new ApiCall(this).get("/projects", new ProjectsParser("result/projects/project")); + return new ApiCall(this).get(new ApiPathBuilder("/projects"), new ProjectsParser("result/projects/project")); } /** @@ -130,7 +130,8 @@ public class RundeckClient implements Serializable { public RundeckProject getProject(String projectName) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !"); - return new ApiCall(this).get("/project/" + projectName, new ProjectParser("result/projects/project")); + return new ApiCall(this).get(new ApiPathBuilder("/project/", projectName), + new ProjectParser("result/projects/project")); } /* @@ -183,18 +184,11 @@ public class RundeckClient implements Serializable { public List getJobs(String project, String jobFilter, String groupPath, String... jobIds) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(project, "project is mandatory to get all jobs !"); - StringBuilder apiPath = new StringBuilder("/jobs"); - apiPath.append("?project=").append(project); - if (StringUtils.isNotBlank(jobFilter)) { - apiPath.append("&jobFilter=").append(jobFilter); - } - if (StringUtils.isNotBlank(groupPath)) { - apiPath.append("&groupPath=").append(groupPath); - } - if (jobIds != null && jobIds.length > 0) { - apiPath.append("&idlist=").append(StringUtils.join(jobIds, ",")); - } - return new ApiCall(this).get(apiPath.toString(), new JobsParser("result/jobs/job")); + return new ApiCall(this).get(new ApiPathBuilder("/jobs").param("project", project) + .param("jobFilter", jobFilter) + .param("groupPath", groupPath) + .param("idlist", StringUtils.join(jobIds, ",")), + new JobsParser("result/jobs/job")); } /** @@ -229,7 +223,7 @@ public class RundeckClient implements Serializable { public RundeckJob getJob(String jobId) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(jobId, "jobId is mandatory to get the details of a job !"); - return new ApiCall(this).get("/job/" + jobId, new JobParser("joblist/job")); + return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId), new JobParser("joblist/job")); } /** @@ -285,16 +279,10 @@ public class RundeckClient implements Serializable { public RundeckExecution triggerJob(String jobId, Properties options, Properties nodeFilters) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(jobId, "jobId is mandatory to trigger a job !"); - StringBuilder apiPath = new StringBuilder("/job/").append(jobId).append("/run?"); - String argString = ParametersUtil.generateArgString(options); - if (StringUtils.isNotBlank(argString)) { - apiPath.append("argString=").append(ParametersUtil.urlEncode(argString)).append("&"); - } - String filters = ParametersUtil.generateNodeFiltersString(nodeFilters); - if (StringUtils.isNotBlank(filters)) { - apiPath.append(filters); - } - return new ApiCall(this).get(apiPath.toString(), new ExecutionParser("result/executions/execution")); + return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/run").param("argString", + ParametersUtil.generateArgString(options)) + .nodeFilters(nodeFilters), + new ExecutionParser("result/executions/execution")); } /** @@ -456,14 +444,10 @@ public class RundeckClient implements Serializable { throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc command !"); AssertUtil.notBlank(command, "command is mandatory to trigger an ad-hoc command !"); - StringBuilder apiPath = new StringBuilder("/run/command"); - apiPath.append("?project=").append(project); - apiPath.append("&exec=").append(ParametersUtil.urlEncode(command)); - String filters = ParametersUtil.generateNodeFiltersString(nodeFilters); - if (StringUtils.isNotBlank(filters)) { - apiPath.append("&").append(filters); - } - RundeckExecution execution = new ApiCall(this).get(apiPath.toString(), new ExecutionParser("result/execution")); + RundeckExecution execution = new ApiCall(this).get(new ApiPathBuilder("/run/command").param("project", project) + .param("exec", command) + .nodeFilters(nodeFilters), + new ExecutionParser("result/execution")); // the first call just returns the ID of the execution, so we need another call to get a "real" execution return getExecution(execution.getId()); } @@ -600,7 +584,7 @@ public class RundeckClient implements Serializable { public List getRunningExecutions(String project) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc command !"); - return new ApiCall(this).get("/executions/running?project=" + project, + return new ApiCall(this).get(new ApiPathBuilder("/executions/running").param("project", project), new ExecutionsParser("result/executions/execution")); } @@ -648,17 +632,11 @@ public class RundeckClient implements Serializable { public List getJobExecutions(String jobId, ExecutionStatus status, Long max, Long offset) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notBlank(jobId, "jobId is mandatory to get the executions of a job !"); - StringBuilder apiPath = new StringBuilder("/job/").append(jobId).append("/executions?"); - if (status != null) { - apiPath.append("status=").append(StringUtils.lowerCase(status.toString())).append("&"); - } - if (max != null && max >= 0) { - apiPath.append("max=").append(max).append("&"); - } - if (offset != null && offset >= 0) { - apiPath.append("offset=").append(offset); - } - return new ApiCall(this).get(apiPath.toString(), new ExecutionsParser("result/executions/execution")); + return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/executions").param("status", + status != null ? StringUtils.lowerCase(status.toString()) : null) + .param("max", max) + .param("offset", offset), + new ExecutionsParser("result/executions/execution")); } /** @@ -673,7 +651,8 @@ public class RundeckClient implements Serializable { public RundeckExecution getExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException { AssertUtil.notNull(executionId, "executionId is mandatory to get the details of an execution !"); - return new ApiCall(this).get("/execution/" + executionId, new ExecutionParser("result/executions/execution")); + return new ApiCall(this).get(new ApiPathBuilder("/execution/", executionId.toString()), + new ExecutionParser("result/executions/execution")); } public String getUrl() {