RegistrationServiceImpl.java

/*
 * Copyright 2005-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openwms.wms.registration.impl;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.ameba.annotation.Measured;
import org.ameba.annotation.TxService;
import org.openwms.core.listener.RemovalNotAllowedException;
import org.openwms.wms.registration.RegistrationService;
import org.openwms.wms.registration.events.DeletionFailedEvent;
import org.openwms.wms.registration.events.EntityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static java.lang.String.format;

/**
 * A RegistrationServiceImpl.
 *
 * @author Heiko Scherrer
 */
@TxService
class RegistrationServiceImpl implements RegistrationService {

    private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationServiceImpl.class);
    private final ApplicationEventPublisher eventPublisher;
    private final RestTemplate aLoadBalanced;
    private final ReplicaRegistryRepository repository;

    RegistrationServiceImpl(ApplicationEventPublisher eventPublisher, RestTemplate aLoadBalanced, ReplicaRegistryRepository repository) {
        this.eventPublisher = eventPublisher;
        this.aLoadBalanced = aLoadBalanced;
        this.repository = repository;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Measured
    public boolean registrarsExist(@NotNull EntityType entityName) {
        return repository.existActiveOnes(entityName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Measured
    public void remove(@NotNull EntityType entityName, @NotBlank String pKey) {
        var registeredServices = repository.findActiveOnes(entityName);
        if (registeredServices.isEmpty()) {
            LOGGER.info("No registrars for entity [{}] registered, so removal is allowed", entityName);
            return;
        }
        try {
            // first ask all services and call the requestRemovalEndpoint
            // might throw RemovalNotAllowedException and exit
            for (var srv : registeredServices) {
                askForRemoval(srv, List.of(pKey));
            }

            // if all are fine then call the removeEndpoint to mark the entity as deleted and not be visible in the foreign service
            for (var srv : registeredServices) {
                remove(srv, List.of(pKey));
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            // if any failure occurs, send a persistent async message to release the deletion for everyone
            eventPublisher.publishEvent(new DeletionFailedEvent(pKey, "product.event.deletion-rollback", EntityType.PRODUCT));
            throw new RemovalNotAllowedException(e.getMessage());
        }
    }

    private void askForRemoval(ReplicaRegistry srv, Collection<String> pKeys) {
        var headers = new HttpHeaders();
        boolean result;
        var endpoint = "http://" + srv.getApplicationName() + srv.getRequestRemovalEndpoint();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Request for removal to the service with API [{}]", endpoint);
        }
        try {
            var response = aLoadBalanced.exchange(
                    endpoint,
                    HttpMethod.POST,
                    new HttpEntity<List<String>>(new ArrayList<>(pKeys), headers),
                    Boolean.class
            );
            result = response.getBody() != null && response.getBody();
            if (result) {
                LOGGER.info("Service [{}] allows to remove all entities", srv.getApplicationName());
            } else {
                LOGGER.info("Service [{}] does not allow to remove entities", srv.getApplicationName());
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            throw new RemovalNotAllowedException("Exception. Removal of entities is declined by service [%s]".formatted(srv.getApplicationName()));
        }
        if (!result) {
            throw new RemovalNotAllowedException("Removal of entities has been declined by service [%s]".formatted(srv.getApplicationName()));
        }
    }

    private void remove(ReplicaRegistry srv, Collection<String> pKeys) {
        var headers = new HttpHeaders();
        var endpoint = "http://" + srv.getApplicationName() + srv.getRemovalEndpoint();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Ask for removal the service with API [{}]", endpoint);
        }
        try {
            aLoadBalanced.exchange(
                    endpoint,
                    HttpMethod.DELETE,
                    new HttpEntity<List<String>>(new ArrayList<>(pKeys), headers),
                    Void.class
            );
            LOGGER.info("Service [{}] removed entities", srv.getApplicationName());
        } catch (Exception e) {
            throw new RemovalNotAllowedException(format("Exception. Removal of entities is declined by service [%s]", srv.getApplicationName()));
        }
    }
}