package de.rwth.idsg.bikeman.ixsi.endpoint;
import de.rwth.idsg.bikeman.config.IxsiConfiguration;
import de.rwth.idsg.bikeman.ixsi.CommunicationContext;
import de.rwth.idsg.bikeman.ixsi.IxsiProcessingException;
import de.rwth.idsg.bikeman.ixsi.store.WebSocketSessionStore;
import de.rwth.idsg.bikeman.ixsi.store.AvailabilityStore;
import de.rwth.idsg.bikeman.ixsi.store.BookingAlertStore;
import de.rwth.idsg.bikeman.ixsi.store.ConsumptionStore;
import de.rwth.idsg.bikeman.ixsi.store.ExternalBookingStore;
import de.rwth.idsg.bikeman.ixsi.store.PlaceAvailabilityStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Deque;
import java.util.Map;
/**
* Created by max on 08/09/14.
*/
@Slf4j
@Component
public class WebSocketEndpoint extends ConcurrentTextWebSocketHandler {
private static final Object LOCK = new Object();
@Autowired private WebSocketSessionStore webSocketSessionStore;
@Autowired private Consumer consumer;
@Autowired private AvailabilityStore availabilityStore;
@Autowired private ConsumptionStore consumptionStore;
@Autowired private ExternalBookingStore externalBookingStore;
@Autowired private PlaceAvailabilityStore placeAvailabilityStore;
@Autowired private BookingAlertStore bookingAlertStore;
/**
* Close the sessions for a graceful shutdown
*/
@PreDestroy
public void destroy() {
Map<String, Deque<WebSocketSession>> sessionMap = webSocketSessionStore.getLookupTable();
for (Deque<WebSocketSession> sessionsForOneSystem : sessionMap.values()) {
for (WebSocketSession session : sessionsForOneSystem) {
closeSession(session);
}
}
}
@Override
public void onMessage(WebSocketSession session, TextMessage webSocketMessage) throws Exception {
log.info("[id={}] Received message: {}", session.getId(), webSocketMessage.getPayload());
String payload = webSocketMessage.getPayload();
CommunicationContext context = new CommunicationContext(session, payload);
try {
consumer.consume(context);
} catch (IxsiProcessingException e) {
handleError(session, payload, e);
}
}
@Override
public void onOpen(WebSocketSession session) throws Exception {
log.info("New connection established: {}", session);
String systemId = (String) session.getAttributes().get(IxsiConfiguration.SYSTEM_ID_KEY);
webSocketSessionStore.add(systemId, session);
}
@Override
public void onClose(WebSocketSession session, CloseStatus closeStatus) throws Exception {
log.info("[id={}] Connection was closed, status: {}", session.getId(), closeStatus);
String systemId = (String) session.getAttributes().get(IxsiConfiguration.SYSTEM_ID_KEY);
synchronized (LOCK) {
webSocketSessionStore.remove(systemId, session);
if (webSocketSessionStore.size(systemId) == 0) {
unSubscribeStores(systemId);
}
}
}
@Override
public void onError(WebSocketSession session, Throwable throwable) throws Exception {
log.error("Oops", throwable);
// TODO catch transportexceptions!
}
@Override
public boolean supportsPartialMessages() {
return false;
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
/**
* If there's something fundamentally wrong with the incoming message or its processing
* (like receiving non-Ixsi strings or parsing the request) from which the system cannot recover
* and send the appropriate IXSI error message, we cannot do anything but send a simple error string
* for debugging purposes and close the session.
*
*/
private void handleError(WebSocketSession session, String payload, IxsiProcessingException e) throws IOException {
log.error("Error occurred", e);
String errorMsg = "IxsiProcessingException: " + e.getLocalizedMessage();
session.sendMessage(new TextMessage(errorMsg + "\nMessage that caused the exception: " + payload));
session.close(CloseStatus.NOT_ACCEPTABLE.withReason(errorMsg));
}
private void closeSession(WebSocketSession session) {
if (session.isOpen()) {
try {
session.close(new CloseStatus(1001, "BikeMan is shutting down"));
} catch (IOException e) {
log.error("Failed to close the session", e);
}
}
}
private void unSubscribeStores(String systemId) {
log.debug("There are no open connections left to system '{}'. "
+ "Removing it from all the subscription stores", systemId);
availabilityStore.unsubscribeAll(systemId);
consumptionStore.unsubscribeAll(systemId);
externalBookingStore.unsubscribeAll(systemId);
placeAvailabilityStore.unsubscribeAll(systemId);
bookingAlertStore.unsubscribeAll(systemId);
}
}