/* 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 { } }