/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.ui.websocket;
import java.util.Collection;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import org.diqube.ui.DiqubeServletContextListener;
import org.diqube.ui.websocket.request.JsonRequest;
import org.diqube.ui.websocket.request.JsonRequestDeserializer;
import org.diqube.ui.websocket.request.JsonRequestRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
/**
* Websocket endpoint that will be used by the JavaScript UI.
*
* This endpoint is instantiated programatically, see {@link DiqubeServletContextListener}. For more information, see
* JSR 356, v1.1, 6.4 "Programmatic Server Deployment".
*
* @author Bastian Gloeckle
*/
public class WebSocketEndpoint {
private static final Logger logger = LoggerFactory.getLogger(WebSocketEndpoint.class);
/** The URL mapping under which this endpoint will be available */
public static final String ENDPOINT_URL_MAPPING = "/socket";
/**
* Property name in this endpoints {@link EndpointConfig#getUserProperties()} and in all
* {@link Session#getUserProperties()} whose value is an {@link ApplicationContext}.
*/
public static final String PROP_BEAN_CONTEXT = "diqube.springContext";
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
ApplicationContext ctx = (ApplicationContext) config.getUserProperties().get(PROP_BEAN_CONTEXT);
session.getUserProperties().put(PROP_BEAN_CONTEXT, ctx);
for (WebSocketEndpointListener listener : ctx.getBeansOfType(WebSocketEndpointListener.class).values()) {
listener.socketOpened();
}
}
@OnMessage
public void onMessage(String msg, Session session) throws Exception {
try {
logger.trace("Received message on session {}: {}", session, msg);
for (WebSocketEndpointListener listener : getBeanCtx(session).getBeansOfType(WebSocketEndpointListener.class)
.values()) {
listener.socketMessage();
}
JsonRequestDeserializer deserializer = getBeanCtx(session).getBean(JsonRequestDeserializer.class);
JsonRequestRegistry requestRegistry = getBeanCtx(session).getBean(JsonRequestRegistry.class);
JsonRequest request = deserializer.deserialize(msg, session);
requestRegistry.registerRequest(session, request);
request.executeCommand();
} catch (Exception e) {
logger.error("Exception on session {}. Swallowing.", session, e);
// we must not re-throw the exception, otherwise the Websocket will be closed - we though do not want to do this
// if just any command threw an exception.
}
}
@OnClose
public void onClose(Session session, CloseReason reason) {
JsonRequestRegistry requestRegistry = getBeanCtx(session).getBean(JsonRequestRegistry.class);
Collection<JsonRequest> requests = requestRegistry.getRequestsOfSession(session);
logger.trace("Closing session {} because the websocket has been closed. This session has {} "
+ "open requests which will be cancelled.", session, requests.size());
for (JsonRequest request : requests)
try {
request.cancel();
} catch (RuntimeException e) {
logger.warn("Could not cancel a request.", e);
}
}
@OnError
public void onError(Session session, Throwable throwable) {
logger.trace("Received ERROR ({})", session, throwable);
}
private ApplicationContext getBeanCtx(Session session) {
return (ApplicationContext) session.getUserProperties().get(PROP_BEAN_CONTEXT);
}
/**
* Listener that is informed about activity on the websocket.
*/
public static interface WebSocketEndpointListener {
public void socketOpened();
public void socketMessage();
}
}