diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5b8d5402 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [khmarbaise] \ No newline at end of file diff --git a/.gitignore b/.gitignore index e73a9e03..9dc43422 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ target .project .settings test-output +.DS_Store diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 00000000..4fc377de --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=8.0.252.hs-adpt diff --git a/.travis.yml b/.travis.yml index c00316c0..6c57468f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,4 @@ --- -sudo: required - services: - docker @@ -35,8 +33,10 @@ after_script: - docker rm -f -v jenkins-for-testing - docker rmi -f jenkins-with-plugins +# http://www.webupd8.org/2017/06/why-oracle-java-7-and-6-installers-no.html +# - oraclejdk8 is not supported anymore. jdk: - - oraclejdk7 + - openjdk8 cache: directories: - $HOME/.m2/repository diff --git a/README.md b/README.md index d01daee5..8489b07c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![MIT Licence](https://img.shields.io/github/license/jenkinsci/java-client-api.svg?label=License)](http://opensource.org/licenses/MIT) [![Maven Central](https://img.shields.io/maven-central/v/com.offbytwo.jenkins/jenkins-client.svg?label=Maven%20Central)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.offbytwo.jenkins%22%20a%3A%22jenkins-client%22) [![Build Status](https://travis-ci.org/jenkinsci/java-client-api.svg?branch=master)](https://travis-ci.org/jenkinsci/java-client-api) +[![Javadocs](https://javadoc.io/badge/com.offbytwo.jenkins/jenkins-client.svg?color=blue)](https://javadoc.io/doc/com.offbytwo.jenkins/jenkins-client) ## Important Note @@ -10,7 +11,7 @@ The Jenkins API Client For Java has now moved under the umbrella of the Jenkins ## What is the "Jenkins API Client for Java"? -This library is a just a piece of java code which uses the REST API of jenkins. +This library is just a piece of java code which uses the REST API of jenkins. This means you can trigger builds, extract informations about jobs or builds etc. The information you can extract will be represented in java objects which you can reuse for other purposes or integrate this library into other parts for @@ -25,15 +26,15 @@ to your project. This can be done by using a Maven dependency like the following com.offbytwo.jenkins jenkins-client - 0.3.7 + 0.3.8 ``` This can also being done by defining a Gradle dependency like this: ``` -compile 'com.offbytwo.jenkins:jenkins-client:0.3.7' -`` +compile 'com.offbytwo.jenkins:jenkins-client:0.3.8' +``` Starting with a future release 0.4.0 the groupId/artifactId will change (NOT YET DONE!) @@ -96,7 +97,8 @@ covered by the usual integration tests. You can find details about the different releases in the [Release Notes](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md). - * [Release 0.3.8 NOT RELEASED YET](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md#release-038). + * [Release 0.3.9 NOT RELEASED YET](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md#release-039). + * [Release 0.3.8](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md#release-038). * [Release 0.3.7](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md#release-037). * [Release 0.3.6](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md#release-036). * [Release 0.3.5](https://github.com/jenkinsci/java-client-api/blob/master/ReleaseNotes.md#release-035). diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 2f4d1d80..71176ed3 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,23 +1,207 @@ # Release Notes -## Release 0.3.8 (NOT RELEASED YET) +## Release 0.4.0 (NOT RELEASED YET) + + * [Fixed Issue 465][issue-465] + + * Remove asciidoctor site parts + * AS preparation for user guide. + + * [Fixed Issue 464][issue-464] + + * Replaced xml-apis with xerces-xmlParserAPI. + + * [Fixed Issue 309][issue-309] + + * Added possibility to get mode detailed data from Maven Modules from Jobs/Builds + + Thanks for that to [Jakub Zacek](https://github.com/dawon). + + * [Fixed Issue 395][issue-395] + + * Remove google guava lib + * Removed also the creation of the shaded artifact `stash` + cause we do not rely on Guava anymore. So you + can use the original artifact directly. + * This results in a bumping of the version + number cause it is a change which is breaking + with previous version 0.3.8. + + * [Fixed Issue 405][issue-405] + + * CVE-2018-14718 + * CVE-2018-14719 + * CVE-2018-14720 + * CVE-2018-14721 + * CVE-2018-19360 + * CVE-2018-19361 + * CVE-2018-19362 + + * [Fixed Issue 402][issue-402] + + Upgrade httpclient/httpmine/httpcore. + + * [Fixed Issue 401][issue-401] + + Upgrade JUnit + + * [Fixed Issue 400][issue-400] + + Upgrade assertj-core. + + * [Fixed Issue 399][issue-399] + + Upgrade Maven Plugins + + * [Fixed Issue 397][issue-397] + + Refactored Code Replaced UrlEscapers calls with EncodingUtils. + + * [Fixed Issue 396][issue-396] + + Add Unit Test for EncodingUtils. + + * [Fixed Issue 394][issue-394] + + Replace `Strings.isNullOrEmpty()` with self implemented code. + + * [Pull Request #386][pull-386] + + Add the crumbFlag as the 2nd parameter of getConsoleOutputText method + + * [JENKINS-56186][jissue-56186] + + Added labels to computers + + ```java + ComputerWithDetails computer = ... + for (ComputerLabel assignedLabel : computer.getAssignedLabels()) { + assignedLabel.getName() + } + ``` + + * [JENKINS-56585][jissue-56585] + Change request method of `QuietDown()` to POST + + +## Release 0.3.8 + + * [Fixed Issue 289][issue-289] + + Added a build.stop() method which takes in a crumbFlag + + + * [Fixed Issue 301][issue-301] + + Decoupled JenkinsServer and JenkinsHttpClient by extracting JenkinsHttpClient + methods into public interface so that different implementations can be plugged + into JenkinsServer if required + + + * [Fixed Issue 298][issue-298] + + Added Closeable support to JenkinsServer and JenkinsHttpClient so that + underlying resources can be cleaned up when instances no longer required + + + * [JENKINS-46472][jissue-46472] + + Added ability to modify offline cause for offline computers. + + ```java + ComputerWithDetails computer = ... + if (!computer.getOffline()){ + computer.toggleOffline(); + computer.changeOfflineCause("Scheduled for termination"); + } + ``` + + * [JENKINS-46445][jissue-46445] + + Add support for both client TLS and basic authentication. + + ```java + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setSslcontext(sslContext); + JenkinsHttpClient client = new JenkinsHttpClient(uri, builder, username, password); + JenkinsServer jenkins = new JenkinsServer(client); + ``` + +* [Refactor Issue 291][issue-291] + + Useful utility methods refactored into utility classes. + + * [Fixed Issue 282][issue-282] + + `NullPointerException` may be thrown if `upstreamUrl` is `null` when + converting cause to `BuildCause` object. + + * [Fixed Issue 268][issue-268] + + NullPointerException is thrown unless isRunning() is called first. + + * [Fixed Issue 98][issue-98] + + Splitting fix made for jacoco reports from Jenkins #98. + Thanks to Shah, Prince. + + * [Fixed Issue 217][issue-217] + + Added new api for streaming build logs + + ```java + BuildWithDetails build = ... + // Get log with initial offset + int offset = 40; + String output = build.getConsoleOutputText(offset); + // Stream logs (when build is in progress) + BuildConsoleStreamListener buildListener = ... + build.streamConsoleOutput(listener, 3, 420); + ``` + + Thanks to Wojciech Trocki. + * [Fixed Issue 222][issue-222] Fixed WARNING during build. * [Fixed Issue 220][issue-220] - `getViews()` Do not use `api/json?depth=1` cause timeout. + `getViews()` Do not use `api/json?depth=1` cause of timeout. + * [Fixed Issue 244][issue-244] + + README.md Code grammar typos; Thanks to . * [Pull Request #229][pull-229] Fix race condition in JenkinsTriggerHelper Thanks for that to [Veske](https://github.com/Veske). + * [Pull Request #239][pull-239] Fixed Bug in build method to prevent + building twice. + + Thanks for the pull request #239 from ladventure/master + + * [Pull Request #240][pull-240] Fixed code duplication. + + Thanks for the pull request #240 from Jonathan Bremer. + + * [Pull Request #247][pull-247] Add JavaDoc. + + Thanks to aisuke-yoshimoto + + * [Pull Request #262][pull-262] Fix typo. + + Thanks for Alberto Scotto. + ### API Changes - * ? + * [Fixed Issue 243](https://github.com/jenkinsci/java-client-api/issues/243) + + Added new methods to JenkinsServer for stopping and restarting Jenkins. The methods are restart(Boolean crumbFlag), safeRestart(Boolean crumbFlag), exit(Boolean crumbFlag) and safeExit(Boolean crumbFlag) + + Thanks for that to [Chids](https://github.com/Chids-gs). ## Release 0.3.7 @@ -913,6 +1097,7 @@ TestReport testReport = mavenJob.getLastSuccessfulBuild().getTestReport(); [issue-89]: https://github.com/jenkinsci/java-client-api/issues/89 [issue-90]: https://github.com/jenkinsci/java-client-api/issues/90 [issue-91]: https://github.com/jenkinsci/java-client-api/issues/91 +[issue-98]: https://github.com/jenkinsci/java-client-api/issues/98 [issue-104]: https://github.com/jenkinsci/java-client-api/issues/104 [issue-111]: https://github.com/jenkinsci/java-client-api/issues/111 [issue-116]: https://github.com/jenkinsci/java-client-api/issues/116 @@ -960,8 +1145,27 @@ TestReport testReport = mavenJob.getLastSuccessfulBuild().getTestReport(); [issue-209]: https://github.com/jenkinsci/java-client-api/issues/209 [issue-211]: https://github.com/jenkinsci/java-client-api/issues/211 [issue-215]: https://github.com/jenkinsci/java-client-api/issues/215 +[issue-217]: https://github.com/jenkinsci/java-client-api/issues/217 [issue-220]: https://github.com/jenkinsci/java-client-api/issues/220 [issue-222]: https://github.com/jenkinsci/java-client-api/issues/222 +[issue-244]: https://github.com/jenkinsci/java-client-api/issues/244 +[issue-268]: https://github.com/jenkinsci/java-client-api/issues/268 +[issue-289]: https://github.com/jenkinsci/java-client-api/issues/289 +[issue-282]: https://github.com/jenkinsci/java-client-api/issues/282 +[issue-291]: https://github.com/jenkinsci/java-client-api/issues/291 +[issue-298]: https://github.com/jenkinsci/java-client-api/issues/298 +[issue-301]: https://github.com/jenkinsci/java-client-api/issues/301 +[issue-309]: https://github.com/jenkinsci/java-client-api/issues/309 +[issue-394]: https://github.com/jenkinsci/java-client-api/issues/394 +[issue-395]: https://github.com/jenkinsci/java-client-api/issues/395 +[issue-396]: https://github.com/jenkinsci/java-client-api/issues/396 +[issue-397]: https://github.com/jenkinsci/java-client-api/issues/397 +[issue-399]: https://github.com/jenkinsci/java-client-api/issues/399 +[issue-400]: https://github.com/jenkinsci/java-client-api/issues/400 +[issue-401]: https://github.com/jenkinsci/java-client-api/issues/401 +[issue-402]: https://github.com/jenkinsci/java-client-api/issues/402 +[issue-405]: https://github.com/jenkinsci/java-client-api/issues/405 +[issue-464]: https://github.com/jenkinsci/java-client-api/issues/464 [pull-123]: https://github.com/jenkinsci/java-client-api/pull/123 [pull-149]: https://github.com/jenkinsci/java-client-api/pull/149 [pull-158]: https://github.com/jenkinsci/java-client-api/pull/158 @@ -969,8 +1173,17 @@ TestReport testReport = mavenJob.getLastSuccessfulBuild().getTestReport(); [pull-204]: https://github.com/jenkinsci/java-client-api/pull/204 [pull-206]: https://github.com/jenkinsci/java-client-api/pull/206 [pull-229]: https://github.com/jenkinsci/java-client-api/pull/229 +[pull-239]: https://github.com/jenkinsci/java-client-api/pull/239 +[pull-240]: https://github.com/jenkinsci/java-client-api/pull/240 +[pull-247]: https://github.com/jenkinsci/java-client-api/pull/247 +[pull-262]: https://github.com/jenkinsci/java-client-api/pull/262 +[pull-386]: https://github.com/jenkinsci/java-client-api/pull/386 [jissue-35002]: https://issues.jenkins-ci.org/browse/JENKINS-35002 [jissue-35108]: https://issues.jenkins-ci.org/browse/JENKINS-35108 [jissue-38787]: https://issues.jenkins-ci.org/browse/JENKINS-38787 [jissue-38816]: https://issues.jenkins-ci.org/browse/JENKINS-38816 [jissue-38823]: https://issues.jenkins-ci.org/browse/JENKINS-38823 +[jissue-46445]: https://issues.jenkins-ci.org/browse/JENKINS-46445 +[jissue-46472]: https://issues.jenkins-ci.org/browse/JENKINS-46472 +[jissue-56186]: https://issues.jenkins-ci.org/browse/JENKINS-56186 +[jissue-56585]: https://issues.jenkins-ci.org/browse/JENKINS-56585 diff --git a/jenkins-client-it-docker/pom.xml b/jenkins-client-it-docker/pom.xml index 72752fee..11e6b485 100644 --- a/jenkins-client-it-docker/pom.xml +++ b/jenkins-client-it-docker/pom.xml @@ -11,7 +11,7 @@ com.offbytwo.jenkins jenkins-client-parent - 0.3.8-SNAPSHOT + 0.4.0-SNAPSHOT jenkins-client-it-docker @@ -43,11 +43,6 @@ assertj-core test - - com.google.guava - guava - test - org.apache.httpcomponents httpclient diff --git a/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobIT.java b/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobIT.java index 279bb40a..35c2aeac 100644 --- a/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobIT.java +++ b/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobIT.java @@ -7,7 +7,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import com.google.common.base.Joiner; import com.offbytwo.jenkins.model.Build; import com.offbytwo.jenkins.model.BuildResult; import com.offbytwo.jenkins.model.BuildWithDetails; @@ -50,7 +49,7 @@ private void checkJob(BuildWithDetails details) throws IOException { "Building in workspace /var/jenkins_home/jobs/test/workspace", "[workspace] $ /bin/sh -xe /tmp/hudson2556403647634111927.sh", "+ echo test", "test", "Finished: SUCCESS", "" }; - String expectedOutput = Joiner.on("\r\n").join(expectedOutputLines); + String expectedOutput = String.join("\r\n", expectedOutputLines); // Hint: It looks like the consoleOutputText contains CR+LF String resultingOutput = details.getConsoleOutputText(); assertThat(resultingOutput).isEqualTo(expectedOutput); diff --git a/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobXmlIT.java b/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobXmlIT.java index 451e7585..38a3507a 100644 --- a/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobXmlIT.java +++ b/jenkins-client-it-docker/src/test/java/com/offbytwo/jenkins/integration/NoExecutorStartedGetJobXmlIT.java @@ -7,8 +7,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import com.google.common.base.Joiner; - @Test(groups = { Groups.NO_EXECUTOR_GROUP }) public class NoExecutorStartedGetJobXmlIT extends AbstractJenkinsIntegrationCase { @@ -47,7 +45,7 @@ public void beforeMethod() throws IOException { @Test public void getJobXmlShouldReturnTheExpectedConfigXml() { - String expectedXml = Joiner.on("\n").join(CONFIG_XML); + String expectedXml = String.join("\n", CONFIG_XML); assertThat(jobXml).isEqualTo(expectedXml); } diff --git a/jenkins-client/pom.xml b/jenkins-client/pom.xml index baf14987..2cbc3df9 100644 --- a/jenkins-client/pom.xml +++ b/jenkins-client/pom.xml @@ -11,7 +11,7 @@ com.offbytwo.jenkins jenkins-client-parent - 0.3.8-SNAPSHOT + 0.4.0-SNAPSHOT jenkins-client @@ -34,7 +34,7 @@ - dom4j + org.dom4j dom4j @@ -52,13 +52,8 @@ - com.google.guava - guava - - - - commons-lang - commons-lang + org.apache.commons + commons-lang3 @@ -71,6 +66,16 @@ httpclient + + org.apache.httpcomponents + httpcore + + + + org.apache.httpcomponents + httpmime + + jaxen jaxen @@ -104,10 +109,9 @@ assertj-core test - - xml-apis - xml-apis + xerces + xmlParserAPIs @@ -117,28 +121,6 @@ org.apache.maven.plugins maven-shade-plugin - - stash - package - - shade - - - - - com.google.guava:guava - - - true - stash - - - com.google.common - com.google.common.jenkins_client_jarjar - - - - httpclient package @@ -154,12 +136,6 @@ true apachehttp - - - com.google.common - com.google.common.jenkins_client_jarjar - - diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java index b9147ad3..01755baa 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.net.URI; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,12 +21,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.offbytwo.jenkins.client.JenkinsHttpClient; +import com.offbytwo.jenkins.client.JenkinsHttpConnection; import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.offbytwo.jenkins.client.util.UrlUtils; import com.offbytwo.jenkins.helper.JenkinsVersion; import com.offbytwo.jenkins.model.Build; import com.offbytwo.jenkins.model.Computer; @@ -42,14 +41,24 @@ import com.offbytwo.jenkins.model.QueueItem; import com.offbytwo.jenkins.model.QueueReference; import com.offbytwo.jenkins.model.View; +import java.io.Closeable; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.offbytwo.jenkins.helper.FunctionalHelper.SET_CLIENT; +import static java.util.stream.Collectors.toMap; /** * The main starting point for interacting with a Jenkins server. */ -public class JenkinsServer { +public class JenkinsServer implements Closeable { private final Logger LOGGER = LoggerFactory.getLogger(getClass()); - private final JenkinsHttpClient client; + /** + * The transport client instance to use. + */ + private final JenkinsHttpConnection client; /** * Create a new Jenkins server reference given only the server address @@ -78,7 +87,7 @@ public JenkinsServer(URI serverUri, String username, String passwordOrToken) { * * @param client Specialized client to use. */ - public JenkinsServer(JenkinsHttpClient client) { + public JenkinsServer(final JenkinsHttpConnection client) { this.client = client; } @@ -101,9 +110,10 @@ public boolean isRunning() { * @return {@link JenkinsVersion} */ public JenkinsVersion getVersion() { - if (client.getJenkinsVersion().isEmpty()) { + if (!client.isJenkinsVersionSet()) { // Force a request to get at least once - // HttpHeader + // HttpHeader. The header contains the version + // information. isRunning(); } JenkinsVersion jv = new JenkinsVersion(client.getJenkinsVersion()); @@ -153,20 +163,17 @@ public Map getJobs(String view) throws IOException { * @throws IOException in case of an error. */ public Map getJobs(FolderJob folder, String view) throws IOException { - String path = toBaseUrl(folder); + String path = UrlUtils.toBaseUrl(folder); Class viewClass = MainView.class; if (view != null) { path = path + "view/" + EncodingUtils.encode(view) + "/"; viewClass = View.class; } List jobs = client.get(path, viewClass).getJobs(); - return Maps.uniqueIndex(jobs, new Function() { - @Override - public String apply(Job job) { - job.setClient(client); - return job.getName(); - } - }); + + return jobs.stream() + .map(SET_CLIENT(this.client)) + .collect(toMap(s -> s.getName(), s -> s)); } /** @@ -190,23 +197,24 @@ public Map getViews() throws IOException { public Map getViews(FolderJob folder) throws IOException { // This is much better than using &depth=2 // http://localhost:8080/api/json?pretty&tree=views[name,url,jobs[name,url]] - List views = client.get(toBaseUrl(folder) + "?tree=views[name,url,jobs[name,url]]", MainView.class).getViews(); - return Maps.uniqueIndex(views, new Function() { - @Override - public String apply(View view) { - view.setClient(client); - // TODO: Think about the following? Does there exists a - // simpler/more elegant method? + List views = client.get(UrlUtils.toBaseUrl(folder) + "?tree=views[name,url,jobs[name,url]]", MainView.class).getViews(); + + //TODO: Think about this Lambda. It's too large? Make it smaller! + return views.stream().map(view -> { + SET_CLIENT(this.client); + + // TODO: Think about the following? Does there exists a simpler/more + // elegant method? for (Job job : view.getJobs()) { - job.setClient(client); + SET_CLIENT(this.client); } - for (View item : view.getViews()) { - item.setClient(client); + for (View viewView : view.getViews()) { + SET_CLIENT(this.client); } - return view.getName(); - } - }); + return view; + }) + .collect(Collectors.toMap(s -> s.getName(), v -> v)); } /** @@ -230,7 +238,7 @@ public View getView(String name) throws IOException { */ public View getView(FolderJob folder, String name) throws IOException { try { - View resultView = client.get(toViewBaseUrl(folder, name) + "/", View.class); + View resultView = client.get(UrlUtils.toViewBaseUrl(folder, name) + "/", View.class); resultView.setClient(client); // TODO: Think about the following? Does there exists a simpler/more @@ -260,7 +268,7 @@ public View getView(FolderJob folder, String name) throws IOException { * @throws IOException in case of an error. */ public JobWithDetails getJob(String jobName) throws IOException { - return getJob(null, jobName); + return getJob(null, UrlUtils.toFullJobPath(jobName)); } /** @@ -273,7 +281,7 @@ public JobWithDetails getJob(String jobName) throws IOException { */ public JobWithDetails getJob(FolderJob folder, String jobName) throws IOException { try { - JobWithDetails job = client.get(toJobBaseUrl(folder, jobName), JobWithDetails.class); + JobWithDetails job = client.get(UrlUtils.toJobBaseUrl(folder, jobName), JobWithDetails.class); job.setClient(client); return job; @@ -288,12 +296,12 @@ public JobWithDetails getJob(FolderJob folder, String jobName) throws IOExceptio } public MavenJobWithDetails getMavenJob(String jobName) throws IOException { - return getMavenJob(null, jobName); + return getMavenJob(null, UrlUtils.toFullJobPath(jobName)); } public MavenJobWithDetails getMavenJob(FolderJob folder, String jobName) throws IOException { try { - MavenJobWithDetails job = client.get(toJobBaseUrl(folder, jobName), MavenJobWithDetails.class); + MavenJobWithDetails job = client.get(UrlUtils.toJobBaseUrl(folder, jobName), MavenJobWithDetails.class); job.setClient(client); return job; @@ -310,10 +318,9 @@ public Optional getFolderJob(Job job) throws IOException { try { FolderJob folder = client.get(job.getUrl(), FolderJob.class); if (!folder.isFolder()) { - return Optional.absent(); + return Optional.empty(); } folder.setClient(client); - return Optional.of(folder); } catch (HttpResponseException e) { LOGGER.debug("getForlderJob(job={}) status={}", job, e.getStatusCode()); @@ -334,8 +341,8 @@ public Optional getFolderJob(Job job) throws IOException { * the job. * @throws IOException in case of an error. */ - public void createJob(String jobName, String jobXml) throws IOException { - createJob(null, jobName, jobXml, false); + public JenkinsServer createJob(String jobName, String jobXml) throws IOException { + return createJob(null, jobName, jobXml, false); } /** @@ -348,8 +355,8 @@ public void createJob(String jobName, String jobXml) throws IOException { * false otherwise. * @throws IOException in case of an error. */ - public void createJob(String jobName, String jobXml, Boolean crumbFlag) throws IOException { - createJob(null, jobName, jobXml, crumbFlag); + public JenkinsServer createJob(String jobName, String jobXml, Boolean crumbFlag) throws IOException { + return createJob(null, jobName, jobXml, crumbFlag); } /** @@ -362,8 +369,8 @@ public void createJob(String jobName, String jobXml, Boolean crumbFlag) throws I * the job. * @throws IOException in case of an error. */ - public void createJob(FolderJob folder, String jobName, String jobXml) throws IOException { - createJob(folder, jobName, jobXml, false); + public JenkinsServer createJob(FolderJob folder, String jobName, String jobXml) throws IOException { + return createJob(folder, jobName, jobXml, false); } /** @@ -378,8 +385,10 @@ public void createJob(FolderJob folder, String jobName, String jobXml) throws IO * false otherwise. * @throws IOException in case of an error. */ - public void createJob(FolderJob folder, String jobName, String jobXml, Boolean crumbFlag) throws IOException { - client.post_xml(toBaseUrl(folder) + "createItem?name=" + EncodingUtils.encodeParam(jobName), jobXml, crumbFlag); + public JenkinsServer createJob(FolderJob folder, String jobName, String jobXml, Boolean crumbFlag) + throws IOException { + client.post_xml(UrlUtils.toBaseUrl(folder) + "createItem?name=" + EncodingUtils.formParameter(jobName), jobXml, crumbFlag); + return this; } /** @@ -389,8 +398,8 @@ public void createJob(FolderJob folder, String jobName, String jobXml, Boolean c * @param viewXml The configuration for the view. * @throws IOException in case of an error. */ - public void createView(String viewName, String viewXml) throws IOException { - createView(null, viewName, viewXml, false); + public JenkinsServer createView(String viewName, String viewXml) throws IOException { + return createView(null, viewName, viewXml, false); } /** @@ -402,8 +411,8 @@ public void createView(String viewName, String viewXml) throws IOException { * false otherwise. * @throws IOException in case of an error. */ - public void createView(String viewName, String viewXml, Boolean crumbFlag) throws IOException { - createView(null, viewName, viewXml, crumbFlag); + public JenkinsServer createView(String viewName, String viewXml, Boolean crumbFlag) throws IOException { + return createView(null, viewName, viewXml, crumbFlag); } /** @@ -415,8 +424,8 @@ public void createView(String viewName, String viewXml, Boolean crumbFlag) throw * @param viewXml The configuration for the view. * @throws IOException in case of an error. */ - public void createView(FolderJob folder, String viewName, String viewXml) throws IOException { - createView(folder, viewName, viewXml, false); + public JenkinsServer createView(FolderJob folder, String viewName, String viewXml) throws IOException { + return createView(folder, viewName, viewXml, false); } /** @@ -430,9 +439,11 @@ public void createView(FolderJob folder, String viewName, String viewXml) throws * false otherwise. * @throws IOException in case of an error. */ - public void createView(FolderJob folder, String viewName, String viewXml, Boolean crumbFlag) throws IOException { - client.post_xml(toBaseUrl(folder) + "createView?name=" + EncodingUtils.encodeParam(viewName), viewXml, + public JenkinsServer createView(FolderJob folder, String viewName, String viewXml, Boolean crumbFlag) + throws IOException { + client.post_xml(UrlUtils.toBaseUrl(folder) + "createView?name=" + EncodingUtils.formParameter(viewName), viewXml, crumbFlag); + return this; } /** @@ -441,8 +452,8 @@ public void createView(FolderJob folder, String viewName, String viewXml, Boolea * @param folderName name of the folder. * @throws IOException in case of an error. */ - public void createFolder(String folderName) throws IOException { - createFolder(null, folderName, false); + public JenkinsServer createFolder(String folderName) throws IOException { + return createFolder(null, folderName, false); } /** @@ -453,8 +464,8 @@ public void createFolder(String folderName) throws IOException { * false otherwise. * @throws IOException in case of an error. */ - public void createFolder(String folderName, Boolean crumbFlag) throws IOException { - createFolder(null, folderName, crumbFlag); + public JenkinsServer createFolder(String folderName, Boolean crumbFlag) throws IOException { + return createFolder(null, folderName, crumbFlag); } /** @@ -464,8 +475,8 @@ public void createFolder(String folderName, Boolean crumbFlag) throws IOExceptio * @param jobName name of the job. * @throws IOException in case of an error. */ - public void createFolder(FolderJob folder, String jobName) throws IOException { - createFolder(folder, jobName, false); + public JenkinsServer createFolder(FolderJob folder, String jobName) throws IOException { + return createFolder(folder, jobName, false); } /** @@ -477,12 +488,17 @@ public void createFolder(FolderJob folder, String jobName) throws IOException { * false otherwise. * @throws IOException in case of an error. */ - public void createFolder(FolderJob folder, String jobName, Boolean crumbFlag) throws IOException { + public JenkinsServer createFolder(FolderJob folder, String jobName, Boolean crumbFlag) throws IOException { // https://gist.github.com/stuart-warren/7786892 was slightly helpful // here - ImmutableMap params = ImmutableMap.of("mode", "com.cloudbees.hudson.plugins.folder.Folder", - "name", EncodingUtils.encodeParam(jobName), "from", "", "Submit", "OK"); - client.post_form(toBaseUrl(folder) + "createItem?", params, crumbFlag); + //TODO: JDK9+: Map.of(...) + Map params = new HashMap<>(); + params.put("mode", "com.cloudbees.hudson.plugins.folder.Folder"); + params.put("name", jobName); + params.put("from", ""); + params.put("Submit", "OK"); + client.post_form(UrlUtils.toBaseUrl(folder) + "createItem?", params, crumbFlag); + return this; } /** @@ -505,7 +521,7 @@ public String getJobXml(String jobName) throws IOException { * @throws IOException in case of an error. */ public String getJobXml(FolderJob folder, String jobName) throws IOException { - return client.get(toJobBaseUrl(folder, jobName) + "/config.xml"); + return client.get(UrlUtils.toJobBaseUrl(folder, jobName) + "/config.xml"); } /** @@ -528,13 +544,9 @@ public LabelWithDetails getLabel(String labelName) throws IOException { */ public Map getComputers() throws IOException { List computers = client.get("computer/", Computer.class).getComputers(); - return Maps.uniqueIndex(computers, new Function() { - @Override - public String apply(Computer computer) { - computer.setClient(client); - return computer.getDisplayName().toLowerCase(); - } - }); + return computers.stream() + .map(SET_CLIENT(this.client)) + .collect(Collectors.toMap(s -> s.getDisplayName().toLowerCase(), Function.identity())); } /** @@ -562,17 +574,28 @@ public PluginManager getPluginManager() throws IOException { /** * Update the xml description of an existing view * - * @throws IOException in case of an error. * @param viewName name of the view. * @param viewXml the view configuration. * @throws IOException in case of an error. */ - public void updateView(String viewName, String viewXml) throws IOException { - this.updateView(viewName, viewXml, true); + public JenkinsServer updateView(String viewName, String viewXml) throws IOException { + return this.updateView(viewName, viewXml, true); } - public void updateView(String viewName, String viewXml, boolean crumbFlag) throws IOException { + public JenkinsServer updateView(String viewName, String viewXml, boolean crumbFlag) throws IOException { client.post_xml("/view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, crumbFlag); + return this; + } + + public JenkinsServer updateView(FolderJob folder, String viewName, String viewXml) throws IOException { + client.post_xml(UrlUtils.toBaseUrl(folder) + "view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, true); + return this; + } + + public JenkinsServer updateView(FolderJob folder, String viewName, String viewXml, boolean crumbFlag) + throws IOException { + client.post_xml(UrlUtils.toBaseUrl(folder) + "view/" + EncodingUtils.encode(viewName) + "/config.xml", viewXml, crumbFlag); + return this; } /** @@ -582,8 +605,8 @@ public void updateView(String viewName, String viewXml, boolean crumbFlag) throw * @param jobXml the configuration to be used for updating. * @throws IOException in case of an error. */ - public void updateJob(String jobName, String jobXml) throws IOException { - this.updateJob(jobName, jobXml, true); + public JenkinsServer updateJob(String jobName, String jobXml) throws IOException { + return this.updateJob(jobName, jobXml, true); } /** @@ -594,8 +617,8 @@ public void updateJob(String jobName, String jobXml) throws IOException { * @param crumbFlag true/false. * @throws IOException in case of an error. */ - public void updateJob(String jobName, String jobXml, boolean crumbFlag) throws IOException { - updateJob(null, jobName, jobXml, crumbFlag); + public JenkinsServer updateJob(String jobName, String jobXml, boolean crumbFlag) throws IOException { + return updateJob(null, jobName, jobXml, crumbFlag); } /** @@ -607,25 +630,27 @@ public void updateJob(String jobName, String jobXml, boolean crumbFlag) throws I * @param crumbFlag true/false. * @throws IOException in case of an error. */ - public void updateJob(FolderJob folder, String jobName, String jobXml, boolean crumbFlag) throws IOException { - client.post_xml(toJobBaseUrl(folder, jobName) + "/config.xml", jobXml, crumbFlag); + public JenkinsServer updateJob(FolderJob folder, String jobName, String jobXml, boolean crumbFlag) + throws IOException { + client.post_xml(UrlUtils.toJobBaseUrl(folder, jobName) + "/config.xml", jobXml, crumbFlag); + return this; } /** * @param jobName name of the job. - * @param name name of the parameter. + * @param name name of the formParameter. * @param description of the parameters. * @param defaultValue the defaultValue for the parameters. * @throws IOException in case of an error. * @throws JAXBException in case of an error. * @throws DocumentException in case of an error. */ - public void addStringParam(String jobName, String name, String description, String defaultValue) + public JenkinsServer addStringParam(String jobName, String name, String description, String defaultValue) throws IOException, JAXBException, DocumentException { String jobXml = this.getJobXml(jobName); JobConfiguration jobConf = new JobConfiguration(jobXml); jobXml = jobConf.addStringParam(name, description, defaultValue).asXml(); - this.updateJob(jobName, jobXml); + return this.updateJob(jobName, jobXml); } /** @@ -633,13 +658,13 @@ public void addStringParam(String jobName, String name, String description, Stri * * @throws IOException in case of an error. */ - public void quietDown() throws IOException { + public JenkinsServer quietDown() throws IOException { try { - client.get("/quietDown/"); + client.post("/quietDown/"); } catch (org.apache.http.client.ClientProtocolException e) { LOGGER.error("quietDown()", e); } - + return this; } /** @@ -647,12 +672,13 @@ public void quietDown() throws IOException { * * @throws IOException in case of an error. */ - public void cancelQuietDown() throws IOException { + public JenkinsServer cancelQuietDown() throws IOException { try { client.post("/cancelQuietDown/"); } catch (org.apache.http.client.ClientProtocolException e) { LOGGER.error("cancelQuietDown()", e); } + return this; } /** @@ -663,8 +689,8 @@ public void cancelQuietDown() throws IOException { * * @throws IOException in case of an error. */ - public void deleteJob(FolderJob folder, String jobName) throws IOException { - deleteJob(folder, jobName, false); + public JenkinsServer deleteJob(FolderJob folder, String jobName) throws IOException { + return deleteJob(folder, jobName, false); } /** @@ -675,8 +701,9 @@ public void deleteJob(FolderJob folder, String jobName) throws IOException { * @param crumbFlag The crumbFlag * @throws IOException in case of problems. */ - public void deleteJob(FolderJob folder, String jobName, boolean crumbFlag) throws IOException { - client.post(toJobBaseUrl(folder, jobName) + "/doDelete", crumbFlag); + public JenkinsServer deleteJob(FolderJob folder, String jobName, boolean crumbFlag) throws IOException { + client.post(UrlUtils.toJobBaseUrl(folder, jobName) + "/doDelete", crumbFlag); + return this; } /** @@ -685,8 +712,8 @@ public void deleteJob(FolderJob folder, String jobName, boolean crumbFlag) throw * @param jobName The name of the job which should be deleted. * @throws IOException in case of an error. */ - public void deleteJob(String jobName) throws IOException { - client.post("/job/" + EncodingUtils.encode(jobName) + "/doDelete"); + public JenkinsServer deleteJob(String jobName) throws IOException { + return deleteJob(jobName, false); } /** @@ -697,8 +724,9 @@ public void deleteJob(String jobName) throws IOException { * false otherwise. * @throws IOException In case of an failure. */ - public void deleteJob(String jobName, boolean crumbFlag) throws IOException { + public JenkinsServer deleteJob(String jobName, boolean crumbFlag) throws IOException { client.post("/job/" + EncodingUtils.encode(jobName) + "/doDelete", crumbFlag); + return this; } /** @@ -707,8 +735,8 @@ public void deleteJob(String jobName, boolean crumbFlag) throws IOException { * @param jobName The name of the job which should be disabled. * @throws IOException in case of an error. */ - public void disableJob(String jobName) throws IOException { - client.post("/job/" + EncodingUtils.encode(jobName) + "/disable"); + public JenkinsServer disableJob(String jobName) throws IOException { + return disableJob(jobName, false); } /** @@ -719,8 +747,9 @@ public void disableJob(String jobName) throws IOException { * false otherwise. * @throws IOException In case of an failure. */ - public void disableJob(String jobName, boolean crumbFlag) throws IOException { + public JenkinsServer disableJob(String jobName, boolean crumbFlag) throws IOException { client.post("/job/" + EncodingUtils.encode(jobName) + "/disable", crumbFlag); + return this; } /** @@ -729,8 +758,8 @@ public void disableJob(String jobName, boolean crumbFlag) throws IOException { * @param jobName name of the job which should be enabled. * @throws IOException In case of an failure. */ - public void enableJob(String jobName) throws IOException { - client.post("/job/" + EncodingUtils.encode(jobName) + "/enable"); + public JenkinsServer enableJob(String jobName) throws IOException { + return enableJob(jobName, false); } /** @@ -741,8 +770,9 @@ public void enableJob(String jobName) throws IOException { * false otherwise. * @throws IOException In case of an failure. */ - public void enableJob(String jobName, boolean crumbFlag) throws IOException { + public JenkinsServer enableJob(String jobName, boolean crumbFlag) throws IOException { client.post("/job/" + EncodingUtils.encode(jobName) + "/enable", crumbFlag); + return this; } /** @@ -828,8 +858,8 @@ public Build getBuild(QueueItem q) throws IOException { * @param newJobName The new job name. * @throws IOException In case of a failure. */ - public void renameJob(String oldJobName, String newJobName) throws IOException { - renameJob(null, oldJobName, newJobName, false); + public JenkinsServer renameJob(String oldJobName, String newJobName) throws IOException { + return renameJob(null, oldJobName, newJobName, false); } /** @@ -841,8 +871,9 @@ public void renameJob(String oldJobName, String newJobName) throws IOException { * false otherwise. * @throws IOException In case of a failure. */ - public void renameJob(String oldJobName, String newJobName, Boolean crumbFlag) throws IOException { + public JenkinsServer renameJob(String oldJobName, String newJobName, Boolean crumbFlag) throws IOException { renameJob(null, oldJobName, newJobName, crumbFlag); + return this; } /** @@ -853,8 +884,8 @@ public void renameJob(String oldJobName, String newJobName, Boolean crumbFlag) t * @param newJobName The new job name. * @throws IOException In case of a failure. */ - public void renameJob(FolderJob folder, String oldJobName, String newJobName) throws IOException { - renameJob(folder, oldJobName, newJobName, false); + public JenkinsServer renameJob(FolderJob folder, String oldJobName, String newJobName) throws IOException { + return renameJob(folder, oldJobName, newJobName, false); } /** @@ -867,46 +898,98 @@ public void renameJob(FolderJob folder, String oldJobName, String newJobName) th * false otherwise. * @throws IOException In case of a failure. */ - public void renameJob(FolderJob folder, String oldJobName, String newJobName, Boolean crumbFlag) + public JenkinsServer renameJob(FolderJob folder, String oldJobName, String newJobName, Boolean crumbFlag) throws IOException { - client.post(toJobBaseUrl(folder, oldJobName) + "/doRename?newName=" + EncodingUtils.encodeParam(newJobName), - crumbFlag); - } - - /** - * Helper to create a base url in case a folder is given - * - * @param folder the folder or {@code null} - * @return The created base url. - */ - private String toBaseUrl(FolderJob folder) { - String path = "/"; - if (folder != null) { - path = folder.getUrl(); - } - return path; - } - - /** - * Helper to create the base url for a job, with or without a given folder - * - * @param folder the folder or {@code null} - * @param jobName the name of the job. - * @return converted base url. - */ - private String toJobBaseUrl(FolderJob folder, String jobName) { - return toBaseUrl(folder) + "job/" + EncodingUtils.encode(jobName); - } - - /** - * Helper to create the base url for a view, with or without a given folder - * - * @param folder the folder or {@code null} - * @param name the of the view. - * @return converted view url. - */ - private String toViewBaseUrl(FolderJob folder, String name) { - return toBaseUrl(folder) + "view/" + EncodingUtils.encode(name); - } - + client.post(UrlUtils.toJobBaseUrl(folder, oldJobName) + + "/doRename?newName=" + EncodingUtils.formParameter(newJobName), + crumbFlag); + return this; + } + + + + /** + * Closes underlying resources. + * Closed instances should no longer be used + * Closing an already closed instance has no side effects + */ + @Override + public void close() { + client.close(); + } + + + /** + * Restart Jenkins without waiting for any existing build to complete + * + * @param crumbFlag + * true to add crumbIssuer false + * otherwise. + * @throws IOException + * in case of an error. + */ + public JenkinsServer restart(Boolean crumbFlag) throws IOException { + try { + client.post("/restart", crumbFlag); + } catch (org.apache.http.client.ClientProtocolException e) { + LOGGER.error("restart()", e); + } + return this; + } + + /** + * safeRestart: Puts Jenkins into the quiet mode, wait for existing builds + * to be completed, and then restart Jenkins + * + * @param crumbFlag + * true to add crumbIssuer false + * otherwise. + * @throws IOException + * in case of an error. + */ + public JenkinsServer safeRestart(Boolean crumbFlag) throws IOException { + try { + client.post("/safeRestart", crumbFlag); + } catch (org.apache.http.client.ClientProtocolException e) { + LOGGER.error("safeRestart()", e); + } + return this; + } + + /** + * Shutdown Jenkins without waiting for any existing build to complete + * + * @param crumbFlag + * true to add crumbIssuer false + * otherwise. + * @throws IOException + * in case of an error. + */ + public JenkinsServer exit(Boolean crumbFlag) throws IOException { + try { + client.post("/exit", crumbFlag); + } catch (org.apache.http.client.ClientProtocolException e) { + LOGGER.error("exit()", e); + } + return this; + } + + /** + * safeExit: Puts Jenkins into the quiet mode, wait for existing builds to + * be completed, and then shut down Jenkins + * + * @param crumbFlag + * true to add crumbIssuer false + * otherwise. + * @throws IOException + * in case of an error. + */ + public JenkinsServer safeExit(Boolean crumbFlag) throws IOException { + try { + client.post("/safeExit", crumbFlag); + } catch (org.apache.http.client.ClientProtocolException e) { + LOGGER.error("safeExit()", e); + } + return this; + } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsTriggerHelper.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsTriggerHelper.java index 3f1ed2b8..8084eafb 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsTriggerHelper.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsTriggerHelper.java @@ -1,5 +1,6 @@ package com.offbytwo.jenkins; +import java.io.File; import java.io.IOException; import java.util.Map; @@ -89,6 +90,49 @@ public BuildWithDetails triggerJobAndWaitUntilFinished(String jobName, Maptrue or false. + * @return In case of an cancelled job you will get + * {@link BuildWithDetails#getResult()} + * {@link BuildResult#CANCELLED}. So you have to check first if the + * build result is {@code CANCELLED}. + * @throws IOException in case of errors. + * @throws InterruptedException In case of interrupts. + */ + public BuildWithDetails triggerJobAndWaitUntilFinished(String jobName, Map params, + Map fileParams, + boolean crumbFlag) throws IOException, InterruptedException { + JobWithDetails job = this.server.getJob(jobName); + QueueReference queueRef = job.build(params, fileParams, crumbFlag); + + return triggerJobAndWaitUntilFinished(jobName, queueRef); + } + + /** + * This method will trigger a build of the given job and will wait until the + * builds is ended or if the build has been cancelled. + * + * @param jobName The name of the job which should be triggered. + * @param params the job parameters + * @param fileParams the job file parameters + * @return In case of an cancelled job you will get + * {@link BuildWithDetails#getResult()} + * {@link BuildResult#CANCELLED}. So you have to check first if the + * build result is {@code CANCELLED}. + * @throws IOException in case of errors. + * @throws InterruptedException In case of interrupts. + */ + public BuildWithDetails triggerJobAndWaitUntilFinished(String jobName, Map params, + Map fileParams) throws IOException, InterruptedException { + return triggerJobAndWaitUntilFinished(jobName, params, fileParams, false); + } + /** * This method will trigger a build of the given job and will wait until the * builds is ended or if the build has been cancelled. diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java old mode 100644 new mode 100755 index df58360f..4eadc5c9 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java @@ -3,30 +3,33 @@ * * Distributed under the MIT license: http://opensource.org/licenses/MIT */ - package com.offbytwo.jenkins.client; -import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; -import static org.apache.commons.lang.StringUtils.isNotBlank; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.List; -import java.util.Map; - +import com.fasterxml.jackson.databind.ObjectMapper; +import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.offbytwo.jenkins.client.util.RequestReleasingInputStream; +import com.offbytwo.jenkins.client.validator.HttpResponseValidator; +import com.offbytwo.jenkins.model.BaseModel; +import com.offbytwo.jenkins.model.Crumb; +import com.offbytwo.jenkins.model.ExtractHeader; +import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; -import org.apache.http.Header; +import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; @@ -38,20 +41,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Lists; -import com.google.common.io.ByteStreams; -import com.offbytwo.jenkins.client.util.EncodingUtils; -import com.offbytwo.jenkins.client.util.RequestReleasingInputStream; -//import com.offbytwo.jenkins.client.util.HttpResponseContentExtractor; -import com.offbytwo.jenkins.client.validator.HttpResponseValidator; -import com.offbytwo.jenkins.model.BaseModel; -import com.offbytwo.jenkins.model.Crumb; -import com.offbytwo.jenkins.model.ExtractHeader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; -import net.sf.json.JSONObject; +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import com.offbytwo.jenkins.client.util.ResponseUtils; +import com.offbytwo.jenkins.client.util.UrlUtils; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class JenkinsHttpClient implements JenkinsHttpConnection { -public class JenkinsHttpClient { private final Logger LOGGER = LoggerFactory.getLogger(getClass()); private URI uri; @@ -65,6 +70,8 @@ public class JenkinsHttpClient { private String jenkinsVersion; + public final static String EMPTY_VERSION = "UNKNOWN"; + /** * Create an unauthenticated Jenkins HTTP client * @@ -81,7 +88,7 @@ public JenkinsHttpClient(URI uri, CloseableHttpClient client) { this.client = client; this.httpResponseValidator = new HttpResponseValidator(); // this.contentExtractor = new HttpResponseContentExtractor(); - this.jenkinsVersion = null; + this.jenkinsVersion = EMPTY_VERSION; LOGGER.debug("uri={}", uri.toString()); } @@ -112,7 +119,19 @@ public JenkinsHttpClient(URI uri) { * @param password Password or auth token to use when connecting */ public JenkinsHttpClient(URI uri, String username, String password) { - this(uri, addAuthentication(HttpClientBuilder.create(), uri, username, password)); + this(uri, HttpClientBuilder.create(), username, password); + } + + /** + * Create an authenticated Jenkins HTTP client + * + * @param uri Location of the jenkins server (ex. http://localhost:8080) + * @param builder Configured HttpClientBuilder to be used + * @param username Username to use when connecting + * @param password Password or auth token to use when connecting + */ + public JenkinsHttpClient(URI uri, HttpClientBuilder builder, String username, String password) { + this(uri, addAuthentication(builder, uri, username, password)); if (isNotBlank(username)) { localContext = new BasicHttpContext(); localContext.setAttribute("preemptive-auth", new BasicScheme()); @@ -120,19 +139,14 @@ public JenkinsHttpClient(URI uri, String username, String password) { } /** - * Perform a GET request and parse the response to the given class - * - * @param path path to request, can be relative or absolute - * @param cls class of the response - * @param type of the response - * @return an instance of the supplied class - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public T get(String path, Class cls) throws IOException { - HttpGet getMethod = new HttpGet(api(path)); + HttpGet getMethod = new HttpGet(UrlUtils.toJsonApiUri(uri, context, path)); HttpResponse response = client.execute(getMethod, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); return objectFromResponse(cls, response); @@ -143,17 +157,13 @@ public T get(String path, Class cls) throws IOException } /** - * Perform a GET request and parse the response and return a simple string - * of the content - * - * @param path path to request, can be relative or absolute - * @return the entity text - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public String get(String path) throws IOException { - HttpGet getMethod = new HttpGet(api(path)); + HttpGet getMethod = new HttpGet(UrlUtils.toJsonApiUri(uri, context, path)); HttpResponse response = client.execute(getMethod, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); LOGGER.debug("get({}), version={}, responseCode={}", path, this.jenkinsVersion, response.getStatusLine().getStatusCode()); try { @@ -167,14 +177,9 @@ public String get(String path) throws IOException { } /** - * Perform a GET request and parse the response to the given class, logging - * any IOException that is thrown rather than propagating it. - * - * @param path path to request, can be relative or absolute - * @param cls class of the response - * @param type of the response - * @return an instance of the supplied class + * {@inheritDoc} */ + @Override public T getQuietly(String path, Class cls) { T value; try { @@ -188,52 +193,63 @@ public T getQuietly(String path, Class cls) { } /** - * Perform a GET request and return the response as InputStream - * - * @param path path to request, can be relative or absolute - * @return the response stream - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public InputStream getFile(URI path) throws IOException { HttpGet getMethod = new HttpGet(path); HttpResponse response = client.execute(getMethod, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); httpResponseValidator.validateResponse(response); return new RequestReleasingInputStream(response.getEntity().getContent(), getMethod); } + /** + * {@inheritDoc} + */ + @Override public R post(String path, D data, Class cls) throws IOException { - return post(path, data, cls, true); + return post(path, data, cls, null, true); } /** - * Perform a POST request and parse the response to the given class - * - * @param path path to request, can be relative or absolute - * @param data data to post - * @param cls class of the response - * @param type of the response - * @param type of the data - * @param crumbFlag true / false. - * @return an instance of the supplied class - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public R post(String path, D data, Class cls, boolean crumbFlag) throws IOException { - HttpPost request = new HttpPost(api(path)); - if (crumbFlag == true) { - Crumb crumb = getQuietly("/crumbIssuer", Crumb.class); - if (crumb != null) { - request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb())); - } - } + return post(path, data, cls, null, crumbFlag); + } + + /** + * {@inheritDoc} + */ + @Override + public R post(String path, D data, Class cls, Map fileParams, boolean crumbFlag) throws IOException { + HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path)); + handleCrumbFlag(crumbFlag, request); if (data != null) { String value = mapper.writeValueAsString(data); StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_JSON); request.setEntity(stringEntity); } + + // Prepare file parameters + if(fileParams != null && !(fileParams.isEmpty())) { + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + for (Map.Entry entry : fileParams.entrySet()) { + FileBody fileBody = new FileBody(entry.getValue()); + builder.addPart(entry.getKey(), fileBody); + } + + HttpEntity entity = builder.build(); + request.setEntity(entity); + } + HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); @@ -257,52 +273,42 @@ public R post(String path, D data, Class cls, boolea } } + private void handleCrumbFlag(boolean crumbFlag, HttpPost request) { + if (crumbFlag) { + Crumb crumb = getQuietly("/crumbIssuer", Crumb.class); + if (crumb != null) { + request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb())); + } + } + } + /** - * Perform a POST request using form url encoding. - * - * This method was added for the purposes of creating folders, but may be - * useful for other API calls as well. - * - * Unlike post and post_xml, the path is *not* modified by adding - * "/api/json". Additionally, the params in data are provided as both - * request parameters including a json parameter, *and* in the - * JSON-formatted StringEntity, because this is what the folder creation - * call required. It is unclear if any other jenkins APIs operate in this - * fashion. - * - * @param path path to request, can be relative or absolute - * @param data data to post - * @param crumbFlag true / false. - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public void post_form(String path, Map data, boolean crumbFlag) throws IOException { HttpPost request; if (data != null) { // https://gist.github.com/stuart-warren/7786892 was slightly // helpful here - List queryParams = Lists.newArrayList(); + List queryParams = new ArrayList<>(); for (String param : data.keySet()) { - queryParams.add(param + "=" + EncodingUtils.encodeParam(data.get(param))); + queryParams.add(param + "=" + EncodingUtils.formParameter(data.get(param))); } - queryParams.add("json=" + EncodingUtils.encodeParam(JSONObject.fromObject(data).toString())); + queryParams.add("json=" + EncodingUtils.formParameter(JSONObject.fromObject(data).toString())); String value = mapper.writeValueAsString(data); StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_FORM_URLENCODED); - request = new HttpPost(noapi(path) + StringUtils.join(queryParams, "&")); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path) + StringUtils.join(queryParams, "&")); request.setEntity(stringEntity); } else { - request = new HttpPost(noapi(path)); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path)); } - if (crumbFlag == true) { - Crumb crumb = get("/crumbIssuer", Crumb.class); - if (crumb != null) { - request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb())); - } - } + handleCrumbFlag(crumbFlag, request); HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { httpResponseValidator.validateResponse(response); @@ -313,34 +319,48 @@ public void post_form(String path, Map data, boolean crumbFlag) } /** - * Perform a POST request of XML (instead of using json mapper) and return a - * string rendering of the response entity. - * - * @param path path to request, can be relative or absolute - * @param xml_data data data to post - * @return A string containing the xml response (if present) - * @throws IOException in case of an error. + * {@inheritDoc} + */ + @Override + public HttpResponse post_form_with_result(String path, List data, boolean crumbFlag) throws IOException { + HttpPost request; + if (data != null) { + UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(data); + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path)); + request.setEntity(urlEncodedFormEntity); + } else { + request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path)); + } + + handleCrumbFlag(crumbFlag, request); + HttpResponse response = client.execute(request, localContext); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); + return response; + } + + /** + * {@inheritDoc} */ + @Override public String post_xml(String path, String xml_data) throws IOException { return post_xml(path, xml_data, true); } + /** + * {@inheritDoc} + */ + @Override public String post_xml(String path, String xml_data, boolean crumbFlag) throws IOException { - HttpPost request = new HttpPost(api(path)); - if (crumbFlag == true) { - Crumb crumb = getQuietly("/crumbIssuer", Crumb.class); - if (crumb != null) { - request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb())); - } - } + HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path)); + handleCrumbFlag(crumbFlag, request); if (xml_data != null) { request.setEntity(new StringEntity(xml_data, ContentType.create("text/xml", "utf-8"))); } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); - httpResponseValidator.validateResponse(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { + httpResponseValidator.validateResponse(response); return IOUtils.toString(response.getEntity().getContent()); } finally { EntityUtils.consume(response.getEntity()); @@ -349,45 +369,29 @@ public String post_xml(String path, String xml_data, boolean crumbFlag) throws I } /** - * Post a text entity to the given URL using the default content type - * - * @param path The path. - * @param textData data. - * @param crumbFlag true/false. - * @return resulting response - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public String post_text(String path, String textData, boolean crumbFlag) throws IOException { return post_text(path, textData, ContentType.DEFAULT_TEXT, crumbFlag); } /** - * Post a text entity to the given URL with the given content type - * - * @param path The path. - * @param textData The data. - * @param contentType {@link ContentType} - * @param crumbFlag true or false. - * @return resulting response - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public String post_text(String path, String textData, ContentType contentType, boolean crumbFlag) throws IOException { - HttpPost request = new HttpPost(api(path)); - if (crumbFlag == true) { - Crumb crumb = get("/crumbIssuer", Crumb.class); - if (crumb != null) { - request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb())); - } - } + HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path)); + handleCrumbFlag(crumbFlag, request); if (textData != null) { request.setEntity(new StringEntity(textData, contentType)); } HttpResponse response = client.execute(request, localContext); - getJenkinsVersionFromHeader(response); - httpResponseValidator.validateResponse(response); + jenkinsVersion = ResponseUtils.getJenkinsVersion(response); try { + httpResponseValidator.validateResponse(response); return IOUtils.toString(response.getEntity().getContent()); } finally { EntityUtils.consume(response.getEntity()); @@ -396,84 +400,62 @@ public String post_text(String path, String textData, ContentType contentType, b } /** - * Perform POST request that takes no parameters and returns no response - * - * @param path path to request - * @throws IOException in case of an error. + * {@inheritDoc} */ + @Override public void post(String path) throws IOException { - post(path, null, null, false); + post(path, null, null, null, false); } + /** + * {@inheritDoc} + */ + @Override public void post(String path, boolean crumbFlag) throws IOException { - post(path, null, null, crumbFlag); - } - - private String urlJoin(String path1, String path2) { - if (!path1.endsWith("/")) { - path1 += "/"; - } - if (path2.startsWith("/")) { - path2 = path2.substring(1); - } - return path1 + path2; - } - - private URI api(String path) { - if (!path.toLowerCase().matches("https?://.*")) { - path = urlJoin(this.context, path); - } - if (!path.contains("?")) { - path = urlJoin(path, "api/json"); - } else { - String[] components = path.split("\\?", 2); - path = urlJoin(components[0], "api/json") + "?" + components[1]; - } - return uri.resolve("/").resolve(path.replace(" ", "%20")); - } - - private URI noapi(String path) { - if (!path.toLowerCase().matches("https?://.*")) { - path = urlJoin(this.context, path); - } - return uri.resolve("/").resolve(path); - } - - private T objectFromResponse(Class cls, HttpResponse response) throws IOException { - InputStream content = response.getEntity().getContent(); - byte[] bytes = ByteStreams.toByteArray(content); - T result = mapper.readValue(bytes, cls); - // TODO: original: - // T result = mapper.readValue(content, cls); - result.setClient(this); - return result; - } - - private ObjectMapper getDefaultMapper() { - ObjectMapper mapper = new ObjectMapper(); - mapper.disable(FAIL_ON_UNKNOWN_PROPERTIES); - return mapper; + post(path, null, null,null, crumbFlag); } /** - * @return the version string. + * {@inheritDoc} */ + @Override public String getJenkinsVersion() { return this.jenkinsVersion; } - private void getJenkinsVersionFromHeader(HttpResponse response) { - Header[] headers = response.getHeaders("X-Jenkins"); - if (headers.length == 1) { - this.jenkinsVersion = headers[0].getValue(); - } + /** + * {@inheritDoc} + */ + @Override + public boolean isJenkinsVersionSet() { + return !EMPTY_VERSION.equals(this.jenkinsVersion); } - private void releaseConnection(HttpRequestBase httpRequestBase) { - httpRequestBase.releaseConnection(); + /** + * Closes underlying resources. Any I/O errors whilst closing are logged + * with debug log level Closed instances should no longer be used Closing an + * already closed instance has no side effects + */ + @Override + public void close() { + try { + client.close(); + } catch (final IOException ex) { + LOGGER.debug("I/O exception closing client", ex); + } } - protected static HttpClientBuilder addAuthentication(HttpClientBuilder builder, URI uri, String username, + + /** + * Add authentication to supplied builder. + * @param builder the builder to configure + * @param uri the server URI + * @param username the username + * @param password the password + * @return the passed in builder + */ + protected static HttpClientBuilder addAuthentication(final HttpClientBuilder builder, + final URI uri, final String username, String password) { if (isNotBlank(username)) { CredentialsProvider provider = new BasicCredentialsProvider(); @@ -487,11 +469,46 @@ protected static HttpClientBuilder addAuthentication(HttpClientBuilder builder, return builder; } + + /** + * Get the local context. + * @return context + */ protected HttpContext getLocalContext() { return localContext; } - protected void setLocalContext(HttpContext localContext) { + + /** + * Set the local context. + * @param localContext the context + */ + protected void setLocalContext(final HttpContext localContext) { this.localContext = localContext; } + + + + + + private T objectFromResponse(Class cls, HttpResponse response) throws IOException { + InputStream content = response.getEntity().getContent(); + byte[] bytes = IOUtils.toByteArray(content); + T result = mapper.readValue(bytes, cls); + // TODO: original: + // T result = mapper.readValue(content, cls); + result.setClient(this); + return result; + } + + private ObjectMapper getDefaultMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.disable(FAIL_ON_UNKNOWN_PROPERTIES); + return mapper; + } + + private void releaseConnection(HttpRequestBase httpRequestBase) { + httpRequestBase.releaseConnection(); + } + } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpConnection.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpConnection.java new file mode 100644 index 00000000..b90bfcaf --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpConnection.java @@ -0,0 +1,230 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.offbytwo.jenkins.client; + +import com.offbytwo.jenkins.model.BaseModel; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Map; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.entity.ContentType; + +/** + * + * @author Dell Green + */ +public interface JenkinsHttpConnection extends Closeable { + + + + /** + * {@inheritDoc} + */ + @Override + public void close(); + + + /** + * Perform a GET request and parse the response to the given class + * + * @param path path to request, can be relative or absolute + * @param cls class of the response + * @param type of the response + * @return an instance of the supplied class + * @throws IOException in case of an error. + */ + T get(String path, Class cls) throws IOException; + + /** + * Perform a GET request and parse the response and return a simple string + * of the content + * + * @param path path to request, can be relative or absolute + * @return the entity text + * @throws IOException in case of an error. + */ + String get(String path) throws IOException; + + /** + * Perform a GET request and return the response as InputStream + * + * @param path path to request, can be relative or absolute + * @return the response stream + * @throws IOException in case of an error. + */ + InputStream getFile(URI path) throws IOException; + + /** + * Perform a GET request and parse the response to the given class, logging + * any IOException that is thrown rather than propagating it. + * + * @param path path to request, can be relative or absolute + * @param cls class of the response + * @param type of the response + * @return an instance of the supplied class + */ + T getQuietly(String path, Class cls); + + /** + * Check to see if the Jenkins version has been set to something different + * than the initialisation value from the constructor. This means there has + * never been made a communication with the Jenkins server. + * @return true if jenkinsVersion has been set by communication, false + * otherwise. + */ + boolean isJenkinsVersionSet(); + + + /** + * Perform a POST request and parse the response to the given class + * + * @param path path to request, can be relative or absolute + * @param data data to post + * @param cls class of the response + * @param type of the response + * @param type of the data + * @return an instance of the supplied class + * @throws IOException in case of an error. + */ + R post(String path, D data, Class cls) throws IOException; + + /** + * Perform a POST request and parse the response to the given class + * + * @param path path to request, can be relative or absolute + * @param data data to post + * @param cls class of the response + * @param type of the response + * @param type of the data + * @param crumbFlag true / false. + * @return an instance of the supplied class + * @throws IOException in case of an error. + */ + R post(String path, D data, Class cls, boolean crumbFlag) throws IOException; + + /** + * Perform a POST request and parse the response to the given class + * + * @param path path to request, can be relative or absolute + * @param data data to post + * @param cls class of the response + * @param fileParams file parameters + * @param type of the response + * @param type of the data + * @param crumbFlag true / false. + * @return an instance of the supplied class + * @throws IOException in case of an error. + */ + R post(String path, D data, Class cls, Map fileParams, boolean crumbFlag) throws IOException; + + /** + * Perform POST request that takes no parameters and returns no response + * + * @param path path to request + * @throws IOException in case of an error. + */ + void post(String path) throws IOException; + + + /** + * Perform POST request that takes no parameters and returns no response + * + * @param path path to request + * @param crumbFlag true / false. + * @throws IOException in case of an error. + */ + void post(String path, boolean crumbFlag) throws IOException; + + /** + * Perform a POST request using form url encoding. + * + * This method was added for the purposes of creating folders, but may be + * useful for other API calls as well. Unlike post and post_xml, the path is + * *not* modified by adding "/toJsonApiUri/json". Additionally, the params + * in data are provided as both request parameters including a json + * formParameter, *and* in the JSON-formatted StringEntity, because this is what + * the folder creation call required. It is unclear if any other jenkins + * APIs operate in this fashion. + * + * @param path path to request, can be relative or absolute + * @param data data to post + * @param crumbFlag true / false. + * @throws IOException in case of an error. + */ + void post_form(String path, Map data, boolean crumbFlag) throws IOException; + + /** + * Perform a POST request using form url encoding and return HttpResponse + * object This method is not performing validation and can be used for more + * generic queries to jenkins. + * + * @param path path to request, can be relative or absolute + * @param data data to post + * @param crumbFlag true / false. + * @return resulting response + * @throws IOException in case of an error. + */ + HttpResponse post_form_with_result(String path, List data, boolean crumbFlag) throws IOException; + + /** + * Post a text entity to the given URL using the default content type + * + * @param path The path. + * @param textData data. + * @param crumbFlag true/false. + * @return resulting response + * @throws IOException in case of an error. + */ + String post_text(String path, String textData, boolean crumbFlag) throws IOException; + + /** + * Post a text entity to the given URL with the given content type + * + * @param path The path. + * @param textData The data. + * @param contentType {@link ContentType} + * @param crumbFlag true or false. + * @return resulting response + * @throws IOException in case of an error. + */ + String post_text(String path, String textData, ContentType contentType, boolean crumbFlag) throws IOException; + + /** + * Perform a POST request of XML (instead of using json mapper) and return a + * string rendering of the response entity. + * + * @param path path to request, can be relative or absolute + * @param xml_data data data to post + * @return A string containing the xml response (if present) + * @throws IOException in case of an error. + */ + String post_xml(String path, String xml_data) throws IOException; + + /** + * Perform a POST request of XML (instead of using json mapper) and return a + * string rendering of the response entity. + * + * @param path path to request, can be relative or absolute + * @param xml_data data data to post + * @param crumbFlag true or false. + * @return A string containing the xml response (if present) + * @throws IOException in case of an error. + */ + String post_xml(String path, String xml_data, boolean crumbFlag) throws IOException; + + /** + * Get the Jenkins server version. + * + * @return the version string + */ + String getJenkinsVersion(); + +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/EncodingUtils.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/EncodingUtils.java index 25661294..4638634b 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/EncodingUtils.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/EncodingUtils.java @@ -1,7 +1,21 @@ +/* + * Copyright (c) 2019 Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ package com.offbytwo.jenkins.client.util; -import com.google.common.net.UrlEscapers; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * This class is a help class to centralize the + * encoding parts which will call an appropriate library function. + * + * @author Karl Heinz Marbaise + */ public final class EncodingUtils { private EncodingUtils() { @@ -9,13 +23,24 @@ private EncodingUtils() { public static String encode(String pathPart) { // jenkins doesn't like the + for space, use %20 instead - String escape = UrlEscapers.urlPathSegmentEscaper().escape(pathPart); - return escape; + try { + return URLEncoder.encode(pathPart, StandardCharsets.UTF_8.displayName()); + } catch (UnsupportedEncodingException e) { + // Should never happen, because that would imply that + // the parameter StandardCharsets.UTF_8 is wrong. + throw new IllegalArgumentException(e); + } } - public static String encodeParam(String pathPart) { + public static String formParameter(String pathPart) { // jenkins doesn't like the + for space, use %20 instead - return UrlEscapers.urlFormParameterEscaper().escape(pathPart); + try { + return URLEncoder.encode(pathPart, StandardCharsets.UTF_8.displayName()); + } catch (UnsupportedEncodingException e) { + // Should never happen, because that would imply that + // the parameter StandardCharsets.UTF_8 is wrong. + throw new IllegalArgumentException(e); + } } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java new file mode 100644 index 00000000..572ff4b6 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/ResponseUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; + +/** + * + * @author Dell Green + */ +public final class ResponseUtils { + + /** + * Utility Class. + */ + private ResponseUtils() { + //do nothing + } + + + + + /** + * Get Jenkins version from supplied response if any. + * @param response the response + * @return the version or empty string + */ + public static String getJenkinsVersion(final HttpResponse response) { + final Header[] hdrs = response.getHeaders("X-Jenkins"); + return hdrs.length == 0 ? "" : hdrs[0].getValue(); + } + + +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java new file mode 100644 index 00000000..6f8b17d9 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ + +package com.offbytwo.jenkins.client.util; + +import com.offbytwo.jenkins.model.FolderJob; +import java.net.URI; + +/** + * Utility class for manipulating API paths. + * @author Dell Green + */ +public final class UrlUtils { + + /** + * The default size to use for string buffers. + */ + private static final int DEFAULT_BUFFER_SIZE = 64; + + /** + * Utility Class. + */ + private UrlUtils() { + //do nothing + } + + + + /** + * Helper to create a base url in case a folder is given + * @param folder the folder or {@code null} + * @return The created base url. + */ + public static String toBaseUrl(final FolderJob folder) { + return folder == null ? "/" : folder.getUrl(); + } + + + + /** + * Helper to create the base url for a job, with or without a given folder + * @param folder the folder or {@code null} + * @param jobName the name of the job. + * @return converted base url. + */ + public static String toJobBaseUrl(final FolderJob folder, final String jobName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + sb.append(UrlUtils.toBaseUrl(folder)); + if (sb.charAt(sb.length() - 1) != '/') sb.append('/'); + sb.append("job/"); + final String[] jobNameParts = jobName.split("/"); + + for (int i = 0; i < jobNameParts.length; i++) { + sb.append(EncodingUtils.encode(jobNameParts[i])); + if (i != jobNameParts.length - 1) sb.append('/'); + } + return sb.toString(); + } + + + /** + * Helper to create the base url for a view, with or without a given folder + * @param folder the folder or {@code null} + * @param name the of the view. + * @return converted view url. + */ + public static String toViewBaseUrl(final FolderJob folder, final String name) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + final String base = UrlUtils.toBaseUrl(folder); + sb.append(base); + if (!base.endsWith("/")) sb.append('/'); + sb.append("view/") + .append(EncodingUtils.encode(name)); + return sb.toString(); + } + + + /** + * Parses the provided job name for folders to get the full path for the job. + * @param jobName the fullName of the job. + * @return the path of the job including folders if present. + */ + public static String toFullJobPath(final String jobName) { + final String[] parts = jobName.split("/"); + if (parts.length == 1) return parts[0]; + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + + for (int i = 0; i < parts.length; i++) { + sb.append(parts[i]); + if (i != parts.length -1) sb.append("/job/"); + } + return sb.toString(); + } + + + + /** + * Join two paths together taking into account leading/trailing slashes. + * @param path1 the first path + * @param path2 the second path + * @return the joins path + */ + public static String join(final String path1, final String path2) { + if (path1.isEmpty() && path2.isEmpty()) return ""; + if (path1.isEmpty() && !path2.isEmpty()) return path2; + if (path2.isEmpty() && !path1.isEmpty()) return path1; + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE); + sb.append(path1); + if (sb.charAt(sb.length() - 1) == '/') sb.setLength(sb.length() - 1); + if (path2.charAt(0) != '/') sb.append('/'); + sb.append(path2); + return sb.toString(); + } + + + + /** + * Create a JSON URI from the supplied parameters. + * @param uri the server URI + * @param context the server context if any + * @param path the specific API path + * @return new full URI instance + */ + public static URI toJsonApiUri(final URI uri, final String context, final String path) { + String p = path; + if (!p.matches("(?i)https?://.*")) p = join(context, p); + + if (!p.contains("?")) { + p = join(p, "api/json"); + } else { + final String[] components = p.split("\\?", 2); + p = join(components[0], "api/json") + "?" + components[1]; + } + return uri.resolve("/").resolve(p.replace(" ", "%20")); + } + + + + /** + * Create a URI from the supplied parameters. + * @param uri the server URI + * @param context the server context if any + * @param path the specific API path + * @return new full URI instance + */ + public static URI toNoApiUri(final URI uri, final String context, final String path) { + final String p = path.matches("(?i)https?://.*") ? path : join(context, path); + return uri.resolve("/").resolve(p); + } + + + +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/BuildConsoleStreamListener.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/BuildConsoleStreamListener.java new file mode 100644 index 00000000..41671fbb --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/BuildConsoleStreamListener.java @@ -0,0 +1,19 @@ +package com.offbytwo.jenkins.helper; + +/** + * Listener interface used to obtain build console logs + */ +public interface BuildConsoleStreamListener { + + /** + * Called by api when new log data available. + * + * @param newLogChunk - string containing latest chunk of logs + */ + void onData(String newLogChunk); + + /** + * Called when streaming console output is finished + */ + void finished(); +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/FunctionalHelper.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/FunctionalHelper.java new file mode 100644 index 00000000..d7f507b7 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/FunctionalHelper.java @@ -0,0 +1,22 @@ +package com.offbytwo.jenkins.helper; + +import com.offbytwo.jenkins.client.JenkinsHttpConnection; +import com.offbytwo.jenkins.model.BaseModel; + +import java.util.function.Function; + +public final class FunctionalHelper { + + private FunctionalHelper() { + // intentionally empty. + } + + public static final Function SET_CLIENT(JenkinsHttpConnection client) { + return s -> { + s.setClient(client); + return s; + }; + } + + +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/Range.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/Range.java index 8b70eb09..cdbdc60a 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/Range.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/helper/Range.java @@ -16,122 +16,127 @@ * The same as {0,N}. *
  • {N}: Just retrieve the N-th element. The same as {N,N+1}.
  • * - * + *

    * You can use the {@link Range} class like this: - * + * *

      * Range fromAndTo = Range.build().from(1).to(5);
      * Range fromOnly = Range.build().from(3).build();
      * Range toOnly = Range.build().to(5).build();
      * Range only = Range.build().only(3);
      * 
    - * - * @author Karl Heinz Marbaise * + * @author Karl Heinz Marbaise */ public final class Range { - public static final String CURLY_BRACKET_OPEN = "%7B"; // { - public static final String CURLY_BRACKET_CLOSE = "%7D"; // } + /** + * This represents {@code {} (left curly bracket). + */ + public static final String CURLY_BRACKET_OPEN = "%7B"; + /** + * This represents {@code }} (right curly bracket). + */ + public static final String CURLY_BRACKET_CLOSE = "%7D"; private Integer from; private Integer to; private Range() { - this.from = null; - this.to = null; + this.from = null; + this.to = null; } private Range setFrom(int from) { - if (from < 0) { - throw new IllegalArgumentException("from value must be greater or equal null."); - } - this.from = new Integer(from); - return this; + if (from < 0) { + throw new IllegalArgumentException("from value must be greater or equal null."); + } + this.from = new Integer(from); + return this; } private Range setTo(int to) { - if (to < 0) { - throw new IllegalArgumentException("to must be greater or equal null."); - } - this.to = new Integer(to); - return this; + if (to < 0) { + throw new IllegalArgumentException("to must be greater or equal null."); + } + this.to = new Integer(to); + return this; } public String getRangeString() { - StringBuilder sb = new StringBuilder(); - sb.append(CURLY_BRACKET_OPEN); - if (this.from != null) { - sb.append(String.format("%d", this.from)); - } + StringBuilder sb = new StringBuilder(); + sb.append(CURLY_BRACKET_OPEN); + if (this.from != null) { + sb.append(String.format("%d", this.from)); + } - sb.append(','); + sb.append(','); - if (this.to != null) { - sb.append(String.format("%d", this.to)); - } + if (this.to != null) { + sb.append(String.format("%d", this.to)); + } - sb.append(CURLY_BRACKET_CLOSE); - return sb.toString(); + sb.append(CURLY_BRACKET_CLOSE); + return sb.toString(); } public static final class FromBuilder { - private Range range; - - public FromBuilder(Range range) { - this.range = range; - } - - public Range to(int t) { - this.range.setTo(t); - if (range.to <= range.from) { - throw new IllegalArgumentException("to must be greater than from"); - } - return this.range; - } - - public Range build() { - return this.range; - } + private Range range; + + public FromBuilder(Range range) { + this.range = range; + } + + public Range to(int t) { + this.range.setTo(t); + if (range.to <= range.from) { + throw new IllegalArgumentException("to must be greater than from"); + } + return this.range; + } + + public Range build() { + return this.range; + } } public static final class ToBuilder { - private Range range; + private Range range; - public ToBuilder(Range range) { - this.range = range; - } + public ToBuilder(Range range) { + this.range = range; + } - public Range build() { - return this.range; - } + public Range build() { + return this.range; + } } public static final class Builder { - private Range range; - - protected Builder() { - this.range = new Range(); - } - - public FromBuilder from(int f) { - this.range.setFrom(f); - return new FromBuilder(this.range); - } - - public ToBuilder to(int t) { - this.range.setTo(t); - return new ToBuilder(this.range); - } - - public Range only(int only) { - this.range.from = new Integer(only); - this.range.to = new Integer(only + 1); - return this.range; - } + private Range range; + + protected Builder() { + this.range = new Range(); + } + + public FromBuilder from(int f) { + this.range.setFrom(f); + return new FromBuilder(this.range); + } + + public ToBuilder to(int t) { + this.range.setTo(t); + return new ToBuilder(this.range); + } + + public Range only(int only) { + this.range.from = new Integer(only); + this.range.to = new Integer(only + 1); + return this.range; + } } public static Builder build() { - return new Builder(); + return new Builder(); } } \ No newline at end of file diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Artifact.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Artifact.java index 6d0b0e69..a6c7a2f0 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Artifact.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Artifact.java @@ -16,24 +16,27 @@ public String getDisplayPath() { return displayPath; } - public void setDisplayPath(String displayPath) { + public Artifact setDisplayPath(String displayPath) { this.displayPath = displayPath; + return this; } public String getFileName() { return fileName; } - public void setFileName(String fileName) { + public Artifact setFileName(String fileName) { this.fileName = fileName; + return this; } public String getRelativePath() { return relativePath; } - public void setRelativePath(String relativePath) { + public Artifact setRelativePath(String relativePath) { this.relativePath = relativePath; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BaseModel.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BaseModel.java index dd473ee8..5275d36b 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BaseModel.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BaseModel.java @@ -6,28 +6,50 @@ package com.offbytwo.jenkins.model; -import com.offbytwo.jenkins.client.JenkinsHttpClient; +import com.offbytwo.jenkins.client.JenkinsHttpConnection; +import java.util.function.Predicate; /** * The base model. - * */ public class BaseModel { + /** + * The class. + */ private String _class; - + + /** + * Get the class. + * @return class + */ public String get_class() { return _class; } //TODO: We should make this private - protected JenkinsHttpClient client; + protected JenkinsHttpConnection client; - public JenkinsHttpClient getClient() { + + /** + * Get the HTTP client. + * @return client + */ + public JenkinsHttpConnection getClient() { return client; } - public void setClient(JenkinsHttpClient client) { + /** + * Set the HTTP client. + * @param client {@link JenkinsHttpConnection}. + */ + public BaseModel setClient(final JenkinsHttpConnection client) { this.client = client; + return this; } + + protected static Predicate isBuildNumberEqualTo(int buildNumber) { + return build -> build.getNumber() == buildNumber; + } + } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Build.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Build.java index 602b4a8f..470707d9 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Build.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Build.java @@ -8,6 +8,7 @@ import java.io.IOException; +import org.apache.http.HttpStatus; import org.apache.http.client.HttpResponseException; public class Build extends BaseModel { @@ -82,16 +83,19 @@ public String getUrl() { return url; } - protected void setNumber(int number) { + protected Build setNumber(int number) { this.number = number; + return this; } - protected void setQueueId(int queueId) { + protected Build setQueueId(int queueId) { this.queueId = queueId; + return this; } - protected void setUrl(String url) { + protected Build setUrl(String url) { this.url = url; + return this; } /** @@ -122,6 +126,7 @@ public TestReport getTestReport() throws IOException { * @throws IOException in case of an error. */ public TestResult getTestResult() throws IOException { + return client.get(this.getUrl() + "/testReport/?depth=1", TestResult.class); } @@ -140,13 +145,40 @@ public String Stop() throws HttpResponseException, IOException { try { return client.get(url + "stop"); - } catch (IOException ex) { - if (((HttpResponseException) ex).getStatusCode() == 405) { + } catch (HttpResponseException ex) { + if (ex.getStatusCode() == HttpStatus.SC_METHOD_NOT_ALLOWED) { stopPost(); return ""; } + throw ex; } - return ""; + } + + /** Stops the build which is currently in progress. This version takes in + * a crumbFlag. In some cases , an error is thrown which reads + * "No valid crumb was included in the request". This stop method is used incase + * those issues occur + * + * @param crumbFlag flag used to specify if a crumb is passed into for the request + * @return the client url + * @throws HttpResponseException in case of an error. + * @throws IOException in case of an error. + */ + public String Stop(boolean crumbFlag) throws HttpResponseException, IOException { + try { + + return client.get(url + "stop"); + } catch (HttpResponseException ex) { + if (ex.getStatusCode() == HttpStatus.SC_METHOD_NOT_ALLOWED) { + stopPost(crumbFlag); + return ""; + } + throw ex; + } + } + + private void stopPost(boolean crumbFlag) throws HttpResponseException, IOException { + client.post(url + "stop", crumbFlag); } private void stopPost() throws HttpResponseException, IOException { diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildCause.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildCause.java index ab15f94f..821a027f 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildCause.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildCause.java @@ -29,48 +29,54 @@ public String getShortDescription() { return shortDescription; } - public void setShortDescription(String shortDescription) { + public BuildCause setShortDescription(String shortDescription) { this.shortDescription = shortDescription; + return this; } public int getUpstreamBuild() { return upstreamBuild; } - public void setUpstreamBuild(Integer upstreamBuild) { + public BuildCause setUpstreamBuild(Integer upstreamBuild) { this.upstreamBuild = upstreamBuild; + return this; } public String getUpstreamProject() { return upstreamProject; } - public void setUpstreamProject(String upstreamProject) { + public BuildCause setUpstreamProject(String upstreamProject) { this.upstreamProject = upstreamProject; + return this; } public String getUpstreamUrl() { return upstreamUrl; } - public void setUpstreamUrl(String upstreamUrl) { + public BuildCause setUpstreamUrl(String upstreamUrl) { this.upstreamUrl = upstreamUrl; + return this; } public String getUserId() { return userId; } - public void setUserId(String userId) { + public BuildCause setUserId(String userId) { this.userId = userId; + return this; } public String getUserName() { return userName; } - public void setUserName(String userName) { + public BuildCause setUserName(String userName) { this.userName = userName; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSet.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSet.java index 1987cc49..2b7cf520 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSet.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSet.java @@ -24,8 +24,9 @@ public class BuildChangeSet { /** * @param items {@link BuildChangeSet} */ - public void setItems(List items) { + public BuildChangeSet setItems(List items) { this.items = items; + return this; } /** @@ -45,8 +46,9 @@ public String getKind() { /** * @param kind the kind of (usually svn, git). */ - public void setKind(String kind) { + public BuildChangeSet setKind(String kind) { this.kind = kind; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetAuthor.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetAuthor.java index 9abdd1ba..af416581 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetAuthor.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetAuthor.java @@ -13,16 +13,18 @@ public String getAbsoluteUrl() { return absoluteUrl; } - public void setAbsoluteUrl(String absoluteUrl) { + public BuildChangeSetAuthor setAbsoluteUrl(String absoluteUrl) { this.absoluteUrl = absoluteUrl; + return this; } public String getFullName() { return fullName; } - public void setFullName(String fullName) { + public BuildChangeSetAuthor setFullName(String fullName) { this.fullName = fullName; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetItem.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetItem.java index e817d12b..922a19b1 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetItem.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetItem.java @@ -27,72 +27,81 @@ public List getAffectedPaths() { return affectedPaths; } - public void setAffectedPaths(List affectedPaths) { + public BuildChangeSetItem setAffectedPaths(List affectedPaths) { this.affectedPaths = affectedPaths; + return this; } public String getCommitId() { return commitId; } - public void setCommitId(String commitId) { + public BuildChangeSetItem setCommitId(String commitId) { this.commitId = commitId; + return this; } public String getTimestamp() { return timestamp; } - public void setTimestamp(String timeStamp) { + public BuildChangeSetItem setTimestamp(String timeStamp) { this.timestamp = timeStamp; + return this; } public String getComment() { return comment; } - public void setComment(String comment) { + public BuildChangeSetItem setComment(String comment) { this.comment = comment; + return this; } public String getDate() { return date; } - public void setDate(String date) { + public BuildChangeSetItem setDate(String date) { this.date = date; + return this; } public String getId() { return id; } - public void setId(String id) { + public BuildChangeSetItem setId(String id) { this.id = id; + return this; } public String getMsg() { return msg; } - public void setMsg(String msg) { + public BuildChangeSetItem setMsg(String msg) { this.msg = msg; + return this; } public List getPaths() { return paths; } - public void setPaths(List paths) { + public BuildChangeSetItem setPaths(List paths) { this.paths = paths; + return this; } public BuildChangeSetAuthor getAuthor() { return author; } - public void setAuthor(BuildChangeSetAuthor author) { + public BuildChangeSetItem setAuthor(BuildChangeSetAuthor author) { this.author = author; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetPath.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetPath.java index 4bd1a055..5758ee78 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetPath.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildChangeSetPath.java @@ -7,26 +7,38 @@ public class BuildChangeSetPath { /** - * Usually {@code edit} etc. + * The SCM operation, add or edit or delete + * @see EditType */ - // TODO: Think about it if its possible to use an enum type? - private String editType; // edit, ? + private String editType; private String file; + /** + * Return the SCM operation. + * @return the SCM operation, add or edit or delete + * @see EditType + */ public String getEditType() { return editType; } - public void setEditType(String editType) { + /** + * Sets the SCM operation. + * @param editType the SCM operation, add or edit or delete + * @see EditType + */ + public BuildChangeSetPath setEditType(String editType) { this.editType = editType; + return this; } public String getFile() { return file; } - public void setFile(String file) { + public BuildChangeSetPath setFile(String file) { this.file = file; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildWithDetails.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildWithDetails.java index 4fbf463c..d3ce5842 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildWithDetails.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildWithDetails.java @@ -6,17 +6,30 @@ package com.offbytwo.jenkins.model; -import com.google.common.base.Predicate; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.offbytwo.jenkins.helper.BuildConsoleStreamListener; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; -import static com.google.common.collect.Collections2.filter; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; /** * This class represents build information with details about what has been done @@ -25,6 +38,11 @@ */ public class BuildWithDetails extends Build { + private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + public final static String TEXT_SIZE_HEADER = "x-text-size"; + public final static String MORE_DATA_HEADER = "x-more-data"; + /** * This will be returned by the API in cases where the build has never run. * For example {@link Build#BUILD_HAS_NEVER_RUN} @@ -91,7 +109,7 @@ public BuildResult getResult() { }; - private List actions; // TODO: Should be improved. + private List>>> actions; // TODO: Should be improved. private boolean building; private String description; private String displayName; @@ -105,6 +123,8 @@ public BuildResult getResult() { private String consoleOutputText; private String consoleOutputHtml; private BuildChangeSet changeSet; + @JsonProperty("changeSets") + private List changeSets; private String builtOn; private List culprits; @@ -141,31 +161,12 @@ public boolean isBuilding() { } public List getCauses() { - // actions is a List List[BuildCause] - Collection causes = filter(actions, new Predicate>() { - @Override - public boolean apply(Map action) { - return action.containsKey("causes"); - } - }); - - List result = new ArrayList(); - - if (causes != null && !causes.isEmpty()) { - // The underlying key-value can be either a or a - // . - List> causes_blob = ((Map>>) causes.toArray()[0]) - .get("causes"); - for (Map cause : causes_blob) { - - BuildCause convertToBuildCause = convertToBuildCause(cause); - - result.add(convertToBuildCause); - } - } - - return result; + return actions.stream() + .filter(item -> item.containsKey("causes")) + .flatMap(item -> item.entrySet().stream()) + .flatMap(sub -> sub.getValue().stream()) + .map(item -> convertToBuildCause(item)) + .collect(toList()); } /** @@ -177,14 +178,19 @@ public boolean apply(Map action) { * @param crumbFlag true or false. * @throws IOException in case of errors. */ - public void updateDisplayNameAndDescription(String displayName, String description, boolean crumbFlag) + public BuildWithDetails updateDisplayNameAndDescription(String displayName, String description, boolean crumbFlag) throws IOException { Objects.requireNonNull(displayName, "displayName is not allowed to be null."); Objects.requireNonNull(description, "description is not allowed to be null."); + //TODO:JDK9+ Map.of()... + Map params = new HashMap<>(); + params.put("displayName", displayName); + params.put("description", description); // TODO: Check what the "core:apply" means? - ImmutableMap params = ImmutableMap.of("displayName", displayName, "description", description, - "core:apply", "", "Submit", "Save"); + params.put("core:apply", ""); + params.put("Submit", "Save"); client.post_form(this.getUrl() + "/configSubmit?", params, crumbFlag); + return this; } /** @@ -195,8 +201,8 @@ public void updateDisplayNameAndDescription(String displayName, String descripti * @param description The description which should be set. * @throws IOException in case of errors. */ - public void updateDisplayNameAndDescription(String displayName, String description) throws IOException { - updateDisplayNameAndDescription(displayName, description, false); + public BuildWithDetails updateDisplayNameAndDescription(String displayName, String description) throws IOException { + return updateDisplayNameAndDescription(displayName, description, false); } /** @@ -206,13 +212,17 @@ public void updateDisplayNameAndDescription(String displayName, String descripti * @param crumbFlag true or false. * @throws IOException in case of errors. */ - public void updateDisplayName(String displayName, boolean crumbFlag) throws IOException { + public BuildWithDetails updateDisplayName(String displayName, boolean crumbFlag) throws IOException { Objects.requireNonNull(displayName, "displayName is not allowed to be null."); String description = getDescription() == null ? "" : getDescription(); + Map params = new HashMap<>(); + params.put("displayName", displayName); + params.put("description", description); // TODO: Check what the "core:apply" means? - ImmutableMap params = ImmutableMap.of("displayName", displayName, "description", description, - "core:apply", "", "Submit", "Save"); + params.put("core:apply", ""); + params.put("Submit", "Save"); client.post_form(this.getUrl() + "/configSubmit?", params, crumbFlag); + return this; } /** @@ -221,8 +231,8 @@ public void updateDisplayName(String displayName, boolean crumbFlag) throws IOEx * @param displayName The new displayName which should be set. * @throws IOException in case of errors. */ - public void updateDisplayName(String displayName) throws IOException { - updateDisplayName(displayName, false); + public BuildWithDetails updateDisplayName(String displayName) throws IOException { + return updateDisplayName(displayName, false); } /** @@ -232,13 +242,18 @@ public void updateDisplayName(String displayName) throws IOException { * @param crumbFlag true or false. * @throws IOException in case of errors. */ - public void updateDescription(String description, boolean crumbFlag) throws IOException { + public BuildWithDetails updateDescription(String description, boolean crumbFlag) throws IOException { Objects.requireNonNull(description, "description is not allowed to be null."); String displayName = getDisplayName() == null ? "" : getDisplayName(); + //JDK9+: Map.of(..) + Map params = new HashMap<>(); + params.put("displayName", displayName); + params.put("description", description); // TODO: Check what the "core:apply" means? - ImmutableMap params = ImmutableMap.of("displayName", displayName, "description", description, - "core:apply", "", "Submit", "Save"); + params.put("core:apply", ""); + params.put("Submit", "Save"); client.post_form(this.getUrl() + "/configSubmit?", params, crumbFlag); + return this; } /** @@ -247,8 +262,12 @@ public void updateDescription(String description, boolean crumbFlag) throws IOEx * @param description The description which should be set. * @throws IOException in case of errors. */ - public void updateDescription(String description) throws IOException { - updateDescription(description, false); + public BuildWithDetails updateDescription(String description) throws IOException { + return updateDescription(description, false); + } + + private boolean isNullOrEmpty(String value) { + return value == null || value.isEmpty(); } private BuildCause convertToBuildCause(Map cause) { @@ -256,7 +275,7 @@ private BuildCause convertToBuildCause(Map cause) { // TODO: Think about it. Can this be done more simpler? String description = (String) cause.get("shortDescription"); - if (!Strings.isNullOrEmpty(description)) { + if (!isNullOrEmpty(description)) { cause_object.setShortDescription(description); } @@ -266,22 +285,22 @@ private BuildCause convertToBuildCause(Map cause) { } String upstreamProject = (String) cause.get("upstreamProject"); - if (!Strings.isNullOrEmpty(upstreamProject)) { + if (!isNullOrEmpty(upstreamProject)) { cause_object.setUpstreamProject(upstreamProject); } String upstreamUrl = (String) cause.get("upstreamUrl"); - if (!Strings.isNullOrEmpty(upstreamProject)) { + if (!isNullOrEmpty(upstreamUrl)) { cause_object.setUpstreamUrl(upstreamUrl); } String userId = (String) cause.get("userId"); - if (!Strings.isNullOrEmpty(userId)) { + if (!isNullOrEmpty(userId)) { cause_object.setUserId(userId); } String userName = (String) cause.get("userName"); - if (!Strings.isNullOrEmpty(userName)) { + if (!isNullOrEmpty(userName)) { cause_object.setUserName(userName); } return cause_object; @@ -327,31 +346,22 @@ public List getActions() { return actions; } - public Map getParameters() { - Collection parameters = filter(actions, new Predicate>() { - @Override - public boolean apply(Map action) { - return action.containsKey("parameters"); - } - }); - - Map params = new HashMap(); - - if (parameters != null && !parameters.isEmpty()) { - for (Map param : ((Map>>) parameters.toArray()[0]) - .get("parameters")) { - String key = (String) param.get("name"); - Object value = param.get("value"); - params.put(key, String.valueOf(value)); - } - } + public Map getParameters() { + Map parameters = actions.stream() + .filter(item -> item.containsKey("parameters")) + .flatMap(item -> item.entrySet().stream()) + .flatMap(sub -> sub.getValue().stream()) + .collect(toMap(k -> (String) k.get("name"), v -> v.get("value"))); - return params; + return parameters; } /** - * @return The console output of the build. The line separation is done by + * @return The full console output of the build. The line separation is done by * {@code CR+LF}. + * + * @see #streamConsoleOutput(BuildConsoleStreamListener, int, int, boolean) method for obtaining logs for running build + * * @throws IOException in case of a failure. */ public String getConsoleOutputText() throws IOException { @@ -359,8 +369,10 @@ public String getConsoleOutputText() throws IOException { } /** - * The console output with HTML. - * + * The full console output with HTML. + * + * @see #streamConsoleOutput(BuildConsoleStreamListener, int, int, boolean) method for obtaining logs for running build + * * @return The console output as HTML. * @throws IOException in case of an error. */ @@ -368,24 +380,148 @@ public String getConsoleOutputHtml() throws IOException { return client.get(getUrl() + "/logText/progressiveHtml"); } + + /** + * Stream build console output log as text using BuildConsoleStreamListener + * Method can be used to asynchronously obtain logs for running build. + * + * @param listener interface used to asynchronously obtain logs + * @param poolingInterval interval (seconds) used to pool jenkins for logs + * @param poolingTimeout pooling timeout (seconds) used to break pooling in case build stuck + * @throws InterruptedException in case of an error. + * @throws IOException in case of an error. + * + */ + public void streamConsoleOutput(final BuildConsoleStreamListener listener, final int poolingInterval, final int poolingTimeout, boolean crumbFlag) throws InterruptedException, IOException { + // Calculate start and timeout + final long startTime = System.currentTimeMillis(); + final long timeoutTime = startTime + (poolingTimeout * 1000); + + int bufferOffset = 0; + while (true) { + Thread.sleep(poolingInterval * 1000); + + ConsoleLog consoleLog = null; + consoleLog = getConsoleOutputText(bufferOffset, crumbFlag); + String logString = consoleLog.getConsoleLog(); + if (logString != null && !logString.isEmpty()) { + listener.onData(logString); + } + if (consoleLog.getHasMoreData()) { + bufferOffset = consoleLog.getCurrentBufferSize(); + } else { + listener.finished(); + break; + } + long currentTime = System.currentTimeMillis(); + + if (currentTime > timeoutTime) { + LOGGER.warn("Pooling for build {0} for {2} timeout! Check if job stuck in jenkins", + BuildWithDetails.this.getDisplayName(), BuildWithDetails.this.getNumber()); + break; + } + } + } + + /** + * Get build console output log as text. + * Use this method to periodically obtain logs from jenkins and skip chunks that were already received + * + * @param bufferOffset offset in console lo + * @param crumbFlag true or false. + * @return ConsoleLog object containing console output of the build. The line separation is done by + * {@code CR+LF}. + * @throws IOException in case of a failure. + */ + public ConsoleLog getConsoleOutputText(int bufferOffset, boolean crumbFlag) throws IOException { + List formData = new ArrayList<>(); + formData.add(new BasicNameValuePair("start", Integer.toString(bufferOffset))); + String path = getUrl() + "logText/progressiveText"; + HttpResponse httpResponse = client.post_form_with_result(path, formData, crumbFlag); + + Header moreDataHeader = httpResponse.getFirstHeader(MORE_DATA_HEADER); + Header textSizeHeader = httpResponse.getFirstHeader(TEXT_SIZE_HEADER); + String response = EntityUtils.toString(httpResponse.getEntity()); + boolean hasMoreData = false; + if (moreDataHeader != null) { + hasMoreData = Boolean.TRUE.toString().equals(moreDataHeader.getValue()); + } + Integer currentBufferSize = bufferOffset; + if (textSizeHeader != null) { + try { + currentBufferSize = Integer.parseInt(textSizeHeader.getValue()); + } catch (NumberFormatException e) { + LOGGER.warn("Cannot parse buffer size for job {0} build {1}. Using current offset!", this.getDisplayName(), this.getNumber()); + } + } + return new ConsoleLog(response, hasMoreData, currentBufferSize); + } + + + /** + * Returns the change set of a build if available. + * + * If a build performs several scm checkouts (i.e. pipeline builds), the change set of the first + * checkout is returned. To get the complete list of change sets for all checkouts, use + * {@link #getChangeSets()} + * + * If no checkout is performed, null is returned. + * + * @return The change set of the build. + * + */ public BuildChangeSet getChangeSet() { - return changeSet; + BuildChangeSet result; + if (changeSet != null) { + result = changeSet; + } else if (changeSets != null && !changeSets.isEmpty()) { + result = changeSets.get(0); + } else { + result = null; + } + return result; } - public void setChangeSet(BuildChangeSet changeSet) { + public BuildWithDetails setChangeSet(BuildChangeSet changeSet) { this.changeSet = changeSet; + return this; + } + + /** + * Returns the complete list of change sets for all checkout the build has performed. If no + * checkouts have been performed, returns null. + * + * @return The complete list of change sets of the build. + */ + public List getChangeSets() { + List result; + if (changeSets != null) { + result = changeSets; + } else if (changeSet != null) { + result = Collections.singletonList(changeSet); + } else { + result = null; + } + return result; + } + + public BuildWithDetails setChangeSets(List changeSets) { + this.changeSets = changeSets; + return this; } public List getCulprits() { return culprits; } - public void setCulprits(List culprits) { + public BuildWithDetails setCulprits(List culprits) { this.culprits = culprits; + return this; } - public void setResult(BuildResult result) { + public BuildWithDetails setResult(BuildResult result) { this.result = result; + return this; } public InputStream downloadArtifact(Artifact a) throws IOException, URISyntaxException { @@ -397,6 +533,17 @@ public InputStream downloadArtifact(Artifact a) throws IOException, URISyntaxExc ""); return client.getFile(artifactUri); } + + /** + * Returns {@link MavenModuleWithDetails} based on its name + * + * @param name module name + * @return {@link MavenModuleWithDetails} + * @throws IOException in case of error. + */ + public MavenModuleWithDetails getModule(String name) throws IOException { + return client.get(getUrl() + name, MavenModuleWithDetails.class); + } @Override public boolean equals(Object obj) { @@ -429,6 +576,11 @@ public boolean equals(Object obj) { return false; } else if (!changeSet.equals(other.changeSet)) return false; + if (changeSets == null) { + if (other.changeSets != null) + return false; + } else if (!changeSets.equals(other.changeSets)) + return false; if (consoleOutputHtml == null) { if (other.consoleOutputHtml != null) return false; @@ -484,6 +636,7 @@ public int hashCode() { result = prime * result + (building ? 1231 : 1237); result = prime * result + ((builtOn == null) ? 0 : builtOn.hashCode()); result = prime * result + ((changeSet == null) ? 0 : changeSet.hashCode()); + result = prime * result + ((changeSets == null) ? 0 : changeSets.hashCode()); result = prime * result + ((consoleOutputHtml == null) ? 0 : consoleOutputHtml.hashCode()); result = prime * result + ((consoleOutputText == null) ? 0 : consoleOutputText.hashCode()); result = prime * result + ((culprits == null) ? 0 : culprits.hashCode()); diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/CauseAction.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/CauseAction.java index e481f03a..dd455b50 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/CauseAction.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/CauseAction.java @@ -9,24 +9,27 @@ public String getShortDescription() { return shortDescription; } - public void setShortDescription(String shortDescription) { + public CauseAction setShortDescription(String shortDescription) { this.shortDescription = shortDescription; + return this; } public String getUserId() { return userId; } - public void setUserId(String userId) { + public CauseAction setUserId(String userId) { this.userId = userId; + return this; } public String getUserName() { return userName; } - public void setUserName(String userName) { + public CauseAction setUserName(String userName) { this.userName = userName; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Computer.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Computer.java index edc7350c..2414f061 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Computer.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Computer.java @@ -9,7 +9,8 @@ import java.io.IOException; import java.util.List; -import com.google.common.net.UrlEscapers; +import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.fasterxml.jackson.annotation.JsonProperty; /** * @author Kelly Plummer @@ -19,17 +20,20 @@ public class Computer extends BaseModel { private String displayName; - public List getComputers() { - return computer; - } + private List computers; - public void setComputer(List computer) { - this.computer = computer; + public Computer() { } - List computer; + @JsonProperty("computer") + public List getComputers() { + return computers; + } - public Computer() { + @JsonProperty("computer") + public Computer setComputers(List computers) { + this.computers = computers; + return this; } public Computer(String displayName) { @@ -46,7 +50,7 @@ public ComputerWithDetails details() throws IOException { if ("master".equals(displayName)) { name = "(master)"; } else { - name = UrlEscapers.urlPathSegmentEscaper().escape(displayName); + name = EncodingUtils.encode(displayName); } // TODO: Check if depth=2 is a good idea or if it could be solved // better. @@ -65,10 +69,10 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; Computer other = (Computer) obj; - if (computer == null) { - if (other.computer != null) + if (computers == null) { + if (other.computers != null) return false; - } else if (!computer.equals(other.computer)) + } else if (!computers.equals(other.computers)) return false; if (displayName == null) { if (other.displayName != null) @@ -82,7 +86,7 @@ public boolean equals(Object obj) { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((computer == null) ? 0 : computer.hashCode()); + result = prime * result + ((computers == null) ? 0 : computers.hashCode()); result = prime * result + ((displayName == null) ? 0 : displayName.hashCode()); return result; } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerLabel.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerLabel.java new file mode 100644 index 00000000..e61ecd2d --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerLabel.java @@ -0,0 +1,34 @@ +package com.offbytwo.jenkins.model; + +public class ComputerLabel { + private String name; + + public String getName() { + return name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ComputerLabel other = (ComputerLabel) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerSet.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerSet.java index 7bebc43f..0c12ba5a 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerSet.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerSet.java @@ -7,9 +7,7 @@ package com.offbytwo.jenkins.model; import java.util.List; - -import com.google.common.base.Function; -import com.google.common.collect.Lists; +import java.util.stream.Collectors; public class ComputerSet extends BaseModel { private int busyExecutors; @@ -26,38 +24,39 @@ public int getBusyExecutors() { return busyExecutors; } - public void setBusyExecutors(int busyExecutors) { + public ComputerSet setBusyExecutors(int busyExecutors) { this.busyExecutors = busyExecutors; + return this; } public String getDisplayName() { return displayName; } - public void setDisplayName(String displayName) { + public ComputerSet setDisplayName(String displayName) { this.displayName = displayName; + return this; } public int getTotalExecutors() { return totalExecutors; } - public void setTotalExecutors(int totalExecutors) { + public ComputerSet setTotalExecutors(int totalExecutors) { this.totalExecutors = totalExecutors; + return this; } public List getComputers() { - return Lists.transform( computer, new Function() { - @Override - public ComputerWithDetails apply(ComputerWithDetails computerWithDetails) { - computerWithDetails.setClient(client); - return computerWithDetails; - } - }); + return computer.stream().map(s -> { + s.setClient(this.client); + return s; + }).collect(Collectors.toList()); } - public void setComputer(List computers) { + public ComputerSet setComputer(List computers) { this.computer = computers; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerWithDetails.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerWithDetails.java index b1df2eef..c57b40a5 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerWithDetails.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerWithDetails.java @@ -10,15 +10,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - -import com.google.common.base.Function; -import com.google.common.net.UrlEscapers; +import com.offbytwo.jenkins.client.util.EncodingUtils; +import java.util.function.Function; public class ComputerWithDetails extends Computer { private String displayName; private List actions; //TODO: What kind of List? private List executors; + private List assignedLabels; private Boolean idle; private Boolean jnlp; private Boolean launchSupported; @@ -34,6 +34,7 @@ public class ComputerWithDetails extends Computer { public ComputerWithDetails() { } + @Override public String getDisplayName() { return displayName; } @@ -46,6 +47,10 @@ public List getExecutors() { return executors; } + public List getAssignedLabels() { + return assignedLabels; + } + public Boolean getIdle() { return idle; } @@ -72,29 +77,45 @@ public LoadStatistics getLoadStatistics() throws IOException { if ("master".equals(displayName)) { name = "(master)"; } else { - name = UrlEscapers.urlPathSegmentEscaper().escape(displayName); + name = EncodingUtils.encode(displayName); } // TODO: ?depth=2 good idea or could this being done better? return client.get("/computer/" + name + "/" + "loadStatistics/?depth=2", LoadStatistics.class); } - public void toggleOffline(boolean crumbFlag) throws IOException { + public ComputerWithDetails toggleOffline(boolean crumbFlag) throws IOException { // curl --data "json=init" -X POST "http://192.168.99.100:8080/computer/(master)/toggleOffline" String name; if ("master".equals(displayName)) { name = "(master)"; } else { - name = UrlEscapers.urlPathSegmentEscaper().escape(displayName); + name = EncodingUtils.encode(displayName); } - Map data = new HashMap(); - data.put( "json", "init" ); client.post( "/computer/" + name + "/toggleOffline", crumbFlag); + return this; + } + + public ComputerWithDetails toggleOffline() throws IOException { + return toggleOffline(false); } - public void toggleOffline() throws IOException { - toggleOffline( false ); + public ComputerWithDetails changeOfflineCause(String cause, boolean crumbFlag) throws IOException { + String name; + if ("master".equals(displayName)) { + name = "(master)"; + } else { + name = EncodingUtils.encode(displayName); + } + Map data = new HashMap(); + data.put("offlineMessage", cause); + client.post_form("/computer/" + name + "/changeOfflineCause?", data, crumbFlag); + return this; + } + + public ComputerWithDetails changeOfflineCause(String cause) throws IOException { + return changeOfflineCause(cause, false); } public Boolean getManualLaunchAllowed() { @@ -153,6 +174,11 @@ public boolean equals(Object obj) { return false; } else if (!executors.equals(other.executors)) return false; + if (assignedLabels == null) { + if (other.assignedLabels != null) + return false; + } else if (!assignedLabels.equals(other.assignedLabels)) + return false; if (idle == null) { if (other.idle != null) return false; @@ -232,11 +258,4 @@ public int hashCode() { return result; } - private class ComputerWithClient implements Function { - @Override - public Computer apply(Computer computer) { - computer.setClient(client); - return computer; - } - } } \ No newline at end of file diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ConsoleLog.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ConsoleLog.java new file mode 100644 index 00000000..503e1ce0 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ConsoleLog.java @@ -0,0 +1,44 @@ +package com.offbytwo.jenkins.model; + +/** + * Represents build console log + */ +public class ConsoleLog { + + private String consoleLog; + private Boolean hasMoreData; + private Integer currentBufferSize; + + public ConsoleLog(String consoleLog, Boolean hasMoreData, Integer currentBufferSize) { + this.consoleLog = consoleLog; + this.hasMoreData = hasMoreData; + this.currentBufferSize = currentBufferSize; + } + + public String getConsoleLog() { + return consoleLog; + } + + public ConsoleLog setConsoleLog(String consoleLog) { + this.consoleLog = consoleLog; + return this; + } + + public Boolean getHasMoreData() { + return hasMoreData; + } + + public ConsoleLog setHasMoreData(Boolean hasMoreData) { + this.hasMoreData = hasMoreData; + return this; + } + + public Integer getCurrentBufferSize() { + return currentBufferSize; + } + + public ConsoleLog setCurrentBufferSize(Integer currentBufferSize) { + this.currentBufferSize = currentBufferSize; + return this; + } +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executable.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executable.java index ff1b6e27..5f044e13 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executable.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executable.java @@ -9,16 +9,18 @@ public Long getNumber() { return number; } - public void setNumber(Long number) { + public Executable setNumber(Long number) { this.number = number; + return this; } public String getUrl() { return url; } - public void setUrl(String url) { + public Executable setUrl(String url) { this.url = url; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executor.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executor.java index 15fcebde..c6914d97 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executor.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Executor.java @@ -23,48 +23,54 @@ public Job getCurrentExecutable() { return currentExecutable; } - public void setCurrentExecutable(Job currentExecutable) { + public Executor setCurrentExecutable(Job currentExecutable) { this.currentExecutable = currentExecutable; + return this; } public Job getCurrentWorkUnit() { return currentWorkUnit; } - public void setCurrentWorkUnit(Job currentWorkUnit) { + public Executor setCurrentWorkUnit(Job currentWorkUnit) { this.currentWorkUnit = currentWorkUnit; + return this; } public Boolean getIdle() { return idle; } - public void setIdle(Boolean idle) { + public Executor setIdle(Boolean idle) { this.idle = idle; + return this; } public Boolean getLikelyStuck() { return likelyStuck; } - public void setLikelyStuck(Boolean likelyStuck) { + public Executor setLikelyStuck(Boolean likelyStuck) { this.likelyStuck = likelyStuck; + return this; } public int getNumber() { return number; } - public void setNumber(int number) { + public Executor setNumber(int number) { this.number = number; + return this; } public int getProgress() { return progress; } - public void setProgress(int progress) { + public Executor setProgress(int progress) { this.progress = progress; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ExtractHeader.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ExtractHeader.java index cb3c683a..6dd43221 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ExtractHeader.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ExtractHeader.java @@ -4,8 +4,9 @@ public class ExtractHeader extends BaseModel { private String location; - public void setLocation(String value) { + public ExtractHeader setLocation(String value) { location = value; + return this; } public String getLocation() { diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/FolderJob.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/FolderJob.java index 1ceffa6f..f2677081 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/FolderJob.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/FolderJob.java @@ -1,13 +1,15 @@ package com.offbytwo.jenkins.model; +import com.offbytwo.jenkins.helper.FunctionalHelper; + import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import com.offbytwo.jenkins.client.util.EncodingUtils; +import static com.offbytwo.jenkins.helper.FunctionalHelper.SET_CLIENT; public class FolderJob extends Job { @@ -20,6 +22,10 @@ public FolderJob() { public FolderJob(String name, String url) { super(name, url); } + + public FolderJob(String name, String url, String fullName) { + super(name, url, fullName); + } public String getDisplayName() { return displayName; @@ -42,16 +48,13 @@ public boolean isFolder() { /** * Get a list of all the defined jobs in this folder * - * @return list of defined jobs (summary level, for details @see Job#details + * @return list of defined jobs (summary level, for details {@link Job#details()}. */ public Map getJobs() { - return Maps.uniqueIndex(jobs, new Function() { - @Override - public String apply(Job job) { - job.setClient(client); - return job.getName(); - } - }); + //FIXME: Check for null of jobs? Can that happen? + return jobs.stream() + .map(SET_CLIENT(this.client)) + .collect(Collectors.toMap(k -> k.getName(), Function.identity())); } /** @@ -59,15 +62,15 @@ public String apply(Job job) { * * @param name the name of the job. * @return the given job + * @throws IllegalArgumentException in case if the {@code name} does not exist. */ public Job getJob(String name) { - return Maps.uniqueIndex(jobs, new Function() { - @Override - public String apply(Job job) { - job.setClient(client); - return job.getName(); - } - }).get(name); + //FIXME: Check for null of jobs? Can that happen? + return jobs.stream() + .map(SET_CLIENT(this.client)) + .filter(item -> item.getName().equals(name)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Job with name " + name + " does not exist.")); } /** @@ -76,8 +79,8 @@ public String apply(Job job) { * @param folderName name of the folder to be created. * @throws IOException in case of an error. */ - public void createFolder(String folderName) throws IOException { - createFolder(folderName, false); + public FolderJob createFolder(String folderName) throws IOException { + return createFolder(folderName, false); } /** @@ -85,14 +88,20 @@ public void createFolder(String folderName) throws IOException { * * @param folderName name of the folder to be created. * @param crumbFlag true/false. + * @return * @throws IOException in case of an error. */ - public void createFolder(String folderName, Boolean crumbFlag) throws IOException { + public FolderJob createFolder(String folderName, Boolean crumbFlag) throws IOException { // https://gist.github.com/stuart-warren/7786892 was slightly helpful // here - ImmutableMap params = ImmutableMap.of("mode", "com.cloudbees.hudson.plugins.folder.Folder", - "name", EncodingUtils.encodeParam(folderName), "from", "", "Submit", "OK"); + //TODO: JDK9+: Map.of(...) + Map params = new HashMap<>(); + params.put("mode", "com.cloudbees.hudson.plugins.folder.Folder"); + params.put("name", folderName); + params.put("from", ""); + params.put("Submit", "OK"); client.post_form(this.getUrl() + "/createItem?", params, crumbFlag); + return this; } /* diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/HourMinSec10.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/HourMinSec10.java index 0dba01dc..66823616 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/HourMinSec10.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/HourMinSec10.java @@ -15,24 +15,27 @@ public Statis getHour() { return hour; } - public void setHour(Statis hour) { + public HourMinSec10 setHour(Statis hour) { this.hour = hour; + return this; } public Statis getMin() { return min; } - public void setMin(Statis min) { + public HourMinSec10 setMin(Statis min) { this.min = min; + return this; } public Statis getSec10() { return sec10; } - public void setSec10(Statis sec10) { + public HourMinSec10 setSec10(Statis sec10) { this.sec10 = sec10; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JacocoCoverageReport.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JacocoCoverageReport.java new file mode 100644 index 00000000..cc1345d5 --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JacocoCoverageReport.java @@ -0,0 +1,56 @@ +package com.offbytwo.jenkins.model; + +public class JacocoCoverageReport extends BaseModel { + + private JacocoCoverageResult lineCoverage; + private JacocoCoverageResult classCoverage; + private JacocoCoverageResult complexityScore; + private JacocoCoverageResult instructionCoverage; + private JacocoCoverageResult branchCoverage; + + public JacocoCoverageResult getLineCoverage() { + return lineCoverage; + } + + public JacocoCoverageReport setLineCoverage(JacocoCoverageResult lineCoverage) { + this.lineCoverage = lineCoverage; + return this; + } + + public JacocoCoverageResult getClassCoverage() { + return classCoverage; + } + + public JacocoCoverageReport setClassCoverage(JacocoCoverageResult classCoverage) { + this.classCoverage = classCoverage; + return this; + } + + public JacocoCoverageResult getComplexityScore() { + return complexityScore; + } + + public JacocoCoverageReport setComplexityScore(JacocoCoverageResult complexityScore) { + this.complexityScore = complexityScore; + return this; + } + + public JacocoCoverageResult getInstructionCoverage() { + return instructionCoverage; + } + + public JacocoCoverageReport setInstructionCoverage(JacocoCoverageResult instructionCoverage) { + this.instructionCoverage = instructionCoverage; + return this; + } + + public JacocoCoverageResult getBranchCoverage() { + return branchCoverage; + } + + public JacocoCoverageReport setBranchCoverage(JacocoCoverageResult branchCoverage) { + this.branchCoverage = branchCoverage; + return this; + } + +} \ No newline at end of file diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JacocoCoverageResult.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JacocoCoverageResult.java new file mode 100644 index 00000000..41f3f43d --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JacocoCoverageResult.java @@ -0,0 +1,56 @@ +package com.offbytwo.jenkins.model; + +public class JacocoCoverageResult { + + private int covered; + private int missed; + private int percentage; + private int percentageFloat; + private int total; + + public int getCovered() { + return covered; + } + + public JacocoCoverageResult setCovered(int covered) { + this.covered = covered; + return this; + } + + public int getMissed() { + return missed; + } + + public JacocoCoverageResult setMissed(int missed) { + this.missed = missed; + return this; + } + + public int getPercentage() { + return percentage; + } + + public JacocoCoverageResult setPercentage(int percentage) { + this.percentage = percentage; + return this; + } + + public int getPercentageFloat() { + return percentageFloat; + } + + public JacocoCoverageResult setPercentageFloat(int percentageFloat) { + this.percentageFloat = percentageFloat; + return this; + } + + public int getTotal() { + return total; + } + + public JacocoCoverageResult setTotal(int total) { + this.total = total; + return this; + } + +} \ No newline at end of file diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Job.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Job.java index edfc9a1f..7d5313fd 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Job.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Job.java @@ -6,31 +6,37 @@ package com.offbytwo.jenkins.model; -import static org.apache.commons.lang.StringUtils.join; - +import com.offbytwo.jenkins.client.util.EncodingUtils; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Map; - -import com.google.common.base.Function; -import com.google.common.collect.Collections2; -import com.google.common.escape.Escaper; -import com.google.common.net.UrlEscapers; +import java.util.function.Function; +import java.util.stream.Collectors; public class Job extends BaseModel { private String name; private String url; + private String fullName; public Job() { } - + public Job(String name, String url) { this(); this.name = name; this.url = url; + this.fullName = null; + } + + public Job(String name, String url, String fullName) { + this(); + this.name = name; + this.url = url; + this.fullName = fullName; } public String getName() { @@ -40,6 +46,10 @@ public String getName() { public String getUrl() { return url; } + + public String getFullName() { + return fullName; + } public JobWithDetails details() throws IOException { return client.get(url, JobWithDetails.class); @@ -89,30 +99,55 @@ public QueueReference build(boolean crumbFlag) throws IOException { } /** - * Trigger a parameterized build + * Trigger a parameterized build with string parameters only * * @param params the job parameters * @return {@link QueueReference} for further analysis of the queued build. * @throws IOException in case of an error. */ public QueueReference build(Map params) throws IOException { - String qs = join(Collections2.transform(params.entrySet(), new MapEntryToQueryStringPair()), "&"); - client.post(url + "buildWithParameters?" + qs); - ExtractHeader location = client.post(url + "buildWithParameters?" + qs, null, ExtractHeader.class, false); - return new QueueReference(location.getLocation()); + return build(params, null,false); } /** - * Trigger a parameterized build + * Trigger a parameterized build with string parameters only * * @param params the job parameters - * @param crumbFlag determines whether crumb flag is used + * @param crumbFlag true or false. * @return {@link QueueReference} for further analysis of the queued build. * @throws IOException in case of an error. */ public QueueReference build(Map params, boolean crumbFlag) throws IOException { - String qs = join(Collections2.transform(params.entrySet(), new MapEntryToQueryStringPair()), "&"); - ExtractHeader location = client.post(url + "buildWithParameters?" + qs, null, ExtractHeader.class, crumbFlag); + return build(params,null,crumbFlag); + } + + /** + * Trigger a parameterized build with file parameters + * + * @param params the job parameters + * @param fileParams the job file parameters + * @return {@link QueueReference} for further analysis of the queued build. + * @throws IOException in case of an error. + */ + public QueueReference build(Map params, Map fileParams) throws IOException { + return build(params,fileParams,false); + } + + /** + * Trigger a parameterized build with file parameters and crumbFlag + * + * @param params the job parameters + * @param fileParams the job file parameters + * @param crumbFlag determines whether crumb flag is used + * @return {@link QueueReference} for further analysis of the queued build. + * @throws IOException in case of an error. + */ + public QueueReference build(Map params, Map fileParams, boolean crumbFlag) throws IOException { + String qs = params.entrySet().stream() + .map(s -> s.getKey() + "=" + s.getValue()) + .collect(Collectors.joining("&")); +// String qs = join(Collections2.transform(params.entrySet(), new MapEntryToQueryStringPair()), "&"); + ExtractHeader location = client.post(url + "buildWithParameters?" + qs,null, ExtractHeader.class, fileParams, crumbFlag); return new QueueReference(location.getLocation()); } @@ -129,6 +164,8 @@ public boolean equals(Object o) { return false; if (url != null ? !url.equals(job.url) : job.url != null) return false; + if (fullName != null ? !fullName.equals(job.fullName) : job.fullName != null) + return false; return true; } @@ -136,15 +173,16 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; - result = 31 * result + (url != null ? url.hashCode() : 0); + result = 31 * result + (url != null ? url.hashCode() : 0) + (fullName != null ? fullName.hashCode() : 0); return result; } private static class MapEntryToQueryStringPair implements Function, String> { @Override public String apply(Map.Entry entry) { - Escaper escaper = UrlEscapers.urlFormParameterEscaper(); - return escaper.escape(entry.getKey()) + "=" + escaper.escape(entry.getValue()); + return EncodingUtils.formParameter(entry.getKey()) + "=" + EncodingUtils.formParameter(entry.getValue()); } } + + } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobConfiguration.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobConfiguration.java index 571430ab..d6a95050 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobConfiguration.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobConfiguration.java @@ -77,7 +77,8 @@ public String getConfigXml() { return configXml; } - public void setConfigXml(String configXml) { + public JobConfiguration setConfigXml(String configXml) { this.configXml = configXml; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobWithDetails.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobWithDetails.java index 50fad5c8..f13903cf 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobWithDetails.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/JobWithDetails.java @@ -6,23 +6,21 @@ package com.offbytwo.jenkins.model; -import static com.google.common.collect.Lists.transform; +import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.offbytwo.jenkins.helper.Range; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpResponseException; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpResponseException; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.offbytwo.jenkins.client.util.EncodingUtils; -import com.offbytwo.jenkins.helper.Range; +import static com.offbytwo.jenkins.helper.FunctionalHelper.SET_CLIENT; +import static java.util.stream.Collectors.toList; public class JobWithDetails extends Job { @@ -32,7 +30,7 @@ public class JobWithDetails extends Job { private boolean buildable; - private List builds; + private List builds = Collections.emptyList(); private Build firstBuild; @@ -59,7 +57,7 @@ public class JobWithDetails extends Job { private List downstreamProjects; private List upstreamProjects; - + public String getDescription() { return description; } @@ -95,12 +93,9 @@ public List getBuilds() { if (builds == null) { return Collections.emptyList(); } else { - return transform(builds, new Function() { - @Override - public Build apply(Build from) { - return buildWithClient(from); - } - }); + return builds.stream() + .map(s -> buildWithClient(s)) + .collect(toList()); } } @@ -129,12 +124,9 @@ public List getAllBuilds() throws IOException { if (builds == null) { return Collections.emptyList(); } else { - return transform(builds, new Function() { - @Override - public Build apply(Build from) { - return buildWithClient(from); - } - }); + return builds.stream() + .map(s -> buildWithClient(s)) + .collect(toList()); } } catch (HttpResponseException e) { // TODO: Thinks about a better handling if the job does not exist? @@ -178,12 +170,9 @@ public List getAllBuilds(Range range) throws IOException { if (builds == null) { return Collections.emptyList(); } else { - return transform(builds, new Function() { - @Override - public Build apply(Build from) { - return buildWithClient(from); - } - }); + return builds.stream() + .map(s -> buildWithClient(s)) + .collect(toList()); } } catch (HttpResponseException e) { // TODO: Thinks about a better handline if the job does not exist? @@ -428,7 +417,9 @@ public List getDownstreamProjects() { if (downstreamProjects == null) { return Collections.emptyList(); } else { - return transform(downstreamProjects, new JobWithClient()); + return downstreamProjects.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } @@ -440,7 +431,9 @@ public List getUpstreamProjects() { if (upstreamProjects == null) { return Collections.emptyList(); } else { - return transform(upstreamProjects, new JobWithClient()); + return upstreamProjects.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } @@ -452,31 +445,24 @@ public QueueItem getQueueItem() { * Get a build by the given buildNumber. * * @param buildNumber The number to select the build by. - * @return The {@link Build} selected by the given buildnumber - * + * @return The an Optional with the {@link Build} selected by the given buildnumber + * */ - public Build getBuildByNumber(final int buildNumber) { - - Predicate isMatchingBuildNumber = new Predicate() { - - @Override - public boolean apply(Build input) { - return input.getNumber() == buildNumber; - } - }; - - Optional optionalBuild = Iterables.tryFind(builds, isMatchingBuildNumber); - // TODO: Check if we could use Build#NO...instead of Null? - return optionalBuild.orNull() == null ? null : buildWithClient(optionalBuild.orNull()); - } - - private class JobWithClient implements Function { - @Override - public Job apply(Job job) { - job.setClient(client); - return job; - } + public Optional getBuildByNumber(final int buildNumber) { + return builds.stream().filter(isBuildNumberEqualTo(buildNumber)).findFirst(); } + + /** + * Get a module of a {@link Job} + * + * @param moduleName name of the {@link MavenModule} + * @return The {@link MavenModuleWithDetails} selected by the given module name + * @throws java.io.IOException in case of errors. + * + */ + public MavenModuleWithDetails getModule(String moduleName) throws IOException { + return client.get(getUrl() + moduleName, MavenModuleWithDetails.class); + } /** * Empty description to be used for {@link #updateDescription(String)} or @@ -492,8 +478,8 @@ public Job apply(Job job) { * {@link #EMPTY_DESCRIPTION}. * @throws IOException in case of errors. */ - public void updateDescription(String description) throws IOException { - updateDescription(description, false); + public JobWithDetails updateDescription(String description) throws IOException { + return updateDescription(description, false); } /** @@ -505,10 +491,14 @@ public void updateDescription(String description) throws IOException { * @param crumbFlag true or false. * @throws IOException in case of errors. */ - public void updateDescription(String description, boolean crumbFlag) throws IOException { + public JobWithDetails updateDescription(String description, boolean crumbFlag) throws IOException { Objects.requireNonNull(description, "description is not allowed to be null."); - ImmutableMap params = ImmutableMap.of("description", description); + //JDK9+ + // Map.of(...); + Map params = new HashMap<>(); + params.put("description", description); client.post_form(this.getUrl() + "/submitDescription?", params, crumbFlag); + return this; } /** @@ -516,8 +506,8 @@ public void updateDescription(String description, boolean crumbFlag) throws IOEx * * @throws IOException in case of errors. */ - public void clearDescription() throws IOException { - updateDescription(EMPTY_DESCRIPTION); + public JobWithDetails clearDescription() throws IOException { + return updateDescription(EMPTY_DESCRIPTION); } /** @@ -526,8 +516,8 @@ public void clearDescription() throws IOException { * @param crumbFlag true or false. * @throws IOException in case of errors. */ - public void clearDescription(boolean crumbFlag) throws IOException { - updateDescription(EMPTY_DESCRIPTION, crumbFlag); + public JobWithDetails clearDescription(boolean crumbFlag) throws IOException { + return updateDescription(EMPTY_DESCRIPTION, crumbFlag); } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/LoadStatistics.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/LoadStatistics.java index df2a5608..fc335e5e 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/LoadStatistics.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/LoadStatistics.java @@ -18,24 +18,27 @@ public HourMinSec10 getBusyExecutors() { return busyExecutors; } - public void setBusyExecutors(HourMinSec10 busyExecutors) { + public LoadStatistics setBusyExecutors(HourMinSec10 busyExecutors) { this.busyExecutors = busyExecutors; + return this; } public HourMinSec10 getQueueLength() { return queueLength; } - public void setQueueLength(HourMinSec10 queueLength) { + public LoadStatistics setQueueLength(HourMinSec10 queueLength) { this.queueLength = queueLength; + return this; } public HourMinSec10 getTotalExecutors() { return totalExecutors; } - public void setTotalExecutors(HourMinSec10 totalExecutors) { + public LoadStatistics setTotalExecutors(HourMinSec10 totalExecutors) { this.totalExecutors = totalExecutors; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MainView.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MainView.java index b239558d..167f6f40 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MainView.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MainView.java @@ -6,11 +6,10 @@ package com.offbytwo.jenkins.model; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.google.common.collect.Lists; - public class MainView extends BaseModel { private List jobs; @@ -18,7 +17,7 @@ public class MainView extends BaseModel { /* default constructor needed for Jackson */ public MainView() { - this(Lists.newArrayList(), Lists.newArrayList()); + this(new ArrayList<>(), new ArrayList<>()); } public MainView(List jobs, List views) { @@ -27,22 +26,24 @@ public MainView(List jobs, List views) { } public MainView(Job... jobs) { - this(Arrays.asList(jobs), Lists.newArrayList()); + this(Arrays.asList(jobs), new ArrayList<>()); } public List getJobs() { return jobs; } - public void setJobs(List jobs) { + public MainView setJobs(List jobs) { this.jobs = jobs; + return this; } public List getViews() { return views; } - public void setViews(List views) { + public MainView setViews(List views) { this.views = views; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenArtifact.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenArtifact.java index 184ccc37..c51ea4ea 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenArtifact.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenArtifact.java @@ -18,63 +18,71 @@ public String getArtifactId() { return artifactId; } - public void setArtifactId(String artifactId) { + public MavenArtifact setArtifactId(String artifactId) { this.artifactId = artifactId; + return this; } public String getCanonicalName() { return canonicalName; } - public void setCanonicalName(String canonicalName) { + public MavenArtifact setCanonicalName(String canonicalName) { this.canonicalName = canonicalName; + return this; } public String getClassifier() { return classifier; } - public void setClassifier(String classifier) { + public MavenArtifact setClassifier(String classifier) { this.classifier = classifier; + return this; } public String getFileName() { return fileName; } - public void setFileName(String fileName) { + public MavenArtifact setFileName(String fileName) { this.fileName = fileName; + return this; } public String getGroupId() { return groupId; } - public void setGroupId(String groupId) { + public MavenArtifact setGroupId(String groupId) { this.groupId = groupId; + return this; } public String getMd5sum() { return md5sum; } - public void setMd5sum(String md5sum) { + public MavenArtifact setMd5sum(String md5sum) { this.md5sum = md5sum; + return this; } public String getType() { return type; } - public void setType(String type) { + public MavenArtifact setType(String type) { this.type = type; + return this; } public String getVersion() { return version; } - public void setVersion(String version) { + public MavenArtifact setVersion(String version) { this.version = version; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenBuild.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenBuild.java index e8cd5c1f..2ab8de4b 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenBuild.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenBuild.java @@ -30,4 +30,8 @@ public MavenBuild(int number, String url) { public MavenModule getMavenModule() throws IOException { return client.get(this.getUrl() + "/mavenArtifacts/", MavenModule.class); } + + public JacocoCoverageReport getJacocoCodeCoverageReport() throws IOException { + return client.get(this.getUrl() + "/jacoco/", JacocoCoverageReport.class); + } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJob.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJob.java index 1815d22f..1204c844 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJob.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJob.java @@ -10,6 +10,10 @@ public MavenJob() { public MavenJob(String name, String url) { super(name, url); } + + public MavenJob(String name, String url, String fullName) { + super(name, url, fullName); + } public MavenJobWithDetails mavenDetails() throws IOException { return client.get(getUrl(), MavenJobWithDetails.class); diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJobWithDetails.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJobWithDetails.java index ae2c7c06..3eaf7fc0 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJobWithDetails.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenJobWithDetails.java @@ -1,21 +1,17 @@ package com.offbytwo.jenkins.model; -import static com.google.common.collect.Lists.transform; +import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.offbytwo.jenkins.helper.Range; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpResponseException; import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.Optional; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpResponseException; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.offbytwo.jenkins.client.util.EncodingUtils; -import com.offbytwo.jenkins.helper.Range; +import static com.offbytwo.jenkins.helper.FunctionalHelper.SET_CLIENT; +import static java.util.stream.Collectors.toList; public class MavenJobWithDetails extends MavenJob { @@ -60,12 +56,9 @@ public List getBuilds() { if (builds == null) { return Collections.emptyList(); } else { - return Lists.transform(builds, new Function() { - @Override - public MavenBuild apply(MavenBuild from) { - return buildWithClient(from); - } - }); + return builds.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } @@ -95,12 +88,9 @@ public List getAllBuilds() throws IOException { if (builds == null) { return Collections.emptyList(); } else { - return transform(builds, new Function() { - @Override - public MavenBuild apply(MavenBuild from) { - return buildWithClient(from); - } - }); + return builds.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } catch (HttpResponseException e) { // TODO: Thinks about a better handling if the job does not exist? @@ -146,12 +136,9 @@ public List getAllBuilds(Range range) throws IOException { if (builds == null) { return Collections.emptyList(); } else { - return transform(builds, new Function() { - @Override - public MavenBuild apply(MavenBuild from) { - return buildWithClient(from); - } - }); + return builds.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } catch (HttpResponseException e) { // TODO: Thinks about a better handline if the job does not exist? @@ -271,7 +258,9 @@ public List getDownstreamProjects() { if (downstreamProjects == null) { return Collections.emptyList(); } else { - return Lists.transform(downstreamProjects, new MavenJobWithClient()); + return downstreamProjects.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } @@ -279,23 +268,20 @@ public List getUpstreamProjects() { if (upstreamProjects == null) { return Collections.emptyList(); } else { - return Lists.transform(upstreamProjects, new MavenJobWithClient()); + return upstreamProjects.stream() + .map(SET_CLIENT(this.client)) + .collect(toList()); } } - public MavenBuild getBuildByNumber(final int buildNumber) { - - Predicate isMatchingBuildNumber = new Predicate() { - - @Override - public boolean apply(MavenBuild input) { - return input.getNumber() == buildNumber; - } - }; - - Optional optionalBuild = Iterables.tryFind(builds, isMatchingBuildNumber); - // TODO: Check if we could use Build#NO...instead of Null? - return optionalBuild.orNull() == null ? null : buildWithClient(optionalBuild.orNull()); + /** + * @param buildNumber The build you would like to select. + * @return Optional which contains the {@link MavenBuild}. + */ + public Optional getBuildByNumber(final int buildNumber) { + return builds.stream() + .filter(isBuildNumberEqualTo(buildNumber)) + .findFirst(); } private MavenBuild buildWithClient(MavenBuild from) { @@ -304,11 +290,4 @@ private MavenBuild buildWithClient(MavenBuild from) { return ret; } - private class MavenJobWithClient implements Function { - @Override - public Job apply(Job job) { - job.setClient(client); - return job; - } - } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModule.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModule.java index 07c61bb1..91c7ef1c 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModule.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModule.java @@ -1,10 +1,59 @@ +/* + * Copyright (c) 2018 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ package com.offbytwo.jenkins.model; +import java.io.IOException; import java.util.List; - +/** + * + * @author Karl Heinz Marbaise, Ricardo Zanini, René Scheibe, Jakub Zacek + */ public class MavenModule extends BaseModel { private List moduleRecords; + private String name; + private String url; + private String color; + private String displayName; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public MavenModuleWithDetails details() throws IOException { + return client.get(url, MavenModuleWithDetails.class); + } public List getModuleRecords() { return moduleRecords; diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleRecord.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleRecord.java index 3684c21d..34fbd424 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleRecord.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleRecord.java @@ -17,39 +17,44 @@ public List getAttachedArtifacts() { return attachedArtifacts; } - public void setAttachedArtifacts(List attachedArtifacts) { + public MavenModuleRecord setAttachedArtifacts(List attachedArtifacts) { this.attachedArtifacts = attachedArtifacts; + return this; } public Build getParent() { return parent; } - public void setParent(Build parent) { + public MavenModuleRecord setParent(Build parent) { this.parent = parent; + return this; } public MavenArtifact getMainArtifact() { return mainArtifact; } - public void setMainArtifact(MavenArtifact mainArtifact) { + public MavenModuleRecord setMainArtifact(MavenArtifact mainArtifact) { this.mainArtifact = mainArtifact; + return this; } public MavenArtifact getPomArtifact() { return pomArtifact; } - public void setPomArtifact(MavenArtifact pomArtifact) { + public MavenModuleRecord setPomArtifact(MavenArtifact pomArtifact) { this.pomArtifact = pomArtifact; + return this; } public String getUrl() { return url; } - public void setUrl(String url) { + public MavenModuleRecord setUrl(String url) { this.url = url; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleWithDetails.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleWithDetails.java new file mode 100644 index 00000000..d168a45e --- /dev/null +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/MavenModuleWithDetails.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.model; + +import static com.offbytwo.jenkins.helper.FunctionalHelper.SET_CLIENT; +import static java.util.stream.Collectors.toList; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Model Class for Maven Modules + * + * @author Jakub Zacek + */ +public class MavenModuleWithDetails extends BaseModel { + + private List builds = Collections.emptyList(); + private List actions = Collections.emptyList(); + private String displayName; + private BuildResult result; + private String url; + private long duration; + private long timestamp; + + public List getActions() { + return actions; + } + + public void setActions(List actions) { + this.actions = actions; + } + + public void setBuilds(List builds) { + this.builds = builds; + } + + public List getBuilds() { + return builds.stream() + .map(SET_CLIENT(this.getClient())) + .collect(toList()); + } + + public Build getBuildByNumber(final int buildNumber) { + return builds.stream() + .filter(isBuildNumberEqualTo(buildNumber)) + .map(SET_CLIENT(this.getClient())) + .findFirst() + .orElse(Build.BUILD_HAS_NEVER_RUN); + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public BuildResult getResult() { + return result; + } + + public void setResult(BuildResult result) { + this.result = result; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public String getConsoleOutputText() throws IOException { + return client.get(getUrl() + "/logText/progressiveText"); + } + + public TestReport getTestReport() throws IOException { + return client.get(this.getUrl() + "/testReport/?depth=1", TestReport.class); + } + +} diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/OfflineCause.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/OfflineCause.java index 66c70cd1..30969dc9 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/OfflineCause.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/OfflineCause.java @@ -17,6 +17,16 @@ public String getDescription() { return description; } + public OfflineCause setTimestamp(Long timestamp) { + this.timestamp = timestamp; + return this; + } + + public OfflineCause setDescription(String description) { + this.description = description; + return this; + } + @Override public int hashCode() { final int prime = 31; @@ -48,12 +58,4 @@ public boolean equals(Object obj) { return true; } - public void setTimestamp(Long timestamp) { - this.timestamp = timestamp; - } - - public void setDescription(String description) { - this.description = description; - } - } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParameterDefinitions.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParameterDefinitions.java index b46c69e4..47247c74 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParameterDefinitions.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParameterDefinitions.java @@ -30,11 +30,13 @@ public List getStringParams() { return stringParams; } - public void setStringParams(List stringParams) { + public ParameterDefinitions setStringParams(List stringParams) { this.stringParams = stringParams; + return this; } - public void addParam(StringParameterDefinition spd) { + public ParameterDefinitions addParam(StringParameterDefinition spd) { stringParams.add(spd); + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParametersDefinitionProperty.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParametersDefinitionProperty.java index efb033f5..bb88a128 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParametersDefinitionProperty.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/ParametersDefinitionProperty.java @@ -26,7 +26,8 @@ public ParameterDefinitions getPd() { return pd; } - public void setPd(ParameterDefinitions pd) { + public ParametersDefinitionProperty setPd(ParameterDefinitions pd) { this.pd = pd; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Plugin.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Plugin.java index fe380202..6c9e09a1 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Plugin.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Plugin.java @@ -27,9 +27,10 @@ public boolean isActive() return active; } - public void setActive( boolean active ) + public Plugin setActive(boolean active) { this.active = active; + return this; } public String getBackupVersion() @@ -37,9 +38,10 @@ public String getBackupVersion() return backupVersion; } - public void setBackupVersion( String backupVersion ) + public Plugin setBackupVersion(String backupVersion) { this.backupVersion = backupVersion; + return this; } public boolean isBundled() @@ -47,9 +49,10 @@ public boolean isBundled() return bundled; } - public void setBundled( boolean bundled ) + public Plugin setBundled(boolean bundled) { this.bundled = bundled; + return this; } public boolean isDowngradable() @@ -57,9 +60,10 @@ public boolean isDowngradable() return downgradable; } - public void setDowngradable( boolean downgradable ) + public Plugin setDowngradable(boolean downgradable) { this.downgradable = downgradable; + return this; } public boolean isEnabled() @@ -67,9 +71,10 @@ public boolean isEnabled() return enabled; } - public void setEnabled( boolean enabled ) + public Plugin setEnabled(boolean enabled) { this.enabled = enabled; + return this; } public boolean isHasUpdate() @@ -77,9 +82,10 @@ public boolean isHasUpdate() return hasUpdate; } - public void setHasUpdate( boolean hasUpdate ) + public Plugin setHasUpdate(boolean hasUpdate) { this.hasUpdate = hasUpdate; + return this; } public String getLongName() @@ -87,9 +93,10 @@ public String getLongName() return longName; } - public void setLongName( String longName ) + public Plugin setLongName(String longName) { this.longName = longName; + return this; } public boolean isPinned() @@ -97,9 +104,10 @@ public boolean isPinned() return pinned; } - public void setPinned( boolean pinned ) + public Plugin setPinned(boolean pinned) { this.pinned = pinned; + return this; } public String getShortName() @@ -107,9 +115,10 @@ public String getShortName() return shortName; } - public void setShortName( String shortName ) + public Plugin setShortName(String shortName) { this.shortName = shortName; + return this; } public String getSupportsDynamicLoad() @@ -117,9 +126,10 @@ public String getSupportsDynamicLoad() return supportsDynamicLoad; } - public void setSupportsDynamicLoad( String supportsDynamicLoad ) + public Plugin setSupportsDynamicLoad(String supportsDynamicLoad) { this.supportsDynamicLoad = supportsDynamicLoad; + return this; } public String getUrl() @@ -127,9 +137,10 @@ public String getUrl() return url; } - public void setUrl( String url ) + public Plugin setUrl(String url) { this.url = url; + return this; } public String getVersion() @@ -137,9 +148,10 @@ public String getVersion() return version; } - public void setVersion( String version ) + public Plugin setVersion(String version) { this.version = version; + return this; } public List getDependencies() @@ -147,9 +159,10 @@ public List getDependencies() return dependencies; } - public void setDependencies( List dependencies ) + public Plugin setDependencies(List dependencies) { this.dependencies = dependencies; + return this; } @Override @@ -247,6 +260,4 @@ else if ( !version.equals( other.version ) ) return true; } - - } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginDependency.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginDependency.java index adb1331d..acac798d 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginDependency.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginDependency.java @@ -11,6 +11,33 @@ public class PluginDependency extends BaseModel private String version; + public boolean isOptional() { + return optional; + } + + public PluginDependency setOptional(boolean optional) { + this.optional = optional; + return this; + } + + public String getShortName() { + return shortName; + } + + public PluginDependency setShortName(String shortName) { + this.shortName = shortName; + return this; + } + + public String getVersion() { + return version; + } + + public PluginDependency setVersion(String version) { + this.version = version; + return this; + } + @Override public int hashCode() { @@ -51,34 +78,4 @@ else if ( !version.equals( other.version ) ) return true; } - public boolean isOptional() - { - return optional; - } - - public void setOptional( boolean optional ) - { - this.optional = optional; - } - - public String getShortName() - { - return shortName; - } - - public void setShortName( String shortName ) - { - this.shortName = shortName; - } - - public String getVersion() - { - return version; - } - - public void setVersion( String version ) - { - this.version = version; - } - } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginManager.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginManager.java index f23ccaba..c08fc713 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginManager.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/PluginManager.java @@ -14,8 +14,9 @@ public List getPlugins() return plugins; } - public void setPlugins(List plugins) { + public PluginManager setPlugins(List plugins) { this.plugins = plugins; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Queue.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Queue.java index 3320ecb5..355dc910 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Queue.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Queue.java @@ -8,8 +8,7 @@ import java.util.List; -public class Queue - extends BaseModel +public class Queue extends BaseModel { private List discoverableItems; @@ -24,9 +23,10 @@ public List getDiscoverableItems() return discoverableItems; } - public void setDiscoverableItems( List discoverableItems ) + public Queue setDiscoverableItems(List discoverableItems) { this.discoverableItems = discoverableItems; + return this; } @Override @@ -44,9 +44,10 @@ public List getItems() return items; } - public void setItems( List items ) + public Queue setItems(List items) { this.items = items; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItem.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItem.java index 662b3e2b..ae2d8d3d 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItem.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItem.java @@ -38,48 +38,54 @@ public List getActions() { return actions; } - public void setActions(List actions) { + public QueueItem setActions(List actions) { this.actions = actions; + return this; } public boolean isBlocked() { return blocked; } - public void setBlocked(boolean blocked) { + public QueueItem setBlocked(boolean blocked) { this.blocked = blocked; + return this; } public boolean isBuildable() { return buildable; } - public void setBuildable(boolean buildable) { + public QueueItem setBuildable(boolean buildable) { this.buildable = buildable; + return this; } public Long getId() { return id; } - public void setId(Long id) { + public QueueItem setId(Long id) { this.id = id; + return this; } public Long getInQueueSince() { return inQueueSince; } - public void setInQueueSince(Long inQueueSince) { + public QueueItem setInQueueSince(Long inQueueSince) { this.inQueueSince = inQueueSince; + return this; } public String getParams() { return params; } - public void setParams(String params) { + public QueueItem setParams(String params) { this.params = params; + return this; } public boolean isStuck() { @@ -90,44 +96,50 @@ public QueueTask getTask() { return task; } - public void setTask(QueueTask task) { + public QueueItem setTask(QueueTask task) { this.task = task; + return this; } - public void setStuck(boolean stuck) { + public QueueItem setStuck(boolean stuck) { this.stuck = stuck; + return this; } public String getUrl() { return url; } - public void setUrl(String url) { + public QueueItem setUrl(String url) { this.url = url; + return this; } public String getWhy() { return why; } - public void setWhy(String why) { + public QueueItem setWhy(String why) { this.why = why; + return this; } public boolean isCancelled() { return cancelled; } - public void setCancelled(boolean cancelled) { + public QueueItem setCancelled(boolean cancelled) { this.cancelled = cancelled; + return this; } public Executable getExecutable() { return executable; } - public void setExecutable(Executable executable) { + public QueueItem setExecutable(Executable executable) { this.executable = executable; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItemActions.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItemActions.java index 4c49ba5f..cbd6e390 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItemActions.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/QueueItemActions.java @@ -15,8 +15,9 @@ public List getCauses() { return causes; } - public void setCauses(List causes) { + public QueueItemActions setCauses(List causes) { this.causes = causes; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Statis.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Statis.java index b2c601c3..bb920ea4 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Statis.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/Statis.java @@ -22,16 +22,18 @@ public List getHistory() { return history; } - public void setHistory(List history) { + public Statis setHistory(List history) { this.history = history; + return this; } public Double getLatest() { return latest; } - public void setLatest(Double latest) { + public Statis setLatest(Double latest) { this.latest = latest; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/StringParameterDefinition.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/StringParameterDefinition.java index 740e3d77..2a89618f 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/StringParameterDefinition.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/StringParameterDefinition.java @@ -34,23 +34,26 @@ public String getName() { return name; } - public void setName(String name) { + public StringParameterDefinition setName(String name) { this.name = name; + return this; } public String getDescription() { return description; } - public void setDescription(String description) { + public StringParameterDefinition setDescription(String description) { this.description = description; + return this; } public String getDefaultValue() { return defaultValue; } - public void setDefaultValue(String defaultValue) { + public StringParameterDefinition setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; + return this; } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestCase.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestCase.java index 8f4f3795..f3ac1331 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestCase.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestCase.java @@ -21,9 +21,10 @@ public int getAge() return age; } - public void setAge( int age ) + public TestCase setAge(int age) { this.age = age; + return this; } public String getClassName() @@ -31,9 +32,10 @@ public String getClassName() return className; } - public void setClassName( String className ) + public TestCase setClassName(String className) { this.className = className; + return this; } public double getDuration() @@ -41,9 +43,10 @@ public double getDuration() return duration; } - public void setDuration( double duration ) + public TestCase setDuration(double duration) { this.duration = duration; + return this; } public int getFailedSince() @@ -51,9 +54,10 @@ public int getFailedSince() return failedSince; } - public void setFailedSince( int failedSince ) + public TestCase setFailedSince(int failedSince) { this.failedSince = failedSince; + return this; } public String getName() @@ -61,9 +65,10 @@ public String getName() return name; } - public void setName( String name ) + public TestCase setName(String name) { this.name = name; + return this; } public boolean isSkipped() @@ -71,9 +76,10 @@ public boolean isSkipped() return skipped; } - public void setSkipped( boolean skipped ) + public TestCase setSkipped(boolean skipped) { this.skipped = skipped; + return this; } public String getStatus() @@ -81,9 +87,10 @@ public String getStatus() return status; } - public void setStatus( String status ) + public TestCase setStatus(String status) { this.status = status; + return this; } public String getErrorDetails() @@ -91,9 +98,10 @@ public String getErrorDetails() return errorDetails; } - public void setErrorDetails( String errorDetails ) + public TestCase setErrorDetails(String errorDetails) { this.errorDetails = errorDetails; + return this; } public String getErrorStackTrace() @@ -101,9 +109,10 @@ public String getErrorStackTrace() return errorStackTrace; } - public void setErrorStackTrace( String errorStackTrace ) + public TestCase setErrorStackTrace(String errorStackTrace) { this.errorStackTrace = errorStackTrace; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChild.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChild.java index 516bb446..a8d50ca0 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChild.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChild.java @@ -12,16 +12,18 @@ public int getNumber() { return number; } - public void setNumber(int number) { + public TestChild setNumber(int number) { this.number = number; + return this; } public String getUrl() { return url; } - public void setUrl(String url) { + public TestChild setUrl(String url) { this.url = url; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChildReport.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChildReport.java index da920bec..7d3ea3f5 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChildReport.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestChildReport.java @@ -13,8 +13,18 @@ public TestChild getChild() { return child; } - public void setChild(TestChild child) { + public TestChildReport setChild(TestChild child) { this.child = child; + return this; + } + + public TestResult getResult() { + return result; + } + + public TestChildReport setResult(TestResult result) { + this.result = result; + return this; } @Override @@ -48,12 +58,4 @@ public boolean equals(Object obj) { return true; } - public TestResult getResult() { - return result; - } - - public void setResult(TestResult result) { - this.result = result; - } - } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestReport.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestReport.java index b775c3f2..de3f0162 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestReport.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestReport.java @@ -47,40 +47,45 @@ public int getFailCount() { return failCount; } - public void setFailCount(int failCount) { + public TestReport setFailCount(int failCount) { this.failCount = failCount; + return this; } public int getSkipCount() { return skipCount; } - public void setSkipCount(int skipCount) { + public TestReport setSkipCount(int skipCount) { this.skipCount = skipCount; + return this; } public int getTotalCount() { return totalCount; } - public void setTotalCount(int totalCount) { + public TestReport setTotalCount(int totalCount) { this.totalCount = totalCount; + return this; } public String getUrlName() { return urlName; } - public void setUrlName(String urlName) { + public TestReport setUrlName(String urlName) { this.urlName = urlName; + return this; } public List getChildReports() { return childReports; } - public void setChildReports(List childReports) { + public TestReport setChildReports(List childReports) { this.childReports = childReports; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestResult.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestResult.java index 934b32d4..5dda306f 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestResult.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestResult.java @@ -20,40 +20,54 @@ public double getDuration() { return duration; } - public void setDuration(double duration) { + public TestResult setDuration(double duration) { this.duration = duration; + return this; } public boolean isEmpty() { return empty; } - public void setEmpty(boolean empty) { + public TestResult setEmpty(boolean empty) { this.empty = empty; + return this; } public int getFailCount() { return failCount; } - public void setFailCount(int failCount) { + public TestResult setFailCount(int failCount) { this.failCount = failCount; + return this; } public int getPassCount() { return passCount; } - public void setPassCount(int passCount) { + public TestResult setPassCount(int passCount) { this.passCount = passCount; + return this; } public int getSkipCount() { return skipCount; } - public void setSkipCount(int skipCount) { + public TestResult setSkipCount(int skipCount) { this.skipCount = skipCount; + return this; + } + + public List getSuites() { + return suites; + } + + public TestResult setSuites(List suites) { + this.suites = suites; + return this; } @Override @@ -97,12 +111,4 @@ public boolean equals(Object obj) { return false; return true; } - - public List getSuites() { - return suites; - } - - public void setSuites(List suites) { - this.suites = suites; - } } diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestSuites.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestSuites.java index 45c96883..030b03b9 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestSuites.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/TestSuites.java @@ -19,40 +19,45 @@ public double getDuration() { return duration; } - public void setDuration(double duration) { + public TestSuites setDuration(double duration) { this.duration = duration; + return this; } public String getId() { return id; } - public void setId(String id) { + public TestSuites setId(String id) { this.id = id; + return this; } public String getName() { return name; } - public void setName(String name) { + public TestSuites setName(String name) { this.name = name; + return this; } public String getTimestamp() { return timestamp; } - public void setTimestamp(String timestamp) { + public TestSuites setTimestamp(String timestamp) { this.timestamp = timestamp; + return this; } public List getCases() { return cases; } - public void setCases(List cases) { + public TestSuites setCases(List cases) { this.cases = cases; + return this; } @Override diff --git a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/View.java b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/View.java index 5267e4f1..422cd5a4 100644 --- a/jenkins-client/src/main/java/com/offbytwo/jenkins/model/View.java +++ b/jenkins-client/src/main/java/com/offbytwo/jenkins/model/View.java @@ -13,8 +13,9 @@ public String getName() { return name; } - public void setName(String name) { + public View setName(String name) { this.name = name; + return this; } public View() { @@ -24,16 +25,18 @@ public String getDescription() { return description; } - public void setDescription(String description) { + public View setDescription(String description) { this.description = description; + return this; } public String getUrl() { return url; } - public void setUrl(String url) { + public View setUrl(String url) { this.url = url; + return this; } @Override diff --git a/jenkins-client/src/site/asciidoc/index.adoc b/jenkins-client/src/site/asciidoc/index.adoc deleted file mode 100644 index 942df55c..00000000 --- a/jenkins-client/src/site/asciidoc/index.adoc +++ /dev/null @@ -1,7 +0,0 @@ -:revnumber: ${project-version} - -== Overview == - -Here you can see the first page. -This is the first. - diff --git a/jenkins-client/src/site/asciidoc/sub/sub.adoc b/jenkins-client/src/site/asciidoc/sub/sub.adoc deleted file mode 100644 index 345e6aef..00000000 --- a/jenkins-client/src/site/asciidoc/sub/sub.adoc +++ /dev/null @@ -1 +0,0 @@ -Test diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/JenkinsServerTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/JenkinsServerTest.java index b98a9fb3..0c553ce1 100644 --- a/jenkins-client/src/test/java/com/offbytwo/jenkins/JenkinsServerTest.java +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/JenkinsServerTest.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.apache.http.entity.ContentType; @@ -25,26 +26,26 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; -import com.google.common.base.Optional; import com.offbytwo.jenkins.client.JenkinsHttpClient; +import com.offbytwo.jenkins.client.JenkinsHttpConnection; import com.offbytwo.jenkins.model.FolderJob; import com.offbytwo.jenkins.model.Job; import com.offbytwo.jenkins.model.JobWithDetails; import com.offbytwo.jenkins.model.MainView; import com.offbytwo.jenkins.model.View; +import java.net.URI; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class JenkinsServerTest extends BaseUnitTest { - private JenkinsHttpClient client = mock(JenkinsHttpClient.class); + private JenkinsHttpConnection client = mock(JenkinsHttpClient.class); private JenkinsServer server = new JenkinsServer(client); private MainView mainView = new MainView(new Job("Hello", "http://localhost/job/Hello/")); - public JenkinsServerTest() throws Exception { - } - @Before public void setUp() throws Exception { given(client.get("/", MainView.class)).willReturn(mainView); @@ -314,6 +315,35 @@ public void testGetJobXmls() throws Exception { shouldGetJobXml("HeLLo"); } + @Test + public void getVersionShouldNotFailWithNPE() + throws Exception + { + when (client.get( "/" )).thenReturn( "TheAnswer"); + when (client.getJenkinsVersion()).thenReturn( "1.23"); + + JenkinsServer server = new JenkinsServer( client); + server.getVersion(); + verify( client, times( 1 )).isJenkinsVersionSet(); + verify( client, times( 1 )).get( "/" ); + verify( client, times( 1 )).getJenkinsVersion(); + + } + + + @Test(expected=IllegalStateException.class) + public void testClose() throws Exception { + final String uri = "http://localhost/jenkins"; + JenkinsServer srv = new JenkinsServer(new URI(uri)); + srv.close(); + srv.close(); //check multiple calls yield no errors + srv.getComputers(); + } + + + + + private void shouldGetFolderJobs(String... jobNames) throws IOException { // given String path = "http://localhost/jobs/someFolder/"; diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java new file mode 100644 index 00000000..2e3bce09 --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/JenkinsHttpClientTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.protocol.HttpContext; +import org.junit.Test; + + + +/** + * + * @author Dell Green + */ +public class JenkinsHttpClientTest { + private static final String URI = "http://localhost/jenkins"; + + + + @Test + public void testGet_String() throws Exception { + final CloseableHttpClient client = mock(CloseableHttpClient.class); + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + final Header versionHeader = mock(Header.class); + final StatusLine statusLine = mock(StatusLine.class); + final HttpEntity entity = mock(HttpEntity.class); + given(client.execute(any(HttpUriRequest.class), eq((HttpContext)null))).willReturn(response); + given(response.getHeaders("X-Jenkins")).willReturn(new Header[]{versionHeader}); + given(response.getStatusLine()).willReturn(statusLine); + given(versionHeader.getValue()).willReturn("1.234"); + given(statusLine.getStatusCode()).willReturn(HttpStatus.SC_OK); + given(response.getEntity()).willReturn(entity); + given(entity.getContent()).willReturn(new ByteArrayInputStream("someJson".getBytes())); + final JenkinsHttpConnection jclient = new JenkinsHttpClient(new URI(URI), client); + final String s = jclient.get("job/someJob"); + assertEquals("someJson", s); + } + + + + @Test(expected=IllegalStateException.class) + public void testClose() throws Exception { + final JenkinsHttpConnection jclient = new JenkinsHttpClient(new URI(URI)); + jclient.close(); + jclient.close(); //check multiple calls yield no errors + jclient.get("job/someJob"); + } + + +} \ No newline at end of file diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/EncodingUtilsTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/EncodingUtilsTest.java new file mode 100644 index 00000000..358ac7f5 --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/EncodingUtilsTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This class is a unit test for the helper class {@link EncodingUtils}. + * + * @author Karl Heinz Marbaise + */ +public class EncodingUtilsTest { + + @Test + public void encodeShouldReturnEncodedDoubleQuoteAndSpace() { + String result = EncodingUtils.encode("!\"& "); + assertThat(result).isEqualTo("%21%22%26+"); + } + + @Test + public void encodeShouldReturnNotEncodeSafeChars() { + String result = EncodingUtils.encode("-._~!$'()*,;&=@:+"); + assertThat(result).isEqualTo("-._%7E%21%24%27%28%29*%2C%3B%26%3D%40%3A%2B"); + } + @Test + public void encodeShouldReturnNotEncodeAlpha() { + String result = EncodingUtils.encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + assertThat(result).isEqualTo("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + } + + @Test + public void encodeShouldReturnEncodingUmlautAndOthers() { + String result = EncodingUtils.encode("äöü#{}"); + assertThat(result).isEqualTo("%C3%A4%C3%B6%C3%BC%23%7B%7D"); + } + + @Test + public void encodeParamShouldReturnEncodedExclamationMarkDoubleQuoteAmpersampSpace() { + String result = EncodingUtils.formParameter("!\"& "); + assertThat(result).isEqualTo("%21%22%26+"); + } + + @Test + public void encodeParamShouldReturnNotEncodeSafeChars() { + String result = EncodingUtils.formParameter("-_.*"); + assertThat(result).isEqualTo("-_.*"); + } + + @Test + public void encodeParamShouldReturnEncodedUmlautAndOthers() { + String result = EncodingUtils.formParameter("äöü#{}"); + assertThat(result).isEqualTo("%C3%A4%C3%B6%C3%BC%23%7B%7D"); + } + + @Test + public void encodeParamShouldReturnEncodedCharacters() { + String result = EncodingUtils.formParameter("-._~!$'()*,;&=@:+"); + assertThat(result).isEqualTo("-._%7E%21%24%27%28%29*%2C%3B%26%3D%40%3A%2B"); + } + +} \ No newline at end of file diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java new file mode 100644 index 00000000..57e001ea --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/ResponseUtilsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import static org.junit.Assert.assertEquals; +import org.junit.Test; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + + + +/** + * + * @author Dell Green + */ +public class ResponseUtilsTest { + + + + @Test + public void testGetJenkinsVersion() { + final Header header = mock(Header.class); + final HttpResponse response = mock(HttpResponse.class); + given(response.getHeaders("X-Jenkins")).willReturn(new Header[]{header}); + given(header.getValue()).willReturn("1.234"); + assertEquals("1.234", ResponseUtils.getJenkinsVersion(response)); + } + + + @Test + public void testGetJenkinsVersion_NoHeader() { + final HttpResponse response = mock(HttpResponse.class); + given(response.getHeaders("X-Jenkins")).willReturn(new Header[0]); + assertEquals("", ResponseUtils.getJenkinsVersion(response)); + } + + + @Test(expected = NullPointerException.class) + public void testGetJenkinsVersion_NullResponse() { + ResponseUtils.getJenkinsVersion(null); + } + + +} \ No newline at end of file diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java new file mode 100644 index 00000000..780600ba --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors. + * + * Distributed under the MIT license: http://opensource.org/licenses/MIT + */ +package com.offbytwo.jenkins.client.util; + +import com.offbytwo.jenkins.model.FolderJob; +import java.net.URI; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Test; + + + +/** + * + * @author Dell Green + */ +public class UrlUtilsTest { + + + + @Test + public void testToBaseUrl_NullFolderJob() { + assertEquals("/", UrlUtils.toBaseUrl(null)); + } + + @Test + public void testToBaseUrl_DefaultFolderJob() { + assertNull("/", UrlUtils.toBaseUrl(new FolderJob())); + } + + + @Test + public void testToBaseUrl() { + final String fpath = "http://localhost/jobs/someFolder/"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + assertEquals(fpath, UrlUtils.toBaseUrl(folderJob)); + } + + + @Test + public void testToJobBaseUrl() { + final String fpath = "http://localhost/jobs/someFolder/"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/job/someJob"; + assertEquals(expected, UrlUtils.toJobBaseUrl(folderJob, "someJob")); + } + + @Test + public void testToJobBaseUrl_NoTrailingFolderSlash() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/job/someJob"; + assertEquals(expected, UrlUtils.toJobBaseUrl(folderJob, "someJob")); + } + + + @Test + public void testToJobBaseUrl_NullFolderJob() { + assertEquals("/job/someJob", UrlUtils.toJobBaseUrl(null, "someJob")); + } + + + @Test + public void testToJobBaseUrl_EmptyJobName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/job/"; + assertEquals(expected, UrlUtils.toJobBaseUrl(folderJob, "")); + } + + @Test(expected = NullPointerException.class) + public void testToJobBaseUrl_NullJobName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + UrlUtils.toJobBaseUrl(folderJob, null); + } + + + @Test + public void testToViewBaseUrl() { + final String fpath = "http://localhost/jobs/someFolder/"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/view/someView"; + assertEquals(expected, UrlUtils.toViewBaseUrl(folderJob, "someView")); + } + + @Test + public void testToViewBaseUrl_NoTrailingFolderSlash() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/view/someView"; + assertEquals(expected, UrlUtils.toViewBaseUrl(folderJob, "someView")); + } + + + @Test + public void testToViewBaseUrl_NullFolderJob() { + assertEquals("/view/someView", UrlUtils.toViewBaseUrl(null, "someView")); + } + + + @Test + public void testToViewBaseUrl_EmptyViewName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + final String expected = "http://localhost/jobs/someFolder/view/"; + assertEquals(expected, UrlUtils.toViewBaseUrl(folderJob, "")); + } + + @Test(expected = NullPointerException.class) + public void testToViewBaseUrl_NullViewName() { + final String fpath = "http://localhost/jobs/someFolder"; + final FolderJob folderJob = new FolderJob("someFolder", fpath); + UrlUtils.toViewBaseUrl(folderJob, null); + } + + + @Test + public void testToFullJobPath_JobNameOnly() { + assertEquals("someJob", UrlUtils.toFullJobPath("someJob")); + } + + + @Test + public void testToFullJobPath_JobNameWithSingleFolder() { + final String expected = "someFolder/job/someJob"; + assertEquals(expected, UrlUtils.toFullJobPath("someFolder/someJob")); + } + + @Test + public void testToFullJobPath_JobNameWithMultipleFolders() { + final String expected = "someFolder1/job/someFolder2/job/someJob"; + assertEquals(expected, UrlUtils.toFullJobPath("someFolder1/someFolder2/someJob")); + } + + @Test(expected = NullPointerException.class) + public void testToFullJobPath_NullJobName() { + UrlUtils.toFullJobPath(null); + } + + + @Test + public void testToFullJobPath_EmptyJobName() { + assertEquals("", UrlUtils.toFullJobPath("")); + } + + + @Test + public void testJoin_EmptyBothPaths() { + assertEquals("", UrlUtils.join("", "")); + } + + @Test + public void testJoin_SlashesOnly() { + assertEquals("/", UrlUtils.join("/", "/")); + } + + @Test + public void testJoin_EmptyPath2() { + assertEquals("1/2/3", UrlUtils.join("1/2/3", "")); + } + + @Test + public void testJoin_EmptyPath1() { + assertEquals("4/5/6", UrlUtils.join("", "4/5/6")); + } + + @Test + public void testJoin_NoTrailingLeadingSlashes() { + assertEquals("1/2/3/4/5/6", UrlUtils.join("1/2/3", "4/5/6")); + + } + + @Test + public void testJoin_TrailingLeadingSlashes() { + assertEquals("/1/2/3/4/5/6/", UrlUtils.join("/1/2/3/", "/4/5/6/")); + } + + @Test + public void testJoin_Path1Trailing_Path2NoLeading() { + assertEquals("/1/2/3/4/5/6/", UrlUtils.join("/1/2/3/", "4/5/6/")); + } + + @Test + public void testJoin_Path1NoTrailing_Path2Leading() { + assertEquals("/1/2/3/4/5/6/", UrlUtils.join("/1/2/3", "/4/5/6/")); + } + + + + + + @Test(expected = NullPointerException.class) + public void testJoin_NullPath1() { + UrlUtils.join(null, "4/5/6"); + } + + @Test(expected = NullPointerException.class) + public void testJoin_NullPath2() { + UrlUtils.join("1/2/3", null); + } + + + @Test + public void testToQueryUri_WithoutQuery() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "job/somejob"); + final String expected = "http://localhost/jenkins/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test + public void testToQueryUri_EmptyContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "", "job/somejob"); + final String expected = "http://localhost/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test + public void testToQueryUri_EmptyPathAndContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "", ""); + final String expected = "http://localhost/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test(expected = NullPointerException.class) + public void testToQueryUri_NullUri() throws Exception { + UrlUtils.toJsonApiUri(null, "jenkins", "job/somejob"); + } + + + @Test(expected = NullPointerException.class) + public void testToQueryUri_NullPath() throws Exception { + UrlUtils.toJsonApiUri(new URI("http://localhost/jenkins"), "jenkins", null); + } + + @Test(expected = NullPointerException.class) + public void testToQueryUri_NullContext() throws Exception { + UrlUtils.toJsonApiUri(new URI("http://localhost/jenkins"), null, "job/ajob"); + } + + @Test + public void testToQueryUri_EmptyPath() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", ""); + final String expected = "http://localhost/jenkins/api/json"; + assertEquals(expected, uri.toString()); + } + + @Test + public void testToQueryUri_UpperCase() throws Exception { + final String suri = "HTTP://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "job/somejob"); + final String expected = "HTTP://localhost/jenkins/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToQueryUri_WithQuery() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "job/somejob?pretty=true"); + final String expected = "http://localhost/jenkins/job/somejob/api/json?pretty=true"; + assertEquals(expected, uri.toString()); + } + + + + @Test + public void testToQueryUri_PathContainsSchemeAndContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toJsonApiUri(new URI(suri), "jenkins", "http://localhost/jenkins/job/somejob"); + final String expected = "http://localhost/jenkins/job/somejob/api/json"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_PathContainsSchemeAndContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "jenkins", "http://localhost/jenkins/job/somejob"); + final String expected = "http://localhost/jenkins/job/somejob"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_UpperCase() throws Exception { + final String suri = "HTTP://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "jenkins", "job/somejob"); + final String expected = "HTTP://localhost/jenkins/job/somejob"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_EmptyPath() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "jenkins", ""); + final String expected = "http://localhost/jenkins"; + assertEquals(expected, uri.toString()); + } + + + @Test + public void testToNoQueryUri_EmptyContext() throws Exception { + final String suri = "http://localhost/jenkins"; + final URI uri = UrlUtils.toNoApiUri(new URI(suri), "", "job/somejob"); + final String expected = "http://localhost/job/somejob"; + assertEquals(expected, uri.toString()); + } + + + @Test(expected = NullPointerException.class) + public void testToNoQueryUri_NullContext() throws Exception { + UrlUtils.toNoApiUri(new URI("http://localhost/jenkins"), null, "job/ajob"); + } + +} \ No newline at end of file diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/helper/RangeTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/helper/RangeTest.java index 5dcc9e38..b56aa467 100644 --- a/jenkins-client/src/test/java/com/offbytwo/jenkins/helper/RangeTest.java +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/helper/RangeTest.java @@ -6,13 +6,10 @@ package com.offbytwo.jenkins.helper; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; -import com.offbytwo.jenkins.helper.Range; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** *
      @@ -23,7 +20,7 @@ * The same as {0,N}. *
    • {N}: Just retrieve the N-th element. The same as {N,N+1}.
    • *
    - * + * * @author Karl Heinz Marbaise */ public class RangeTest { @@ -56,27 +53,24 @@ public void onlyGiven() { assertThat(r.getRangeString()).isEqualTo(getEscaped("3,4")); } - @Rule - public ExpectedException exception = ExpectedException.none(); - @Test public void toIsGivenLargerThanFromShouldResultInIllegalArgumentException() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("to must be greater than from"); - Range.build().from(5).to(1); + assertThatIllegalArgumentException() + .isThrownBy(() -> Range.build().from(5).to(1)) + .withMessage("to must be greater than from"); } @Test public void fromGivenNegativeValueShouldResultInIllegalArgumentException() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("from value must be greater or equal null."); - Range.build().from(-1); + assertThatIllegalArgumentException() + .isThrownBy(() -> Range.build().from(-1)) + .withMessage("from value must be greater or equal null."); } @Test public void fromGivenPositiveToNegativeValueShouldResultInIllegalArgumentException() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage("to must be greater or equal null."); - Range.build().from(5).to(-1); + assertThatIllegalArgumentException() + .isThrownBy(() -> Range.build().from(5).to(-1)) + .withMessage("to must be greater or equal null."); } } diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/FolderTestsIT.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/FolderTestsIT.java index c6549413..098102c0 100644 --- a/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/FolderTestsIT.java +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/FolderTestsIT.java @@ -5,6 +5,7 @@ import hudson.model.UpdateCenter; import java.util.List; +import java.util.Optional; import java.util.Vector; import jenkins.model.Jenkins; @@ -19,7 +20,6 @@ import org.kohsuke.stapler.StaplerResponse; import org.mockito.Mockito; -import com.google.common.base.Optional; import com.offbytwo.jenkins.JenkinsServer; import com.offbytwo.jenkins.model.FolderJob; import com.offbytwo.jenkins.model.JobWithDetails; @@ -67,7 +67,7 @@ public void testFolderPluginAPIs() throws Exception { JobWithDetails root = server.getJob("root"); Assert.assertNotNull(root); - Optional rootFolder = server.getFolderJob(root); + java.util.Optional rootFolder = server.getFolderJob(root); Assert.assertTrue(rootFolder.isPresent()); server.createFolder(rootFolder.get(), "subfolder"); diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/JenkinsServerIT.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/JenkinsServerIT.java index 59100302..b32d027b 100644 --- a/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/JenkinsServerIT.java +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/integration/JenkinsServerIT.java @@ -88,7 +88,7 @@ public void shouldSupportBooleanParameters() throws Exception { JobWithDetails job = server.getJobs().get(JENKINS_TEST_JOB).details(); BuildWithDetails build = job.getBuilds().get(0).details(); - assertEquals("true", build.getParameters().get("someValue")); + assertEquals(Boolean.TRUE, build.getParameters().get("someValue")); } @Test @@ -183,4 +183,26 @@ public void testUpdateJob() throws Exception { String confirmXml = server.getJobXml(JENKINS_TEST_JOB); assertTrue(confirmXml.contains(description)); } + + + @Test(expected=IllegalStateException.class) + public void testClose_ReuseAfterClosed() throws Exception { + final FreeStyleProject proj = jenkinsRule.getInstance().createProject( + FreeStyleProject.class, JENKINS_TEST_JOB); + final Map jobs = server.getJobs(); + assertNotNull(jobs.get(proj.getName())); + server.close(); + server.getJobs(); + } + + + @Test + public void testClose_CloseMultipleTimes() throws Exception { + final FreeStyleProject proj = jenkinsRule.getInstance().createProject( + FreeStyleProject.class, JENKINS_TEST_JOB); + final Map jobs = server.getJobs(); + assertNotNull(jobs.get(proj.getName())); + server.close(); + server.close(); + } } diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/model/BuildWithDetailsTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/model/BuildWithDetailsTest.java new file mode 100644 index 00000000..dd4969b3 --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/model/BuildWithDetailsTest.java @@ -0,0 +1,123 @@ +package com.offbytwo.jenkins.model; + +import com.offbytwo.jenkins.BaseUnitTest; +import com.offbytwo.jenkins.client.JenkinsHttpClient; +import com.offbytwo.jenkins.client.JenkinsHttpConnection; +import com.offbytwo.jenkins.helper.BuildConsoleStreamListener; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicHeader; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyListOf; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; + +/** + */ +public class BuildWithDetailsTest extends BaseUnitTest { + + private JenkinsHttpConnection client; + private BuildWithDetails buildWithDetails; + + @Before + public void setUp() { + client = mock(JenkinsHttpClient.class); + buildWithDetails = givenBuild(); + } + + private BuildWithDetails givenBuild() { + BuildWithDetails buildWithDetails = new BuildWithDetails(); + buildWithDetails.setClient(client); + return buildWithDetails; + } + + @Test + public void getBuildLogWithBuffer() { + try { + HttpResponse response = mock(HttpResponse.class); + String text = "test test test"; + int textLength = text.length(); + given(response.getEntity()).willReturn(new StringEntity(text)); + BasicHeader moreDataHeader = new BasicHeader(BuildWithDetails.MORE_DATA_HEADER, "false"); + BasicHeader textSizeHeader = new BasicHeader(BuildWithDetails.TEXT_SIZE_HEADER, Integer.toString(textLength)); + given(response.getFirstHeader(BuildWithDetails.MORE_DATA_HEADER)).willReturn(moreDataHeader); + given(response.getFirstHeader(BuildWithDetails.TEXT_SIZE_HEADER)).willReturn(textSizeHeader); + given(client.post_form_with_result(anyString(),anyListOf(NameValuePair.class),anyBoolean())).willReturn(response); + ConsoleLog consoleOutputText = buildWithDetails.getConsoleOutputText(500, false); + assertThat(consoleOutputText.getConsoleLog()).isEqualTo(text); + assertThat(consoleOutputText.getCurrentBufferSize()).isEqualTo(textLength); + assertThat(consoleOutputText.getHasMoreData()).isFalse(); + } catch (IOException e) { + fail("Should not return exception",e); + } + } + + + @Test + public void poolBuildLog() throws InterruptedException { + try { + HttpResponse response = mock(HttpResponse.class); + final String text = "test test test"; + int textLength = text.length(); + given(response.getEntity()).willReturn(new StringEntity(text)); + BasicHeader moreDataHeader = new BasicHeader(BuildWithDetails.MORE_DATA_HEADER, "false"); + BasicHeader textSizeHeader = new BasicHeader(BuildWithDetails.TEXT_SIZE_HEADER, Integer.toString(textLength)); + given(response.getFirstHeader(BuildWithDetails.MORE_DATA_HEADER)).willReturn(moreDataHeader); + given(response.getFirstHeader(BuildWithDetails.TEXT_SIZE_HEADER)).willReturn(textSizeHeader); + given(client.post_form_with_result(anyString(),anyListOf(NameValuePair.class),anyBoolean())).willReturn(response); + final StringBuffer buffer=new StringBuffer(); + buildWithDetails.streamConsoleOutput(new BuildConsoleStreamListener() { + @Override + public void onData(String newLogChunk) { + assertThat(newLogChunk).isEqualTo(text); + buffer.append(text); + } + + @Override + public void finished() { + assertThat(buffer.toString()).isEqualTo(text); + } + },1,2, false); + } catch (IOException e) { + fail("Should not return exception",e); + } + } + + @Test + public void poolBuildLogShouldTimeout() throws InterruptedException { + try { + HttpResponse response = mock(HttpResponse.class); + final String text = "test test test"; + int textLength = text.length(); + given(response.getEntity()).willReturn(new StringEntity(text)); + BasicHeader moreDataHeader = new BasicHeader(BuildWithDetails.MORE_DATA_HEADER, "true"); + BasicHeader textSizeHeader = new BasicHeader(BuildWithDetails.TEXT_SIZE_HEADER, Integer.toString(textLength)); + given(response.getFirstHeader(BuildWithDetails.MORE_DATA_HEADER)).willReturn(moreDataHeader); + given(response.getFirstHeader(BuildWithDetails.TEXT_SIZE_HEADER)).willReturn(textSizeHeader); + given(client.post_form_with_result(anyString(),anyListOf(NameValuePair.class),anyBoolean())).willReturn(response); + buildWithDetails.streamConsoleOutput(new BuildConsoleStreamListener() { + @Override + public void onData(String newLogChunk) { + assertThat(newLogChunk).isEqualTo(text); + } + + @Override + public void finished() { + fail("Should timeout"); + } + },1,2, false); + } catch (IOException e) { + fail("Should not return exception",e); + } + } + +} diff --git a/jenkins-client/src/test/java/com/offbytwo/jenkins/model/ChangeSetTest.java b/jenkins-client/src/test/java/com/offbytwo/jenkins/model/ChangeSetTest.java new file mode 100644 index 00000000..b1e7ae33 --- /dev/null +++ b/jenkins-client/src/test/java/com/offbytwo/jenkins/model/ChangeSetTest.java @@ -0,0 +1,74 @@ +package com.offbytwo.jenkins.model; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import static org.junit.Assert.*; + + +import org.junit.Test; + + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class ChangeSetTest { + + private BuildWithDetails getBuildFromJson(String json) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + mapper.disable(FAIL_ON_UNKNOWN_PROPERTIES); + return mapper.readValue(json, BuildWithDetails.class); + } + + final String changeSetExampleJson = " {" + + " \"_class\" : \"hudson.plugins.git.GitChangeSetList\"," + + " \"items\" : [" + + " {" + + " \"_class\" : \"hudson.plugins.git.GitChangeSet\"," + + " \"affectedPaths\" : [" + + " \"README.md\"" + + " ]," + + " \"commitId\" : \"ba40ff32c60f692918c1d51f5c80842124ed04af\"," + + " \"timestamp\" : 1519306808000," + + " \"author\" : {" + + " \"absoluteUrl\" : \"https://my.jenkins/user/john.doe\"," + + " \"fullName\" : \"john.doe\"" + + " }," + + " \"authorEmail\" : \"john.doe@flap.com\"," + + + " \"comment\" : \"longer\\ncommit message\\n\"," + + " \"date\" : \"2018-02-22 13:40:08 +0000\"," + + " \"id\" : \"ba40ff32c60f692918c1d51f5c80842124ed04af\"," + + " \"msg\" : \"longer\"," + + " \"paths\" : [" + + " {" + + " \"editType\" : \"edit\"," + + " \"file\" : \"README.md\"" + + " }" + + " ]" + + " }" + + " ]," + + " \"kind\" : \"git\"" + + " }"; + + + @Test + public void getChangeSet__forBuildWithChangeSetAsSingleObject() throws Exception { + String json = String.format("{ \"changeSet\" : %s }", changeSetExampleJson); + + BuildWithDetails examinee = getBuildFromJson(json); + + BuildChangeSetItem item = examinee.getChangeSet().getItems().get(0); + assertEquals(item.getAuthor().getFullName(), "john.doe"); + + } + + @Test + public void getChangeSet__forBuildWithChangeSetsAsList() throws Exception { + String json = String.format("{ \"changeSets\" : [ %s ] }", changeSetExampleJson); + + BuildWithDetails examinee = getBuildFromJson(json); + + BuildChangeSetItem item = examinee.getChangeSet().getItems().get(0); + assertEquals(item.getAuthor().getFullName(), "john.doe"); + + assertEquals(examinee.getChangeSets().size(), 1); + } +} diff --git a/pom.xml b/pom.xml index cb1291fb..f88539d9 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.offbytwo.jenkins jenkins-client-parent - 0.3.8-SNAPSHOT + 0.4.0-SNAPSHOT pom @@ -32,7 +32,7 @@ github - scm:git:https://github.com/jenkinsci/java-client-api.git + scm:git:ssh://git@github.com/jenkinsci/java-client-api.git @@ -43,25 +43,25 @@ UTF-8 UTF-8 - 1.7 - 1.7 + 1.8 + 1.8 true true - 1.5.3 + 2.0.0 1.644 - 4.11 - 1.9.5 + 4.12 + 3.0.0 2.4 1.4.7-jenkins-1 - 1.6.1 - 2.6 - 17.0 + 3.9 2.4 - 4.3.6 - 2.3.4 + 4.5.8 + 4.4.11 + 4.5.8 + 2.10.3 @@ -104,9 +104,9 @@ - dom4j + org.dom4j dom4j - ${dom4j.version} + 2.1.3 @@ -125,14 +125,8 @@ - com.google.guava - guava - ${guava.version} - - - - commons-lang - commons-lang + org.apache.commons + commons-lang3 ${commons-lang.version} @@ -148,6 +142,18 @@ ${httpclient.version} + + org.apache.httpcomponents + httpcore + ${httpcore.version} + + + + org.apache.httpcomponents + httpmime + ${httpmime.version} + + jaxen jaxen @@ -158,55 +164,54 @@ org.testng testng - 6.8.21 - test + 7.0.0 + + + org.junit + junit-bom + 5.5.2 + import + pom junit junit ${junit.version} - test org.mockito mockito-core ${mockito-core.version} - test org.jenkins-ci.main jenkins-test-harness ${jenkins-version} - test org.assertj assertj-core - 1.7.1 - test + 3.12.2 org.slf4j slf4j-api - 1.7.21 + 1.7.25 org.apache.logging.log4j - log4j-core - 2.5 + log4j-bom + 2.12.1 + import + pom - org.apache.logging.log4j - log4j-slf4j-impl - 2.5 - - - xml-apis - xml-apis - 1.4.01 + xerces + xmlParserAPIs + 2.6.1 @@ -214,20 +219,25 @@ + + org.apache.maven.plugins + maven-help-plugin + 3.2.0 + org.apache.maven.plugins maven-clean-plugin - 3.0.0 + 3.1.0 org.apache.maven.plugins maven-resources-plugin - 3.0.2 + 3.1.0 org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.1.0 - - font - coderay - style - 2 - ${project.version} - true - true - - - @@ -394,31 +385,18 @@ org.apache.maven.plugins maven-project-info-reports-plugin
    - @@ -466,7 +444,8 @@ attach-javadocs - jar + javadoc-no-fork + test-javadoc-no-fork @@ -479,7 +458,7 @@ repo.jenkins-ci.org - http://repo.jenkins-ci.org/public/ + https://repo.jenkins-ci.org/public/