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

org.visallo.web.Messaging Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
package org.visallo.web;

import com.codahale.metrics.Counter;
import com.google.inject.Inject;
import com.google.inject.Injector;
import org.apache.commons.lang.StringUtils;
import org.atmosphere.cache.UUIDBroadcasterCache;
import org.atmosphere.client.TrackMessageSizeInterceptor;
import org.atmosphere.config.service.AtmosphereHandlerService;
import org.atmosphere.cpr.*;
import org.atmosphere.interceptor.AtmosphereResourceLifecycleInterceptor;
import org.atmosphere.interceptor.BroadcastOnPostAtmosphereInterceptor;
import org.atmosphere.interceptor.HeartbeatInterceptor;
import org.atmosphere.interceptor.JavaScriptProtocol;
import org.json.JSONArray;
import org.json.JSONObject;
import org.visallo.core.exception.VisalloException;
import org.visallo.core.model.user.UserRepository;
import org.visallo.core.model.user.UserSessionCounterRepository;
import org.visallo.core.model.workQueue.WorkQueueRepository;
import org.visallo.core.model.workspace.Workspace;
import org.visallo.core.model.workspace.WorkspaceRepository;
import org.visallo.core.status.JmxMetricsManager;
import org.visallo.core.user.User;
import org.visallo.core.util.JSONUtil;
import org.visallo.core.util.VisalloLogger;
import org.visallo.core.util.VisalloLoggerFactory;
import org.visallo.web.clientapi.model.UserStatus;

import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static com.google.common.base.Preconditions.checkNotNull;

@AtmosphereHandlerService(
        path = Messaging.PATH,
        broadcasterCache = UUIDBroadcasterCache.class,
        interceptors = {
                AtmosphereResourceLifecycleInterceptor.class,
                BroadcastOnPostAtmosphereInterceptor.class,
                TrackMessageSizeInterceptor.class,
                HeartbeatInterceptor.class,
                JavaScriptProtocol.class
        })
public class Messaging implements AtmosphereHandler { //extends AbstractReflectorAtmosphereHandler {
    public static final String PATH = "/messaging";

    private static final VisalloLogger LOGGER = VisalloLoggerFactory.getLogger(Messaging.class);

    private UserRepository userRepository;

    // TODO should we save off this broadcaster? When using the BroadcasterFactory
    //      we always get null when trying to get the default broadcaster
    private static Broadcaster broadcaster;
    private WorkspaceRepository workspaceRepository;
    private WorkQueueRepository workQueueRepository;
    private UserSessionCounterRepository userSessionCounterRepository;
    private boolean subscribedToBroadcast = false;
    private Map requestsCounters = new HashMap<>();

    @Override
    public void onRequest(AtmosphereResource resource) throws IOException {
        ensureInitialized(resource);

        CurrentUser.setUserInLogMappedDiagnosticContexts(resource.getRequest());
        try {
            Counter requestsCounter = requestsCounters.get(resource.transport());
            if (requestsCounter == null) {
                LOGGER.error("unexpected transport: " + resource.transport());
            } else {
                requestsCounter.inc();
            }

            AtmosphereRequest request = resource.getRequest();
            BufferedReader reader = request.getReader();
            String requestData = org.apache.commons.io.IOUtils.toString(reader);
            try {
                if (!StringUtils.isBlank(requestData)) {
                    processRequestData(resource, requestData);
                }
            } catch (Exception ex) {
                LOGGER.error("Could not handle async message: " + requestData, ex);
            }

            if (request.getMethod().equalsIgnoreCase("GET")) {
                onOpen(resource);
                resource.suspend();
            } else if (request.getMethod().equalsIgnoreCase("POST")) {
                LOGGER.debug("onRequest() POST: %s", requestData);
                resource.getBroadcaster().broadcast(requestData);
            }
        } finally {
            CurrentUser.clearUserFromLogMappedDiagnosticContexts();
        }
    }

    private void ensureInitialized(AtmosphereResource resource) {
        if (userRepository == null) {
            Injector injector = (Injector) resource.getAtmosphereConfig().getServletContext().getAttribute(Injector.class.getName());
            injector.injectMembers(this);
        }

        if (!subscribedToBroadcast) {
            this.workQueueRepository.subscribeToBroadcastMessages(new WorkQueueRepository.BroadcastConsumer() {
                @Override
                public void broadcastReceived(JSONObject json) {
                    if (broadcaster != null) {
                        broadcaster.broadcast(json.toString());
                    }
                }
            });
            subscribedToBroadcast = true;
        }
        broadcaster = resource.getBroadcaster();
    }

    @Override
    public void destroy() {
        LOGGER.debug("destroy");
    }

    @Override
    public void onStateChange(AtmosphereResourceEvent event) throws IOException {
        ensureInitialized(event.getResource());
        AtmosphereResponse response = ((AtmosphereResourceImpl) event.getResource()).getResponse(false);

        if (event.getMessage() != null && List.class.isAssignableFrom(event.getMessage().getClass())) {
            List messages = List.class.cast(event.getMessage());
            for (String t : messages) {
                onMessage(event, response, t);
            }

        } else if (event.isClosedByApplication() || event.isClosedByClient() || event.isCancelled()) {
            onDisconnect(event, response);
        } else if (event.isSuspended()) {
            onMessage(event, response, (String) event.getMessage());
        } else if (event.isResuming()) {
            onResume(event, response);
        } else if (event.isResumedOnTimeout()) {
            onTimeout(event, response);
        }
    }

    public void onOpen(AtmosphereResource resource) throws IOException {
        setStatus(resource, UserStatus.ACTIVE);
        incrementUserSessionCount(resource);
    }

    public void onResume(AtmosphereResourceEvent event, AtmosphereResponse response) throws IOException {
        LOGGER.debug("onResume");
    }

    public void onTimeout(AtmosphereResourceEvent event, AtmosphereResponse response) throws IOException {
        LOGGER.debug("onTimeout");
    }

    public void onDisconnect(AtmosphereResourceEvent event, AtmosphereResponse response) throws IOException {
        onDisconnectOrClose(event);
    }

    public void onClose(AtmosphereResourceEvent event, AtmosphereResponse response) {
        onDisconnectOrClose(event);
    }

    private void onDisconnectOrClose(AtmosphereResourceEvent event) {
        // If POST /logout was called first the session will be invalidated.
        if (event.getResource().getRequest().getSession() == null) {
            return;
        }

        boolean lastSession = decrementUserSessionCount(event.getResource());
        if (lastSession) {
            LOGGER.info("last session for user %s", getCurrentUserId(event.getResource()));
            setStatus(event.getResource(), UserStatus.OFFLINE);
        }
    }

    public void onMessage(AtmosphereResourceEvent event, AtmosphereResponse response, String message) throws IOException {
        try {
            if (!StringUtils.isBlank(message)) {
                processRequestData(event.getResource(), message);
            }
        } catch (Exception ex) {
            LOGGER.error("Could not handle async message: " + message, ex);
        }
        if (message != null) {
            response.write(message);
        } else {
            onDisconnectOrClose(event);
        }
    }

    private void processRequestData(AtmosphereResource resource, String message) {
        JSONObject messageJson = new JSONObject(message);
        String type = messageJson.optString("type", null);

        // Handle posts form client
        if (type != null) {
            switch (type) {
                case "setActiveWorkspace":
                    String authUserId = getCurrentUserId(resource);
                    JSONObject dataJson = messageJson.optJSONObject("data");
                    if (dataJson != null) {
                        String workspaceId = dataJson.getString("workspaceId");
                        String userId = dataJson.getString("userId");
                        if (userId.equals(authUserId)) {
                            switchWorkspace(authUserId, workspaceId);
                        }
                    }
                    break;
            }
        }
    }

    private void switchWorkspace(String authUserId, String workspaceId) {
        if (!workspaceId.equals(userRepository.getCurrentWorkspaceId(authUserId))) {
            User authUser = userRepository.findById(authUserId);
            Workspace workspace = workspaceRepository.findById(workspaceId, authUser);
            userRepository.setCurrentWorkspace(authUserId, workspace.getWorkspaceId());
            workQueueRepository.pushUserCurrentWorkspaceChange(authUser, workspace.getWorkspaceId());

            LOGGER.debug("User %s switched current workspace to %s", authUserId, workspaceId);
        }
    }

    private void setStatus(AtmosphereResource resource, UserStatus status) {
        broadcaster = resource.getBroadcaster();
        try {
            String authUserId = CurrentUser.getUserId(resource.getRequest());
            checkNotNull(authUserId, "Could not find user in session");
            User authUser = userRepository.findById(authUserId);
            checkNotNull(authUser, "Could not find user with id: " + authUserId);

            if (authUser.getUserStatus() != status) {
                LOGGER.debug("Setting user %s status to %s", authUserId, status.toString());
                userRepository.setStatus(authUserId, status);

                this.workQueueRepository.pushUserStatusChange(authUser, status);
            }
        } catch (Exception ex) {
            LOGGER.error("Could not update status", ex);
        } finally {
            // TODO session is held open by getAppSession
            // session.close();
        }
    }

    private void incrementUserSessionCount(AtmosphereResource resource) {
        String userId = getCurrentUserId(resource);
        boolean autoDelete = !(resource.transport() == AtmosphereResource.TRANSPORT.WEBSOCKET);
        userSessionCounterRepository.updateSession(userId, resource.uuid(), autoDelete);
    }

    private boolean decrementUserSessionCount(AtmosphereResource resource) {
        String userId = getCurrentUserId(resource);
        return userSessionCounterRepository.deleteSession(userId, resource.uuid()) < 1;
    }

    private String getCurrentUserId(AtmosphereResource resource) {
        if (resource.getRequest().getSession() == null) {
            return null;
        }

        String userId = CurrentUser.getUserId(resource.getRequest());
        if (userId != null && userId.trim().length() > 0) {
            return userId;
        }
        throw new VisalloException("failed to get a current userId via an AtmosphereResource");
    }

    @Inject
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Inject
    public void setWorkspaceRepository(WorkspaceRepository workspaceRepository) {
        this.workspaceRepository = workspaceRepository;
    }

    @Inject
    public void setWorkQueueRepository(WorkQueueRepository workQueueRepository) {
        this.workQueueRepository = workQueueRepository;
    }

    @Inject
    public void setUserSessionCounterRepository(UserSessionCounterRepository userSessionCounterRepository) {
        this.userSessionCounterRepository = userSessionCounterRepository;
    }

    @Inject
    public void setMetricsManager(JmxMetricsManager metricsManager) {
        String namePrefix = metricsManager.getNamePrefix(this);
        for (AtmosphereResource.TRANSPORT transport : AtmosphereResource.TRANSPORT.values()) {
            requestsCounters.put(transport, metricsManager.counter(namePrefix + transport.name()));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy