/*
* 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.CORE_DISPATCHER_NAME;
import static org.kaaproject.kaa.server.operations.service.akka.DefaultAkkaService.USER_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.server.operations.service.akka.AkkaContext;
import org.kaaproject.kaa.server.operations.service.akka.actors.core.user.GlobalUserActor;
import org.kaaproject.kaa.server.operations.service.akka.actors.core.user.LocalUserActor;
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.lb.ClusterUpdateMessage;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.notification.ThriftNotificationMessage;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointActorMsg;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.RouteMessage;
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.stats.StatusRequestState;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.stats.TenantActorStatusResponse;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointEventSendMessage;
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.GlobalUserAwareMessage;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.RouteInfoMessage;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.UserAwareMessage;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.UserRouteInfoMessage;
import org.kaaproject.kaa.server.transport.message.SessionControlMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
public class TenantActor extends UntypedActor {
private static final Logger LOG = LoggerFactory.getLogger(TenantActor.class);
/**
* The Akka service context.
*/
private final AkkaContext context;
private final Map<String, ActorRef> applications;
private final Map<String, ActorRef> localUsers;
private final Map<String, ActorRef> globalUsers;
private final String tenantId;
private final Map<UUID, StatusRequestState> statusRequestStatesMap;
private TenantActor(AkkaContext context, String tenantId) {
super();
this.context = context;
this.tenantId = tenantId;
this.applications = new HashMap<>();
this.localUsers = new HashMap<>();
this.globalUsers = new HashMap<>();
this.statusRequestStatesMap = new HashMap<>();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return SupervisionStrategyFactory.createTenantActorStrategy(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: {}", tenantId, message);
} else {
LOG.debug("[{}] Received: {}", tenantId, message.getClass().getName());
}
if (message instanceof EndpointActorMsg) {
processEndpointActorMsg((EndpointActorMsg) message);
} else if (message instanceof EndpointAwareMessage) {
processEndpointAwareMessage((EndpointAwareMessage) message);
} else if (message instanceof GlobalUserAwareMessage) {
processGlobalUserAwareMessage((GlobalUserAwareMessage) message);
} else if (message instanceof SessionControlMessage) {
processSessionControlMessage((SessionControlMessage) message);
} else if (message instanceof RouteMessage) {
processRouteMessage((RouteMessage<?>) message);
} else if (message instanceof UserAwareMessage) {
processUserAwareMessage((UserAwareMessage) message);
} else if (message instanceof Terminated) {
processTermination((Terminated) message);
} else if (message instanceof ThriftNotificationMessage) {
processNotificationMessage((ThriftNotificationMessage) message);
} else if (message instanceof EndpointUserActionRouteMessage) {
processEndpointUserActionRouteMessage((EndpointUserActionRouteMessage) message);
} else if (message instanceof ClusterUpdateMessage) {
processClusterUpdate((ClusterUpdateMessage) message);
} else if (message instanceof StatusRequestMessage) {
processStatusRequest((StatusRequestMessage) message);
} else if (message instanceof ApplicationActorStatusResponse) {
processStatusResponse((ApplicationActorStatusResponse) message);
}
}
private void processRouteMessage(RouteMessage<?> msg) {
if (msg.getAppToken() != null) {
ActorRef applicationActor = getOrCreateApplicationActor(msg.getAppToken());
applicationActor.tell(msg, self());
}
}
private void processClusterUpdate(ClusterUpdateMessage message) {
for (ActorRef userActor : globalUsers.values()) {
userActor.tell(message, ActorRef.noSender());
}
for (ActorRef userActor : localUsers.values()) {
userActor.tell(message, ActorRef.noSender());
}
for (ActorRef appActor : applications.values()) {
appActor.tell(message, ActorRef.noSender());
}
}
private void processStatusRequest(StatusRequestMessage message) {
LOG.debug("[{}] Processing status request", message.getId());
statusRequestStatesMap.put(message.getId(),
new StatusRequestState(message, applications.size()));
for (ActorRef tenant : applications.values()) {
tenant.tell(new StatusRequestMessage(message.getId()), this.getSelf());
}
}
private void processStatusResponse(ApplicationActorStatusResponse message) {
StatusRequestState state = statusRequestStatesMap.get(message.getRequestId());
if (state != null) {
if (state.processResponse(message)) {
int endpointCount = state.getEndpontCount();
context().parent().tell(
new TenantActorStatusResponse(message.getRequestId(), endpointCount),
ActorRef.noSender());
}
} else {
LOG.warn("[{}] State for status request is not found", message.getRequestId());
}
}
private void processSessionControlMessage(SessionControlMessage message) {
ActorRef applicationActor = getOrCreateApplicationActor(
message.getSessionInfo().getApplicationToken());
applicationActor.tell(message, self());
}
/**
* Process notification message.
*
* @param message the message
*/
private void processNotificationMessage(ThriftNotificationMessage message) {
ActorRef applicationActor = getOrCreateApplicationActor(message.getAppToken());
applicationActor.tell(message, self());
}
private void processEndpointActorMsg(EndpointActorMsg message) {
ActorRef applicationActor = getOrCreateApplicationActor(message.getAddress().getAppToken());
applicationActor.tell(message, self());
}
/**
* Process endpoint aware message.
*
* @param message the message
*/
private void processEndpointAwareMessage(EndpointAwareMessage message) {
if (message instanceof EndpointUserConnectMessage) {
processUserAwareMessage((EndpointUserConnectMessage) message);
} else if (message instanceof EndpointUserDisconnectMessage) {
processUserAwareMessage((EndpointUserDisconnectMessage) message);
} else if (message instanceof EndpointEventSendMessage) {
processUserAwareMessage((EndpointEventSendMessage) message);
} else {
ActorRef applicationActor = getOrCreateApplicationActor(message.getAppToken());
applicationActor.tell(message, self());
}
}
private void processEndpointUserActionRouteMessage(EndpointUserActionRouteMessage message) {
for (Entry<String, ActorRef> entry : applications.entrySet()) {
if (!entry.getKey().equals(message.getOriginalApplicationToken())) {
LOG.debug("[{}] Forwarding message to [{}] application", tenantId, entry.getKey());
entry.getValue().tell(message, self());
}
}
}
private void processUserAwareMessage(UserAwareMessage message) {
ActorRef userActor;
if (message instanceof RouteInfoMessage || message instanceof UserRouteInfoMessage) {
LOG.debug("Find user actor by id: {} for message {}", message.getUserId(), message);
userActor = localUsers.get(toLocal(message.getUserId()));
} else {
userActor = getOrCreateUserActor(message.getUserId());
}
if (userActor != null) {
userActor.tell(message, self());
} else {
LOG.debug("[{}] user aware message ignored due to no such user actor: [{}]",
tenantId, message.getUserId());
}
}
private void processGlobalUserAwareMessage(GlobalUserAwareMessage message) {
getOrCreateGlobalUserActor(message.getUserId()).tell(message, self());
}
private ActorRef getOrCreateUserActor(String userId) {
String localUserId = toLocal(userId);
ActorRef userActor = localUsers.get(localUserId);
if (userActor == null && userId != null) {
userActor = context().actorOf(
Props.create(new LocalUserActor.ActorCreator(context, userId, tenantId))
.withDispatcher(USER_DISPATCHER_NAME),
localUserId);
LOG.debug("Create local user actor with id {}", userId);
localUsers.put(localUserId, userActor);
context().watch(userActor);
}
return userActor;
}
private ActorRef getOrCreateGlobalUserActor(String userId) {
String globalUserId = toGlobal(userId);
ActorRef userActor = globalUsers.get(globalUserId);
if (userActor == null && userId != null) {
userActor = context().actorOf(
Props.create(new GlobalUserActor.ActorCreator(context, userId, tenantId))
.withDispatcher(USER_DISPATCHER_NAME),
globalUserId);
LOG.debug("Create global user actor with id {}", userId);
globalUsers.put(globalUserId, userActor);
context().watch(userActor);
}
return userActor;
}
/**
* Gets the or create application actor.
*
* @param appToken the app token
* @return the or create application actor
*/
private ActorRef getOrCreateApplicationActor(String appToken) {
ActorRef applicationActor = applications.get(appToken);
if (applicationActor == null) {
applicationActor = context().actorOf(
Props.create(new ApplicationActor.ActorCreator(context, tenantId, appToken))
.withDispatcher(CORE_DISPATCHER_NAME),
appToken);
applications.put(appToken, applicationActor);
}
return applicationActor;
}
/**
* 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();
if (applications.remove(name) != null) {
LOG.debug("[{}] removed application: {}", tenantId, localActor);
} else if (localUsers.remove(name) != null) {
LOG.debug("[{}] removed local user: {}", tenantId, localActor);
} else if (globalUsers.remove(name) != null) {
LOG.debug("[{}] removed global user: {}", tenantId, localActor);
}
} else {
LOG.warn("remove commands for remote actors are not supported yet!");
}
}
private String toLocal(String name) {
return "LOCAL_" + name;
}
private String toGlobal(String name) {
return "GLOBAL_" + name;
}
/*
* (non-Javadoc)
*
* @see akka.actor.UntypedActor#preStart()
*/
@Override
public void preStart() {
LOG.info("[{}] Starting", tenantId);
}
/*
* (non-Javadoc)
*
* @see akka.actor.UntypedActor#postStop()
*/
@Override
public void postStop() {
LOG.info("[{}] Stoped", tenantId);
}
public static class ActorCreator implements Creator<TenantActor> {
private static final long serialVersionUID = 1L;
private final AkkaContext context;
private final String tenantId;
/**
* Instantiates a new actor creator.
*
* @param context the context
* @param tenantId the tenant id
*/
public ActorCreator(AkkaContext context, String tenantId) {
super();
this.context = context;
this.tenantId = tenantId;
}
/*
* (non-Javadoc)
*
* @see akka.japi.Creator#create()
*/
@Override
public TenantActor create() throws Exception {
return new TenantActor(context, tenantId);
}
}
}