/* USE THIS FILE ACCORDING TO THE COPYRIGHT RULES IN LICENSE.TXT WHICH IS PART OF THE SOURCE CODE PACKAGE */
package com.wilutions.jsfs;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import byps.BAsyncResult;
import byps.BClient;
import byps.BException;
import byps.BExceptionC;
import byps.RemoteException;
import byps.http.HSession;
/**
* Implementation of the JSFS Dispatcher.
* This class stores for all subscribers the mapping between access tokens and service interfaces
* in static hash maps. Each browser and each JSFS Agent has its own JSFS Dispatcher object which is
* linked to the application server session, respectively the BYPS session object.
*/
public class JsfsDispatcher extends BSkeleton_DispatcherService {
/**
* Maps token to FileSystemService object.
* The JSFS Agent registers its FileSystemService with the JFSF Dispatcher.
*/
private static ConcurrentHashMap<String, FileSystemService> mapOfAgents = new ConcurrentHashMap<String, FileSystemService>();
/**
* Maps token to FileSystemNotify object.
* The browser registers its FileSystemNotify service with the JSFS Dispatcher.
*/
private static ConcurrentHashMap<String, FileSystemNotify> mapOfBrowserNotifies = new ConcurrentHashMap<String, FileSystemNotify>();
/**
* The token that has been passed to {@link #registerService(String, FileSystemService)}.
* This token is stored here in order to remove the mapping when the JSFS Agent
* server session is invalidated.
*/
private String myTokenReceivedFromAgent;
/**
* The token that has been passed to {@link #registerNotifyService(String, FileSystemNotify)}.
* This token is stored here in order to remove the mapping when the browser
* server session is invalidated.
*/
private String myTokenReceivedFromBrowser;
/**
* Back-reference to the BYPS session object.
*/
private HSession mySession;
public JsfsDispatcher(HSession sess) {
this.mySession = sess;
}
@Override
public void registerService(String token, FileSystemService service) throws RemoteException {
myTokenReceivedFromAgent = token;
mapOfAgents.put(token, service);
// This function is called from JSFS Agent.
// We trust JSFS Agent, that the token passed in the parameter has been generated by "Your Web Application".
// Thus the session lifetime can be extended.
mySession.setSessionAuthenticated();
}
@Override
public void unregisterService(String token) throws RemoteException {
mapOfAgents.remove(token);
}
@Override
public void getService(String token, boolean onlyHere, final BAsyncResult<FileSystemService> asyncResult) {
FileSystemService agent = mapOfAgents.get(token);
final BException exIfNotFound = new BException(BExceptionC.CLIENT_DIED, "JSFS Agent is not connected.");
try {
if (agent != null) {
asyncResult.setAsyncResult(agent, null);
// This function is called from the browser.
// Since we found a service interface, the token is valid and is not created by an attacker.
// Thus the session lifetime can be extended.
mySession.setSessionAuthenticated();
}
else if (onlyHere) {
throw exIfNotFound;
}
else {
// This block is reached, if the token cannot be found in the map and the
// request should be forwarded to other web applications that implement a
// JSFS Dispatcher service.
// The context.xml file in the Servers/Tomcatv7.0... folder describes
// how to use multiple JSFS Dispatcher services.
final Collection<BClient> clients = mySession.getServerContext().getServerRegistry().getForwardClientsToOtherServers();
BAsyncResult<FileSystemService> outerResult = new BAsyncResult<FileSystemService>() {
boolean finished;
int countDown = clients.size();
public synchronized void setAsyncResult(FileSystemService fso, Throwable e) {
if (finished) return;
finished = fso != null;
if (finished) {
mySession.setSessionAuthenticated();
asyncResult.setAsyncResult(fso, e);
}
else if (--countDown == 0) {
mySession.done();
asyncResult.setAsyncResult(null, exIfNotFound);
}
}
};
if (clients.size() != 0) {
for (BClient client : clients) {
BClient_JSFS myclient = (BClient_JSFS)client;
myclient.dispatcherService.getService(token, true, outerResult);
}
}
else {
throw exIfNotFound;
}
}
}
catch (Throwable e) {
// Drop session if JSFS Agent is not running.
// A Denial of Service attack should not be able to create many sessions.
mySession.done();
asyncResult.setAsyncResult(null, e);
}
}
@Override
public void registerNotifyService(String token, FileSystemNotify service) throws RemoteException {
// This function is called from the browser.
// We allow to register a notify interface only in the case of JSFS Agent has already
// registered its service.
// Otherwise an attacker could cause an OutOfMemory error by registering a large number of
// notify interfaces.
FileSystemService agent = mapOfAgents.get(token);
if (agent != null) {
myTokenReceivedFromBrowser = token;
mapOfBrowserNotifies.put(token, service);
}
}
@Override
public void unregisterNotifyService(String token) throws RemoteException {
mapOfBrowserNotifies.remove(token);
}
@Override
public void getNotifyService(String token, boolean onlyHere, final BAsyncResult<FileSystemNotify> asyncResult) {
FileSystemNotify browserNotify = mapOfBrowserNotifies.get(token);
final BException exIfNotFound = new BException(BExceptionC.CLIENT_DIED, "JSFS browser module is not connected.");
try {
if (browserNotify != null) {
asyncResult.setAsyncResult(browserNotify, null);
}
else if (onlyHere) {
throw exIfNotFound;
}
else {
final Collection<BClient> clients = mySession.getServerContext().getServerRegistry().getForwardClientsToOtherServers();
BAsyncResult<FileSystemNotify> outerResult = new BAsyncResult<FileSystemNotify>() {
boolean finished;
int countDown = clients.size();
public synchronized void setAsyncResult(FileSystemNotify fso, Throwable e) {
if (finished) return;
finished = fso != null;
if (finished) {
asyncResult.setAsyncResult(fso, e);
}
else if (--countDown == 0) {
asyncResult.setAsyncResult(null, exIfNotFound);
}
}
};
if (clients.size() != 0) {
for (BClient client : clients) {
BClient_JSFS myclient = (BClient_JSFS)client;
myclient.dispatcherService.getNotifyService(token, true, outerResult);
}
}
else {
throw exIfNotFound;
}
}
}
catch (Throwable e) {
asyncResult.setAsyncResult(null, e);
}
}
/**
* Removes the service mapping.
* This function is called from class MySession if the
* application server session is invalidated.
* JSFS Agent and the browser try to hold the sessions valid.
* If this mechanism is disturbed for some reason and the sessions get invalidated,
* the service references are removed here. Otherwise a half-life service reference
* could be retunred by a getService or getNotifyService call.
*/
public void removeMyTokenBecauseSessionWasInvalidated() {
if (myTokenReceivedFromAgent != null) {
mapOfAgents.remove(myTokenReceivedFromAgent);
}
else if (myTokenReceivedFromBrowser != null) {
mapOfBrowserNotifies.remove(myTokenReceivedFromBrowser);
}
}
@Override
public void keepAlive(String token) throws RemoteException {
}
}