OpenAPI System using Ontimize REST controllers

Introduction

Ontimize OpenAPI Generator allows you to generate, from the interface files, the API code for Ontimize 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-ontimize-controllers-initial

Final example

/$ git clone https://github.com/ontimize/ontimize-examples
/ontimize-examples$ cd ontimize-examples
/ontimize-examples$ git checkout boot-openapi-ontimize-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:
  /users/user:
    post:
      tags:
        - Users
      summary: Inserts a user.
      description: >
        This resource represents a user in the system.
      x-restcontroller: orestcontroller
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InsertParameter'
            examples:
              Laura Bugle:
                value:
                  data:
                    USER_: 'laura'
                    PASSWORD: '1432'
                    NAME: 'Laura'
                    SURNAME: 'Bugle'
                    EMAIL: 'laurabugle@gmail.com'
                    NIF: '11111111H'
                    USERBLOCKED: '2016-09-19T12:00:00Z'
                    LASTPASSWORDUPDATE: '2021-06-01T12:00:00'
                    FIRSTLOGIN: true
                  sqltypes:
                    USER_: 12
                    PASSWORD: 12
                    NAME: 12
                    SURNAME: 12
                    EMAIL: 12
                    NIF: 12
                    USERBLOCKED: 93
                    LASTPASSWORDUPDATE: 93
                    FIRSTLOGIN: 16
      responses:
        '200':
          $ref: '#/components/responses/EntityResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Unexpected'

    get:
      tags:
        - Users
      summary: Returns a list of users.
      description: >
        This resource represents a list of users in the system.
      x-restcontroller: orestcontroller
      parameters:
        - in: query
          name: filter
          description: Filter
          schema:
            type: string
          required: false
          examples:
            user_:
              value: 'USER_=laura'
            password:
              value: 'PASSWORD=1432'
            name:
              value: 'NAME=Laura'
            surname:
              value: 'SURNAME=Bugle'
            email:
              value: 'EMAIL=laurabugle@gmail.com'
            nif:
              value: 'NIF=11111111H'
        - in: query
          name: columns
          description: Columns
          required: false
          schema:
            type: string
            example: USER_,PASSWORD,NAME,SURNAME,EMAIL,NIF,USERBLOCKED,LASTPASSWORDUPDATE,FIRSTLOGIN
      responses:
        '200':
          $ref: '#/components/responses/EntityResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Unexpected'

    put:
      tags:
        - Users
      summary: Updates a user.
      description: >
        This resource represents a user in the system.
      x-restcontroller: orestcontroller
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateParameter'
            examples:
              pasword:
                value:
                  data:
                    PASSWORD: 'mT765HkqjY_34:76l'
                  filter:
                    USER_: 'laura'
                  sqltypes:
                    USER_: 12
              email:
                value:
                  data:
                    EMAIL: 'laura.bugle@gmail.com'
                  filter:
                    USER_: 'laura'
                  sqltypes:
                    USER_: 12
      responses:
        '200':
          $ref: '#/components/responses/EntityResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Unexpected'

    delete:
      tags:
        - Users
      summary: Deletes a user.
      description: >
        This resource represents a user in the system.
      x-restcontroller: orestcontroller
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteParameter'
            examples:
              userid:
                value:
                  filter:
                    USER_: 'laura'
                  sqltypes:
                    USER_: 12
      responses:
        '200':
          $ref: '#/components/responses/EntityResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Unexpected'

  /users/user/search:
    post:
      tags:
        - Users
      summary: Searches and returns a list of users.
      description: >
        This resource represents a list of users in the system.
      x-restcontroller: orestcontroller
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QueryParameter'
            examples:
              surname:
                value:
                  filter:
                    SURNAME: 'Bugle'
                  columns:
                    - USER_
                    - PASSWORD
                    - NAME
                    - SURNAME
                    - EMAIL
                    - NIF
                    - USERBLOCKED
                    - LASTPASSWORDUPDATE
                    - FIRSTLOGIN
                  sqltypes:
                    SURNAME: 12
      responses:
        '200':
          $ref: '#/components/responses/EntityResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Unexpected'

  /users/login:
    post:
      operationId: login
      tags:
        - Users
      summary: Validates the current session.
      description: >
        This resource represents a session in the system.
      x-hasparentpath: true
      responses:
        '200':
          $ref: '#/components/responses/EntityResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Unexpected'

components:
  responses:
    EntityResult:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/EntityResult'
    BadRequest:
      description: Bad request
    NotFound:
      description: Not found
    Unauthorized:
      description: Unauthorized
    Unexpected:
      description: Unexpected error

  schemas:
    DeleteParameter:
      type: object
      properties:
        filter:
          description: Filter
          type: object
          additionalProperties:
            type: string
        sqltypes:
          description: SQL types
          type: object
          additionalProperties:
            type: string
    EntityResult:
      type: object
      properties:
        type:
          description: Type
          type: integer
        code:
          description: Code
          type: integer
        message:
          description: Message
          type: string
        details:
          description: Details
          type: string
        messageParameter:
          description: Message parameter
          type: array
          items:
            type: object
        compressionLevel:
          description: Compression level
          type: integer
        compressionThreshold:
          description: Compression threshold
          type: integer
        columnsSQLTypes:
          description: Columns SQL types
          type: object
          additionalProperties:
            type: integer
        operationId:
          description: Operation Id
          type: string
    InsertParameter:
      type: object
      properties:
        data:
          description: Data
          type: object
          additionalProperties:
            type: string
        sqltypes:
          description: SQL types
          type: object
          additionalProperties:
            type: string
    QueryParameter:
      type: object
      properties:
        filter:
          description: Filter
          type: object
          additionalProperties:
            type: string
        columns:
          description: Columns
          type: array
          items:
            type: string
        sqltypes:
          description: SQL types
          type: object
          additionalProperties:
            type: string
    UpdateParameter:
      type: object
      properties:
        data:
          description: Data
          type: object
          additionalProperties:
            type: string
        filter:
          description: Filter
          type: object
          additionalProperties:
            type: string
        sqltypes:
          description: SQL types
          type: object
          additionalProperties:
            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.

IUsersApi.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 IUsersApi {
	/**
	* Deletes a user.
	* This resource represents a user in the system.
	* @param name (required)
	* @param deleteParameter (required)
	* @return com.ontimize.jee.common.dto.EntityResult
	*
	* This interface must be implemented on orestcontroller based classes.
	* Path: /users/user
	*/

	public ResponseEntity<com.ontimize.jee.common.dto.EntityResult> delete(String name, com.ontimize.jee.server.rest.DeleteParameter deleteParameter);

	/**
	* Inserts a user.
	* This resource represents a user in the system.
	* @param name (required)
	* @param insertParameter (required)
	* @return com.ontimize.jee.common.dto.EntityResult
	*
	* This interface must be implemented on orestcontroller based classes.
	* Path: /users/user
	*/

	public ResponseEntity<com.ontimize.jee.common.dto.EntityResult> insert(String name, com.ontimize.jee.server.rest.InsertParameter insertParameter);

	/**
	* Validates the current session.
	* This resource represents a session in the system.
	* @return com.ontimize.jee.common.dto.EntityResult
	*
	*/
	@RequestMapping(path = "/login", method = RequestMethod.POST,
		produces = { "application/json" })
	public ResponseEntity<com.ontimize.jee.common.dto.EntityResult> login();

	/**
	* Returns a list of users.
	* This resource represents a list of users in the system.
	* @param name (required)
	* @param filter Filter (optional)
	* @param columns Columns (optional)
	* @return com.ontimize.jee.common.dto.EntityResult
	*
	* This interface must be implemented on orestcontroller based classes.
	* Path: /users/user
	*/

	public ResponseEntity<com.ontimize.jee.common.dto.EntityResult> query(String name, String filter, String columns);

	/**
	* Searches and returns a list of users.
	* This resource represents a list of users in the system.
	* @param name (required)
	* @param queryParameter (optional)
	* @return com.ontimize.jee.common.dto.EntityResult
	*
	* This interface must be implemented on orestcontroller based classes.
	* Path: /users/user/search
	*/

	public ResponseEntity<com.ontimize.jee.common.dto.EntityResult> query(String name, com.ontimize.jee.server.rest.QueryParameter queryParameter) throws Exception;

	/**
	* Updates a user.
	* This resource represents a user in the system.
	* @param name (required)
	* @param updateParameter (required)
	* @return com.ontimize.jee.common.dto.EntityResult
	*
	* This interface must be implemented on orestcontroller based classes.
	* Path: /users/user
	*/

	public ResponseEntity<com.ontimize.jee.common.dto.EntityResult> update(String name, com.ontimize.jee.server.rest.UpdateParameter updateParameter);
}

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>
...

UserRestController.java

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


import com.ontimize.projectwiki.openapi.core.service.IUsersApi;
import org.springframework.beans.factory.annotation.Autowired;
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;

import com.ontimize.projectwiki.api.core.service.IUserService;
import com.ontimize.jee.common.dto.EntityResult;
import com.ontimize.jee.server.rest.ORestController;

@RestController
@RequestMapping("/users")
public class UserRestController extends ORestController<IUserService> implements IUsersApi {

	@Autowired
	private IUserService userSrv;

	@Override
	public IUserService getService() {
		return this.userSrv;
	}

	/* @RequestMapping(
		value = "/login",
		method = RequestMethod.POST,
		produces = MediaType.APPLICATION_JSON_VALUE) */
	@Override
	public ResponseEntity<EntityResult> login() {
		return new ResponseEntity<>(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_ontimize_rest_swagger_1.png

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

openapi_ontimize_rest_swagger_2.png

Now, we will insert a new user on the users table, to do this we will expand the insert section, click on the Try it out and the Execute buttons.

openapi_ontimize_rest_swagger_3.png

The Swagger UI will make the request to the API and will show that the operation was completed successfully.

openapi_ontimize_rest_swagger_4.png

After the insertion, we will request the user info, to do this we will expand the query section, click on the Try it out and the Execute buttons.

openapi_ontimize_rest_swagger_5.png

The Swagger UI will make the request to the API and will show the user info.

openapi_ontimize_rest_swagger_6.png