OpenAPI System using standard REST controllers

Introduction

Ontimize OpenAPI Generator allows you to generate, from the interface files, the API code for standard REST controllers.

Prerequisites

There are 2 options to follow this tutorial, clone the repository with the initial state and follow the tutorial step by step, or download the final example and see which files are new and which have been updated.

Initial project

/$ git clone https://github.com/ontimize/ontimize-examples
/ontimize-examples$ cd ontimize-examples
/ontimize-examples$ git checkout boot-openapi-standard-controllers-initial

Final example

/$ git clone https://github.com/ontimize/ontimize-examples
/ontimize-examples$ cd ontimize-examples
/ontimize-examples$ git checkout boot-openapi-standard-controllers

To simplify the code being written, three dots (…) may appear in some parts of the code. This indicates that there may be previous code before and after those dots.

Steps

Add the OpenAPI module

We will add a new module to our project that will contain the API definition. Also this module will generate the Java interfaces that later we need to implement in our REST controllers.

openapi_module_1.png

openapi_module_2.png

Add the dependencies to the project

In the main pom.xml we need to add the new module and the Swagger dependencies:

pom.xml

...
  <properties>
    ...
    <swagger-annotations.version>1.6.2</swagger-annotations.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      ...
      <dependency>
        <groupId>com.ontimize</groupId>
        <artifactId>projectwiki-openapi</artifactId>
        <version>1.0.0-SNAPSHOT</version>
      </dependency>
      <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-annotations</artifactId>
        <version>${swagger-annotations.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
...
  <modules>
    <module>projectwiki-api</module>
    <module>projectwiki-openapi</module>
    <module>projectwiki-model</module>
    <module>projectwiki-ws</module>
    <module>projectwiki-boot</module>
  </modules>
...

Add the dependencies to the OpenAPI module

In the pom.xml of the OpenAPI module we need to add the following dependencies:

pom.xml

...
  <dependencies>
    ...
    <dependency>
      <groupId>com.ontimize.jee</groupId>
      <artifactId>ontimize-jee-server-rest</artifactId>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
  </dependencies>
...

Add the API definition

Following the OpenAPI specification we will create the definition for the test controller (More information in this link):

openapi-rest.yml

openapi: 3.0.3
info:
  title: projectwiki API
  version: '1.0'
  description: The projectwiki API
servers:
  - url: '/'
    description: Localhost

paths:
  /test/test:
    get:
      operationId: testRest
      tags:
        - Test
      summary: Test
      description: >
        Develop purposes only.
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                type: string
        '400':
          description: Bad request
        '401':
          description: Not found
        '404':
          description: Unauthorized
        default:
          description: Unexpected error
    post:
      operationId: postTest
      tags:
        - Test
      summary: POST Test
      description: >
        Develop purposes only.
      x-codegen-request-body-name: person
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Person'
            examples:
              Laura Bugle:
                value:
                  name: 'Laura'
                  surname: 'Bugle'
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                type: string
        '400':
          description: Bad request
        '401':
          description: Not found
        '404':
          description: Unauthorized
        default:
          description: Unexpected error

components:
  schemas:
    Person:
      type: object
      properties:
        name:
          description: Name
          type: string
        surname:
          description: Surname
          type: string

  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic

security:
  - BasicAuth: []

Configure the OpenAPI system

This functionality can be enabled by creating the marker file marker-ws-ontimize-openapi-generator in the src/main/ontimize folder on the OpenAPI module. This file should only exist and be empty.

To comply with the project structure, we will generate the interfaces and the models in the package com.ontimize.projectwiki.openapi.core. Also, we will enable the bean validation functionality.

We can configure these functionalities adding the following properties it in the pom.xml file of the OpenAPI module (More information in this link).

pom.xml

...
  <properties>
    ...
    <ontimize.openapi.generator.maven.plugin.packageName>com.ontimize.projectwiki.openapi.core.service</ontimize.openapi.generator.maven.plugin.packageName>
    <ontimize.openapi.generator.maven.plugin.modelPackageName>com.ontimize.projectwiki.openapi.core.dto</ontimize.openapi.generator.maven.plugin.modelPackageName>
    <ontimize.openapi.generator.maven.plugin.useBeanValidation>true</ontimize.openapi.generator.maven.plugin.useBeanValidation>
  </properties>
...

Generate the interfaces

Now, we need to build the project to generate the interfaces and models, these interfaces will be placed in the target folder of the OpenAPI module.

ITestApi.java

package com.ontimize.projectwiki.openapi.core.service;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.*;

@Validated
public interface ITestApi {
	/**
	* POST Test
	* Develop purposes only.
	* @param person (required)
	* @return String
	*
	*/
	@RequestMapping(path = "/test/test", method = RequestMethod.POST,
		consumes = { "application/json" },
		produces = { "text/plain" })
	public ResponseEntity<String> postTest(@Valid @RequestBody com.ontimize.projectwiki.openapi.core.dto.Person person);

	/**
	* Test
	* Develop purposes only.
	* @return String
	*
	*/
	@RequestMapping(path = "/test/test", method = RequestMethod.GET,
		produces = { "text/plain" })
	public ResponseEntity<String> testRest();
}

Person.java

package com.ontimize.projectwiki.openapi.core.dto;

import java.util.Objects;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.Valid;
import javax.validation.constraints.*;

/**
 * Person
 */
@javax.annotation.Generated(value = "com.ontimize.openapi.ServerCodegen", date = "2024-04-09T11:09:49.679913+02:00[Europe/Madrid]")
public class Person   {
  private String name;

  private String surname;

  public Person name(String name) {
    this.name = name;
    return this;
  }

  /**
   * Name
   * @return name
  */
  @ApiModelProperty(value = "Name")


  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Person surname(String surname) {
    this.surname = surname;
    return this;
  }

  /**
   * Surname
   * @return surname
  */
  @ApiModelProperty(value = "Surname")


  public String getSurname() {
    return surname;
  }

  public void setSurname(String surname) {
    this.surname = surname;
  }


  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Person person = (Person) o;
    return Objects.equals(this.name, person.name) &&
        Objects.equals(this.surname, person.surname);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name, surname);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Person {\n");

    sb.append("    name: ").append(toIndentedString(name)).append("\n");
    sb.append("    surname: ").append(toIndentedString(surname)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}

Implements the generated interfaces on the web services module

Once the interfaces have been generated, we need to add the OpenAPI module dependency to the web services module and implement the interface on the REST controller.

The request mapping annotations are declared on the generated interface and should not be declared again on the controller.

pom.xml

...
  <dependencies>
    <dependency>
      <groupId>com.ontimize</groupId>
      <artifactId>projectwiki-openapi</artifactId>
    </dependency>
    ...
  <dependencies>
...

TestRestController.java

package com.ontimize.projectwiki.ws.core.rest;

import com.ontimize.projectwiki.openapi.core.dto.Person;
import com.ontimize.projectwiki.openapi.core.service.ITestApi;
import org.springframework.http.HttpStatus;
//import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
//@RequestMapping("/test")
public class TestRestController implements ITestApi {

	@Override
	public ResponseEntity<String> postTest(Person person) {
		return new ResponseEntity<>(String.format("Hi %s!", person.getName()), HttpStatus.OK);
	}

	//@RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
	@Override
	public ResponseEntity<String> testRest() {
		return new ResponseEntity<>("It Works!", HttpStatus.OK);
	}

}

Testing

To test this functionality we will use the Swagger tool provided by Ontimize.

Enable the Swagger User Interface

To enable the Swagger UI we will add the following property it in the pom.xml file of the OpenAPI module (More information in this link).

pom.xml

...
  <properties>
    ...
    <ontimize.openapi.swagger-ui.enabled>true</ontimize.openapi.swagger-ui.enabled>
  </properties>
...

Redirect to the Swagger User Interface (Optional)

Now, we will add a custom index.html that will redirect to the Swagger UI when we open the application in the browser.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>projectwiki</title>
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="application/javascript">
        location.href = location.origin + "/swagger-ui/index.html";
     </script>
  </head>
  <body>

  </body>
</html>

Allow to access to the API specification and to the Swagger User Interface

The API specification and the Swagger UI must be accesible withouth authentication. To do this, we must provide them as static resources and exclude them from the list of protected paths.

application.yml

...
ontimize:
   ...
   security:
      ...
      ignore-paths: '/index.html,/restapi/**,/swagger-ui/**'
      ...
spring:
   ...
   resources:
      ...
      static-locations: classpath:/public/
...

Once the changes has been applied, we must built and run the project, then we can access to the application by opening a web browser and navigating to http://localhost:33333):

openapi_standard_rest_swagger_1.png

We must open the authorization dialog by clicking on the Authorize button, provide the credentials and click on the Authorize button.

openapi_standard_rest_swagger_2.png

Now, we must close the authorization dialog, expand the POST section, click on the Try it out and the Execute buttons.

openapi_standard_rest_swagger_3.png

The Swagger UI will make the request to the API and will show the response message.

openapi_ontimize_rest_swagger_4.png