Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.usergrid.persistence.actorsystem; import akka.actor.ActorSelection; import akka.actor.Address; import akka.actor.UntypedActor; import akka.cluster.Cluster; import akka.cluster.ClusterEvent; import akka.cluster.Member; import akka.cluster.MemberStatus; import org.apache.commons.lang.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.*; import java.util.concurrent.ThreadLocalRandom; /** * Once notified of nodes, sends unique propertyValue requests to ClusterSingletonRouter via it's local proxy. */ public class ClientActor extends UntypedActor { private static final Logger logger = LoggerFactory.getLogger(ClientActor.class); private final String name = RandomStringUtils.randomAlphanumeric(4); private final Set<Address> nodes = new HashSet<>(); private final Cluster cluster = Cluster.get(getContext().system()); private final Map<Class, String> routersByMessageType; private boolean ready = false; public ClientActor(Map<Class, String> routersByMessageType) { this.routersByMessageType = routersByMessageType; } // subscribe to cluster changes, MemberEvent @Override public void preStart() { logger.debug("{} role {} address {}:{} starting up, subscribing to cluster events...", name, cluster.getSelfRoles().iterator().next(), cluster.readView().selfAddress().host(), cluster.readView().selfAddress().hostPort()); cluster.subscribe(getSelf(), ClusterEvent.MemberEvent.class, ClusterEvent.ReachabilityEvent.class); } // re-subscribe when restart @Override public void postStop() { cluster.unsubscribe(getSelf()); } @Override public void onReceive(Object message) { int startSize = nodes.size(); String routerPath = routersByMessageType.get(message.getClass()); if (routerPath != null && ready) { // just pick any node, the ClusterSingletonRouter will do the consistent hash routing List<Address> nodesList = new ArrayList<>(nodes); Address address = nodesList.get(ThreadLocalRandom.current().nextInt(nodesList.size())); ActorSelection service = getContext().actorSelection(address + routerPath); service.tell(message, getSender()); } else if (routerPath != null && !ready) { logger.debug("{} responding with status unknown", name); getSender().tell(new ErrorResponse("ClientActor not ready"), getSender()); } else if (message instanceof StatusRequest) { if (ready) { getSender().tell(new StatusMessage(name, StatusMessage.Status.READY), getSender()); } else { getSender().tell(new StatusMessage(name, StatusMessage.Status.INITIALIZING), getSender()); } return; } else { processAsClusterEvent(message); } if (logger.isDebugEnabled() && startSize != nodes.size()) { logger.debug("{} now knows {} nodes", name, nodes.size()); } if (!nodes.isEmpty() && !ready) { logger.debug(name + " is ready"); ready = true; } else if (nodes.isEmpty() && ready) { ready = false; } } /** * Process messages about nodes up, down, reachable and unreachable. */ private void processAsClusterEvent(Object message) { if (message instanceof ClusterEvent.CurrentClusterState) { ClusterEvent.CurrentClusterState state = (ClusterEvent.CurrentClusterState) message; nodes.clear(); for (Member member : state.getMembers()) { if (member.hasRole("io") && member.status().equals(MemberStatus.up())) { nodes.add(member.address()); logger.debug("RequestActor {} received cluster-state member-up for {}", name, member.address()); } } } else if (message instanceof ClusterEvent.MemberUp) { ClusterEvent.MemberUp mUp = (ClusterEvent.MemberUp) message; if (mUp.member().hasRole("io")) { nodes.add(mUp.member().address()); } logger.debug("{} received member-up for {}", name, mUp.member().address()); } else if (message instanceof ClusterEvent.MemberEvent) { ClusterEvent.MemberEvent other = (ClusterEvent.MemberEvent) message; nodes.remove(other.member().address()); } else if (message instanceof ClusterEvent.UnreachableMember) { ClusterEvent.UnreachableMember unreachable = (ClusterEvent.UnreachableMember) message; nodes.remove(unreachable.member().address()); logger.debug("{} received un-reachable for {}", name, unreachable.member().address()); } else if (message instanceof ClusterEvent.ReachableMember) { ClusterEvent.ReachableMember reachable = (ClusterEvent.ReachableMember) message; if (reachable.member().hasRole("io")) { nodes.add(reachable.member().address()); } logger.debug("{} received reachable for {}", name, reachable.member().address()); } else { logger.error("{}: unhandled message: {}", name, message.toString()); unhandled(message); } } /** * RequestAction responds to StatusRequests. */ public static class StatusRequest implements Serializable { } /** * RequestActor responds with, and some times unilaterally sends StatusMessages. */ public static class StatusMessage implements Serializable { final String name; public Status getStatus() { return status; } public enum Status { INITIALIZING, READY } private final Status status; public StatusMessage(String name, Status status) { this.name = name; this.status = status; } public String getName() { return name; } public boolean isReady() { return status.equals(Status.READY); } } public static class ErrorResponse implements Serializable { private String message; public ErrorResponse(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } }