/* * Copyright 2014-2016 CyberVision, Inc. * * 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 org.kaaproject.kaa.server.operations.service.akka.actors.core; import static org.kaaproject.kaa.server.operations.service.akka.DefaultAkkaService.ENDPOINT_DISPATCHER_NAME; import static org.kaaproject.kaa.server.operations.service.akka.DefaultAkkaService.LOG_DISPATCHER_NAME; import static org.kaaproject.kaa.server.operations.service.akka.DefaultAkkaService.TOPIC_DISPATCHER_NAME; import static org.kaaproject.kaa.server.operations.service.akka.DefaultAkkaService.VERIFIER_DISPATCHER_NAME; import akka.actor.ActorRef; import akka.actor.LocalActorRef; import akka.actor.Props; import akka.actor.SupervisorStrategy; import akka.actor.Terminated; import akka.actor.UntypedActor; import akka.japi.Creator; import org.kaaproject.kaa.common.hash.EndpointObjectHash; import org.kaaproject.kaa.server.common.thrift.gen.operations.Notification; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftEndpointDeregistrationMessage; import org.kaaproject.kaa.server.operations.service.akka.AkkaContext; import org.kaaproject.kaa.server.operations.service.akka.actors.core.endpoint.global.GlobalEndpointActorCreator; import org.kaaproject.kaa.server.operations.service.akka.actors.core.endpoint.local.LocalEndpointActorCreator; import org.kaaproject.kaa.server.operations.service.akka.actors.supervision.SupervisionStrategyFactory; import org.kaaproject.kaa.server.operations.service.akka.messages.core.endpoint.EndpointAwareMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.endpoint.EndpointStopMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.lb.ClusterUpdateMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.logs.LogEventPackMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.notification.ThriftNotificationMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.ActorClassifier; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointActorMsg; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointAddress; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointClusterAddress; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointRouteMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.RouteMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.RouteOperation; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.ThriftEndpointActorMsg; import org.kaaproject.kaa.server.operations.service.akka.messages.core.stats.ApplicationActorStatusResponse; import org.kaaproject.kaa.server.operations.service.akka.messages.core.stats.StatusRequestMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.topic.TopicSubscriptionMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointEventDeliveryMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointEventDeliveryMessage.EventDeliveryStatus; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointEventReceiveMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointEventSendMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointUserActionMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointUserActionRouteMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointUserConnectMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointUserDisconnectMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.verification.UserVerificationRequestMessage; import org.kaaproject.kaa.server.transport.session.SessionAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; public class ApplicationActor extends UntypedActor { private static final Logger LOG = LoggerFactory.getLogger(ApplicationActor.class); private final AkkaContext context; private final Map<EndpointObjectHash, GlobalEndpointActorMetaData> globalEndpointSessions; /** * The endpoint sessions. */ private final Map<EndpointObjectHash, LocalEndpointActorMetaData> localEndpointSessions; private final Map<String, EndpointObjectHash> endpointActorMap; /** * The topic sessions. */ private final Map<String, ActorRef> topicSessions; private final String nodeId; private final String tenantId; private final String appToken; private final Map<String, ActorRef> logsSessions; private final Map<String, ActorRef> userVerifierSessions; private ActorRef applicationLogActor; private ActorRef userVerifierActor; /** * Instantiates a new application actor. * * @param context the context * @param applicationToken the application token */ private ApplicationActor(AkkaContext context, String tenantId, String applicationToken) { this.context = context; this.nodeId = context.getClusterService().getNodeId(); this.tenantId = tenantId; this.appToken = applicationToken; this.globalEndpointSessions = new HashMap<>(); this.localEndpointSessions = new HashMap<>(); this.endpointActorMap = new HashMap<>(); this.topicSessions = new HashMap<>(); this.logsSessions = new HashMap<>(); this.userVerifierSessions = new HashMap<>(); this.applicationLogActor = getOrCreateLogActor(); this.userVerifierActor = getOrCreateUserVerifierActor(); } /** * Builds the topic key. * * @param topicId the topic id * @return the string */ public static String buildTopicKey(String topicId) { // TODO: Improve; return topicId; } @Override public SupervisorStrategy supervisorStrategy() { return SupervisionStrategyFactory.createApplicationActorStrategy(context); } /* * (non-Javadoc) * * @see akka.actor.UntypedActor#onReceive(java.lang.Object) */ @Override public void onReceive(Object message) throws Exception { if (LOG.isTraceEnabled()) { LOG.trace("[{}] Received: {}", appToken, message); } else { LOG.debug("[{}] Received: {}", appToken, message.getClass().getName()); } if (message instanceof EndpointAwareMessage) { processEndpointAwareMessage((EndpointAwareMessage) message); } if (message instanceof EndpointActorMsg) { processEndpointActorMsg((EndpointActorMsg) message); } else if (message instanceof SessionAware) { processSessionAwareMessage((SessionAware) message); } else if (message instanceof EndpointEventDeliveryMessage) { processEndpointEventDeliveryMessage((EndpointEventDeliveryMessage) message); } else if (message instanceof Terminated) { processTermination((Terminated) message); } else if (message instanceof ThriftNotificationMessage) { processThriftNotification((ThriftNotificationMessage) message); } else if (message instanceof EndpointStopMessage) { updateEndpointActor((EndpointStopMessage) message); } else if (message instanceof LogEventPackMessage) { processLogEventPackMessage((LogEventPackMessage) message); } else if (message instanceof UserVerificationRequestMessage) { processUserVerificationRequestMessage((UserVerificationRequestMessage) message); } else if (message instanceof EndpointUserActionMessage) { processEndpointUserActionMessage((EndpointUserActionMessage) message, true); } else if (message instanceof EndpointUserActionRouteMessage) { processEndpointUserActionMessage(((EndpointUserActionRouteMessage) message).getMessage(), false); } else if (message instanceof StatusRequestMessage) { processStatusRequest((StatusRequestMessage) message); } else if (message instanceof ClusterUpdateMessage) { processClusterUpdate((ClusterUpdateMessage) message); } else if (message instanceof RouteMessage<?>) { processRouteMessage((RouteMessage<?>) message); } } private void processEndpointActorMsg(EndpointActorMsg message) { EndpointAddress address = message.getAddress(); EndpointObjectHash endpointId = EndpointObjectHash.fromBytes(address.getEntityId()); ActorClassifier classifier = message.getClassifier(); if (classifier == ActorClassifier.APPLICATION) { boolean processed = false; if (message instanceof ThriftEndpointActorMsg<?>) { processed = processCommonThriftEndpointActorMsg( endpointId, (ThriftEndpointActorMsg<?>) message); } if (!processed) { LOG.warn("[{}] Failed to lookup processor for endpoint msg {}.", endpointId, message); } } else { EndpointActorMetaData actorMetaData = null; if (classifier == ActorClassifier.GLOBAL) { actorMetaData = globalEndpointSessions.get(endpointId); } else if (classifier == ActorClassifier.LOCAL) { actorMetaData = localEndpointSessions.get(endpointId); } if (actorMetaData != null) { actorMetaData.actorRef.tell(message, context().self()); } else { LOG.warn("[{}] Failed to lookup {} actor for endpoint.", endpointId, classifier.name()); } } } private boolean processCommonThriftEndpointActorMsg(EndpointObjectHash endpointId, ThriftEndpointActorMsg<?> msg) { if (msg.getMsg() instanceof ThriftEndpointDeregistrationMessage) { forwardMessageQuietly(globalEndpointSessions.get(endpointId), msg); forwardMessageQuietly(localEndpointSessions.get(endpointId), msg); return true; } else { return false; } } private void forwardMessageQuietly(EndpointActorMetaData actorMetaData, Object msg) { if (actorMetaData != null) { actorMetaData.actorRef.tell(msg, context().self()); } } private void processClusterUpdate(ClusterUpdateMessage message) { for (Entry<EndpointObjectHash, LocalEndpointActorMetaData> entry : localEndpointSessions.entrySet()) { String globalActorNodeId = getGlobalEndpointActorNodeId(entry.getKey()); if (!globalActorNodeId.equals(entry.getValue().globalActorNodeId)) { entry.getValue().globalActorNodeId = globalActorNodeId; notifyGlobalEndpointActor(entry.getKey(), globalActorNodeId); } } for (GlobalEndpointActorMetaData entry : globalEndpointSessions.values()) { entry.actorRef.tell(message, context().self()); } } /** * Process log event pack message. * * @param message the message */ private void processLogEventPackMessage(LogEventPackMessage message) { LOG.debug("[{}] Processing log event pack message", appToken); applicationLogActor.tell(message, self()); } private void processUserVerificationRequestMessage(UserVerificationRequestMessage message) { LOG.debug("[{}] Processing user verification request message", appToken); userVerifierActor.tell(message, self()); } private void processLogNotificationMessage(ThriftNotificationMessage message) { processThriftNotificationMessage(applicationLogActor, message); } private void processUserVerifierNotificationMessage(ThriftNotificationMessage message) { processThriftNotificationMessage(userVerifierActor, message); } private void processThriftNotificationMessage(ActorRef actor, ThriftNotificationMessage message) { LOG.debug("[{}] Processing thrift notification message {}", appToken, message); actor.tell(message, self()); } private void processStatusRequest(StatusRequestMessage message) { LOG.debug("[{}] Processing status request", message.getId()); int endpointCount = localEndpointSessions.size(); context().parent() .tell(new ApplicationActorStatusResponse(message.getId(), endpointCount), ActorRef.noSender()); } /** * Process thrift notification. * * @param message the message */ private void processThriftNotification(ThriftNotificationMessage message) { Notification notification = message.getNotification(); if (notification.isSetNotificationId()) { LOG.debug("[{}] Forwarding message to specific topic", appToken); sendToSpecificTopic(message); } else if (notification.isSetAppenderId()) { LOG.debug("[{}] Forwarding message to application log actor", appToken); processLogNotificationMessage(message); } else if (notification.isSetUserVerifierToken()) { LOG.debug("[{}] Forwarding message to application user verifier actor", appToken); processUserVerifierNotificationMessage(message); } else { LOG.debug("[{}] Broadcasting message to all endpoints", appToken); broadcastToAllEndpoints(message); } } /** * Send to specific topic. * * @param message the message */ private void sendToSpecificTopic(ThriftNotificationMessage message) { Notification notification = message.getNotification(); ActorRef topicActor = getOrCreateTopic(notification.getTopicId()); topicActor.tell(message, self()); } /** * Gets the or create topic. * * @param topicId the topic id * @return the or create topic */ private ActorRef getOrCreateTopic(String topicId) { ActorRef topicActor = topicSessions.get(topicId); if (topicActor == null) { topicActor = context().actorOf( Props.create(new TopicActor.ActorCreator(context.getNotificationDeltaService())) .withDispatcher(TOPIC_DISPATCHER_NAME), buildTopicKey(topicId) ); topicSessions.put(topicId, topicActor); context().watch(topicActor); } return topicActor; } /** * Broadcast to all endpoints. * * @param message the message */ private void broadcastToAllEndpoints(ThriftNotificationMessage message) { for (LocalEndpointActorMetaData endpoint : localEndpointSessions.values()) { endpoint.actorRef.tell(message, self()); } } /** * Process endpoint aware message. * * @param message the message */ private void processEndpointAwareMessage(EndpointAwareMessage message) { if (message instanceof TopicSubscriptionMessage) { processEndpointTopicRegistration((TopicSubscriptionMessage) message); } else if (message instanceof EndpointUserConnectMessage) { processEndpointUserRegistration((EndpointUserConnectMessage) message); } else if (message instanceof EndpointUserDisconnectMessage) { processEndpointUserDeregistration((EndpointUserDisconnectMessage) message); } else if (message instanceof EndpointEventSendMessage) { processEndpointEventSendMessage((EndpointEventSendMessage) message); } else if (message instanceof EndpointEventReceiveMessage) { processEndpointEventReceiveMessage((EndpointEventReceiveMessage) message); } else { processEndpointRequest(message); } } /** * Process endpoint aware message. * * @param message the message */ private void processSessionAwareMessage(SessionAware message) { LocalEndpointActorMetaData endpointMetaData = localEndpointSessions .get(message.getSessionInfo().getKey()); if (endpointMetaData != null) { endpointMetaData.actorRef.tell(message, self()); } else { LOG.debug("[{}] Can't find endpoint actor that corresponds to {}", appToken, message.getSessionInfo().getKey()); } } private void processEndpointEventReceiveMessage(EndpointEventReceiveMessage message) { LocalEndpointActorMetaData endpointActor = localEndpointSessions.get(message.getKey()); if (endpointActor != null) { endpointActor.actorRef.tell(message, self()); } else { LOG.debug("[{}] Can't find endpoint actor that corresponds to {}", appToken, message.getKey()); context().parent() .tell(new EndpointEventDeliveryMessage(message, EventDeliveryStatus.FAILURE), self()); } } private void processEndpointEventSendMessage(EndpointEventSendMessage message) { LOG.debug("[{}] Forwarding message to specific user", appToken, message.getUserId()); context().parent().tell(message, self()); } private void processEndpointEventDeliveryMessage(EndpointEventDeliveryMessage message) { LOG.debug("[{}] Forwarding message to specific user", appToken, message.getUserId()); context().parent().tell(message, self()); } /** * Process endpoint registration. * * @param message the message */ private void processEndpointTopicRegistration(TopicSubscriptionMessage message) { ActorRef topicActor = getOrCreateTopic(message.getTopicId()); topicActor.tell(message, self()); } /** * Process endpoint registration. * * @param message the message */ private void processEndpointUserRegistration(EndpointUserConnectMessage message) { context().parent().tell(message, self()); } /** * Process endpoint deregistration. * * @param message the message */ private void processEndpointUserDeregistration(EndpointUserDisconnectMessage message) { context().parent().tell(message, self()); } /** * Process session endpoint request. * * @param message the message */ private void processEndpointRequest(EndpointAwareMessage message) { LocalEndpointActorMetaData actorMetaData = localEndpointSessions.get(message.getKey()); if (actorMetaData == null) { EndpointObjectHash endpointKey = message.getKey(); String endpointActorId = LocalEndpointActorCreator.generateActorKey(); LOG.debug("[{}] Creating actor with endpointKey: {}", appToken, endpointActorId); String globalActorNodeId = getGlobalEndpointActorNodeId(endpointKey); actorMetaData = new LocalEndpointActorMetaData(context() .actorOf(Props .create(new LocalEndpointActorCreator( context, endpointActorId, message.getAppToken(), message.getKey() )).withDispatcher(ENDPOINT_DISPATCHER_NAME), endpointActorId), endpointActorId, globalActorNodeId); localEndpointSessions.put(message.getKey(), actorMetaData); endpointActorMap.put(endpointActorId, message.getKey()); context().watch(actorMetaData.actorRef); notifyGlobalEndpointActor(endpointKey, globalActorNodeId); } actorMetaData.actorRef.tell(message, self()); } private String getGlobalEndpointActorNodeId(EndpointObjectHash endpointKey) { return context.getClusterService().getEntityNode(endpointKey); } private void notifyGlobalEndpointActor(EndpointObjectHash endpointKey, String globalActorNodeId) { notifyGlobalEndpointActor(endpointKey, globalActorNodeId, RouteOperation.ADD); } private void notifyGlobalEndpointActor(EndpointObjectHash endpointKey, String globalActorNodeId, RouteOperation operation) { EndpointRouteMessage msg = new EndpointRouteMessage( new EndpointClusterAddress(nodeId, tenantId, appToken, endpointKey), operation); if (globalActorNodeId.equals(nodeId)) { processEndpointRouteMessage(msg); } else { context.getClusterService().sendRouteMessage(msg); } } private void processRouteMessage(RouteMessage<?> msg) { if (msg instanceof EndpointRouteMessage) { processEndpointRouteMessage((EndpointRouteMessage) msg); } } private void processEndpointRouteMessage(EndpointRouteMessage msg) { EndpointObjectHash endpointKey = msg.getAddress().getEndpointKey(); GlobalEndpointActorMetaData actorMetaData = globalEndpointSessions.get(endpointKey); if (actorMetaData == null) { String endpointActorId = GlobalEndpointActorCreator.generateActorKey(); LOG.debug("[{}] Creating global endpoint actor for endpointKey: {}", appToken, endpointKey); actorMetaData = new GlobalEndpointActorMetaData( context().actorOf(Props.create( new GlobalEndpointActorCreator(context, endpointActorId, appToken, endpointKey)) .withDispatcher(ENDPOINT_DISPATCHER_NAME), endpointActorId), endpointActorId); globalEndpointSessions.put(endpointKey, actorMetaData); context().watch(actorMetaData.actorRef); } actorMetaData.actorRef.tell(msg, self()); } private void processEndpointUserActionMessage(EndpointUserActionMessage message, boolean escalate) { LocalEndpointActorMetaData endpointMetaData = localEndpointSessions.get(message.getKey()); if (endpointMetaData != null) { LOG.debug("[{}] Found affected endpoint and forwarding message to it", appToken); endpointMetaData.actorRef.tell(message, self()); } else if (escalate) { LOG.debug("[{}] Failed to fing affected endpoint in scope of current application." + " Forwarding message to tenant actor", appToken); EndpointUserActionRouteMessage routeMessage = new EndpointUserActionRouteMessage(message, appToken); context().parent().tell(routeMessage, self()); } } private void updateEndpointActor(EndpointStopMessage message) { String actorKey = message.getActorKey(); EndpointObjectHash endpointKey = message.getEndpointKey(); LOG.debug("[{}] Stoping actor [{}] with [{}]", appToken, message.getActorKey(), endpointKey); LocalEndpointActorMetaData endpointMetaData = localEndpointSessions.get(endpointKey); if (endpointMetaData != null) { if (actorKey.equals(endpointMetaData.actorId)) { localEndpointSessions.remove(endpointKey); LOG.debug("[{}] Removed actor [{}] from endpoint sessions map", appToken, actorKey); } } else { LOG.warn("[{}] EndpointSession for actor {} is not found!", appToken, endpointKey); } endpointActorMap.remove(actorKey); message.getOriginator().tell(message, self()); } /** * Process termination. * * @param message the message */ private void processTermination(Terminated message) { ActorRef terminated = message.actor(); if (terminated instanceof LocalActorRef) { LocalActorRef localActor = (LocalActorRef) terminated; String name = localActor.path().name(); EndpointObjectHash endpointHash = endpointActorMap.remove(name); if (endpointHash != null) { LocalEndpointActorMetaData actorMetaData = localEndpointSessions.get(endpointHash); if (actorMetaData != null && actorMetaData.actorRef.equals(localActor)) { localEndpointSessions.remove(endpointHash); LOG.debug("[{}] removed endpoint: {}", appToken, localActor); notifyGlobalEndpointActor(endpointHash, actorMetaData.globalActorNodeId, RouteOperation.DELETE); } } else if (topicSessions.remove(name) != null) { LOG.debug("[{}] removed topic: {}", appToken, localActor); } else if (logsSessions.remove(name) != null) { LOG.debug("[{}] removed log: {}", appToken, localActor); applicationLogActor = getOrCreateLogActor(name); LOG.debug("[{}] created log: {}", appToken, applicationLogActor); } else if (userVerifierSessions.remove(name) != null) { LOG.debug("[{}] removed log: {}", appToken, localActor); userVerifierActor = getOrCreateUserVerifierActor(name); LOG.debug("[{}] created log: {}", appToken, applicationLogActor); } } else { LOG.warn("remove commands for remote actors are not supported yet!"); } } private ActorRef getOrCreateLogActor() { return getOrCreateLogActor(null); } private ActorRef getOrCreateLogActor(String name) { ActorRef logActor = logsSessions.get(name); if (logActor == null) { logActor = context().actorOf( Props.create(new ApplicationLogActor.ActorCreator(context, appToken)) .withDispatcher(LOG_DISPATCHER_NAME) ); context().watch(logActor); logsSessions.put(logActor.path().name(), logActor); } return logActor; } private ActorRef getOrCreateUserVerifierActor() { return getOrCreateUserVerifierActor(null); } private ActorRef getOrCreateUserVerifierActor(String name) { ActorRef userVerifierActor = userVerifierSessions.get(name); if (userVerifierActor == null) { userVerifierActor = context() .actorOf( Props.create(new ApplicationUserVerifierActor.ActorCreator(context, appToken)) .withDispatcher(VERIFIER_DISPATCHER_NAME) ); context().watch(userVerifierActor); userVerifierSessions.put(userVerifierActor.path().name(), userVerifierActor); } return userVerifierActor; } /* * (non-Javadoc) * * @see akka.actor.UntypedActor#preStart() */ @Override public void preStart() { LOG.info("[{}] Starting ", appToken); } /* * (non-Javadoc) * * @see akka.actor.UntypedActor#postStop() */ @Override public void postStop() { LOG.info("[{}] Stoped ", appToken); } public static class ActorCreator implements Creator<ApplicationActor> { private static final long serialVersionUID = 1L; private final AkkaContext context; private final String tenantId; private final String appToken; /** * Instantiates a new actor creator. * * @param context the context * @param tenantId the tenant id * @param appToken the application token */ public ActorCreator(AkkaContext context, String tenantId, String appToken) { super(); this.context = context; this.tenantId = tenantId; this.appToken = appToken; } /* * (non-Javadoc) * * @see akka.japi.Creator#create() */ @Override public ApplicationActor create() throws Exception { return new ApplicationActor(context, tenantId, appToken); } } }