All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zepben.evolve.streaming.get.NetworkConsumerClient.kt Maven / Gradle / Ivy

There is a newer version: 0.24.0rc1
Show newest version
/*
 * Copyright 2020 Zeppelin Bend Pty Ltd
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package com.zepben.evolve.streaming.get

import com.zepben.evolve.cim.iec61968.operations.OperationalRestriction
import com.zepben.evolve.cim.iec61970.base.core.*
import com.zepben.evolve.cim.iec61970.infiec61970.feeder.Loop
import com.zepben.evolve.services.common.BaseService
import com.zepben.evolve.services.common.extensions.typeNameAndMRID
import com.zepben.evolve.services.common.translator.mRID
import com.zepben.evolve.services.network.NetworkService
import com.zepben.evolve.services.network.translator.NetworkProtoToCim
import com.zepben.evolve.services.network.translator.mRID
import com.zepben.evolve.streaming.get.hierarchy.NetworkHierarchy
import com.zepben.evolve.streaming.grpc.GrpcChannel
import com.zepben.evolve.streaming.grpc.GrpcResult
import com.zepben.protobuf.metadata.GetMetadataRequest
import com.zepben.protobuf.metadata.GetMetadataResponse
import com.zepben.protobuf.nc.*
import com.zepben.protobuf.nc.NetworkIdentifiedObject.IdentifiedObjectCase.*
import io.grpc.CallCredentials
import io.grpc.Channel
import java.io.IOException
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

/**
 * Consumer client for a [NetworkService].
 *
 * WARNING: The [MultiObjectResult] operations below are not atomic upon a [NetworkService], and thus if processing fails partway through, any previously
 * successful additions will have been processed by the service, and thus you may have an incomplete service. Also note that adding to the service may not
 * occur for an object if another object with the same mRID is already present in service. [MultiObjectResult.failed] can be used to check for mRIDs that
 * were retrieved but not added to service. This should not be the case unless you are processing things concurrently.
 *
 * @property stub The gRPC stub to be used to communicate with the server
 * @property service The [NetworkService] to store fetched objects in.
 * @param executor An optional [ExecutorService] to use with the stub. If provided, it will be cleaned up when this client is closed.
 */
class NetworkConsumerClient(
    private val stub: NetworkConsumerGrpc.NetworkConsumerStub,
    override val service: NetworkService = NetworkService(),
    override val protoToCim: NetworkProtoToCim = NetworkProtoToCim(service),
    executor: ExecutorService? = null
) : CimConsumerClient(executor) {

    private var networkHierarchy: NetworkHierarchy? = null

    /**
     * Create a [NetworkConsumerClient]
     *
     * @param channel [Channel] to build a stub from.
     * @param callCredentials [CallCredentials] to be attached to the stub.
     */
    @JvmOverloads
    constructor(channel: Channel, callCredentials: CallCredentials? = null) :
        this(
            NetworkConsumerGrpc.newStub(channel).apply { callCredentials?.let { withCallCredentials(it) } },
            executor = Executors.newSingleThreadExecutor()
        )

    /**
     * Create a [NetworkConsumerClient]
     *
     * @param channel [GrpcChannel] to build a stub from.
     * @param callCredentials [CallCredentials] to be attached to the stub.
     */
    @JvmOverloads
    constructor(channel: GrpcChannel, callCredentials: CallCredentials? = null) : this(channel.channel, callCredentials)

    override fun runGetMetadata(getMetadataRequest: GetMetadataRequest, streamObserver: AwaitableStreamObserver) {
        stub.getMetadata(getMetadataRequest, streamObserver)
    }

    /**
     * Retrieve the [Equipment] for [equipmentContainer]
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param equipmentContainer The [EquipmentContainer] to fetch equipment for.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    @JvmOverloads
    fun getEquipmentForContainer(
        equipmentContainer: EquipmentContainer,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        getEquipmentForContainer(equipmentContainer.mRID, includeEnergizingContainers, includeEnergizedContainers)

    /**
     * Retrieve the [Equipment] for the [EquipmentContainer] represented by [mRID]
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRID The mRID of the [EquipmentContainer] to fetch equipment for.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    @JvmOverloads
    fun getEquipmentForContainer(
        mRID: String,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        getEquipmentForContainers(sequenceOf(mRID), includeEnergizingContainers, includeEnergizedContainers)

    /**
     * Retrieve the [Equipment] for all [EquipmentContainer]s represented by [mRIDs]
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRIDs The mRIDs of the [EquipmentContainer]s to fetch equipment for.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    @JvmOverloads
    fun getEquipmentForContainers(
        mRIDs: Iterable,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        getEquipmentForContainers(mRIDs.asSequence(), includeEnergizingContainers, includeEnergizedContainers)

    /**
     * Retrieve the [Equipment] for all [EquipmentContainer]s represented by [mRIDs]
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRIDs The mRIDs of the [EquipmentContainer]s to fetch equipment for.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    @JvmOverloads
    fun getEquipmentForContainers(
        mRIDs: Sequence,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        handleMultiObjectRPC { processEquipmentForContainers(mRIDs, includeEnergizingContainers, includeEnergizedContainers) }

    /**
     * Retrieve the [Equipment] for [operationalRestriction].
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param operationalRestriction The [OperationalRestriction] to fetch equipment for.
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getEquipmentForRestriction(operationalRestriction: OperationalRestriction): GrpcResult =
        getEquipmentForRestriction(operationalRestriction.mRID)

    /**
     * Retrieve the [Equipment] for the [OperationalRestriction] represented by [mRID].
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRID The mRID of the [OperationalRestriction] to fetch equipment for.
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getEquipmentForRestriction(mRID: String): GrpcResult =
        handleMultiObjectRPC { processRestriction(mRID) }

    /**
     * Retrieve the current [Equipment] for [feeder]. The current equipment is the equipment connected to the Feeder based on
     * the current phasing and switching of the network.
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param feeder The [Feeder] to fetch current equipment for.
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getCurrentEquipmentForFeeder(feeder: Feeder): GrpcResult =
        getCurrentEquipmentForFeeder(feeder.mRID)

    /**
     * Retrieve the current [Equipment] for the [Feeder] represented by [mRID]. The current equipment is the equipment connected to the Feeder based on
     * the current phasing and switching of the network.
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRID The mRID of the [Feeder] to fetch current equipment for.
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getCurrentEquipmentForFeeder(mRID: String): GrpcResult =
        handleMultiObjectRPC { processFeeder(mRID) }

    /**
     * Retrieve the [Terminal]s for [connectivityNode].
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param connectivityNode The [ConnectivityNode] to fetch terminals for.
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getTerminalsForConnectivityNode(connectivityNode: ConnectivityNode): GrpcResult =
        getTerminalsForConnectivityNode(connectivityNode.mRID)

    /**
     * Retrieve the [Terminal]s for the [ConnectivityNode] represented by [mRID].
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRID The mRID of the [ConnectivityNode] to fetch terminals for.
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getTerminalsForConnectivityNode(mRID: String): GrpcResult =
        handleMultiObjectRPC { processConnectivityNode(mRID) }

    /***
     * Retrieve the network hierarchy.
     *
     *
     * @return A simplified version of the network hierarchy that can be used to make further in-depth requests.
     */
    fun getNetworkHierarchy(): GrpcResult =
        tryRpc {
            if (networkHierarchy == null) {
                val streamObserver = AwaitableStreamObserver { response ->
                    networkHierarchy = NetworkHierarchy(
                        toMap(response.geographicalRegionsList) { getOrAdd(it.mRID()) { addFromPb(it) } },
                        toMap(response.subGeographicalRegionsList) { getOrAdd(it.mRID()) { addFromPb(it) } },
                        toMap(response.substationsList) { getOrAdd(it.mRID()) { addFromPb(it) } },
                        toMap(response.feedersList) { getOrAdd(it.mRID()) { addFromPb(it) } },
                        toMap(response.circuitsList) { getOrAdd(it.mRID()) { addFromPb(it) } },
                        toMap(response.loopsList) { getOrAdd(it.mRID()) { addFromPb(it) } }
                    )
                }

                stub.getNetworkHierarchy(GetNetworkHierarchyRequest.newBuilder().build(), streamObserver)

                streamObserver.await()
            }
            networkHierarchy ?: throw IOException("No network hierarchy was received before GRPC channel was closed.")
        }

    /***
     * Retrieve the equipment container network for the specified [mRID] and store the results in the [service].
     *
     * This is a convenience method that will fetch the container object and all the equipment contained, along with all subsequent
     * references. This should entail a complete connectivity model for the container, however not the connectivity between multiple containers.
     *
     * @param mRID The mRID of the [EquipmentContainer] to fetch.
     * @param expectedClass The expected type of the fetched container.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] of a [MultiObjectResult]. If successful, containing a map keyed by mRID of all the objects retrieved. If an item couldn't be added to
     * [service], its mRID will be present in [MultiObjectResult.failed].
     *
     * In addition to normal gRPC errors, you may also receive an unsuccessful [GrpcResult] with the following errors:
     * - [NoSuchElementException] if the requested object was not found.
     * - [ClassCastException] if the requested object was of the wrong type.
     */
    @JvmOverloads
    fun getEquipmentContainer(
        mRID: String,
        expectedClass: Class = EquipmentContainer::class.java,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        tryRpc {
            val result = getEquipmentContainers(sequenceOf(mRID), expectedClass, includeEnergizingContainers, includeEnergizedContainers)
            if (result.wasFailure)
                throw result.thrown

            if (result.value.objects.isEmpty())
                throw NoSuchElementException("No object with mRID $mRID could be found.")

            result.value
        }

    /***
     * Retrieve the equipment container networks for the specified [mRID]s and store the results in the [service].
     *
     * This is a convenience method that will fetch the container objects and all the equipment contained, along with all subsequent
     * references. This should entail a complete connectivity model for the containers, however not the connectivity between multiple containers.
     *
     * @param mRIDs The mRIDs of the [EquipmentContainer]s to fetch.
     * @param expectedClass The expected type of the fetched containers.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] of a [MultiObjectResult]. If successful, containing a map keyed by mRID of all the objects retrieved. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed]
     *
     * In addition to normal gRPC errors, you may also receive an unsuccessful [GrpcResult] with the following errors:
     * - [ClassCastException] if the requested object was of the wrong type.
     */
    @JvmOverloads
    fun getEquipmentContainers(
        mRIDs: Iterable,
        expectedClass: Class = EquipmentContainer::class.java,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        getEquipmentContainers(mRIDs.asSequence(), expectedClass, includeEnergizingContainers, includeEnergizedContainers)

    /***
     * Retrieve the equipment container networks for the specified [mRID]s and store the results in the [service].
     *
     * This is a convenience method that will fetch the container objects and all the equipment contained, along with all subsequent
     * references. This should entail a complete connectivity model for the containers, however not the connectivity between multiple containers.
     *
     * @param mRIDs The mRIDs of the [EquipmentContainer]s to fetch.
     * @param expectedClass The expected type of the fetched containers.
     * @param includeEnergizingContainers The level of energizing containers to include equipment from.
     * @param includeEnergizedContainers The level of energized containers to include equipment from.
     *
     * @return A [GrpcResult] of a [MultiObjectResult]. If successful, containing a map keyed by mRID of all the objects retrieved. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed]
     *
     * In addition to normal gRPC errors, you may also receive an unsuccessful [GrpcResult] with the following errors:
     * - [ClassCastException] if the requested object was of the wrong type.
     */
    @JvmOverloads
    fun getEquipmentContainers(
        mRIDs: Sequence,
        expectedClass: Class = EquipmentContainer::class.java,
        includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
        includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
    ): GrpcResult =
        getWithReferences(mRIDs, expectedClass) { it, (objects, _) ->
            objects.putAll(getEquipmentForContainers(it.map { eq -> eq.mRID }, includeEnergizingContainers, includeEnergizedContainers)
                .onError { thrown, wasHandled -> return@getWithReferences GrpcResult.ofError(thrown, wasHandled) }
                .value.objects
            )
            null
        }

    /**
     * Retrieve the [Equipment] for the [loop]
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param loop The [Loop] to fetch equipment for.
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getEquipmentForLoop(loop: Loop): GrpcResult =
        getEquipmentForLoop(loop.mRID)

    /**
     * Retrieve the [Equipment] for the [Loop] represented by [mRID]
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     * @param mRID The mRID of the [Loop] to fetch equipment for.
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getEquipmentForLoop(mRID: String): GrpcResult =
        getWithReferences(mRID, Loop::class.java) { loop, (objects, _) ->
            objects.putAll(loop.circuits.associateBy { it.mRID })
            objects.putAll(loop.substations.associateBy { it.mRID })
            objects.putAll(loop.energizingSubstations.associateBy { it.mRID })

            val containers = loop.circuits.asSequence() + loop.substations.asSequence() + loop.energizingSubstations.asSequence()
            objects.putAll(getEquipmentForContainers(containers.map { it.mRID })
                .onError { thrown, wasHandled -> return@getWithReferences GrpcResult.ofError(thrown, wasHandled) }
                .value.objects)
            null
        }

    /**
     * Retrieve the [Equipment] for all [Loop]s
     *
     * Exceptions that occur during retrieval will be caught and passed to all error handlers that have been registered against this client.
     *
     *
     * @return A [GrpcResult] with a result of one of the following:
     * - When [GrpcResult.wasSuccessful], a map containing the retrieved objects keyed by mRID, accessible via [GrpcResult.value]. If an item was not found, or
     * couldn't be added to [service], it will be excluded from the map and its mRID will be present in [MultiObjectResult.failed] (see [BaseService.add]).
     * - When [GrpcResult.wasFailure], the error that occurred retrieving or processing the object, accessible via [GrpcResult.thrown].
     * Note the [NetworkConsumerClient] warning in this case.
     */
    fun getAllLoops(): GrpcResult {
        val response = getNetworkHierarchy()
        val hierarchy = response.onError { thrown, wasHandled -> return GrpcResult.ofError(thrown, wasHandled) }.value

        val mor = MultiObjectResult()
        mor.objects.putAll(hierarchy.geographicalRegions)
        mor.objects.putAll(hierarchy.subGeographicalRegions)
        mor.objects.putAll(hierarchy.substations)
        mor.objects.putAll(hierarchy.feeders)
        mor.objects.putAll(hierarchy.circuits)
        mor.objects.putAll(hierarchy.loops)

        mor.objects.putAll(getEquipmentForContainers(hierarchy.loops.values
            .asSequence()
            .flatMap { it.circuits.asSequence() + it.substations.asSequence() + it.energizingSubstations.asSequence() }
            .distinct()
            .map { it.mRID })
            .onError { thrown, wasHandled -> return GrpcResult.ofError(thrown, wasHandled) }
            .value.objects
        )

        resolveReferences(mor)?.let { return it }

        return GrpcResult(mor)
    }

    override fun processIdentifiedObjects(mRIDs: Sequence): Sequence {
        val extractResults = mutableListOf()
        val streamObserver = AwaitableStreamObserver { response ->
            response.identifiedObjectsList.forEach {
                extractResults.add(extractIdentifiedObject(it))
            }
        }

        val request = stub.getIdentifiedObjects(streamObserver)
        val builder = GetIdentifiedObjectsRequest.newBuilder()

        batchSend(mRIDs, builder::addMrids) {
            if (builder.mridsList.isNotEmpty())
                request.onNext(builder.build())
            builder.clearMrids()
        }

        request.onCompleted()
        streamObserver.await()

        return extractResults.asSequence()
    }

    private fun processEquipmentForContainers(
        mRIDs: Sequence,
        includeEnergizingContainers: IncludedEnergizingContainers,
        includeEnergizedContainers: IncludedEnergizedContainers
    ): Sequence {
        val extractResults = mutableListOf()
        val streamObserver = AwaitableStreamObserver { response ->
            response.identifiedObjectsList.forEach {
                extractResults.add(extractIdentifiedObject(it))
            }
        }

        val request = stub.getEquipmentForContainers(streamObserver)
        val builder = GetEquipmentForContainersRequest.newBuilder()

        builder.includeEnergizingContainers = includeEnergizingContainers
        builder.includeEnergizedContainers = includeEnergizedContainers

        batchSend(mRIDs, builder::addMrids) {
            if (builder.mridsList.isNotEmpty())
                request.onNext(builder.build())
            builder.clearMrids()
        }

        request.onCompleted()
        streamObserver.await()

        return extractResults.asSequence()
    }

    private fun processFeeder(mRID: String): Sequence {
        val extractResults = mutableListOf()
        val streamObserver = AwaitableStreamObserver { response ->
            response.identifiedObjectsList.forEach {
                extractResults.add(extractIdentifiedObject(it))
            }
        }

        stub.getCurrentEquipmentForFeeder(GetCurrentEquipmentForFeederRequest.newBuilder().setMrid(mRID).build(), streamObserver)
        streamObserver.await()

        return extractResults.asSequence()
    }

    private fun processRestriction(mRID: String): Sequence {
        val extractResults = mutableListOf()
        val streamObserver = AwaitableStreamObserver { response ->
            response.identifiedObjectsList.forEach {
                extractResults.add(extractIdentifiedObject(it))
            }
        }

        stub.getEquipmentForRestriction(GetEquipmentForRestrictionRequest.newBuilder().setMrid(mRID).build(), streamObserver)
        streamObserver.await()

        return extractResults.asSequence()
    }

    private fun processConnectivityNode(mRID: String): Sequence {
        val extractResults = mutableListOf()
        val streamObserver = AwaitableStreamObserver { response ->
            extractResults.add(extractResult(response.terminal.mRID()) { addFromPb(response.terminal) })
        }

        stub.getTerminalsForNode(GetTerminalsForNodeRequest.newBuilder().setMrid(mRID).build(), streamObserver)
        streamObserver.await()

        return extractResults.asSequence()
    }

    private fun extractIdentifiedObject(io: NetworkIdentifiedObject): ExtractResult =
        when (io.identifiedObjectCase) {
            BATTERYUNIT -> extractResult(io.batteryUnit.mRID()) { addFromPb(io.batteryUnit) }
            PHOTOVOLTAICUNIT -> extractResult(io.photoVoltaicUnit.mRID()) { addFromPb(io.photoVoltaicUnit) }
            POWERELECTRONICSWINDUNIT -> extractResult(io.powerElectronicsWindUnit.mRID()) { addFromPb(io.powerElectronicsWindUnit) }
            CABLEINFO -> extractResult(io.cableInfo.mRID()) { addFromPb(io.cableInfo) }
            OVERHEADWIREINFO -> extractResult(io.overheadWireInfo.mRID()) { addFromPb(io.overheadWireInfo) }
            POWERTRANSFORMERINFO -> extractResult(io.powerTransformerInfo.mRID()) { addFromPb(io.powerTransformerInfo) }
            ASSETOWNER -> extractResult(io.assetOwner.mRID()) { addFromPb(io.assetOwner) }
            ORGANISATION -> extractResult(io.organisation.mRID()) { addFromPb(io.organisation) }
            LOCATION -> extractResult(io.location.mRID()) { addFromPb(io.location) }
            METER -> extractResult(io.meter.mRID()) { addFromPb(io.meter) }
            USAGEPOINT -> extractResult(io.usagePoint.mRID()) { addFromPb(io.usagePoint) }
            OPERATIONALRESTRICTION -> extractResult(io.operationalRestriction.mRID()) { addFromPb(io.operationalRestriction) }
            FAULTINDICATOR -> extractResult(io.faultIndicator.mRID()) { addFromPb(io.faultIndicator) }
            BASEVOLTAGE -> extractResult(io.baseVoltage.mRID()) { addFromPb(io.baseVoltage) }
            CONNECTIVITYNODE -> extractResult(io.connectivityNode.mRID()) { addFromPb(io.connectivityNode) }
            FEEDER -> extractResult(io.feeder.mRID()) { addFromPb(io.feeder) }
            GEOGRAPHICALREGION -> extractResult(io.geographicalRegion.mRID()) { addFromPb(io.geographicalRegion) }
            SITE -> extractResult(io.site.mRID()) { addFromPb(io.site) }
            SUBGEOGRAPHICALREGION -> extractResult(io.subGeographicalRegion.mRID()) { addFromPb(io.subGeographicalRegion) }
            SUBSTATION -> extractResult(io.substation.mRID()) { addFromPb(io.substation) }
            TERMINAL -> extractResult(io.terminal.mRID()) { addFromPb(io.terminal) }
            ACLINESEGMENT -> extractResult(io.acLineSegment.mRID()) { addFromPb(io.acLineSegment) }
            BREAKER -> extractResult(io.breaker.mRID()) { addFromPb(io.breaker) }
            LOADBREAKSWITCH -> extractResult(io.loadBreakSwitch.mRID()) { addFromPb(io.loadBreakSwitch) }
            DISCONNECTOR -> extractResult(io.disconnector.mRID()) { addFromPb(io.disconnector) }
            ENERGYCONSUMER -> extractResult(io.energyConsumer.mRID()) { addFromPb(io.energyConsumer) }
            ENERGYCONSUMERPHASE -> extractResult(io.energyConsumerPhase.mRID()) { addFromPb(io.energyConsumerPhase) }
            ENERGYSOURCE -> extractResult(io.energySource.mRID()) { addFromPb(io.energySource) }
            ENERGYSOURCEPHASE -> extractResult(io.energySourcePhase.mRID()) { addFromPb(io.energySourcePhase) }
            FUSE -> extractResult(io.fuse.mRID()) { addFromPb(io.fuse) }
            JUMPER -> extractResult(io.jumper.mRID()) { addFromPb(io.jumper) }
            JUNCTION -> extractResult(io.junction.mRID()) { addFromPb(io.junction) }
            LINEARSHUNTCOMPENSATOR -> extractResult(io.linearShuntCompensator.mRID()) { addFromPb(io.linearShuntCompensator) }
            PERLENGTHSEQUENCEIMPEDANCE -> extractResult(io.perLengthSequenceImpedance.mRID()) { addFromPb(io.perLengthSequenceImpedance) }
            POWERELECTRONICSCONNECTION -> extractResult(io.powerElectronicsConnection.mRID()) { addFromPb(io.powerElectronicsConnection) }
            POWERELECTRONICSCONNECTIONPHASE -> extractResult(io.powerElectronicsConnectionPhase.mRID()) { addFromPb(io.powerElectronicsConnectionPhase) }
            POWERTRANSFORMER -> extractResult(io.powerTransformer.mRID()) { addFromPb(io.powerTransformer) }
            POWERTRANSFORMEREND -> extractResult(io.powerTransformerEnd.mRID()) { addFromPb(io.powerTransformerEnd) }
            RATIOTAPCHANGER -> extractResult(io.ratioTapChanger.mRID()) { addFromPb(io.ratioTapChanger) }
            RECLOSER -> extractResult(io.recloser.mRID()) { addFromPb(io.recloser) }
            BUSBARSECTION -> extractResult(io.busbarSection.mRID()) { addFromPb(io.busbarSection) }
            CIRCUIT -> extractResult(io.circuit.mRID()) { addFromPb(io.circuit) }
            LOOP -> extractResult(io.loop.mRID()) { addFromPb(io.loop) }
            POLE -> extractResult(io.pole.mRID()) { addFromPb(io.pole) }
            STREETLIGHT -> extractResult(io.streetlight.mRID()) { addFromPb(io.streetlight) }
            ACCUMULATOR -> extractResult(io.accumulator.measurement.mRID()) { addFromPb(io.accumulator) }
            ANALOG -> extractResult(io.analog.measurement.mRID()) { addFromPb(io.analog) }
            DISCRETE -> extractResult(io.discrete.measurement.mRID()) { addFromPb(io.discrete) }
            CONTROL -> extractResult(io.control.mRID()) { addFromPb(io.control) }
            REMOTECONTROL -> extractResult(io.remoteControl.mRID()) { addFromPb(io.remoteControl) }
            REMOTESOURCE -> extractResult(io.remoteSource.mRID()) { addFromPb(io.remoteSource) }
            TRANSFORMERSTARIMPEDANCE -> extractResult(io.transformerStarImpedance.mRID()) { addFromPb(io.transformerStarImpedance) }
            TRANSFORMERENDINFO -> extractResult(io.transformerEndInfo.mRID()) { addFromPb(io.transformerEndInfo) }
            TRANSFORMERTANKINFO -> extractResult(io.transformerTankInfo.mRID()) { addFromPb(io.transformerTankInfo) }
            NOLOADTEST -> extractResult(io.noLoadTest.mRID()) { addFromPb(io.noLoadTest) }
            OPENCIRCUITTEST -> extractResult(io.openCircuitTest.mRID()) { addFromPb(io.openCircuitTest) }
            SHORTCIRCUITTEST -> extractResult(io.shortCircuitTest.mRID()) { addFromPb(io.shortCircuitTest) }
            EQUIVALENTBRANCH -> extractResult(io.equivalentBranch.mRID()) { addFromPb(io.equivalentBranch) }
            SHUNTCOMPENSATORINFO -> extractResult(io.shuntCompensatorInfo.mRID()) { addFromPb(io.shuntCompensatorInfo) }
            LVFEEDER -> extractResult(io.lvFeeder.mRID()) { addFromPb(io.lvFeeder) }
            CURRENTTRANSFORMER -> extractResult(io.currentTransformer.mRID()) { addFromPb(io.currentTransformer) }
            POTENTIALTRANSFORMER -> extractResult(io.potentialTransformer.mRID()) { addFromPb(io.potentialTransformer) }
            CURRENTTRANSFORMERINFO -> extractResult(io.currentTransformerInfo.mRID()) { addFromPb(io.currentTransformerInfo) }
            POTENTIALTRANSFORMERINFO -> extractResult(io.potentialTransformerInfo.mRID()) { addFromPb(io.potentialTransformerInfo) }
            SWITCHINFO -> extractResult(io.switchInfo.mRID()) { addFromPb(io.switchInfo) }
            RELAYINFO -> extractResult(io.relayInfo.mRID()) { addFromPb(io.relayInfo) }
            CURRENTRELAY -> extractResult(io.currentRelay.mRID()) { addFromPb(io.currentRelay) }
            EVCHARGINGUNIT -> extractResult(io.evChargingUnit.mRID()) { addFromPb(io.evChargingUnit) }
            TAPCHANGERCONTROL -> extractResult(io.tapChangerControl.mRID()) { addFromPb(io.tapChangerControl) }
            SERIESCOMPENSATOR -> extractResult(io.seriesCompensator.mRID()) { addFromPb(io.seriesCompensator) }
            GROUND -> extractResult(io.ground.mRID()) { addFromPb(io.ground) }
            GROUNDDISCONNECTOR -> extractResult(io.groundDisconnector.mRID()) { addFromPb(io.groundDisconnector) }
            PROTECTIONRELAYSCHEME -> extractResult(io.protectionRelayScheme.mRID()) { addFromPb(io.protectionRelayScheme) }
            PROTECTIONRELAYSYSTEM -> extractResult(io.protectionRelaySystem.mRID()) { addFromPb(io.protectionRelaySystem) }
            VOLTAGERELAY -> extractResult(io.voltageRelay.mRID()) { addFromPb(io.voltageRelay) }
            DISTANCERELAY -> extractResult(io.distanceRelay.mRID()) { addFromPb(io.distanceRelay) }
            REACTIVECAPABILITYCURVE -> extractResult(io.reactiveCapabilityCurve.mRID()) { addFromPb(io.reactiveCapabilityCurve) }
            SYNCHRONOUSMACHINE -> extractResult(io.synchronousMachine.mRID()) { addFromPb(io.synchronousMachine) }
            GROUNDINGIMPEDANCE -> extractResult(io.groundingImpedance.mRID()) { addFromPb(io.groundingImpedance) }
            PETERSENCOIL -> extractResult(io.petersenCoil.mRID()) { addFromPb(io.petersenCoil) }
            OTHER, IDENTIFIEDOBJECT_NOT_SET, null -> throw UnsupportedOperationException(
                "Identified object type ${io.identifiedObjectCase} is not supported by the network service"
            )
        }

    private fun  toMap(objects: Iterable, mapper: (T) -> U?): Map =
        objects
            .mapNotNull(mapper)
            .associateBy { it.mRID }

    private inline fun  getWithReferences(
        mRID: String,
        expectedClass: Class,
        getAdditional: (T, MultiObjectResult) -> GrpcResult?
    ): GrpcResult =
        getWithReferences(sequenceOf(mRID), expectedClass) { it, mor -> getAdditional(it.elementAt(0), mor) }

    private inline fun  getWithReferences(
        mRIDs: Sequence,
        expectedClass: Class,
        getAdditional: (Sequence, MultiObjectResult) -> GrpcResult?
    ): GrpcResult {
        val mor = MultiObjectResult()

        networkHierarchy ?: getNetworkHierarchy().onError { thrown, wasHandled -> return@getWithReferences GrpcResult.ofError(thrown, wasHandled) }

        val toFetch = mutableListOf()
        mRIDs.forEach { mRID ->  // Only process mRIDs not already present in service
            service.get(mRID)?.let { mor.objects[it.mRID] = it } ?: toFetch.add(mRID)
        }

        val response = getIdentifiedObjects(toFetch.asSequence())
        val result = response.onError { thrown, wasHandled -> return@getWithReferences GrpcResult.ofError(thrown, wasHandled) }.value

        val invalid = result.objects.values.filter { !expectedClass.isInstance(it) }.toMutableSet()
        if (invalid.isNotEmpty()) {
            val e = ClassCastException("Unable to extract ${expectedClass.simpleName} networks from ${invalid.map { it.typeNameAndMRID() }}.")
            return GrpcResult.ofError(e, tryHandleError(e))
        }

        mor.objects.putAll(result.objects)
        mor.failed.addAll(result.failed)

        getAdditional(mor.objects.values.asSequence().filterIsInstance(expectedClass), mor)?.let { return@getWithReferences it }
        resolveReferences(mor)?.let { return@getWithReferences it }

        return GrpcResult(mor)
    }

    internal fun resolveReferences(mor: MultiObjectResult): GrpcResult? {
        var res = mor
        var subsequent = false
        do {
            // Skip any reference trying to resolve from an EquipmentContainer on subsequent passes - e.g a PowerTransformer trying to pull in its LvFeeder.
            // EquipmentContainers should be retrieved explicitly or via a hierarchy call.
            val toResolve = res.objects.keys
                .flatMap { service.getUnresolvedReferencesFrom(it) }
                .filterNot { subsequent && EquipmentContainer::class.java.isAssignableFrom(it.resolver.fromClass) }
                .map { it.toMrid }
                .distinct()
                .toList()

            res = getIdentifiedObjects(toResolve).onError { thrown, wasHandled ->
                return GrpcResult.ofError(thrown, wasHandled)
            }.value
            mor.objects.putAll(res.objects)

            subsequent = true
        } while (res.objects.isNotEmpty())
        return null
    }

}

inline fun  NetworkConsumerClient.getEquipmentContainer(
    mRID: String,
    includeEnergizingContainers: IncludedEnergizingContainers = IncludedEnergizingContainers.EXCLUDE_ENERGIZING_CONTAINERS,
    includeEnergizedContainers: IncludedEnergizedContainers = IncludedEnergizedContainers.EXCLUDE_ENERGIZED_CONTAINERS
): GrpcResult {
    return getEquipmentContainer(mRID, T::class.java, includeEnergizingContainers, includeEnergizedContainers)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy