Generation Java client libraries for REST service with swagger-gen Azure DevOps
Image from Pexels

Generation Java client libraries for REST service with swagger-gen Azure DevOps

Compiling and serving MAVEN packages for Java with Azure DevOps using PowerShell

Generating client libraries for REST API which are documented with Swagger can significantly reduce product development time, especially on the front-end side. Well documented API can be easily interpreted by various tools to generate language specific libraries which can be then just easily integrated in in the API service consuming application.

Some time ago I wrote an article Stop writing clients in C# for your Web APIs which explains how to use NSwag to generate NuGet packages for Swagger documented Web API REST services and to push them to Azure DevOps Artifact Feed. This allows you to create clients out of your API ASP.NET Core projects, pack them to NuGet packages and push the NuGet repository which is a part of Azure DevOps Artifact Feed.

Unfortunately, NSwag does not support generating of clients for Java and since I wanted to make my services available for Java projects as well, I found Swagger-CodeGen CLI that allows to generate Java project from the REST service endpoint.

Since I am using Windows as development platform, I used PowerShell to build a script for automating Swagger API client generation. The script and steps that I am going to share in this article can be used on local development Windows machine as well as on Azure DevOps build/release agent.

To simplify script, I am going to split it in few phases where each and every phase depends on the previous one in order to work, therefore script snippets will be concatenated for each next phase. 

Script phases that are going to be executed are:

  • Check the requirements on the environment
  • Download the swagger-gen cli
  • Run swagger-gen cli to create Java project
  • Alter project pom.xml file
  • Configure and run maven deploy plugin
Note

Running the script on local machine is a little bit different than running it on Azure DevOps but script changes and adjustments are minor and I will describe the for each phase

Check the requirements on the environment

Before we even try to run any swagger-gen command, we first need to make sure our local or Azure DevOps environment. I happen to have JDK11 and maven 3.6 on my Windows machine, but Azure DevOps is not that much up to date with JDK releases. It also has Open JDK instead of Oracle JDK because of the licensing.

At the time I am writing this article Azure DevOps have pre-installed OpenJDK 8 on their Windows based build agents. To cut the story short, rather than running separate commands for JDK and maven and environment variable setup, just run the following PowerShell script.

# Check JDK
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "java"
$pinfo.Arguments = "-version"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$output = $p.StandardOutput.ReadToEnd()
$output += $p.StandardError.ReadToEnd()
$output

# Check Maven
Invoke-Expression "mvn -version"

# Get JAVA_HOME environment variable
Write-Output "JAVA_HOME = $env:JAVA_HOME"
    
Note

I will not go into the local development machine setup for JDK and maven as there is a fair amount of articles online about that, but if you have troubles making this work on your local development machine, feel free to post a question at the bottom of the page

The output will depend on your local maven and JDK setup, but is you take an output from Azure DevOps build agent you will get something like this 

Starting: Get Java environment
==============================================================================
Task         : PowerShell
Description  : Run a PowerShell script on Linux, macOS, or Windows
Version      : 2.151.2
Author       : Microsoft Corporation
Help         : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/powershell
==============================================================================
Generating script.
========================== Starting Command Output ===========================
"C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\39d98d77-eeb5-4550-9dfb-ea1429cdd1d5.ps1'"
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (Zulu 8.40.0.25-win64)-Microsoft-Azure-restricted (build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (Zulu 8.40.0.25-win64)-Microsoft-Azure-restricted (build 25.222-b10, mixed mode)

Apache Maven 3.6.2 (40f52333136460af0dc0d7232c0dc0bcf0d9e117; 2019-08-27T15:06:16Z)
Maven home: C:\ProgramData\chocolatey\lib\maven\apache-maven-3.6.2\bin\..
Java version: 1.8.0_222, vendor: Azul Systems, Inc., runtime: C:\Program Files\Java\zulu-8-azure-jdk_8.40.0.25-8.0.222-win_x64\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows server 2019", version: "10.0", arch: "amd64", family: "windows"
JAVA_HOME = C:\Program Files\Java\zulu-8-azure-jdk_8.40.0.25-8.0.222-win_x64
Finishing: Get Java environment

Download the swagger-gen cli

Now that your environment is ready to run Java and maven, we first need to have the swagger-gen executable downloaded. This applies both for local machine or Azure DevOps build/release agent run

# Azure DevOps folder
$folder = "$(build.artifactstagingdirectory)"

# Local machine folder
#$folder = $PSScriptRoot

# ---- Download CLI ---- #
$cliVersion ="2.4.9"
$cliFilename = "swagger-codegen-cli-$cliVersion.jar"
$cliUrl = "https://search.maven.org/classic/remotecontent?filepath=io/swagger/swagger-codegen-cli/$cliVersion/$cliFilename"
$cliOutput = "$folder\$cliFilename"

Invoke-WebRequest -Uri $cliUrl -OutFile $cliOutput
    

To run PowerShell script on local you need to set $folder = $PSScriptRoot instead of $folder = "$(build.artifactstagingdirectory)".

Run swagger-gen cli to create Java project

We have the cli now downloaded and we are ready to generate the Java project that we'll eventually compile and push to remote maven package repository on Azure DevOps.

I am not going to develop REST API with Swagger documentation, but will rather use public Petstore sample REST API and point swagger-gen cli to it's OpenAPI json definition url. We are going to build Java client package for the sample Petstore REST API with downloaded cli.

Petstore Api

All we have to do is to point downloaded cli to OpenAPI definition URL of the service and set names for the package components in the PowerShell script.

# Azure DevOps folder
$folder = "$(build.artifactstagingdirectory)"

# Local machine folder
#$folder = $PSScriptRoot

# ---- Download CLI ---- #
$cliVersion ="2.4.9"
$cliFilename = "swagger-codegen-cli-$cliVersion.jar"
$cliUrl = "https://search.maven.org/classic/remotecontent?filepath=io/swagger/swagger-codegen-cli/$cliVersion/$cliFilename"
$cliOutput = "$folder\$cliFilename"

Invoke-WebRequest -Uri $cliUrl -OutFile $cliOutput

# ---- Generate client ---- #
$serviceUrl="https://petstore.swagger.io/v2/swagger.json"
$company = "petcare"
$project = "petstore"
$artifactId = "petstore-api-client"
$version = "0.9.0"
$clientFolder = "$folder\$artifactId"

# Clean up client project folder on local machine
#Remove-Item $clientFolder -Recurse -ErrorAction Ignore
    

Since we might run the script over and over on our development machine, you may want to delete the previously generated client project (just uncomment commented Remove-Item line). this is not necessary on Azure DevOps build/release agent as it is always provisioned as a new instance from Azure, unless you have your own registered build/release agent machine.

Alter project pom.xml file

Although we have Java project ready after running swagger-gen cli, you may still get some compilation errors if you try to compile it especially if you have JDK10+ installed on your machine. Apparently there are some breaking changes in JDK10+ compared to JDK8 and JDK9 and you will have to add an additional package reference to your pom.xml file

<dependency>
    <groupId>com.sun.xml.ws</groupId>
    <artifactId>jaxws-ri</artifactId>
    <version>2.3.0</version>
    <scope>runtime</scope>
</dependency>
    

Now since we want to automate everything, we do not want to do this manually although we could, but updating the XML from PowerShell is easy since PowerShell can use .NET libraries, so we are going to make this step part of our script.

# Azure DevOps folder
$folder = "$(build.artifactstagingdirectory)"

# Local machine folder
#$folder = $PSScriptRoot

# ---- Download CLI ---- #
$cliVersion ="2.4.9"
$cliFilename = "swagger-codegen-cli-$cliVersion.jar"
$cliUrl = "https://search.maven.org/classic/remotecontent?filepath=io/swagger/swagger-codegen-cli/$cliVersion/$cliFilename"
$cliOutput = "$folder\$cliFilename"

Invoke-WebRequest -Uri $cliUrl -OutFile $cliOutput

# ---- Generate client ---- #
$serviceUrl="https://petstore.swagger.io/v2/swagger.json"
$company = "petcare"
$project = "petstore"
$artifactId = "petstore-api-client"
$version = "0.9.0"
$clientFolder = "$folder\$artifactId"

# Clean up client project folder on local machine
#Remove-Item $clientFolder -Recurse -ErrorAction Ignore

$command = "-jar $cliOutput generate -i $serviceUrl --api-package com.$company.$project.client.api --model-package com.$company.$project.client.model --invoker-package com.$company.$project.client.invoker --group-id com.$company --artifact-id $artifactId --artifact-version $version -l java --library resttemplate -o $artifactId"
Start-Process java -ArgumentList $command -WorkingDirectory $folder -Wait -NoNewWindow

# ---- Update POM ---- #
$doc = New-Object System.Xml.XmlDocument
$pomFile = "$clientFolder\pom.xml"
$doc.Load($pomFile)

## Add JDK8 compatibility package to support JDK10+
$dependenciesNode = $doc.project.dependencies
$dependencyNode = $doc.CreateElement("dependency", $dependenciesNode.NamespaceURI)

$groupIdNode = $doc.CreateElement("groupId", $dependenciesNode.NamespaceURI)
$groupIdNode.AppendChild($doc.CreateTextNode("com.sun.xml.ws")) | Out-Null
$dependencyNode.AppendChild($groupIdNode) | Out-Null

$artifactIdNode = $doc.CreateElement("artifactId", $dependenciesNode.NamespaceURI)
$artifactIdNode.AppendChild($doc.CreateTextNode("jaxws-ri")) | Out-Null
$dependencyNode.AppendChild($artifactIdNode) | Out-Null

$versionNode = $doc.CreateElement("version", $dependenciesNode.NamespaceURI)
$versionNode.AppendChild($doc.CreateTextNode("2.3.0")) | Out-Null
$dependencyNode.AppendChild($versionNode) | Out-Null

$typeNode = $doc.CreateElement("type", $dependenciesNode.NamespaceURI)
$typeNode.AppendChild($doc.CreateTextNode("pom")) | Out-Null
$dependencyNode.AppendChild($typeNode) | Out-Null

$dependencyNode.RemoveAttribute("xmlns");
$dependenciesNode.AppendChild($dependencyNode)

    

Configure and run maven deploy plugin

Apart from JDK10+ compatibility, pom.xml needs to be updated for maven deploy plugin as well. Some configuration settings for maven for Azure DevOps can be found in article Configure Azure DevOps Services for Maven package management in Microsoft documentation.

To make maven able to publish the package we need to add the following xml snippet to pom.xml

  <repositories>
    <repository>
      <id>Petstore-Packages</id>
      <url>https://pkgs.dev.azure.com/[Company name]/[Project name]/_packaging/[Artifact name]/maven/v1</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
  <distributionManagement>
    <repository>
      <id>Petstore-Packages</id>
      <name>Petstore-Packages</name>
      <url>https://pkgs.dev.azure.com/[Company name]/[Project name]/_packaging/[Artifact name]/maven/v1</url>
    </repository>
  </distributionManagement>
    

The following placeholders in the xml need to be replaced with actual values from your Azure DevOps setup

  • [Company name] - Azure DevOps company name set for the account
  • [Project name] - project in Azure DevOps where the target Artifact resides
  • [Artifact name] - target artifact name where Java maven package will be deployed to

Of course, we do not want to do this manually, so same way we updated pom.xml for JDK10+ compatibility we are going to make these changes using PowerShell

# Azure DevOps folder
$folder = "$(build.artifactstagingdirectory)"
# Local machine folder
#$folder = $PSScriptRoot
# ---- Download CLI ---- #
$cliVersion ="2.4.9"
$cliFilename = "swagger-codegen-cli-$cliVersion.jar"
$cliUrl = "https://search.maven.org/classic/remotecontent?filepath=io/swagger/swagger-codegen-cli/$cliVersion/$cliFilename"
$cliOutput = "$folder\$cliFilename"
Invoke-WebRequest -Uri $cliUrl -OutFile $cliOutput
# ---- Generate client ---- #
$serviceUrl="https://petstore.swagger.io/v2/swagger.json"
$company = "petcare"
$project = "petstore"
$artifactId = "petstore-api-client"
$version = "0.9.0"
$clientFolder = "$folder\$artifactId"
# Clean up client project folder on local machine
#Remove-Item $clientFolder -Recurse -ErrorAction Ignore
$command = "-jar $cliOutput generate -i $serviceUrl --api-package com.$company.$project.client.api --model-package com.$company.$project.client.model --invoker-package com.$company.$project.client.invoker --group-id com.$company --artifact-id $artifactId --artifact-version $version -l java --library resttemplate -o $artifactId"
Start-Process java -ArgumentList $command -WorkingDirectory $folder -Wait -NoNewWindow
# ---- Update POM ---- #
$doc = New-Object System.Xml.XmlDocument
$pomFile = "$clientFolder\pom.xml"
$doc.Load($pomFile)
## Add JDK8 compatibility package to support JDK10+
$dependenciesNode = $doc.project.dependencies
$dependencyNode = $doc.CreateElement("dependency", $dependenciesNode.NamespaceURI)
$groupIdNode = $doc.CreateElement("groupId", $dependenciesNode.NamespaceURI)
$groupIdNode.AppendChild($doc.CreateTextNode("com.sun.xml.ws")) | Out-Null
$dependencyNode.AppendChild($groupIdNode) | Out-Null
$artifactIdNode = $doc.CreateElement("artifactId", $dependenciesNode.NamespaceURI)
$artifactIdNode.AppendChild($doc.CreateTextNode("jaxws-ri")) | Out-Null
$dependencyNode.AppendChild($artifactIdNode) | Out-Null
$versionNode = $doc.CreateElement("version", $dependenciesNode.NamespaceURI)
$versionNode.AppendChild($doc.CreateTextNode("2.3.0")) | Out-Null
$dependencyNode.AppendChild($versionNode) | Out-Null
$typeNode = $doc.CreateElement("type", $dependenciesNode.NamespaceURI)
$typeNode.AppendChild($doc.CreateTextNode("pom")) | Out-Null
$dependencyNode.AppendChild($typeNode) | Out-Null

$dependencyNode.RemoveAttribute("xmlns");
$dependenciesNode.AppendChild($dependencyNode)

## Add repository node
$projectNode = $doc.project
$repoId = "PetStore-Packages"
$repoUrl = "https://pkgs.dev.azure.com/[Company name]/[Project name]/_packaging/[Artifact name]/maven/v1"

$repositoriesNode = $doc.CreateElement("repositories", $projectNode.NamespaceURI)
$repoNode = $doc.CreateElement("repository", $projectNode.NamespaceURI)

$repoIdNode = $doc.CreateElement("id", $projectNode.NamespaceURI)
$repoIdNode.AppendChild($doc.CreateTextNode($repoId)) | Out-Null
$repoNode.AppendChild($repoIdNode)

$repoUrlNode = $doc.CreateElement("url", $projectNode.NamespaceURI)
$repoUrlNode.AppendChild($doc.CreateTextNode($repoUrl)) | Out-Null
$repoNode.AppendChild($repoUrlNode)

$repoReleaseNode = $doc.CreateElement("releases", $projectNode.NamespaceURI)
$repoReleaseEnabledNode = $doc.CreateElement("enabled", $projectNode.NamespaceURI)
$repoReleaseEnabledNode.AppendChild($doc.CreateTextNode("true")) | Out-Null
$repoReleaseNode.AppendChild($repoReleaseEnabledNode)
$repoNode.AppendChild($repoReleaseNode)

$repoSnapshotNode = $doc.CreateElement("snapshots", $projectNode.NamespaceURI)
$repoSnapshotEnabledNode = $doc.CreateElement("enabled", $projectNode.NamespaceURI)
$repoSnapshotEnabledNode.AppendChild($doc.CreateTextNode("true")) | Out-Null
$repoSnapshotNode.AppendChild($repoSnapshotEnabledNode)
$repoNode.AppendChild($repoSnapshotNode)

$repositoriesNode.AppendChild($repoNode)
$projectNode.AppendChild($repositoriesNode)

## Distribution management node
$dmNode = $doc.CreateElement("distributionManagement", $projectNode.NamespaceURI)
$dmRepoNode = $doc.CreateElement("repository", $projectNode.NamespaceURI)
$dmNode.AppendChild($dmRepoNode)

$dmIdNode = $doc.CreateElement("id", $projectNode.NamespaceURI)
$dmIdNode.AppendChild($doc.CreateTextNode($repoId)) | Out-Null
$dmRepoNode.AppendChild($dmIdNode)

$dmNameNode = $doc.CreateElement("name", $projectNode.NamespaceURI)
$dmNameNode.AppendChild($doc.CreateTextNode($repoId)) | Out-Null
$dmRepoNode.AppendChild($dmNameNode)

$dmUrlNode = $doc.CreateElement("url", $projectNode.NamespaceURI)
$dmUrlNode.AppendChild($doc.CreateTextNode($repoUrl)) | Out-Null
$dmRepoNode.AppendChild($dmUrlNode)

$projectNode.AppendChild($dmNode)

$doc.Save($pomFile)

# ---- Publish the package to Azure DevOps artifact feed ---- #
Start-Process mvn -ArgumentList "deploy -Dmaven.javadoc.skip=true -s $(Agent.TempDirectory)\maven\ci-settings.xml" -WorkingDirectory $clientFolder -Wait -NoNewWindow

    

Apart from pom.xml file update, we also need to add maven settings in order for maven to know how to authenticate to the artifact and deploy the package. Before we do that, you need to generate personal access token (PAT) in Azure DevOps. When creating PAT, you need to make sure that the following permissions are selected in order to be able to upload the package to artifact feed

Azure Devops Package Pat

Maven package setting file structure is as following

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                              https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <servers>
    <server>
      <id>[Artifact name]</id>
      <username>[Personal Access Token name]</username>
      <password>[Pesonal Access Token]</password>
    </server>
  </servers>
</settings>
    

and on local machine this file should be stored in %USERPROFILE%\.m2\settings.xml folder, but for Azure DevOps pipeline you should do it a little bit different. Alternatively this settings file can be also forced as it is done with the last line in the PowerShell script above. This is useful when we are running the script on Azure provisioned Windows or Linux container as we can force maven to use specific repository settings file.

Setting up Azure DevOps pipeline 

We see that there are no big differences in PowerShell script whether it is running on premise or Azure DevOps managed build/release agent. However, there has to be pipeline in place with pipeline steps which involve

  • PoweShell - environment report
  • File Creator - to generate maven repository settings file
  • PowerShell - build and publish client
Note

These steps can part of build or release pipeline as a part of build or deploy application process

Azure Devops Ci

Once the last step is executed the package will appear in the Artifact feed and you can use "Connect to feed" option on the artifact to get instructions how to consume packages from the specific artifact maven feed.

Note

Since maven is executed from PowerShell script as an external process you may see it as succeeded, but you should actually rely on pipeline step logs in order to be sure that package is actually delivered to the artifact feed

Once successfully executed you should be able to find your maven package among other maven packages inside the artifact feed list

Azure Devops Ci Maven Feed

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

.NET

read more

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

Comments for this article