/* * 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.event; import org.apache.thrift.TException; import org.kaaproject.kaa.common.avro.AvroByteArrayConverter; import org.kaaproject.kaa.common.hash.EndpointObjectHash; import org.kaaproject.kaa.server.common.thrift.KaaThriftService; import org.kaaproject.kaa.server.common.thrift.gen.operations.EndpointRouteUpdate; import org.kaaproject.kaa.server.common.thrift.gen.operations.EndpointStateUpdate; import org.kaaproject.kaa.server.common.thrift.gen.operations.Event; import org.kaaproject.kaa.server.common.thrift.gen.operations.EventMessageType; import org.kaaproject.kaa.server.common.thrift.gen.operations.EventRoute; import org.kaaproject.kaa.server.common.thrift.gen.operations.EventRouteUpdateType; import org.kaaproject.kaa.server.common.thrift.gen.operations.Message; import org.kaaproject.kaa.server.common.thrift.gen.operations.OperationsThriftService.Iface; import org.kaaproject.kaa.server.common.thrift.gen.operations.RouteAddress; import org.kaaproject.kaa.server.common.thrift.gen.operations.UserRouteInfo; import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo; import org.kaaproject.kaa.server.common.zk.operations.OperationsNode; import org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.RouteOperation; import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointUserConfigurationUpdate; import org.kaaproject.kaa.server.operations.service.config.OperationsServerConfig; import org.kaaproject.kaa.server.resolve.OperationsServerResolver; import org.kaaproject.kaa.server.sync.platform.AvroEncDec; import org.kaaproject.kaa.server.thrift.NeighborConnection; import org.kaaproject.kaa.server.thrift.NeighborTemplate; import org.kaaproject.kaa.server.thrift.Neighbors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; /** * EventService interface realization Class. Accept UserRouteInfo, RouteInfo and * RemoteEndpointEvent Repacks this messages into thrift data structures and * send through network. After receiving such packs from network repacks it back * to UserRouteInfo, RouteInfo and RemoteEndpointEvent * * @author Andrey Panasenko * @author Andrew Shvayka */ @Service public class DefaultEventService implements EventService { /** * The Constant LOG. */ private static final Logger LOG = LoggerFactory.getLogger(DefaultEventService.class); private static final AtomicLong eventSequence = new AtomicLong( UUID.randomUUID().getLeastSignificantBits()); //NOSONAR /** * AVRO event converter. */ private final ThreadLocal<AvroByteArrayConverter<org.kaaproject.kaa.common.endpoint.gen.Event>> eventConverter = new ThreadLocal<AvroByteArrayConverter<org.kaaproject.kaa.common.endpoint.gen.Event>>() { @Override protected AvroByteArrayConverter<org.kaaproject.kaa.common.endpoint.gen.Event> initialValue() { return new AvroByteArrayConverter<>( org.kaaproject.kaa.common.endpoint.gen.Event.class); } }; @Autowired private OperationsServerConfig operationsServerConfig; /** * ID is thriftHost:thriftPort. */ private volatile String id; private volatile Neighbors<MessageTemplate, Message> neighbors; private volatile OperationsNode operationsNode; private volatile OperationsServerResolver resolver; /** * Listeners list which registered to receive event from thrift server. */ private Set<EventServiceListener> listeners; /** * Default constructor. */ public DefaultEventService() { super(); } /** * Map RouteOperation into EventRouteUpdateType. * * @param operation RouteOperation * @return EventRouteUpdateType */ private static EventRouteUpdateType transformUpdateType(RouteOperation operation) { switch (operation) { case ADD: return EventRouteUpdateType.ADD; case DELETE: return EventRouteUpdateType.DELETE; case UPDATE: return EventRouteUpdateType.UPDATE; default: break; } return EventRouteUpdateType.UPDATE; } /** * Map EventRouteUpdateType into RouteOperation. * * @param updateType EventRouteUpdateType * @return RouteOperation */ private static RouteOperation transformUpdateType(EventRouteUpdateType updateType) { switch (updateType) { case ADD: return RouteOperation.ADD; case DELETE: return RouteOperation.DELETE; default: return RouteOperation.UPDATE; } } /** * Transform List(EventClassFamilyVersion) into Thrift * List(org.kaaproject.kaa.server.common.thrift.gen.operations.EventClassFamilyVersion). * * @param ecfVersions List(EventClassFamilyVersion) * @return thrift List(EventClassFamilyVersion) */ private static List<org.kaaproject.kaa.server.common.thrift.gen.operations.EventClassFamilyVersion> transformEcfv(List<EventClassFamilyVersion> ecfVersions) { List<org.kaaproject.kaa.server.common.thrift.gen.operations.EventClassFamilyVersion> ecfvThL = new ArrayList<>(); if (ecfVersions != null) { for (EventClassFamilyVersion ecfv : ecfVersions) { org.kaaproject.kaa.server.common.thrift.gen.operations.EventClassFamilyVersion ecfvTh = new org.kaaproject.kaa.server.common.thrift.gen.operations.EventClassFamilyVersion(); ecfvTh.setEndpointClassFamilyId(ecfv.getEcfId()); ecfvTh.setEndpointClassFamilyVersion(ecfv.getVersion()); ecfvThL.add(ecfvTh); } } return ecfvThL; } /** * Bean init-method. */ @PostConstruct public void initBean() { LOG.info("Init default event service."); listeners = Collections.newSetFromMap(new ConcurrentHashMap<EventServiceListener, Boolean>()); neighbors = new Neighbors<>(KaaThriftService.OPERATIONS_SERVICE, new MessageTemplate(this), operationsServerConfig.getMaxNumberNeighborConnections()); } /** * Bean destroy-method. */ @PreDestroy public void onStop() { if (neighbors != null) { LOG.info("Shutdown of control service neighbors started!"); neighbors.shutdown(); LOG.info("Shutdown of control service neighbors complete!"); } } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.event.EventService#sendEvent * (org.kaaproject.kaa.server.operations.service.event.RemoteEndpointEvent) */ @Override public void sendEvent(RemoteEndpointEvent remoteEndpointEvent) { String serverId = remoteEndpointEvent.getRecipient().getServerId(); NeighborConnection<MessageTemplate, Message> server = neighbors.getNeghborConnection(serverId); if (server == null) { LOG.debug("sendRouteInfo() specified server {} not found in neighbors list", serverId); notifyListenersOnServerProblem(serverId); return; } RouteAddress routeAddress = new RouteAddress( ByteBuffer.wrap(remoteEndpointEvent.getRecipient().getEndpointKey().getData()), remoteEndpointEvent.getRecipient().getApplicationToken(), serverId); ByteBuffer eventData; try { org.kaaproject.kaa.server.sync.Event eventSource = remoteEndpointEvent.getEvent().getEvent(); eventData = ByteBuffer.wrap( eventConverter.get().toByteArray(AvroEncDec.convert(eventSource))); org.kaaproject.kaa.server.common.thrift.gen.operations.EndpointEvent endpointEvent = new org.kaaproject.kaa.server.common.thrift.gen.operations.EndpointEvent( remoteEndpointEvent.getEvent().getId().toString(), ByteBuffer.wrap( remoteEndpointEvent.getEvent().getSender().getData()), eventData, remoteEndpointEvent.getEvent() .getCreateTime(), remoteEndpointEvent.getEvent().getVersion()); Event event = new Event( remoteEndpointEvent.getUserId(), remoteEndpointEvent.getTenantId(), endpointEvent, routeAddress); server.sendMessages(packMessage(event)); } catch (IOException ex) { LOG.error("Error on converting Event to byte array: skiping this event message", ex); } catch (InterruptedException ex) { LOG.error("Error sending events to server: ", ex); notifyListenersOnServerProblem(serverId); } } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.event.EventService#sendRouteInfo * (org.kaaproject.kaa.server.operations.service.event.RouteInfo, * java.lang.String[]) */ @Override public void sendRouteInfo(RouteInfo routeInfo, String... serverIdList) { Collection<RouteInfo> routeInfos = new ArrayList<>(); routeInfos.add(routeInfo); sendRouteInfo(routeInfos, serverIdList); } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.event.EventService#sendRouteInfo * (java.util.Collection, java.lang.String[]) */ @Override public void sendRouteInfo(Collection<RouteInfo> routeInfos, String... serverIdList) { List<EventRoute> routes = transformEventRouteFromRouteInfoCollection(routeInfos); String[] listServers = serverIdList; if (listServers == null || listServers.length <= 0) { List<NeighborConnection<MessageTemplate, Message>> servers = neighbors.getNeighbors(); listServers = servers.toArray(new String[servers.size()]); } for (String serverId : listServers) { sendMessagesToServer(packMessage(routes), serverId); } } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.operations.service.event.EventService# * sendUserRouteInfo * (org.kaaproject.kaa.server.operations.service.event.UserRouteInfo) */ @Override public void sendUserRouteInfo( org.kaaproject.kaa.server.operations.service.event.UserRouteInfo routeInfo) { LOG.debug("EventService: sendUserRouteInfo()....."); List<NeighborConnection<MessageTemplate, Message>> servers = neighbors.getNeighbors(); UserRouteInfo userRoute = new UserRouteInfo(routeInfo.getUserId(), routeInfo.getTenantId(), id, transformUpdateType(routeInfo.getRouteOperation())); List<Message> messages = packMessage(userRoute); for (NeighborConnection<MessageTemplate, Message> server : servers) { LOG.debug("Send UserRouteInfo {} to neighbor {}....", userRoute, server.getId()); sendMessagesToServer(server, messages); } } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.event.EventService#shutdown * () */ @Override public void shutdown() { LOG.info("Event Service shutdown()...."); listeners.clear(); neighbors.shutdown(); } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.event.EventService#addListener * (org.kaaproject.kaa.server.operations.service.event.EventServiceListener) */ @Override public void addListener(EventServiceListener listener) { listeners.add(listener); } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.operations.service.event.EventService# * removeListener * (org.kaaproject.kaa.server.operations.service.event.EventServiceListener) */ @Override public void removeListener(EventServiceListener listener) { listeners.remove(listener); } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.operations.service.event.EventService# * sendEventMessage(java.util.List) */ @Override public void sendEventMessage(List<Message> messages) { for (Message message : messages) { switch (message.getType()) { case ROUTE_UPDATE: onRouteUpdate(message.getRoute()); break; case USER_ROUTE_INFO: onUserRouteInfo(message.getUserRoute()); break; case EVENT: onEvent(message.getEvent()); break; case ENDPOINT_ROUTE_UPDATE: onEndpointRouteUpdate(message.getEndpointRouteUpdate()); break; case ENDPOINT_STATE_UPDATE: onEndpointStateUpdate(message.getEndpointStateUpdate()); break; default: break; } } } @Override public void sendEndpointRouteInfo(GlobalRouteInfo routeInfo) { LOG.trace("calculating server for user {}", routeInfo.getUserId()); String serverId = Neighbors.getServerId( resolver.getNode(routeInfo.getUserId()).getConnectionInfo()); sendMessagesToServer(packMessage(routeInfo), serverId); } @Override public void sendEndpointStateInfo(String serverId, EndpointUserConfigurationUpdate update) { sendMessagesToServer(packMessage(update), serverId); } @Override public boolean isMainUserNode(String userId) { OperationsNodeInfo info = resolver.getNode(userId); if (info == null) { return false; } LOG.trace("comparing {} to {} for user {}", id, info.getConnectionInfo(), userId); return id.equals(Neighbors.getServerId(info.getConnectionInfo())); } @Override public String getUserNode(String userId) { OperationsNodeInfo info = resolver.getNode(userId); if (info != null) { return Neighbors.getServerId(info.getConnectionInfo()); } return null; } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.event.EventService#setZkNode * (org.kaaproject.kaa.server.common.zk.operations.OperationsNode) */ @Override public void setZkNode(OperationsNode operationsNode) { this.operationsNode = operationsNode; this.id = Neighbors.getServerId( KaaThriftService.OPERATIONS_SERVICE, this.operationsNode.getNodeInfo().getConnectionInfo()); neighbors.setZkNode( KaaThriftService.OPERATIONS_SERVICE, this.operationsNode.getNodeInfo().getConnectionInfo(), operationsNode); if (resolver != null) { updateResolver(this.resolver); } } @Override public void setResolver(OperationsServerResolver resolver) { this.resolver = resolver; if (operationsNode != null) { updateResolver(this.resolver); } } /** * Repack list of EventRoute messages to list of EventMessage. * * @param routes List(EventRoute) * @return List(EventMessage) */ private List<Message> packMessage(List<EventRoute> routes) { EventMessageType type = EventMessageType.ROUTE_UPDATE; List<Message> messages = new LinkedList<>(); for (EventRoute route : routes) { messages.add(new Message(type, getEventId(), null, route, null, null, null)); } return messages; } /** * Pack UserRouteInfo into list of EventMessage. * * @param userRoute UserRouteInfo * @return List(EventMessage) */ private List<Message> packMessage(UserRouteInfo userRoute) { return Collections.singletonList(new Message( EventMessageType.USER_ROUTE_INFO, getEventId(), null, null, userRoute, null, null)); } /** * Pack Event into list of EventMessage. * * @param event Event * @return List(EventMessage) */ private List<Message> packMessage(Event event) { return Collections.singletonList(new Message( EventMessageType.EVENT, getEventId(), event, null, null, null, null)); } private List<Message> packMessage(GlobalRouteInfo routeInfo) { final EventMessageType type = EventMessageType.ENDPOINT_ROUTE_UPDATE; final List<Message> messages = new LinkedList<>(); EndpointRouteUpdate route = new EndpointRouteUpdate(); route.setTenantId(routeInfo.getTenantId()); route.setUserId(routeInfo.getUserId()); route.setUpdateType(transformUpdateType(routeInfo.getRouteOperation())); route.setCfSchemaVersion(routeInfo.getCfVersion()); route.setUcfHash(routeInfo.getUcfHash()); String opsServerId = routeInfo.getAddress().getServerId(); if (opsServerId == null) { opsServerId = id; } RouteAddress routeAddress = new RouteAddress( ByteBuffer.wrap(routeInfo.getAddress().getEndpointKey().getData()), routeInfo .getAddress().getApplicationToken(), opsServerId); route.setRouteAddress(routeAddress); messages.add(new Message(type, getEventId(), null, null, null, route, null)); return messages; } private List<Message> packMessage(EndpointUserConfigurationUpdate update) { final EventMessageType type = EventMessageType.ENDPOINT_STATE_UPDATE; final List<Message> messages = new LinkedList<>(); EndpointStateUpdate msg = new EndpointStateUpdate(); msg.setTenantId(update.getTenantId()); msg.setUserId(update.getUserId()); msg.setApplicationToken(update.getApplicationToken()); msg.setEndpointKey(update.getKey().getData()); msg.setUcfHash(update.getHash()); messages.add(new Message(type, getEventId(), null, null, null, null, msg)); return messages; } /** * Notify EventService listeners if specified Operations Servers failed. * * @param id Operations server id String */ protected void notifyListenersOnServerProblem(String id) { for (EventServiceListener listener : listeners) { listener.onServerError(id); } } /** * Transform Collection(RouteInfo) into List(EventRoute). * * @param routeInfos Collection(RouteInfo) * @return List(EventRoute) */ private List<EventRoute> transformEventRouteFromRouteInfoCollection( Collection<RouteInfo> routeInfos) { List<EventRoute> routes = new ArrayList<>(); HashMap<UserTenantKey, List<org.kaaproject.kaa.server.common.thrift.gen.operations.RouteInfo>> routeInfosTh = new HashMap<>(); // NOSONAR for (RouteInfo ri : routeInfos) { org.kaaproject.kaa.server.common.thrift.gen.operations.RouteInfo riTh = new org.kaaproject.kaa.server.common.thrift.gen.operations.RouteInfo( transformUpdateType(ri.getRouteOperation()), transformEcfv(ri.getEcfVersions()), ri.getAddress().getApplicationToken(), ByteBuffer.wrap(ri.getAddress().getEndpointKey().getData())); UserTenantKey key = new UserTenantKey(ri.getUserId(), ri.getTenantId()); if (!routeInfosTh.containsKey(key)) { routeInfosTh.put(key, new ArrayList<>()); } routeInfosTh.get(key).add(riTh); } for (UserTenantKey key : routeInfosTh.keySet()) { routes.add(new EventRoute(key.getUserId(), key.getTenantId(), routeInfosTh.get(key), id)); } return routes; } /** * Next event sequence getter. * * @return long */ private long getEventId() { return eventSequence.getAndIncrement(); } private void sendMessagesToServer(List<Message> messages, String serverId) { NeighborConnection<MessageTemplate, Message> server = neighbors.getNeghborConnection(serverId); if (server == null) { LOG.debug("specified server {} not found in neighbors list", serverId); notifyListenersOnServerProblem(serverId); return; } sendMessagesToServer(server, messages); } private void sendMessagesToServer(NeighborConnection<MessageTemplate, Message> server, List<Message> messages) { try { LOG.trace("Sending to server {} messages: {}", server.getId(), messages); server.sendMessages(messages); } catch (InterruptedException ex) { LOG.error("Error sending events to server: ", ex); notifyListenersOnServerProblem(server.getId()); } } private void onEndpointRouteUpdate(EndpointRouteUpdate update) { LOG.debug("Updating {} listeners with {}", listeners.size(), update); GlobalRouteInfo msg = GlobalRouteInfo.fromThrift(update); for (EventServiceListener listener : listeners) { listener.onEndpointRouteUpdate(msg); } } private void onEndpointStateUpdate(EndpointStateUpdate update) { LOG.debug("Updating {} listeners with {}", listeners.size(), update); EndpointUserConfigurationUpdate msg = EndpointUserConfigurationUpdate.fromThrift(update); for (EventServiceListener listener : listeners) { listener.onEndpointStateUpdate(msg); } } /** * Transform EventRoute message from thrift service into RouteInfo and push * it to EventService listeners. * * @param route EventRoute */ private void onRouteUpdate(EventRoute route) { LOG.debug("onEventRouteUpdate .... {} routes updated in {} listeners", route.getRouteInfo().size(), listeners.size()); for (EventServiceListener listener : listeners) { for (org.kaaproject.kaa.server.common.thrift.gen.operations.RouteInfo routeInfo : route.getRouteInfo()) { String applicationToken = routeInfo.getApplicationToken(); EndpointObjectHash endpointKey = EndpointObjectHash.fromBytes(routeInfo.getEndpointId()); RouteTableAddress address = new RouteTableAddress( endpointKey, applicationToken, route.getOperationsServerId()); List<EventClassFamilyVersion> ecfVersions = new ArrayList<>(); for (org.kaaproject.kaa.server.common.thrift.gen.operations.EventClassFamilyVersion ecfv : routeInfo.getEventClassFamilyVersion()) { EventClassFamilyVersion ecf = new EventClassFamilyVersion( ecfv.getEndpointClassFamilyId(), ecfv.getEndpointClassFamilyVersion()); ecfVersions.add(ecf); } listener.onRouteInfo(new RouteInfo( route.getTenantId(), route.getUserId(), address, ecfVersions)); } } } /** * Transform UserRouteInfo message from thrift service into UserRouteInfo * and push it to EventService listeners. * * @param userRoute UserRouteInfo object */ private void onUserRouteInfo(UserRouteInfo userRoute) { LOG.debug("eventUserRouteInfo .... User routes updates in {} listeners", listeners.size()); LOG.debug("UserRouteInfo UserId={}; TenantId={}; OperationServerId={}", userRoute.getUserId(), userRoute.getTenantId(), userRoute.getOperationsServerId()); for (EventServiceListener listener : listeners) { org.kaaproject.kaa.server.operations.service.event.UserRouteInfo routeInfo = new org.kaaproject.kaa.server.operations.service.event.UserRouteInfo( userRoute.getTenantId(), userRoute.getUserId(), userRoute.getOperationsServerId(), transformUpdateType(userRoute.getUpdateType())); listener.onUserRouteInfo(routeInfo); } } /** * Transform Event message from thrift service into RemoteEndpointEvent and * push it to EventService listeners. * * @param event Event */ private void onEvent(Event event) { LOG.debug("onEvent .... event in {} listeners", listeners.size()); LOG.debug("Event: {}", event.toString()); for (EventServiceListener listener : listeners) { org.kaaproject.kaa.server.sync.Event localEvent; try { localEvent = AvroEncDec.convert(eventConverter.get().fromByteArray( event.getEndpointEvent().getEventData())); EndpointEvent endpointEvent = new EndpointEvent( EndpointObjectHash.fromBytes(event.getEndpointEvent().getSender()), localEvent, UUID.fromString( event.getEndpointEvent().getUuid()), event.getEndpointEvent().getCreateTime(), event.getEndpointEvent().getVersion()); RouteTableAddress recipient = new RouteTableAddress( EndpointObjectHash.fromBytes(event.getRouteAddress().getEndpointKey()), event.getRouteAddress().getApplicationToken(), event.getRouteAddress().getOperationsServerId()); RemoteEndpointEvent remoteEvent = new RemoteEndpointEvent( event.getTenantId(), event.getUserId(), endpointEvent, recipient); listener.onEvent(remoteEvent); } catch (IOException ex) { LOG.error("Error on converting byte array to Event: skiping this event message", ex); } } } private void updateResolver(final OperationsServerResolver resolver) { operationsNode.addListener(new OperationsNodeListener() { @Override public void onNodeUpdated(OperationsNodeInfo node) { LOG.debug("Update of node {} is pushed to resolver {}", node, resolver); resolver.onNodeUpdated(node); } @Override public void onNodeRemoved(OperationsNodeInfo node) { LOG.debug("Remove of node {} is pushed to resolver {}", node, resolver); resolver.onNodeRemoved(node); } @Override public void onNodeAdded(OperationsNodeInfo node) { LOG.debug("Add of node {} is pushed to resolver {}", node, resolver); resolver.onNodeAdded(node); } }); for (OperationsNodeInfo info : operationsNode.getCurrentOperationServerNodes()) { resolver.onNodeUpdated(info); } } private static class MessageTemplate implements NeighborTemplate<Message> { private final DefaultEventService service; public MessageTemplate(DefaultEventService service) { super(); this.service = service; } @Override public void process(Iface client, List<Message> messages) throws TException { client.sendMessages(messages); } @Override public void onServerError(String serverId, Exception ex) { service.notifyListenersOnServerProblem(serverId); } } }