energy.usef.agr.workflow.validate.flexoffer.AgrFlexOfferCoordinator.java Source code

Java tutorial

Introduction

Here is the source code for energy.usef.agr.workflow.validate.flexoffer.AgrFlexOfferCoordinator.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.agr.workflow.validate.flexoffer;

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.core.data.xml.bean.message.MessagePrecedence.ROUTINE;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import energy.usef.agr.config.ConfigAgr;
import energy.usef.agr.service.business.AgrPlanboardBusinessService;
import energy.usef.agr.service.business.AgrPortfolioBusinessService;
import energy.usef.agr.workflow.AgrWorkflowStep;
import energy.usef.core.config.Config;
import energy.usef.core.config.ConfigParam;
import energy.usef.core.data.xml.bean.message.FlexOffer;
import energy.usef.core.data.xml.bean.message.USEFRole;
import energy.usef.core.exception.BusinessException;
import energy.usef.core.exception.TechnicalException;
import energy.usef.core.model.BrpConnectionGroup;
import energy.usef.core.model.CongestionPointConnectionGroup;
import energy.usef.core.model.Connection;
import energy.usef.core.model.ConnectionGroup;
import energy.usef.core.model.DocumentStatus;
import energy.usef.core.model.DocumentType;
import energy.usef.core.model.PlanboardMessage;
import energy.usef.core.model.PrognosisType;
import energy.usef.core.model.PtuFlexRequest;
import energy.usef.core.service.business.CorePlanboardBusinessService;
import energy.usef.core.service.business.SequenceGeneratorService;
import energy.usef.core.service.helper.JMSHelperService;
import energy.usef.core.service.helper.MessageMetadataBuilder;
import energy.usef.core.service.validation.CorePlanboardValidatorService;
import energy.usef.core.transformer.PtuListConverter;
import energy.usef.core.util.DateTimeUtil;
import energy.usef.core.util.XMLUtil;
import energy.usef.core.workflow.DefaultWorkflowContext;
import energy.usef.core.workflow.WorkflowContext;
import energy.usef.core.workflow.dto.FlexOfferDto;
import energy.usef.core.workflow.dto.FlexRequestDto;
import energy.usef.core.workflow.dto.PrognosisDto;
import energy.usef.core.workflow.step.WorkflowStepExecuter;
import energy.usef.core.workflow.transformer.FlexOfferTransformer;
import energy.usef.core.workflow.transformer.FlexRequestTransformer;
import energy.usef.core.workflow.transformer.PrognosisTransformer;
import energy.usef.core.workflow.transformer.USEFRoleTransformer;
import energy.usef.core.workflow.util.WorkflowUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.ejb.Singleton;
import javax.enterprise.event.Observes;
import javax.inject.Inject;

import org.joda.time.LocalDate;
import org.joda.time.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This coordinater executes the business logic for Creating and Sending FlexOffer's.
 */
@Singleton
public class AgrFlexOfferCoordinator {

    private static final Logger LOGGER = LoggerFactory.getLogger(AgrFlexOfferCoordinator.class);

    @Inject
    private WorkflowStepExecuter workflowStepExecuter;
    @Inject
    private CorePlanboardBusinessService corePlanboardBusinessService;
    @Inject
    private AgrPlanboardBusinessService agrPlanboardBusinessService;
    @Inject
    private AgrPortfolioBusinessService agrPortfolioBusinessService;
    @Inject
    private CorePlanboardValidatorService planboardValidatorService;
    @Inject
    private JMSHelperService jmsHelperService;
    @Inject
    private Config config;
    @Inject
    private ConfigAgr configAgr;
    @Inject
    private SequenceGeneratorService sequenceGeneratorService;

    /**
     * Handles the business logic triggered by a {@link FlexOfferEvent}.
     *
     * @param event {@link FlexOfferEvent} event instance which will trigger the workflow.
     */
    public void handleEvent(@Observes FlexOfferEvent event) throws BusinessException {
        LOGGER.info(LOG_COORDINATOR_START_HANDLING_EVENT, event);
        // get processable FlexRequests
        Map<USEFRole, List<PlanboardMessage>> flexRequestsPerRole = agrPlanboardBusinessService
                .findAcceptedRequests(DocumentType.FLEX_REQUEST).stream().filter(this::validateRequest)
                .collect(groupingBy(this::determineUsefRoleOfPlanboardMessage));

        for (USEFRole usefRole : flexRequestsPerRole.keySet()) {
            Map<LocalDate, List<PlanboardMessage>> flexRequestsPerPeriod = flexRequestsPerRole.get(usefRole)
                    .stream().collect(groupingBy(PlanboardMessage::getPeriod));
            for (LocalDate period : flexRequestsPerPeriod.keySet()) {
                LOGGER.info("Processing flex offers for the Role: [{}].", usefRole);

                // invoke the PBC to generate flex offers.
                List<FlexOfferDto> flexOfferDtos = generateFlexOffersInWithPBC(usefRole, period,
                        flexRequestsPerPeriod.get(period));

                for (FlexOfferDto flexOfferDto : flexOfferDtos) {
                    PlanboardMessage associatedFlexRequest = flexRequestsPerPeriod.get(period).stream()
                            .filter(flexRequest -> flexOfferDto.getFlexRequestSequenceNumber()
                                    .equals(flexRequest.getSequence()))
                            .findFirst()
                            .orElseThrow(() -> new BusinessException(
                                    () -> "The flex offer returned by the PBC has no related flex request with sequence ["
                                            + flexOfferDto.getFlexRequestSequenceNumber() + "]"));

                    // build flexoffer Message
                    FlexOffer flexOfferMessage = buildFlexOfferMessage(usefRole, flexOfferDto);
                    // store message
                    corePlanboardBusinessService.storeFlexOffer(flexOfferDto.getConnectionGroupEntityAddress(),
                            flexOfferMessage, DocumentStatus.SENT, flexOfferDto.getParticipantDomain());
                    // send message
                    jmsHelperService.sendMessageToOutQueue(XMLUtil.messageObjectToXml(flexOfferMessage));
                    // mark the flex request as processed
                    associatedFlexRequest.setDocumentStatus(DocumentStatus.PROCESSED);
                }
            }
        }
        LOGGER.info(LOG_COORDINATOR_FINISHED_HANDLING_EVENT, event);
    }

    private boolean validateRequest(PlanboardMessage flexRequestMessage) {
        String usefIdentifier = flexRequestMessage.getConnectionGroup().getUsefIdentifier();

        PtuFlexRequest flexRequest = agrPlanboardBusinessService
                .findLastFlexRequestDocumentWithDispositionRequested(usefIdentifier, flexRequestMessage.getPeriod(),
                        flexRequestMessage.getSequence());

        if (flexRequest == null) {
            LOGGER.error(
                    "Impossible to find the latest flex request with disposition requested for: connectionGroup={}; period={}; "
                            + "sequence={}",
                    usefIdentifier, flexRequestMessage.getPeriod(), flexRequestMessage.getSequence());
            throw new TechnicalException("Impossible to validate flex requests. Impossible situation.");
        }
        if (planboardValidatorService.isPlanboardItemWithingIntradayGateClosureTime(flexRequest)) {
            flexRequestMessage.setDocumentStatus(DocumentStatus.EXPIRED);
            LOGGER.warn("No FlexOFfer Sent, IntradayGateClosure has passed for Requested flexibility. ");
            return false;
        }
        if (flexRequestMessage.getExpirationDate().isBefore(DateTimeUtil.getCurrentDateTime())) {
            flexRequestMessage.setDocumentStatus(DocumentStatus.EXPIRED);
            LOGGER.info("FlexRequest {} ignored, it expired {}.", flexRequestMessage.getSequence(),
                    flexRequestMessage.getExpirationDate());
            return false;
        }
        return true;
    }

    private Map<String, List<String>> buildConnectionGroupToConnectionsMap(
            List<PlanboardMessage> flexRequestsPerPeriod, LocalDate period) {
        List<String> connectionGroupUsefIdentifier = flexRequestsPerPeriod.stream()
                .map(flexRequestMessage -> flexRequestMessage.getConnectionGroup().getUsefIdentifier()).distinct()
                .collect(toList());
        Map<ConnectionGroup, List<Connection>> connectionsWithConnectionGroups = corePlanboardBusinessService
                .findConnectionsWithConnectionGroups(connectionGroupUsefIdentifier, period);
        return connectionsWithConnectionGroups.entrySet().stream()
                .collect(toMap(entry -> entry.getKey().getUsefIdentifier(),
                        entry -> entry.getValue().stream().map(Connection::getEntityAddress).collect(toList())));
    }

    @SuppressWarnings("unchecked")
    private List<FlexOfferDto> generateFlexOffersInWithPBC(USEFRole usefRole, LocalDate period,
            List<PlanboardMessage> flexRequestsForPeriod) {

        List<PrognosisDto> aPlanDtos = corePlanboardBusinessService.findLastPrognoses(period, PrognosisType.A_PLAN)
                .stream()
                .collect(groupingBy(ptuAPlan -> "" + ptuAPlan.getParticipantDomain() + ptuAPlan.getSequence()
                        + ptuAPlan.getConnectionGroup().getUsefIdentifier()))
                .values().stream().map(PrognosisTransformer::mapToPrognosis).collect(toList());

        List<PrognosisDto> dPrognosisDtos = agrPlanboardBusinessService.findLastPrognoses(period,
                PrognosisType.D_PROGNOSIS, Optional.empty());

        List<FlexRequestDto> flexRequestDtos = getFlexRequestDtos(usefRole, flexRequestsForPeriod);

        // fetch connections related to the connection groups for the given period
        Map<String, List<String>> connectionGroupToConnectionAddressMap = buildConnectionGroupToConnectionsMap(
                flexRequestsForPeriod, period);

        WorkflowContext workflowContext = new DefaultWorkflowContext();
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.FLEX_REQUEST_DTO_LIST.name(),
                flexRequestDtos);
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.PTU_DURATION.name(),
                config.getIntegerProperty(ConfigParam.PTU_DURATION));
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.PERIOD.name(), period);
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.LATEST_A_PLANS_DTO_LIST.name(),
                aPlanDtos);
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.LATEST_D_PROGNOSES_DTO_LIST.name(),
                dPrognosisDtos);
        workflowContext.setValue(
                FlexOfferDetermineFlexibilityStepParameter.IN.CONNECTION_GROUPS_TO_CONNECTIONS_MAP.name(),
                connectionGroupToConnectionAddressMap);
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.CONNECTION_PORTFOLIO_DTO.name(),
                agrPortfolioBusinessService.findConnectionPortfolioDto(period));
        workflowContext.setValue(FlexOfferDetermineFlexibilityStepParameter.IN.FLEX_OFFER_DTO_LIST.name(),
                fetchPlacedFlexOffersForPeriod(period));

        // determine flexibility
        workflowContext = workflowStepExecuter.invoke(AgrWorkflowStep.AGR_FLEX_OFFER_DETERMINE_FLEXIBILITY.name(),
                workflowContext);

        // validate context
        WorkflowUtil.validateContext(AgrWorkflowStep.AGR_FLEX_OFFER_DETERMINE_FLEXIBILITY.name(), workflowContext,
                FlexOfferDetermineFlexibilityStepParameter.OUT.values());

        return (List<FlexOfferDto>) workflowContext
                .getValue(FlexOfferDetermineFlexibilityStepParameter.OUT.FLEX_OFFER_DTO_LIST.name());
    }

    private List<FlexRequestDto> getFlexRequestDtos(USEFRole usefRole,
            List<PlanboardMessage> flexRequestsForPeriod) {
        List<FlexRequestDto> flexRequestDtoList = new ArrayList<>();

        for (PlanboardMessage flexRequest : flexRequestsForPeriod) {
            List<PtuFlexRequest> ptuFlexRequests = agrPlanboardBusinessService.findPtuFlexRequestWithSequence(
                    flexRequest.getConnectionGroup().getUsefIdentifier(), flexRequest.getSequence(),
                    flexRequest.getParticipantDomain());

            if (!ptuFlexRequests.isEmpty()) {
                FlexRequestDto dto = FlexRequestTransformer.transformFlexRequest(ptuFlexRequests);
                dto.setParticipantRole(USEFRoleTransformer.transform(usefRole));
                dto.setExpirationDateTime(flexRequest.getExpirationDate());
                flexRequestDtoList.add(dto);
            }
        }

        return flexRequestDtoList;
    }

    private List<FlexOfferDto> fetchPlacedFlexOffersForPeriod(LocalDate period) {
        return new ArrayList<>(corePlanboardBusinessService.findPlacedFlexOffers(period).stream()
                .collect(groupingBy(
                        ptuFlexOffer -> "" + ptuFlexOffer.getParticipantDomain() + ptuFlexOffer.getSequence(),
                        collectingAndThen(toList(), FlexOfferTransformer::transformPtuFlexOffers)))
                .values());
    }

    private FlexOffer buildFlexOfferMessage(USEFRole recipientRole, FlexOfferDto flexOfferDto) {
        FlexOffer flexOffer = new FlexOffer();

        flexOffer.setMessageMetadata(MessageMetadataBuilder
                .build(flexOfferDto.getParticipantDomain(), recipientRole,
                        config.getProperty(ConfigParam.HOST_DOMAIN), USEFRole.AGR, ROUTINE)
                .validUntil(flexOfferDto.getExpirationDateTime()).build());
        if (USEFRole.DSO == recipientRole) {
            flexOffer.setCongestionPoint(flexOfferDto.getConnectionGroupEntityAddress());
        }

        flexOffer.setExpirationDateTime(flexOfferDto.getExpirationDateTime());
        flexOffer.setFlexRequestOrigin(flexOfferDto.getParticipantDomain());
        flexOffer.setFlexRequestSequence(flexOfferDto.getFlexRequestSequenceNumber());
        flexOffer.setSequence(sequenceGeneratorService.next());
        flexOffer.setPeriod(flexOfferDto.getPeriod());
        flexOffer.setTimeZone(config.getProperty(ConfigParam.TIME_ZONE));
        flexOffer.setCurrency(config.getProperty(ConfigParam.CURRENCY));
        flexOffer.setPTUDuration(Period.minutes(config.getIntegerProperty(ConfigParam.PTU_DURATION)));
        if (!flexOfferDto.getPtus().isEmpty()) {
            flexOffer.getPTU().addAll(PtuListConverter.compact(
                    flexOfferDto.getPtus().stream().map(FlexOfferTransformer::transformToPTU).collect(toList())));
        }
        return flexOffer;
    }

    private USEFRole determineUsefRoleOfPlanboardMessage(PlanboardMessage message) {
        if (message.getConnectionGroup() instanceof BrpConnectionGroup) {
            return USEFRole.BRP;
        } else if (message.getConnectionGroup() instanceof CongestionPointConnectionGroup) {
            return USEFRole.DSO;
        } else {
            return null;
        }
    }
}