LocationSynchronizerImpl.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.location.impl;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.ameba.annotation.Measured;
import org.ameba.annotation.TxService;
import org.ameba.exception.NotFoundException;
import org.ameba.i18n.Translator;
import org.openwms.common.location.api.events.LocationEvent;
import org.openwms.common.location.api.messages.LocationMO;
import org.openwms.wms.location.Location;
import org.openwms.wms.location.LocationPK;
import org.openwms.wms.location.LocationSynchronizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.validation.annotation.Validated;

import static org.openwms.common.location.api.events.LocationEvent.LocationEventType.CREATED;
import static org.openwms.wms.InventoryMessageCodes.LOCATION_NOT_FOUND_BY_FOREIGN_PKEY;

/**
 * A LocationSynchronizerImpl is a Spring managed transaction service that is responsible to synchronize the state of {@link Location}s with
 * the golden source.
 *
 * @author Heiko Scherrer
 */
@Validated
@TxService
class LocationSynchronizerImpl implements LocationSynchronizer {

    private static final Logger LOGGER = LoggerFactory.getLogger(LocationSynchronizerImpl.class);
    private final Translator translator;
    private final LocationMapper mapper;
    private final LocationRepository repository;
    private final ApplicationEventPublisher eventPublisher;

    LocationSynchronizerImpl(Translator translator, LocationMapper mapper, LocationRepository repository, ApplicationEventPublisher eventPublisher) {
        this.translator = translator;
        this.mapper = mapper;
        this.repository = repository;
        this.eventPublisher = eventPublisher;
    }

    /**
     * {@inheritDoc}
     */
    @Measured
    @Override
    public void create(@NotNull LocationMO location) {
        var existing = repository.findByForeignPKey(location.pKey());
        if (existing.isPresent()) {
            LOGGER.info("Location with pKey [{}] already exists and cannot be created", location.pKey());
            return;
        }
        existing = repository.findByLocationId(LocationPK.fromString(location.id()));
        if (existing.isPresent()) {
            LOGGER.info("Location with id [{}] already exists and cannot be created", location.id());
            return;
        }
        var newOne = mapper.convertFromMO(location);
        newOne = repository.save(newOne);
        eventPublisher.publishEvent(LocationEvent.of(newOne, CREATED));
    }

    /**
     * {@inheritDoc}
     */
    @Measured
    @Override
    public void changeState(@NotBlank String foreignPKey, Boolean incomingActive, Boolean outgoingActive, Integer plcState) {
        var location = findByForeignPKeyInternal(foreignPKey);
        if (incomingActive != null) {
            location.setIncomingActive(incomingActive);
        }
        if (outgoingActive != null) {
            location.setOutgoingActive(outgoingActive);
        }
        if (plcState != null) {
            location.setPlcState(plcState);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Measured
    @Override
    public void delete(@NotBlank String foreignPKey) {
        // Only allow to delete those who where previously marked for deletion
        var locationOpt = repository.findByForeignPKeyAndDeleted(foreignPKey, true);
        if (locationOpt.isEmpty()) {
            LOGGER.warn("Location to delete does not exist, foreignPKey [{}]", foreignPKey);
            return;
        }
        repository.delete(locationOpt.get());
        LOGGER.info("Deleted Location with foreignPKey [{}]", foreignPKey);
    }

    /**
     * {@inheritDoc}
     */
    @Measured
    @Override
    public void rollbackDeletionMark(@NotBlank String foreignPKey) {
        var locationOpt = repository.findByForeignPKeyAndDeleted(foreignPKey, true);
        if (locationOpt.isEmpty()) {
            LOGGER.warn("Location with foreignPKey [{}] does either not exist or is not marked for deletion", foreignPKey);
            return;
        }
        locationOpt.get().setMarkForDeletion(false);
        repository.save(locationOpt.get());
    }

    private Location findByForeignPKeyInternal(String foreignPKey) {
        return repository.findByForeignPKey(foreignPKey).orElseThrow(() ->
                new NotFoundException(translator, LOCATION_NOT_FOUND_BY_FOREIGN_PKEY, new String[]{foreignPKey}, foreignPKey));
    }
}