/*
* 2012-4 Red Hat Inc. and/or its affiliates and other contributors.
*
* 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.overlord.rtgov.active.collection.ws;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.overlord.commons.services.ServiceRegistryUtil;
import org.overlord.rtgov.active.collection.command.ActiveChangeNotification;
import org.overlord.rtgov.active.collection.command.ActiveChangeNotification.ActiveChangeType;
import org.overlord.rtgov.active.collection.command.ActiveCollectionCommand;
import org.overlord.rtgov.active.collection.ActiveChangeListener;
import org.overlord.rtgov.active.collection.ActiveCollection;
import org.overlord.rtgov.active.collection.ActiveCollectionManager;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/**
* This class represents the Websocket interface to the active collection server.
*
*/
@ServerEndpoint("/acmws")
@ApplicationScoped
public class WSActiveCollectionServer {
private static final Logger LOG=Logger.getLogger(WSActiveCollectionServer.class.getName());
private static final ObjectMapper MAPPER=new ObjectMapper();
private ActiveCollectionManager _acmManager=null;
private org.overlord.commons.services.ServiceListener<ActiveCollectionManager> _listener;
private java.util.Map<String, ACMListener> _acmListeners=new java.util.HashMap<String, ACMListener>();
/**
* This is the default constructor.
*/
public WSActiveCollectionServer() {
}
/**
* This method initializes the active collection REST service.
*/
@PostConstruct
public void init() {
_listener = new org.overlord.commons.services.ServiceListener<ActiveCollectionManager>() {
@Override
public void registered(ActiveCollectionManager service) {
_acmManager = service;
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Active collection manager="+_acmManager);
}
}
@Override
public void unregistered(ActiveCollectionManager service) {
_acmManager = null;
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Unset active collection manager");
}
}
};
ServiceRegistryUtil.addServiceListener(ActiveCollectionManager.class, _listener);
}
/**
* This method sets the active collection manager.
*
* @param acm The active collection manager
*/
public void setActiveCollectionManager(ActiveCollectionManager acm) {
LOG.info("Set Active Collection Manager="+acm);
_acmManager = acm;
}
/**
* This method returns the active collection manager.
*
* @return The active collection manager
*/
public ActiveCollectionManager getActiveCollectionManager() {
return (_acmManager);
}
/**
* This method handles a new websocket connection.
*
* @param session The session
*/
@OnOpen
public void onOpen(Session session) {
synchronized (_acmListeners) {
if (_acmListeners.containsKey(session.getId())) {
LOG.severe("Websocket session already registered");
} else {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Registering websocket session '"+session.getId()+"'");
}
_acmListeners.put(session.getId(), new ACMListener(session,_acmManager));
}
}
}
/**
* This method processes an inbound message on the websocket.
*
* @param message The message
* @param session The associated websocket session
*/
@OnMessage
public void onMessage(String message, Session session) {
synchronized (_acmListeners) {
if (!_acmListeners.containsKey(session.getId())) {
LOG.severe("Websocket session not registered");
} else {
ACMListener l=_acmListeners.get(session.getId());
try {
ActiveCollectionCommand command=MAPPER.readValue(message.getBytes(), ActiveCollectionCommand.class);
if (command.getRegister() != null) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Register listener for notifications on active collection '"+command.getRegister().getCollection()+"'");
}
l.register(command.getRegister().getCollection());
}
if (command.getUnregister() != null) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Unregister listener for notifications on active collection '"+command.getUnregister().getCollection()+"'");
}
l.register(command.getUnregister().getCollection());
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to deserialize the active collection command", e);
}
}
}
}
/**
* This method closes a websocket connection.
*
* @param session The session
*/
@OnClose
public void onClose(Session session) {
synchronized (_acmListeners) {
if (!_acmListeners.containsKey(session.getId())) {
LOG.severe("Websocket session not registered");
} else {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Unregistering websocket session '"+session.getId()+"'");
}
ACMListener l=_acmListeners.remove(session.getId());
l.close();
}
}
}
/**
* This class manages the active collection listeners for a particular websocket
* session.
*
*/
public static class ACMListener {
private ActiveCollectionManager _activeCollectionManager;
private Session _session;
private java.util.Map<String, ActiveChangeListener> _listeners=new java.util.HashMap<String, ActiveChangeListener>();
/**
* This is the constructor.
*
* @param session The session
* @param acm The active collection manager
*/
public ACMListener(Session session, ActiveCollectionManager acm) {
_session = session;
_activeCollectionManager = acm;
}
/**
* This method registers a new active collection listener.
*
* @param name The active collection name
*/
public void register(String name) {
ActiveCollection ac=_activeCollectionManager.getActiveCollection(name);
if (ac != null) {
ActiveChangeListener l=new ActiveChangeListener() {
@Override
public void inserted(Object key, Object value) {
try {
_session.getAsyncRemote().sendText(MAPPER.writeValueAsString(
new ActiveChangeNotification(ActiveChangeType.Insert, key, value)));
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to send notification", e);
}
}
@Override
public void updated(Object key, Object value) {
try {
_session.getAsyncRemote().sendObject(MAPPER.writeValueAsString(
new ActiveChangeNotification(ActiveChangeType.Update, key, value)));
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to send notification", e);
}
}
@Override
public void removed(Object key, Object value) {
try {
_session.getAsyncRemote().sendObject(MAPPER.writeValueAsString(
new ActiveChangeNotification(ActiveChangeType.Remove, key, value)));
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to send notification", e);
}
}
};
ac.addActiveChangeListener(l);
_listeners.put(name, l);
} else {
LOG.severe("Active collection '"+name+"' not found");
}
}
/**
* This method unregisters an active collection listener.
*
* @param name The active collection name
*/
public void unregister(String name) {
if (_listeners.containsKey(name)) {
unregisterListener(name, _listeners.remove(name));
} else {
LOG.severe("Active collection '"+name+"' was not registered");
}
}
/**
* This method unregisters the supplied listener from the active collection
* with the specified name.
*
* @param name The active collection name
* @param l The listener
*/
protected void unregisterListener(String name, ActiveChangeListener l) {
ActiveCollection ac=_activeCollectionManager.getActiveCollection(name);
if (ac != null) {
ac.removeActiveChangeListener(l);
} else {
LOG.severe("Active change listener for '"+name+"' was found, but not the active collection");
}
}
/**
* This method closes the active collection listeners associated with the
* websocket session.
*/
public void close() {
for (String name : _listeners.keySet()) {
unregisterListener(name, _listeners.remove(name));
}
}
}
}