Validation of Properties using Catalogs

An example Webhook consumer service set up using Java and Spring Boot for the validation of object properties using catalogs.

Table of Contents

Introduction

As of 2021 Winter.


For some properties of your DMS objects managed by yuuvis® Momentum, you might want to allow only certain string values that are defined in a catalog. One possible way to implement such a custom validation is the usage of a webhook dms.request.objects.upsert.database-before that is calling the CATALOG Service. In this tutorial, you will be guided through the set up of such an example webhook consumer service. It verifies that the value of a certain string property is contained in a specified catalog. Thus, this webhook service will provide an extra validation layer for object import and/or update.

Requirements

In this tutorial, we'll create a Spring Boot Service using Java, meaning the requirements for the project are derived from Spring Boot. Thus, a JDK version 1.8 or later and Maven 3.2 or later are required. 

Furthermore, a running instance of the CATALOG Service is required, as well as a global catalog against which the example webhook checks the property values.

The object property which should be validated, must be defined in the global schema that is available in all tenants.

Setting up the Webhook Service

To implement the processing of incoming metadata using our example webhook service, we need to configure a controller class with the corresponding validation endpoint. In the example code block below, we define the class ValidationWebhookRestController with the method validateObjectProperty. The URL defined in the RequestMapping annotation of the controller class and the PostMapping annotation of the endpoint method will be referenced in the system hook configuration of our yuuvis® Momentum system as shown later in this article. We will also define the method propertyValidationService.validateObject for working with the incoming metadata, which is called within our endpoint method.

Controller Class with Endpoint for Webhook Consumption
@RestController
@RequestMapping("/api/dms/request")
public class ValidationWebhookRestController
{
    @Autowired
    private PropertyValidationService propertyValidationService;

    @PostMapping(value = "/upsert", produces = {"application/json"})
    public Map<String, Object> validateObjectProperty (@RequestBody Map<String, Object> dmsApiObjectList,
                                                	   @RequestHeader(value = "Authorization", required = true) String authorization) throws HookException {
        this.propertyValidationService.validateObject(dmsApiObjectList, authorization);
        return dmsApiObjectList;
    }
}

Creating the Validation Service for Incoming Metadata

This is the heart of our example webhook consumer service. Create the class propertyValidationService and annotate it as @Service. This indicates that it holds the business logic and will communicate with the REPOSITORY service.

Within the class, we firstly specify the object property we want to validate. Secondly, we specify the name of the catalog against which we want to validate.

Set PropertyId and Name of the Catalog
final String propertyId = "[Name of property]";
final String catalogueName = "[Name of catalog]";

Now we implement our method validateObject for metadata handling. Here, we iterate through the imported or updated object and search for the requested property by using a method from the utility class PropertyUtils. If the property is found, it is send to the private validation method validateProperty together with the authentication token.

Method for handling incomming Metadata
public void validateObject(Map<String, Object> dmsApiObjectList, String authorization) throws HookException
    {
        String propertyValue = null;

        //Get object properties and send for validation
        List<Map<String, Object>> newObjectList = (List<Map<String, Object>>) dmsApiObjectList.get("objects");

        for (Map<String, Object> newObject : newObjectList) {
            propertyValue = (String) PropertyUtils.getPropertyValue(newObject, propertyId);
            validateProperty(propertyValue, authorization);
        }
    }

The utility class PropertyUtils providing the above referenced method getPropertyValues we implement as follows:

PropertyUtils class for iterating through a objects' Metadata
public static Object getPropertyValue(Map<String, Object> object, String propertyId)
    {
        @SuppressWarnings("unchecked")
        Map<String, Object> properties = (Map<String, Object>)object.get("properties");

        return getValue(properties, propertyId);
    }

private static Object getValue(Map<String, Object> properties, String propertyId)
    {
        @SuppressWarnings("unchecked")
        Map<String, Object> property = (Map<String, Object>)properties.get(propertyId);
        if(property != null)
        {
            return property.get("value");
        }
        return null;
    }

The validateProperty method shown in the next code block is a private method within our example service. It ensures that the string given as propertyValue is not empty or null and then proceeds to create and send a HEAD request for validating the property using the given catalog name. Each successful response is logged and the import or update of the object is finished. In case of a failed validation, the method throws a HookException as defined below.

Method for validating a Property
private void validateProperty (String propertyValue, String authorization) throws HookException {
        //Validate property with catalogue entry
        if (propertyValue != null) {
            if (!propertyValue.isBlank()) {
                String url = "http://catalog/api/catalogs/" + catalogueName + "/" + propertyValue;

                HttpHeaders headers = new HttpHeaders();
                headers.set("Authorization", authorization);

                HttpEntity requestEntity = new HttpEntity<>(null, headers);

                try {
                    ResponseEntity<Void> response =
                            this.restTemplate.exchange(url, HttpMethod.HEAD, requestEntity, Void.class);

                    if (response.getStatusCode().is2xxSuccessful()) {
                        LOGGER.debug("Property [" + propertyValue + "] validation was successful");
                    } else {
                        throw new HookException("Catalogue [" + catalogueName + "] does not exist or property [" + propertyValue + "] does not match any catalogue entry.");
                    }
                } catch (HttpStatusCodeException e) {
                    if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
                        throw new HookException("Catalogue [" + catalogueName + "] does not exist or property [" + propertyValue + "] does not match any catalogue entry.");
                    } else {
                        //Error handling
                        throw new HookException("Something went wrong.");
                    }
                }
            } else {
                throw new HookException("The property [" + propertyId + "] is empty.");
            }
        } else {
            LOGGER.debug("This object does not have the property: " + propertyId);
        }
}

The following code block shows the definition of the HookException realized in the custom class HookException.

HookException
public class HookException extends Exception
{
    private static final long serialVersionUID = 1L;

    public HookException(String message)
    {
        super(message);
    }

    public HookException(String string, Throwable exception)
    {
        super(string,exception);
    }
}

Webhook System Configuration

In the systemHookConfiguration.json file, add the following configuration (SpEL) and save it to the configuration server. In this configuration, we specify the type of our validation webhook dms.request.objects.database-before and its URL http://validationWebhook/api/dms/request/upsert. We also specify the predicate for triggering of the webhook. In this example, we want our webhook to be activated if an object is imported or updated that has the property "Name" assigned.

Webhook Configuration
{
	"enable": true,
	"predicate": "spel:properties['Name']!=null && (properties['Name']['value'])!=null",
	"type": "dms.request.objects.upsert.database-before",
	"url": "http://validationWebhook/api/dms/request/upsert",
	"useDiscovery": true
}

Summary

In this tutorial we went through the steps of creating and configuring a webhook service for validating object properties using a catalog. Find the complete code project described in this tutorial in this GitHub Repository.
>> GitHub

Read on

CATALOG Service

The service provides the usage of catalogs defined as document object types in a tenant schema. Keep reading

systemHookConfiguration.json

Configuration of system hooks, split in AMQP hooks and webhooks. Keep reading

Webhooks

A system hook that extends a function call by an HTTP call under defined conditions. Keep reading