How to disable OCC endpoints and hide them in Swagger

SAP Commerce automatically creates Swagger documentation for all endpoints, including both OOTB and custom ones, as well as those from OOTB extensions like “b2bocc”. The extensive number of OOTB endpoints can lead to a crowded Swagger interface, making it hard to navigate and find particular endpoint.

Problem Description

To improve navigation in Swagger there is a sense to disable unused endpoints and also disable endpoints itself in OCC, so no one can hit it and use undocumented functionality. Unfortunately, there is no OOTB way to remove endpoints from Swagger documentation.

The first idea would be to write a BeanFactoryPostProcessor to stop the creation of Controller class beans, what would disable endpoints in both OCC and Swagger. While this approach worked, it only allowed disabling the entire Controller, not specific endpoints.

The next idea would be to create a custom RequestMappingHandlerMapping. However, the OCC layer already has a custom CommerceHandlerMapping implementation and does not provide an easy way to replace it. The next logical step would be to use HandlerInterceptor, what leads to the discovery of an existing OOTB BaseEndpointRestrictionsInterceptor implementation of HandlerInterceptor.

That OOTB implementation allows to disable OCC endpoints via properties. However, the disabled endpoints are still visible in Swagger.

Technical Solution

To solve the problem of unnecessary endpoints in Swagger, two steps need to be taken:

  1. Disable OCC Endpoints by Operation ID utilizing OOTB implementation
  2. Remove Endpoints from Swagger with custom implementation of OpenApiCustomiser, which filters out endpoints based on OOTB implementation of OCC endpoints disabling

Implementation Details

Disable OCC Endpoints

The main idea of OOTB implementation is to use properties *.api.restrictions.disabled.endpoints with a list of Operation IDs for disabling (API Endpoint Deactivation documentation).

1
commercewebservices.api.restrictions.disabled.endpoints = getCurrentOrgCart,doAddOrgCartEntries

The first part of property commercewebservices is dynamic part and should match the name of the web extension, in which endpoint is defined. However, in modern OCC implementations, all endpoints are combined under the commercewebservices web extension.

In case of usage of a deprecated OCC web services implementation, the prefix should be replaced with corresponding OCC web service extension.

The disabling itself occurs at the request handling level in Spring MVC, that’s why such endpoints are still present in Swagger.

Remove Endpoints from Swagger Documentation

The UnusedEndpointsFilterOpenApiCustomiser class is designed to post process OpenAPI documentation and remove specified endpoints from documentation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.blog.occ.swagger;

import de.hybris.platform.webservicescommons.api.restrictions.strategies.EndpointRestrictionsConfigStrategy;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.tags.Tag;
import org.springdoc.core.customizers.OpenApiCustomiser;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class UnusedEndpointsFilterOpenApiCustomiser implements OpenApiCustomiser {

    private final static List<String> WEB_EXTENSIONS_PREFIXES = List.of("commercewebservices");

    @Resource
    private EndpointRestrictionsConfigStrategy endpointRestrictionsConfigStrategy;

    @Override
    public void customise(OpenAPI openApi) {
        removeDisabledEndpointsFromDocumentation(openApi);
        cleanUpEmptyTags(openApi);
    }

    private void removeDisabledEndpointsFromDocumentation(OpenAPI openApi) {
        Paths paths = openApi.getPaths();

        Set<String> pathsToRemove = new HashSet<>();
        for (Map.Entry<String, PathItem> entry : paths.entrySet()) {
            String pathKey = entry.getKey();
            PathItem pathItem = entry.getValue();

            for (Operation operation : pathItem.readOperations()) {
                boolean endpointDisabled = isEndpointDisabled(operation);
                if (endpointDisabled) {
                    pathsToRemove.add(pathKey);
                }
            }
        }

        pathsToRemove.forEach(paths::remove);
    }

    private boolean isEndpointDisabled(Operation operation) {
        for (String webExtensionsPrefix : WEB_EXTENSIONS_PREFIXES) {
            boolean endpointDisabled = endpointRestrictionsConfigStrategy.isEndpointDisabled(webExtensionsPrefix, operation.getOperationId());
            if (endpointDisabled) {
                return true;
            }
        }
        return false;
    }

    private static void cleanUpEmptyTags(OpenAPI openApi) {
        openApi.setTags(findNonEmptyTags(openApi));
    }

    private static List<Tag> findNonEmptyTags(OpenAPI openApi) {
        Set<String> tagNamesWithOperations = openApi.getPaths().values().stream()
                .flatMap(pathItem -> pathItem.readOperationsMap().values().stream())
                .filter(operation -> operation.getTags() != null)
                .flatMap(operation -> operation.getTags().stream())
                .collect(Collectors.toSet());

        List<Tag> currentTags = openApi.getTags();
        return currentTags.stream()
                .filter(tag -> tagNamesWithOperations.contains(tag.getName()))
                .collect(Collectors.toList());
    }

}

To manage disabled endpoints, the class uses the WEB_EXTENSIONS_PREFIXES constant, which lists prefixes for different web extensions. It relies on the OOTB endpointRestrictionsConfigStrategy#isEndpointDisabled method to determine if specific Operation ID is disabled for each of web extension prefix. In case of usage of a deprecated OCC web services implementation, list should be adjusted to include all relevant web extensions.

List could be extended with not existing web extension, which would allow to create custom properties to remove endpoints from Swagger only.

After removing endpoints there could be left empty tags in Swagger(collapsible headers), which should be also removed. This is done by resetting tags with openApi.setTags and findNonEmptyTags method, which identifies and retains tags that has at least one operation assigned.

comments powered by Disqus