package io.lumify.web;
import com.google.inject.Inject;
import com.google.inject.Injector;
import org.apache.curator.framework.CuratorFramework;
import io.lumify.core.config.Configuration;
import io.lumify.core.exception.LumifyException;
import io.lumify.core.model.user.UserRepository;
import io.lumify.core.model.user.UserSessionCounterRepository;
import io.lumify.core.model.workQueue.WorkQueueRepository;
import io.lumify.core.model.workspace.Workspace;
import io.lumify.core.model.workspace.WorkspaceRepository;
import io.lumify.core.user.User;
import io.lumify.core.util.LumifyLogger;
import io.lumify.core.util.LumifyLoggerFactory;
import io.lumify.web.clientapi.model.UserStatus;
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.JSONObject;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
@AtmosphereHandlerService(
path = "/messaging",
broadcasterCache = UUIDBroadcasterCache.class,
interceptors = {
AtmosphereResourceLifecycleInterceptor.class,
BroadcastOnPostAtmosphereInterceptor.class,
TrackMessageSizeInterceptor.class,
HeartbeatInterceptor.class,
JavaScriptProtocol.class
})
public class Messaging implements AtmosphereHandler { //extends AbstractReflectorAtmosphereHandler {
private static final LumifyLogger LOGGER = LumifyLoggerFactory.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 CuratorFramework curatorFramework;
private Configuration configuration;
private UserSessionCounterRepository userSessionCounterRepository;
private boolean subscribedToBroadcast = false;
@Override
public void onRequest(AtmosphereResource resource) throws IOException {
ensureInitialized(resource);
String requestData = org.apache.commons.io.IOUtils.toString(resource.getRequest().getInputStream(), "UTF-8");
try {
if (!StringUtils.isBlank(requestData)) {
processRequestData(resource, requestData);
}
} catch (Exception ex) {
LOGGER.error("Could not handle async message: " + requestData, ex);
}
AtmosphereRequest req = resource.getRequest();
if (req.getMethod().equalsIgnoreCase("GET")) {
onOpen(resource);
resource.suspend();
} else if (req.getMethod().equalsIgnoreCase("POST")) {
String line = req.getReader().readLine().trim();
LOGGER.debug("onRequest() POST: %s", line);
resource.getBroadcaster().broadcast(line);
}
}
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();
if (userSessionCounterRepository == null) {
userSessionCounterRepository = new UserSessionCounterRepository(curatorFramework, configuration);
}
}
@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<String> 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) {
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");
if (type == null) {
return;
}
JSONObject dataJson = messageJson.optJSONObject("data");
if (dataJson == null) {
return;
}
if ("setActiveWorkspace".equals(type)) {
String authUserId = getCurrentUserId(resource);
String workspaceId = dataJson.getString("workspaceId");
String userId = dataJson.getString("userId");
if (userId.equals(authUserId)) {
switchWorkspace(authUserId, workspaceId);
}
}
}
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.get(resource.getRequest());
if (authUserId == null) {
throw new RuntimeException("Could not find user in session");
}
User authUser = userRepository.findById(authUserId);
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);
userSessionCounterRepository.incrementAndGet(userId);
}
private boolean decrementUserSessionCount(AtmosphereResource resource) {
String userId = getCurrentUserId(resource);
return userSessionCounterRepository.decrementAndGet(userId) < 1;
}
private String getCurrentUserId(AtmosphereResource resource) {
String userId = CurrentUser.get(resource.getRequest());
if (userId != null && userId.trim().length() > 0) {
return userId;
}
throw new LumifyException("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 setCuratorFramework(CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
}
@Inject
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
}