// ---------------------------------------------------------------------------
// Copyright (c) 2010 Innotrade GmbH, jWebSocket.org
// ---------------------------------------------------------------------------
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser 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 Lesser General Public License for
// more details.
// You should have received a copy of the GNU Lesser General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
// ---------------------------------------------------------------------------
package org.jwebsocket.plugins.channels;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jwebsocket.api.WebSocketConnector;
import org.jwebsocket.config.JWebSocketCommonConstants;
import org.jwebsocket.config.JWebSocketConfig;
import org.jwebsocket.config.JWebSocketServerConstants;
import org.jwebsocket.config.xml.ChannelConfig;
import org.jwebsocket.security.SecurityFactory;
import org.jwebsocket.security.User;
import org.jwebsocket.token.Token;
import org.jwebsocket.token.TokenFactory;
/**
* Manager class responsible for all the channel operations within the
* jWebSocket server system.
*
* @author puran
* @version $Id$
*/
public class ChannelManager {
/** id for the logger channel */
private static final String LOGGER_CHANNEL_ID = "jws.logger.channel";
/** id for the admin channel */
private static final String ADMIN_CHANNEL_ID = "jws.admin.channel";
/** settings key strings */
private static final String USE_PERSISTENT_STORE = "use_persistent_store";
private static final String ALLOW_NEW_CHANNELS = "allow_new_channels";
/** persistent storage objects */
private final ChannelStore mChannelStore = new BaseChannelStore();
private final SubscriberStore mSubscriberStore = new BaseSubscriberStore();
private final PublisherStore mPublisherStore = new BasePublisherStore();
/** in-memory store maps */
private final Map<String, Channel> mSystemChannels = new ConcurrentHashMap<String, Channel>();
private final Map<String, Channel> mPrivateChannels = new ConcurrentHashMap<String, Channel>();
private final Map<String, Channel> mPublicChannels = new ConcurrentHashMap<String, Channel>();
private Map<String, String> mChannelPluginSettings = null;
/**
* Logger channel that publish all the logs in jWebSocket system
*/
private static Channel mLoggerChannel = null;
/**
* admin channel to monitor channel plugin activity
*/
private static Channel mAdminChannel = null;
/** setting to check for persistent storage or not */
public static boolean usePersistentStore;
/** setting to check if new channel creation or registration is allowed */
public static boolean allowNewChannels;
private ChannelManager(Map<String, String> aSettings) {
this.mChannelPluginSettings = new ConcurrentHashMap<String, String>(aSettings);
String lUsePersisentStore = mChannelPluginSettings.get(USE_PERSISTENT_STORE);
if (lUsePersisentStore != null && lUsePersisentStore.equals("true")) {
usePersistentStore = true;
}
String lAllowNewChannels = mChannelPluginSettings.get(ALLOW_NEW_CHANNELS);
if (lAllowNewChannels != null && lAllowNewChannels.equals("true")) {
allowNewChannels = true;
}
}
/**
* @return the static manager instance
*/
public static ChannelManager getChannelManager(Map<String, String> aSettings) {
return new ChannelManager(aSettings);
}
/**
* Starts the system channels within the jWebSocket system configured via
* jWebSocket.xml, Note that it doesn't insert the system channels to the
* channel store.
*
* @throws ChannelLifeCycleException
* if exception starting the system channels
*/
public void startSystemChannels() throws ChannelLifeCycleException {
User lRoot = SecurityFactory.getRootUser();
// TODO: Process if no root user could be found!
JWebSocketConfig lConfig = JWebSocketConfig.getConfig();
for (ChannelConfig lCfg : lConfig.getChannels()) {
if (lCfg.isSystemChannel()) {
lCfg.validate();
if (LOGGER_CHANNEL_ID.equals(lCfg.getId())) {
mLoggerChannel = new Channel(lCfg);
mLoggerChannel.start(lRoot.getLoginname());
} else if (ADMIN_CHANNEL_ID.equals(lCfg.getId())) {
mAdminChannel = new Channel(lCfg);
mAdminChannel.start(lRoot.getLoginname());
} else {
Channel lChannel = new Channel(lCfg);
lChannel.start(lRoot.getLoginname());
// put in system channels map
mSystemChannels.put(lChannel.getId(), lChannel);
}
}
}
}
/**
* Stops all the system channels running in the system and clears the system
* channels map
*/
public void stopSystemChannels() throws ChannelLifeCycleException {
User root = SecurityFactory.getRootUser();
for (Map.Entry<String, Channel> entry : mSystemChannels.entrySet()) {
Channel channel = entry.getValue();
channel.stop(root.getLoginname());
}
mSystemChannels.clear();
if (mLoggerChannel != null) {
mLoggerChannel.stop(root.getLoginname());
}
if (mAdminChannel != null) {
mAdminChannel.stop(root.getLoginname());
}
}
/**
* Returns the channel registered in the jWebSocket system based on channel
* id it does a various lookup and then if it doesn't find anywhere from the
* memory it loads the channel from the database. If it doesn' find anything
* then it returns the null object
*
* @param aChannelId
* @return channel object, null if not found
*/
public Channel getChannel(String aChannelId) {
if (mSystemChannels.containsKey(aChannelId)) {
return mSystemChannels.get(aChannelId);
}
if (mPrivateChannels.containsKey(aChannelId)) {
return mPrivateChannels.get(aChannelId);
}
if (mPublicChannels.containsKey(aChannelId)) {
return mPublicChannels.get(aChannelId);
}
if (usePersistentStore) {
// if not anywhere then look in the channel store
Channel channel = mChannelStore.getChannel(aChannelId);
if (channel != null) {
mPublicChannels.put(aChannelId, channel);
}
return channel;
}
return null;
}
/**
* Register the given channel to the list of channels maintained by the
* jWebSocket system.
*
* @param aChannel
* the channel to store.
*/
public void registerChannel(Channel aChannel) {
if (aChannel.isSystemChannel()) {
mSystemChannels.put(aChannel.getId(), aChannel);
} else if (aChannel.isPrivateChannel()) {
mPrivateChannels.put(aChannel.getId(), aChannel);
} else {
mPublicChannels.put(aChannel.getId(), aChannel);
}
if (usePersistentStore) {
mChannelStore.storeChannel(aChannel);
}
}
/**
* Returns the registered subscriber object for the given subscriber id
*
* @param aSubscriberId
* the subscriber id
* @return the subscriber object
*/
public Subscriber getSubscriber(String aSubscriberId) {
return mSubscriberStore.getSubscriber(aSubscriberId);
}
/**
* Stores the registered subscriber information in the channel store
*
* @param aSubscriber
* the subscriber to register
*/
public void storeSubscriber(Subscriber aSubscriber) {
mSubscriberStore.storeSubscriber(aSubscriber);
}
/**
* Removes the given subscriber information from channel store
*
* @param aSubscriber
* the subscriber object
*/
public void removeSubscriber(Subscriber aSubscriber) {
mSubscriberStore.removeSubscriber(aSubscriber.getId());
}
/**
* Returns the registered publisher for the given publisher id
*
* @param aPublisherId
* the publisher id
* @return the publisher object
*/
public Publisher getPublisher(String aPublisherId) {
return mPublisherStore.getPublisher(aPublisherId);
}
/**
* Stores the given publisher to the channel store
*
* @param publisher
* the publisher object to store
*/
public void storePublisher(Publisher aPublisher) {
mPublisherStore.storePublisher(aPublisher);
}
/**
* Removes the publisher from the channel store permanently
*
* @param aPublisher
* the publisher to remove
*/
public void removePublisher(Publisher aPublisher) {
mPublisherStore.removePublisher(aPublisher.getId());
}
/**
* Returns the instance of the logger channel.If not initialized for some
* reason returns null.
*
* @return the logger channel
*/
public static Channel getLoggerChannel() {
return mLoggerChannel;
}
/**
* Returns the instance of the admin channel. If not initialized for some
* reasons returns null.
*
* @return the admin channel
*/
public static Channel getAdminChannel() {
return mAdminChannel;
}
public void publishToLoggerChannel(Token aToken) {
Channel lLoggerChannel = getLoggerChannel();
// Added by Alex:
if (lLoggerChannel != null) {
lLoggerChannel.broadcastToken(aToken);
}
}
/**
* Returns the error token
*
* @param aConnector
* the target connector object
* @param aChannelId
* the channelId
* @param aMessage
* the error message
* @return the error token
*/
public Token getErrorToken(WebSocketConnector aConnector, String aChannelId, String aMessage) {
Token logToken = getBaseChannelResponse(aConnector, aChannelId);
logToken.setString("event", "error");
logToken.setString("error", aMessage);
return logToken;
}
/**
* Returns the basic response token for a channel
*
* @param aConnector
* the target connector object
* @param aChannel
* the target channel
* @return the base token of type channel
*/
public Token getBaseChannelResponse(WebSocketConnector aConnector, String aChannel) {
Token channelToken = TokenFactory.createToken("channel");
// TODO: In clusters, especially for service nodes we will post these fields on a lower level!
// check! Commented out by Alex
// channelToken.setString("vendor", JWebSocketCommonConstants.VENDOR);
// channelToken.setString("version", JWebSocketServerConstants.VERSION_STR);
// Alex: These fields are mandatory
channelToken.setString("sourceId", aConnector.getId());
channelToken.setString("channelId", aChannel);
return channelToken;
}
public Token getChannelSuccessToken(WebSocketConnector aConnector, String aChannel, ChannelEventEnum aEventType) {
Token lToken = getBaseChannelResponse(aConnector, aChannel);
String lEvent = "";
switch (aEventType) {
case LOGIN:
lEvent = "login";
break;
case AUTHORIZE:
lEvent = "authorize";
break;
case PUBLISH:
lEvent = "publish";
break;
case SUSCRIBE:
lEvent = "subscribe";
case UNSUSCRIBE:
lEvent = "unsuscribe";
break;
default:
break;
}
lToken.setString("event", lEvent);
lToken.setString("status", "ok");
return lToken;
}
/**
* @return the system channels
*/
public Map<String, Channel> getSystemChannels() {
return Collections.unmodifiableMap(mSystemChannels);
}
/**
* @return the private channels
*/
public Map<String, Channel> getPrivateChannels() {
return Collections.unmodifiableMap(mPrivateChannels);
}
/**
* @return the public channels
*/
public Map<String, Channel> getPublicChannels() {
return Collections.unmodifiableMap(mPublicChannels);
}
}