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

net.e6tech.elements.network.cluster.invocation.Registrar Maven / Gradle / Ivy

/*
 * Copyright 2015-2019 Futeh Kao
 *
 * 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 net.e6tech.elements.network.cluster.invocation;


import akka.actor.Status;
import akka.actor.typed.ActorRef;
import akka.actor.typed.DispatcherSelector;
import akka.actor.typed.Terminated;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.GroupRouter;
import akka.actor.typed.javadsl.Routers;
import akka.actor.typed.receptionist.Receptionist;
import akka.actor.typed.receptionist.ServiceKey;
import net.e6tech.elements.common.actor.GenesisActor;
import net.e6tech.elements.common.actor.typed.Receptor;
import net.e6tech.elements.common.actor.typed.Typed;
import net.e6tech.elements.common.resources.NotAvailableException;
import scala.concurrent.ExecutionContextExecutor;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static net.e6tech.elements.network.cluster.invocation.InvocationEvents.*;

public class Registrar extends Receptor {

    private Map> routes = new HashMap<>(); // key is the context@method
    private Map>> actors = new ConcurrentHashMap<>();
    private Map, String> actorKeys = new ConcurrentHashMap<>();
    private RegistryActor registry;

    public Registrar(RegistryActor registry) {
        this.registry = registry;
    }

    // spawn a RegistryEntry
    @Typed
    private void registration(Registration registration) {
        String dispatcher;
        ExecutionContextExecutor executor = getContext()
                .getSystem()
                .dispatchers()
                .lookup(DispatcherSelector.fromConfig(RegistryActor.REGISTRY_DISPATCHER));
        if (executor != null) {
            dispatcher = RegistryActor.REGISTRY_DISPATCHER;
        } else {
            dispatcher = GenesisActor.WORKER_POOL_DISPATCHER;
        }

        // spawn a child to listen for RegistryEntry
        ServiceKey key = ServiceKey.create(InvocationEvents.Request.class, registration.getPath());
        getContext().spawnAnonymous(Behaviors.setup(
                ctx -> {
                    // Subscribe to Receptionist using key
                    ctx.getSystem().receptionist().tell(Receptionist.subscribe(key, ctx.getSelf().narrow()));

                    // watch for Receptionist.Listing and modify actors and actorKeys
                    // need to be synchronized because the method terminated also modify actors and actorKeys.
                    return Behaviors.receive(Object.class)
                            .onMessage(Receptionist.Listing.class,
                                    msg -> {
                                        talk(Receptionist.Listing.class).tell(msg);
                                        return Behaviors.same();
                                    })
                            .build();
                }
        ));

        // spawn a RegistryEntry and register it with Receptionist using key
        if (registration.getFunction() != null) {
            ActorRef registryEntry = this.childActor(RegistryEntry.class)
                    .withProps(DispatcherSelector.fromConfig(dispatcher))
                    //.afterSetup(child -> getSystem().receptionist().tell(Receptionist.register(key, child.getSelf())))
                    .spawn(new RegistryEntry(registration));
            getSystem().receptionist().tell(Receptionist.register(key, registryEntry));
        }

        routes.computeIfAbsent(registration.getPath(),
                k -> {
                    GroupRouter g = Routers.group(key).withRoundRobinRouting();
                    return getContext().spawnAnonymous(g);
                });
    }

    @Typed
    private void listing(Receptionist.Listing listing) {
        ServiceKey key = listing.getKey();
        synchronized (actors) {
            Set> set = actors.getOrDefault(key.id(), Collections.emptySet());
            // remove from actorKeys because actors with registration.getPath() will be replaced with a new list
            for (ActorRef ref : set) {
                actorKeys.remove(ref);
            }

            // record new actors from msg.getServiceInstance
            // for each record, we need to save in actorKeys because the previous step to clear those
            // actors from actorKeys.
            for (ActorRef ref : listing.getServiceInstances(key)) {
                actorKeys.put(ref, key.id());
                if (!set.contains(ref)) {
                    getContext().watch(ref); // watch for Terminated event
                    registry.onAnnouncement(key.id());
                }
            }
            actors.put(key.id(), new LinkedHashSet<>(listing.getServiceInstances(key)));
        }
    }

    // Forward request to router
    @SuppressWarnings("unchecked")
    @Typed
    private void request(Request request) {
        ActorRef router = routes.get(request.getPath());
        if (router == null) {
            request.getSender().tell(new Status.Failure(new NotAvailableException("Service not available.")));
        } else {
            router.tell(request);
        }
    }

    // received terminated from RegistryEntry
    @Typed
    private void terminated(Terminated terminated) {
        ActorRef actor = terminated.getRef();
        String key;
        synchronized (actors) {
            key = actorKeys.get(actor);
            if (key != null) {
                Set> set = actors.get(key);
                if (set != null) {
                    set.remove(actor);
                }
            }
            actorKeys.remove(actor);
        }

        if (key != null)
            registry.onTerminated(key, actor);
    }

    @SuppressWarnings("unchecked")
    @Typed
    private void routes(Routes message) {
        Set> actorsForKey = this.actors.get(message.getPath());
        if (actorsForKey == null) {
            message.getSender().tell(new Response(getSelf(), Collections.emptySet()));
        } else {
            message.getSender().tell(new Response(getSelf(), actorsForKey));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy