// ---------------------------------------------------------------------------
// jWebSocket - WebSocket Token Server (manages JSON, CSV and XML Tokens)
// Copyright (c) 2010 Alexander Schulze, Innotrade GmbH
// ---------------------------------------------------------------------------
// 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.server;
import java.util.List;
import javolution.util.FastMap;
import org.apache.log4j.Logger;
import org.jwebsocket.api.ServerConfiguration;
import org.jwebsocket.api.WebSocketPacket;
import org.jwebsocket.config.JWebSocketServerConstants;
import org.jwebsocket.kit.WebSocketException;
import org.jwebsocket.logging.Logging;
import org.jwebsocket.api.WebSocketPlugIn;
import org.jwebsocket.api.WebSocketConnector;
import org.jwebsocket.api.WebSocketEngine;
import org.jwebsocket.api.WebSocketServerListener;
import org.jwebsocket.config.JWebSocketCommonConstants;
import org.jwebsocket.filter.TokenFilterChain;
import org.jwebsocket.kit.BroadcastOptions;
import org.jwebsocket.kit.CloseReason;
import org.jwebsocket.kit.FilterResponse;
import org.jwebsocket.listener.WebSocketServerTokenEvent;
import org.jwebsocket.listener.WebSocketServerTokenListener;
import org.jwebsocket.packetProcessors.CSVProcessor;
import org.jwebsocket.packetProcessors.JSONProcessor;
import org.jwebsocket.plugins.TokenPlugInChain;
import org.jwebsocket.token.Token;
import org.jwebsocket.packetProcessors.XMLProcessor;
/**
*
* @author aschulze
*/
public class TokenServer extends BaseServer {
private static Logger mLog = Logging.getLogger(TokenServer.class);
// specify name space for token server
private static final String NS_TOKENSERVER = JWebSocketServerConstants.NS_BASE + ".tokenserver";
// specify shared connector variables
public static final String VAR_IS_TOKENSERVER = NS_TOKENSERVER + ".isTS";
private volatile boolean mIsAlive = false;
/**
*
* @param aId
*/
public TokenServer(ServerConfiguration aServerConfig) {
super(aServerConfig);
plugInChain = new TokenPlugInChain(this);
filterChain = new TokenFilterChain(this);
}
@Override
public void startServer()
throws WebSocketException {
mIsAlive = true;
if (mLog.isInfoEnabled()) {
mLog.info("Token server '" + getId() + "' started.");
}
}
@Override
public boolean isAlive() {
// nothing special to do here.
// Token server does not contain any thread or similar.
return mIsAlive;
}
@Override
public void stopServer()
throws WebSocketException {
mIsAlive = false;
if (mLog.isInfoEnabled()) {
mLog.info("Token server '" + getId() + "' stopped.");
}
}
/**
* removes a plug-in from the plug-in chain of the server.
* @param aPlugIn
*/
public void removePlugIn(WebSocketPlugIn aPlugIn) {
plugInChain.removePlugIn(aPlugIn);
}
@Override
public void engineStarted(WebSocketEngine aEngine) {
if (mLog.isDebugEnabled()) {
mLog.debug("Processing engine '" + aEngine.getId() + "' started...");
}
plugInChain.engineStarted(aEngine);
}
@Override
public void engineStopped(WebSocketEngine aEngine) {
if (mLog.isDebugEnabled()) {
mLog.debug("Processing engine '" + aEngine.getId() + "' stopped...");
}
plugInChain.engineStopped(aEngine);
}
/**
* {@inheritDoc }
*/
@Override
public void connectorStarted(WebSocketConnector aConnector) {
String lSubProt = aConnector.getHeader().getSubProtocol(null);
if ((lSubProt != null)
&& (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_JSON)
|| lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_CSV)
|| lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_XML))) {
aConnector.setBoolean(VAR_IS_TOKENSERVER, true);
if (mLog.isDebugEnabled()) {
mLog.debug("Processing connector '" + aConnector.getId() + "' started...");
}
// notify plugins that a connector has started,
// i.e. a client was sconnected.
plugInChain.connectorStarted(aConnector);
}
super.connectorStarted(aConnector);
}
@Override
public void connectorStopped(WebSocketConnector aConnector, CloseReason aCloseReason) {
// notify plugins that a connector has stopped,
// i.e. a client was disconnected.
if (aConnector.getBool(VAR_IS_TOKENSERVER)) {
if (mLog.isDebugEnabled()) {
mLog.debug("Processing connector '" + aConnector.getId() + "' stopped...");
}
plugInChain.connectorStopped(aConnector, aCloseReason);
}
super.connectorStopped(aConnector, aCloseReason);
}
/**
*
* @param aConnector
* @param aDataPacket
* @return
*/
public Token packetToToken(WebSocketConnector aConnector, WebSocketPacket aDataPacket) {
String lSubProt = aConnector.getHeader().getSubProtocol(JWebSocketCommonConstants.SUB_PROT_DEFAULT);
Token lToken = null;
if (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_JSON)) {
lToken = JSONProcessor.packetToToken(aDataPacket);
} else if (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_CSV)) {
lToken = CSVProcessor.packetToToken(aDataPacket);
} else if (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_XML)) {
lToken = XMLProcessor.packetToToken(aDataPacket);
}
return lToken;
}
/**
*
* @param aConnector
* @param aToken
* @return
*/
public WebSocketPacket tokenToPacket(WebSocketConnector aConnector, Token aToken) {
String lSubProt = aConnector.getHeader().getSubProtocol(JWebSocketCommonConstants.SUB_PROT_DEFAULT);
WebSocketPacket lPacket = null;
if (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_JSON)) {
lPacket = JSONProcessor.tokenToPacket(aToken);
} else if (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_CSV)) {
lPacket = CSVProcessor.tokenToPacket(aToken);
} else if (lSubProt.equals(JWebSocketCommonConstants.SUB_PROT_XML)) {
lPacket = XMLProcessor.tokenToPacket(aToken);
}
return lPacket;
}
private void processToken(WebSocketConnector aConnector, Token aToken) {
// before forwarding the token to the plug-ins push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenIn(aConnector, aToken);
// only forward the token to the plug-in chain
// if filter chain does not response "aborted"
if (!filterResponse.isRejected()) {
getPlugInChain().processToken(aConnector, aToken);
// forward the token to the listener chain
List<WebSocketServerListener> lListeners = getListeners();
WebSocketServerTokenEvent lEvent = new WebSocketServerTokenEvent(aConnector, this);
for (WebSocketServerListener lListener : lListeners) {
if (lListener != null && lListener instanceof WebSocketServerTokenListener) {
((WebSocketServerTokenListener) lListener).processToken(lEvent, aToken);
}
}
}
}
@Override
public void processPacket(WebSocketEngine aEngine, final WebSocketConnector aConnector, WebSocketPacket aDataPacket) {
// is the data packet supposed to be interpreted as token?
if (aConnector.getBool(VAR_IS_TOKENSERVER)) {
final Token lToken = packetToToken(aConnector, aDataPacket);
if (lToken != null) {
boolean lRunReqInOwnThread = "true".equals(lToken.getString("spawnThread"));
// TODO: create list of running threads and close all properly on shutdown
if (lRunReqInOwnThread) {
if (mLog.isDebugEnabled()) {
mLog.debug("Processing threaded token '" + lToken.toString() + "' from '" + aConnector + "'...");
}
new Thread(new Runnable() {
@Override
public void run() {
processToken(aConnector, lToken);
}
}).start();
} else {
if (mLog.isDebugEnabled()) {
mLog.debug("Processing token '" + lToken.toString() + "' from '" + aConnector + "'...");
}
processToken(aConnector, lToken);
}
/*
// before forwarding the token to the plug-ins push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenIn(aConnector, lToken);
// only forward the token to the plug-in chain
// if filter chain does not response "aborted"
if (!filterResponse.isRejected()) {
getPlugInChain().processToken(aConnector, lToken);
// forward the token to the listener chain
List<WebSocketServerListener> lListeners = getListeners();
WebSocketServerTokenEvent lEvent = new WebSocketServerTokenEvent(aConnector, this);
for (WebSocketServerListener lListener : lListeners) {
if (lListener != null && lListener instanceof WebSocketServerTokenListener) {
((WebSocketServerTokenListener) lListener).processToken(lEvent, lToken);
}
}
}
*/
} else {
mLog.error("Packet '" + aDataPacket.toString() + "' could not be converted into token.");
}
}
super.processPacket(aEngine, aConnector, aDataPacket);
}
/**
*
* @param aTarget
* @param aToken
*/
public void sendToken(WebSocketConnector aSource, WebSocketConnector aTarget, Token aToken) {
if (aTarget.getBool(VAR_IS_TOKENSERVER)) {
// before sending the token push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenOut(aSource, aTarget, aToken);
// only forward the token to the plug-in chain
// if filter chain does not response "aborted"
if (!filterResponse.isRejected()) {
if (mLog.isDebugEnabled()) {
mLog.debug("Sending token '" + aToken + "' to '" + aTarget + "'...");
}
WebSocketPacket aPacket = tokenToPacket(aTarget, aToken);
super.sendPacket(aTarget, aPacket);
} else {
if (mLog.isDebugEnabled()) {
mLog.debug("");
}
}
} else {
mLog.warn("Connector not supposed to handle tokens.");
}
}
/**
*
* @param aTarget
* @param aToken
*/
public void sendToken(WebSocketConnector aTarget, Token aToken) {
sendToken(null, aTarget, aToken);
}
/**
*
* @param aEngineId
* @param aConnectorId
* @param aToken
*/
public void sendToken(String aEngineId, String aConnectorId, Token aToken) {
// TODO: return meaningful result here.
WebSocketConnector lTargetConnector = getConnector(aEngineId, aConnectorId);
if (lTargetConnector != null) {
if (lTargetConnector.getBool(VAR_IS_TOKENSERVER)) {
// before sending the token push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenOut(null, lTargetConnector, aToken);
if (mLog.isDebugEnabled()) {
mLog.debug("Sending token '" + aToken + "' to '" + lTargetConnector + "'...");
}
super.sendPacket(lTargetConnector, tokenToPacket(lTargetConnector, aToken));
} else {
mLog.warn("Connector not supposed to handle tokens.");
}
} else {
mLog.warn("Target connector '" + aConnectorId + "' not found.");
}
}
/**
* iterates through all connectors of all engines and sends the token to
* each connector. The token format is considered for each connection
* individually so that the application can broadcast a token to all kinds
* of clients.
* @param aSource
* @param aToken
* @param aBroadcastOptions
*/
public void broadcastToken(WebSocketConnector aSource, Token aToken,
BroadcastOptions aBroadcastOptions) {
if (mLog.isDebugEnabled()) {
mLog.debug("Broadcasting token '" + aToken + " to all token based connectors...");
}
// before sending the token push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenOut(aSource, null, aToken);
FastMap<String, Object> lFilter = new FastMap<String, Object>();
lFilter.put(VAR_IS_TOKENSERVER, true);
// TODO: converting the token within the loop is not that efficient!
for (WebSocketConnector lConnector : selectConnectors(lFilter).values()) {
if (!aSource.equals(lConnector) || aBroadcastOptions.isSenderIncluded()) {
// every connector could have it's own sub protocol
sendPacket(lConnector, tokenToPacket(lConnector, aToken));
}
}
}
/**
* Broadcasts to all connector, except the sender (aSource).
* @param aSource
* @param aToken
*/
public void broadcastToken(WebSocketConnector aSource, Token aToken) {
if (mLog.isDebugEnabled()) {
mLog.debug("Broadcasting token '" + aToken + " to all token based connectors...");
}
// before sending the token push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenOut(aSource, null, aToken);
FastMap<String, Object> lFilter = new FastMap<String, Object>();
lFilter.put(VAR_IS_TOKENSERVER, true);
// TODO: converting the token within the loop is not that efficient!
for (WebSocketConnector lConnector : selectConnectors(lFilter).values()) {
if (!aSource.equals(lConnector)) {
sendPacket(lConnector, tokenToPacket(lConnector, aToken));
}
}
}
/**
* Broadcasts the passed token to all token based connectors of the underlying
* engines.
* @param aToken
*/
public void broadcastToken(Token aToken) {
if (mLog.isDebugEnabled()) {
mLog.debug("Broadcasting token '" + aToken + " to all token based connectors...");
}
// before sending the token push it through filter chain
FilterResponse filterResponse = getFilterChain().processTokenOut(null, null, aToken);
FastMap<String, Object> lFilter = new FastMap<String, Object>();
lFilter.put(VAR_IS_TOKENSERVER, true);
// TODO: converting the token within the loop is not that efficient!
for (WebSocketConnector lConnector : selectConnectors(lFilter).values()) {
sendPacket(lConnector, tokenToPacket(lConnector, aToken));
}
}
/**
* creates a standard response
* @param aInToken
* @return
*/
public Token createResponse(Token aInToken) {
Integer lTokenId = aInToken.getInteger("utid", -1);
String lType = aInToken.getString("type");
String lNS = aInToken.getString("ns");
Token lResToken = new Token("response");
lResToken.put("code", 0);
lResToken.put("msg", "ok");
if (lTokenId != null) {
lResToken.put("utid", lTokenId);
}
if (lNS != null) {
lResToken.put("ns", lNS);
}
if (lType != null) {
lResToken.put("reqType", lType);
}
return lResToken;
}
/**
* creates a response with the standard "not authenticated" message
* @param aInToken
* @return
*/
public Token createNotAuthToken(Token aInToken) {
Token lResToken = createResponse(aInToken);
lResToken.put("code", -1);
lResToken.put("msg", "not authenticated");
return lResToken;
}
/**
* creates a response with the standard "not granted" message
* @param aInToken
* @return
*/
public Token createAccessDenied(Token aInToken) {
Token lResToken = createResponse(aInToken);
lResToken.put("code", -1);
lResToken.put("msg", "access denied");
return lResToken;
}
/**
* @return the plugInChain
*/
@Override
public TokenPlugInChain getPlugInChain() {
return (TokenPlugInChain) plugInChain;
}
/**
* @return the filterChain
*/
@Override
public TokenFilterChain getFilterChain() {
return (TokenFilterChain) filterChain;
}
}