energy.usef.dso.workflow.validate.gridsafetyanalysis.DsoGridSafetyAnalysisCoordinator.java Source code

Java tutorial

Introduction

Here is the source code for energy.usef.dso.workflow.validate.gridsafetyanalysis.DsoGridSafetyAnalysisCoordinator.java

Source

/*
 * Copyright 2015-2016 USEF Foundation
 *
 * 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 energy.usef.dso.workflow.validate.gridsafetyanalysis;

import static energy.usef.core.constant.USEFConstants.LOG_COORDINATOR_FINISHED_HANDLING_EVENT;
import static energy.usef.core.constant.USEFConstants.LOG_COORDINATOR_START_HANDLING_EVENT;
import static energy.usef.dso.workflow.DsoWorkflowStep.DSO_CREATE_GRID_SAFETY_ANALYSIS;
import static energy.usef.dso.workflow.validate.gridsafetyanalysis.CreateGridSafetyAnalysisStepParameter.IN;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import javax.ejb.Asynchronous;
import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.enterprise.event.TransactionPhase;
import javax.inject.Inject;
import javax.transaction.Transactional;

import energy.usef.core.event.validation.EventValidationService;
import energy.usef.core.exception.BusinessValidationException;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import energy.usef.core.config.Config;
import energy.usef.core.config.ConfigParam;
import energy.usef.core.model.ConnectionGroup;
import energy.usef.core.model.DispositionAvailableRequested;
import energy.usef.core.model.PrognosisType;
import energy.usef.core.model.PtuContainer;
import energy.usef.core.model.PtuPrognosis;
import energy.usef.core.model.PtuState;
import energy.usef.core.model.RegimeType;
import energy.usef.core.service.business.CorePlanboardBusinessService;
import energy.usef.core.service.business.SequenceGeneratorService;
import energy.usef.core.util.ConcurrentUtil;
import energy.usef.core.util.DateTimeUtil;
import energy.usef.core.util.PtuUtil;
import energy.usef.core.workflow.DefaultWorkflowContext;
import energy.usef.core.workflow.WorkflowContext;
import energy.usef.core.workflow.dto.DispositionTypeDto;
import energy.usef.core.workflow.dto.PrognosisDto;
import energy.usef.core.workflow.step.WorkflowStepExecuter;
import energy.usef.core.workflow.transformer.PrognosisTransformer;
import energy.usef.core.workflow.util.WorkflowUtil;
import energy.usef.dso.config.ConfigDso;
import energy.usef.dso.config.ConfigDsoParam;
import energy.usef.dso.model.GridSafetyAnalysis;
import energy.usef.dso.service.business.DsoPlanboardBusinessService;
import energy.usef.dso.workflow.coloring.ColoringProcessEvent;
import energy.usef.dso.workflow.dto.GridSafetyAnalysisDto;
import energy.usef.dso.workflow.dto.NonAggregatorForecastDto;
import energy.usef.dso.workflow.dto.PtuGridSafetyAnalysisDto;
import energy.usef.dso.workflow.transformer.NonAggregatorForecastDtoTransformer;
import energy.usef.dso.workflow.validate.create.flexrequest.CreateFlexRequestEvent;

/**
 * Grid Safety Analysis workflow coordinator.
 * <p>
 * This is not a singleton, because this process should be able to run per congestionPoint.
 */
@Stateless
@Transactional(Transactional.TxType.REQUIRES_NEW)
public class DsoGridSafetyAnalysisCoordinator {
    private static final Logger LOGGER = LoggerFactory.getLogger(DsoGridSafetyAnalysisCoordinator.class);

    @Inject
    private WorkflowStepExecuter workflowStepExecuter;

    @Inject
    private CorePlanboardBusinessService corePlanboardBusinessService;

    @Inject
    private DsoPlanboardBusinessService dsoPlanboardBusinessService;

    @Inject
    private Config config;

    @Inject
    private ConfigDso configDso;

    @Inject
    private ConcurrentUtil concurrentUtil;

    @Inject
    private SequenceGeneratorService sequenceGeneratorService;

    @Inject
    private Event<StoreGridSafetyAnalysisEvent> storeGridSafetyEventManager;
    @Inject
    private Event<CreateFlexRequestEvent> flexRequestEventManager;

    @Inject
    private Event<ColoringProcessEvent> coloringEventManager;

    @Inject
    private EventValidationService eventValidationService;

    /**
     * This method handles the GridSafetyAnalysisEvent. This Event creates GridSafetyAnalysis / CongestionPoint.
     *
     * @param event The {@link GridSafetyAnalysisEvent} that triggers the process.
     */

    @Asynchronous
    public void startGridSafetyAnalysis(
            @Observes(during = TransactionPhase.AFTER_COMPLETION) GridSafetyAnalysisEvent event)
            throws BusinessValidationException {
        LOGGER.info(LOG_COORDINATOR_START_HANDLING_EVENT, event);
        eventValidationService.validateEventPeriodTodayOrInFuture(event);

        String entityAddress = event.getCongestionPointEntityAddress();
        LocalDate period = event.getPeriod();

        // PrognosisDto List, one for each participant
        List<PtuPrognosis> prognosisList = findLastPrognoses(period, entityAddress);
        WorkflowContext inContext = prepareInContext(event, prognosisList);
        Long timeout = configDso.getLongProperty(ConfigDsoParam.DSO_GRID_SAFETY_ANALYSIS_EXPIRATION_IN_MINUTES);

        CompletableFuture<WorkflowContext> completableFuture = CompletableFuture
                .supplyAsync(() -> callPluggableBusinessComponent(inContext));

        if (timeout != null && timeout > 0) {
            completableFuture = concurrentUtil.within(completableFuture, Duration.ofMinutes(timeout),
                    String.format("Grid Safety Analysis for %s on %s timed out after ", entityAddress,
                            period.toString("yyyy-MM-dd")));
        }

        completableFuture.whenCompleteAsync((result, throwable) -> {
            LOGGER.info("Processing Grid Gafety Analysis for {}",
                    result.getValue(IN.CONGESTION_POINT_ENTITY_ADDRESS.name()));
            WorkflowUtil.validateContext(DSO_CREATE_GRID_SAFETY_ANALYSIS.name(), result,
                    CreateGridSafetyAnalysisStepParameter.OUT.values());
            GridSafetyAnalysisDto dto = result.get(
                    CreateGridSafetyAnalysisStepParameter.OUT.GRID_SAFETY_ANALYSIS.name(),
                    GridSafetyAnalysisDto.class);

            storeGridSafetyEventManager.fire(new StoreGridSafetyAnalysisEvent(entityAddress, period, dto));
        }).exceptionally(throwable -> {
            LOGGER.error(throwable.getMessage());
            return null;
        });

        LOGGER.info(LOG_COORDINATOR_FINISHED_HANDLING_EVENT, event);
    }

    public WorkflowContext callPluggableBusinessComponent(WorkflowContext inContext) {
        LOGGER.info("Invoke Pluggable Business Component {}", DSO_CREATE_GRID_SAFETY_ANALYSIS.name());
        return workflowStepExecuter.invoke(DSO_CREATE_GRID_SAFETY_ANALYSIS.name(), inContext);
    }

    /**
     * This method process the StoreGridSafetyAnalysisEvent. Now that gridsafety PBC is async,
     * this process is completely seperate from the initiation of the GSA.
     *
     * @param event
     */
    @Asynchronous
    public void saveAndProcessGridSafetyAnalysis(@Observes StoreGridSafetyAnalysisEvent event)
            throws BusinessValidationException {
        eventValidationService.validateEventPeriodTodayOrInFuture(event);
        LocalDate period = event.getPeriod();
        String entityAddress = event.getCongestionPointEntityAddress();
        List<PtuPrognosis> lastPrognosisList = findLastPrognoses(period, entityAddress);

        LOGGER.debug("Storing Grid Safety Analysis for {} on {}", entityAddress, period);

        boolean aggregatorsAvailable = 0 < dsoPlanboardBusinessService
                .countActiveAggregatorsForCongestionPointOnDay(entityAddress, period);
        LOGGER.debug("Aggregators available: {} ", aggregatorsAvailable);

        long sequence = sequenceGeneratorService.next();

        // re-group by ptu index
        Map<Integer, List<PtuPrognosis>> prognosisByPtuIndex = lastPrognosisList.stream()
                .collect(Collectors.groupingBy(p -> p.getPtuContainer().getPtuIndex()));

        List<PtuGridSafetyAnalysisDto> flexRequestList = new ArrayList<>();

        ConnectionGroup connectionGroup = corePlanboardBusinessService.findConnectionGroup(entityAddress);
        Map<Integer, PtuContainer> ptuContainers = dsoPlanboardBusinessService.findPTUContainersForPeriod(period);

        int numberOfRowsDeleted = dsoPlanboardBusinessService.deletePreviousGridSafetyAnalysis(entityAddress,
                period);
        LOGGER.debug("Number of previous GSA records deleted {}", numberOfRowsDeleted);

        int currentPtuIndex = PtuUtil.getPtuIndex(DateTimeUtil.getCurrentDateTime(),
                config.getIntegerProperty(ConfigParam.PTU_DURATION));
        LocalDate today = DateTimeUtil.getCurrentDate();
        boolean futurePeriod = period.isAfter(today);

        int startPtu;
        if (futurePeriod) {
            startPtu = 1;
        } else {
            startPtu = currentPtuIndex;
        }

        GridSafetyAnalysisDto gridSafetyAnalysisDto = event.getGridSafetyAnalysisDto();
        for (int ptuIndex = startPtu; ptuIndex <= gridSafetyAnalysisDto.getPtus().size(); ptuIndex++) {
            PtuGridSafetyAnalysisDto ptuGridSafetyAnalysisDto = gridSafetyAnalysisDto.getPtus().get(ptuIndex - 1);
            GridSafetyAnalysis gridSafetyAnalysis;
            gridSafetyAnalysis = createGridSafetyAnalysis(sequence, prognosisByPtuIndex, connectionGroup,
                    ptuContainers, ptuGridSafetyAnalysisDto);

            dsoPlanboardBusinessService.storeGridSafetyAnalysis(gridSafetyAnalysis);

            PtuState ptuState = corePlanboardBusinessService.findOrCreatePtuState(
                    gridSafetyAnalysis.getPtuContainer(), gridSafetyAnalysis.getConnectionGroup());

            // Changing PTU regime and preparing gridSafetyAnalysis items for further processing
            if (DispositionTypeDto.REQUESTED.equals(ptuGridSafetyAnalysisDto.getDisposition())
                    && aggregatorsAvailable) {
                ptuState.setRegime(RegimeType.YELLOW);
                flexRequestList.add(ptuGridSafetyAnalysisDto);
            }
        }

        // fire next steps
        if (flexRequestList.isEmpty()) {
            // no flex requests possible, start the coloring process
            startColoringProcess(event);
        } else {
            // there are flex requests possible, send them
            sendFlexRequests(event, flexRequestList);
        }

        LOGGER.debug("Ended saving GridSafetyAnalysis");
    }

    private List<PtuPrognosis> findLastPrognoses(LocalDate period, String usefIdentifier) {
        return corePlanboardBusinessService.findLastPrognoses(period, PrognosisType.D_PROGNOSIS, usefIdentifier);
    }

    private WorkflowContext prepareInContext(GridSafetyAnalysisEvent event, List<PtuPrognosis> prognosisList) {
        WorkflowContext inContext = new DefaultWorkflowContext();
        int ptuDuration = config.getIntegerProperty(ConfigParam.PTU_DURATION);

        inContext.setValue(IN.PTU_DURATION.name(), ptuDuration);
        inContext.setValue(IN.CONGESTION_POINT_ENTITY_ADDRESS.name(), event.getCongestionPointEntityAddress());
        // Getting non aggregator connection forecast
        NonAggregatorForecastDto nonAggregatorForecastDto = createNonAggregatorForecastInputMap(event);
        inContext.setValue(IN.NON_AGGREGATOR_FORECAST.name(), nonAggregatorForecastDto);
        inContext.setValue(IN.PERIOD.name(), event.getPeriod());
        List<PrognosisDto> dPrognosisDtos = prognosisList.stream()
                .collect(Collectors.groupingBy(PtuPrognosis::getParticipantDomain)).values().stream()
                .map(PrognosisTransformer::mapToPrognosis).collect(Collectors.toList());
        inContext.setValue(IN.D_PROGNOSIS_LIST.name(), dPrognosisDtos);

        return inContext;
    }

    private NonAggregatorForecastDto createNonAggregatorForecastInputMap(GridSafetyAnalysisEvent event) {
        NonAggregatorForecastDto nonAggregatorForecastDto = NonAggregatorForecastDtoTransformer
                .transform(dsoPlanboardBusinessService
                        .findLastNonAggregatorForecasts(event.getPeriod(),
                                Optional.of(event.getCongestionPointEntityAddress()))
                        .stream().collect(Collectors.toList()));

        if (nonAggregatorForecastDto == null) {
            nonAggregatorForecastDto = new NonAggregatorForecastDto();
        }

        LOGGER.debug("Got [{}] Non Aggregator Connection Forecast", nonAggregatorForecastDto.getPtus().size());
        return nonAggregatorForecastDto;
    }

    private GridSafetyAnalysis createGridSafetyAnalysis(long sequence,
            Map<Integer, List<PtuPrognosis>> prognosisByPtuIndex, ConnectionGroup connectionGroup,
            Map<Integer, PtuContainer> ptuContainers, PtuGridSafetyAnalysisDto ptuGridSafetyAnalysisDto) {
        GridSafetyAnalysis gridSafetyAnalysis = new GridSafetyAnalysis();
        gridSafetyAnalysis.setConnectionGroup(connectionGroup);
        gridSafetyAnalysis.setSequence(sequence);
        gridSafetyAnalysis.setPtuContainer(ptuContainers.get(ptuGridSafetyAnalysisDto.getPtuIndex()));
        gridSafetyAnalysis.setPrognoses(prognosisByPtuIndex.get(ptuGridSafetyAnalysisDto.getPtuIndex()));
        gridSafetyAnalysis.setPower(ptuGridSafetyAnalysisDto.getPower());

        if (DispositionTypeDto.AVAILABLE.equals(ptuGridSafetyAnalysisDto.getDisposition())) {
            gridSafetyAnalysis.setDisposition(DispositionAvailableRequested.AVAILABLE);
        } else {
            gridSafetyAnalysis.setDisposition(DispositionAvailableRequested.REQUESTED);
        }
        return gridSafetyAnalysis;
    }

    /**
     * Start the coloring process to determine if PTU(s) become orange.
     *
     * @param event
     */
    private void startColoringProcess(StoreGridSafetyAnalysisEvent event) {
        coloringEventManager
                .fire(new ColoringProcessEvent(event.getPeriod(), event.getCongestionPointEntityAddress()));
    }

    /*
     * Call the workflow to send flex requests.
     */
    private void sendFlexRequests(StoreGridSafetyAnalysisEvent event,
            List<PtuGridSafetyAnalysisDto> flexRequestList) {
        if (flexRequestList.isEmpty()) {
            // no reason to send flex requests.
            return;
        }

        Integer[] ptuIndexes = flexRequestList.stream().map(PtuGridSafetyAnalysisDto::getPtuIndex)
                .toArray(Integer[]::new);

        LOGGER.debug(
                "Sending a flexibility request for Entity Adress: {}, PTU Date: {}, PTU Index array length: {}",
                event.getCongestionPointEntityAddress(), event.getPeriod(), ptuIndexes.length);
        flexRequestEventManager.fire(
                new CreateFlexRequestEvent(event.getCongestionPointEntityAddress(), event.getPeriod(), ptuIndexes));
    }
}